1. 整体描述
之前项目需要在验证码模块,增加滑动验证码,用来给手机端使用的,大概看了下,主要方法就是将图片切割,然后记住偏移量,进行滑动,前端验证的时候,需要用前端传入的偏移量和生成的偏移量进行对比,如果在阈值之内,就验证通过,否则就不通过。具体实现方式见下文。之前没时间写,最近记录一下。
2. 具体实现
本工程主要依赖springboot框架,并且需要redis存验证码的信息,还需要几个图片,用来生成验证码。
2.1 滑动验证码实体类
package com.thcb.captchademo.captcha.domain; import lombok.data; /** * 滑动验证码 * * @author thcb * @date 2023-05-25 */ @data public class captcha { /** * 随机字符串 **/ private string noncestr; /** * 验证值 **/ private string value; /** * 生成的画布的base64 **/ private string canvassrc; /** * 画布宽度 **/ private integer canvaswidth; /** * 画布高度 **/ private integer canvasheight; /** * 生成的阻塞块的base64 **/ private string blocksrc; /** * 阻塞块宽度 **/ private integer blockwidth; /** * 阻塞块高度 **/ private integer blockheight; /** * 阻塞块凸凹半径 **/ private integer blockradius; /** * 阻塞块的横轴坐标 **/ private integer blockx; /** * 阻塞块的纵轴坐标 **/ private integer blocky; /** * 图片获取位置 **/ private integer place; }
2.2 滑动验证码登录vo
package com.thcb.captchademo.captcha.domain; import lombok.data; /** * 滑动验证码登录vo * * @author thcb * @date 2023-05-25 */ @data public class loginvo { /** * 随机字符串 **/ private string noncestr; /** * 验证值 **/ private string value; }
2.3 滑动验证码接口返回类
package com.thcb.captchademo.captcha.utils; import java.util.hashmap; /** * 操作消息提醒 * * @author thcb */ public class ajaxresult extends hashmap<string, object> { private static final long serialversionuid = 1l; /** * 状态码 */ public static final string code_tag = "code"; /** * 返回内容 */ public static final string msg_tag = "msg"; /** * 数据对象 */ public static final string data_tag = "data"; /** * 初始化一个新创建的 ajaxresult 对象,使其表示一个空消息。 */ public ajaxresult() { } /** * 初始化一个新创建的 ajaxresult 对象 * * @param code 状态码 * @param msg 返回内容 */ public ajaxresult(int code, string msg) { super.put(code_tag, code); super.put(msg_tag, msg); } /** * 初始化一个新创建的 ajaxresult 对象 * * @param code 状态码 * @param msg 返回内容 * @param data 数据对象 */ public ajaxresult(int code, string msg, object data) { super.put(code_tag, code); super.put(msg_tag, msg); if (data != null && !data.equals("")) { super.put(data_tag, data); } } /** * 返回成功消息 * * @return 成功消息 */ public static ajaxresult success() { return ajaxresult.success("操作成功"); } /** * 返回成功数据 * * @return 成功消息 */ public static ajaxresult success(object data) { return ajaxresult.success("操作成功", data); } /** * 返回成功消息 * * @param msg 返回内容 * @return 成功消息 */ public static ajaxresult success(string msg) { return ajaxresult.success(msg, null); } /** * 返回成功消息 * * @param msg 返回内容 * @param data 数据对象 * @return 成功消息 */ public static ajaxresult success(string msg, object data) { return new ajaxresult(200, msg, data); } /** * 返回错误消息 * * @return */ public static ajaxresult error() { return ajaxresult.error("操作失败"); } /** * 返回错误消息 * * @param msg 返回内容 * @return 警告消息 */ public static ajaxresult error(string msg) { return ajaxresult.error(msg, null); } /** * 返回错误消息 * * @param msg 返回内容 * @param data 数据对象 * @return 警告消息 */ public static ajaxresult error(string msg, object data) { return new ajaxresult(500, msg, data); } /** * 返回错误消息 * * @param code 状态码 * @param msg 返回内容 * @return 警告消息 */ public static ajaxresult error(int code, string msg) { return new ajaxresult(code, msg, null); } }
2.4 滑动验证码工具类
此类是核心类,主要实现了图片的切割功能。
package com.thcb.captchademo.captcha.utils; import com.thcb.captchademo.captcha.domain.captcha; import javax.imageio.imageio; import java.awt.*; import java.awt.image.bufferedimage; import java.io.bytearrayoutputstream; import java.io.file; import java.io.ioexception; import java.net.url; import java.util.base64; import java.util.objects; import java.util.random; /** * 滑动验证码工具类 * * @author thcb * @date 2023-05-25 */ public class captchautils { /** * 网络图片地址 **/ private final static string img_url = "https://loyer.wang/view/ftp/wallpaper/%s.jpg"; /** * 本地图片地址 **/ private final static string img_path = "e:\\caphcha\\%s.jpg"; /** * 入参校验设置默认值 **/ public static void checkcaptcha(captcha captcha) { //设置画布宽度默认值 if (captcha.getcanvaswidth() == null) { captcha.setcanvaswidth(320); } //设置画布高度默认值 if (captcha.getcanvasheight() == null) { captcha.setcanvasheight(155); } //设置阻塞块宽度默认值 if (captcha.getblockwidth() == null) { captcha.setblockwidth(65); } //设置阻塞块高度默认值 if (captcha.getblockheight() == null) { captcha.setblockheight(55); } //设置阻塞块凹凸半径默认值 if (captcha.getblockradius() == null) { captcha.setblockradius(9); } //设置图片来源默认值 if (captcha.getplace() == null) { captcha.setplace(1); } } /** * 获取指定范围内的随机数 **/ public static int getnoncebyrange(int start, int end) { random random = new random(); return random.nextint(end - start + 1) + start; } /** * 获取验证码资源图 **/ public static bufferedimage getbufferedimage(integer place) { try { //随机图片 //获取网络资源图片 if (0 == place) { int nonce = getnoncebyrange(0, 1000); string imgurl = string.format(img_url, nonce); url url = new url(imgurl); return imageio.read(url.openstream()); } //获取本地图片 else { int nonce = getnoncebyrange(0, 20); string imgpath = string.format(img_path, nonce); file file = new file(imgpath); return imageio.read(file); } } catch (exception e) { system.out.println("获取拼图资源失败"); //异常处理 return null; } } /** * 调整图片大小 **/ public static bufferedimage imageresize(bufferedimage bufferedimage, int width, int height) { image image = bufferedimage.getscaledinstance(width, height, image.scale_smooth); bufferedimage resultimage = new bufferedimage(width, height, bufferedimage.type_int_argb); graphics2d graphics2d = resultimage.creategraphics(); graphics2d.drawimage(image, 0, 0, null); graphics2d.dispose(); return resultimage; } /** * 抠图,并生成阻塞块 **/ public static void cutbytemplate(bufferedimage canvasimage, bufferedimage blockimage, int blockwidth, int blockheight, int blockradius, int blockx, int blocky) { bufferedimage waterimage = new bufferedimage(blockwidth, blockheight, bufferedimage.type_4byte_abgr); //阻塞块的轮廓图 int[][] blockdata = getblockdata(blockwidth, blockheight, blockradius); //创建阻塞块具体形状 for (int i = 0; i < blockwidth; i++) { for (int j = 0; j < blockheight; j++) { try { //原图中对应位置变色处理 if (blockdata[i][j] == 1) { //背景设置为黑色 waterimage.setrgb(i, j, color.black.getrgb()); blockimage.setrgb(i, j, canvasimage.getrgb(blockx + i, blocky + j)); //轮廓设置为白色,取带像素和无像素的界点,判断该点是不是临界轮廓点 if (blockdata[i + 1][j] == 0 || blockdata[i][j + 1] == 0 || blockdata[i - 1][j] == 0 || blockdata[i][j - 1] == 0) { blockimage.setrgb(i, j, color.white.getrgb()); waterimage.setrgb(i, j, color.white.getrgb()); } } //这里把背景设为透明 else { blockimage.setrgb(i, j, color.translucent); waterimage.setrgb(i, j, color.translucent); } } catch (arrayindexoutofboundsexception e) { //防止数组下标越界异常 } } } //在画布上添加阻塞块水印 addblockwatermark(canvasimage, waterimage, blockx, blocky); } /** * 构建拼图轮廓轨迹 **/ private static int[][] getblockdata(int blockwidth, int blockheight, int blockradius) { int[][] data = new int[blockwidth][blockheight]; double po = math.pow(blockradius, 2); //随机生成两个圆的坐标,在4个方向上 随机找到2个方向添加凸/凹 //凸/凹1 random random1 = new random(); int face1 = random1.nextint(4); //凸/凹2 int face2; //保证两个凸/凹不在同一位置 do { random random2 = new random(); face2 = random2.nextint(4); } while (face1 == face2); //获取凸/凹起位置坐标 int[] circle1 = getcirclecoords(face1, blockwidth, blockheight, blockradius); int[] circle2 = getcirclecoords(face2, blockwidth, blockheight, blockradius); //随机凸/凹类型 int shape = getnoncebyrange(0, 1); //圆的标准方程 (x-a)²+(y-b)²=r²,标识圆心(a,b),半径为r的圆 //计算需要的小图轮廓,用二维数组来表示,二维数组有两张值,0和1,其中0表示没有颜色,1有颜色 for (int i = 0; i < blockwidth; i++) { for (int j = 0; j < blockheight; j++) { data[i][j] = 0; //创建中间的方形区域 if ((i >= blockradius && i <= blockwidth - blockradius && j >= blockradius && j <= blockheight - blockradius)) { data[i][j] = 1; } double d1 = math.pow(i - objects.requirenonnull(circle1)[0], 2) + math.pow(j - circle1[1], 2); double d2 = math.pow(i - objects.requirenonnull(circle2)[0], 2) + math.pow(j - circle2[1], 2); //创建两个凸/凹 if (d1 <= po || d2 <= po) { data[i][j] = shape; } } } return data; } /** * 根据朝向获取圆心坐标 */ private static int[] getcirclecoords(int face, int blockwidth, int blockheight, int blockradius) { //上 if (0 == face) { return new int[]{blockwidth / 2 - 1, blockradius}; } //左 else if (1 == face) { return new int[]{blockradius, blockheight / 2 - 1}; } //下 else if (2 == face) { return new int[]{blockwidth / 2 - 1, blockheight - blockradius - 1}; } //右 else if (3 == face) { return new int[]{blockwidth - blockradius - 1, blockheight / 2 - 1}; } return null; } /** * 在画布上添加阻塞块水印 */ private static void addblockwatermark(bufferedimage canvasimage, bufferedimage blockimage, int x, int y) { graphics2d graphics2d = canvasimage.creategraphics(); graphics2d.setcomposite(alphacomposite.getinstance(alphacomposite.src_atop, 0.8f)); graphics2d.drawimage(blockimage, x, y, null); graphics2d.dispose(); } /** * bufferedimage转base64 */ public static string tobase64(bufferedimage bufferedimage, string type) { try { bytearrayoutputstream bytearrayoutputstream = new bytearrayoutputstream(); imageio.write(bufferedimage, type, bytearrayoutputstream); string base64 = base64.getencoder().encodetostring(bytearrayoutputstream.tobytearray()); return string.format("data:image/%s;base64,%s", type, base64); } catch (ioexception e) { system.out.println("图片资源转换base64失败"); //异常处理 return null; } } }
2.5 滑动验证码service
service层,封装了工具类的一些方法,这里为了简单没写接口,正常应该是service和impl两个类。
package com.thcb.captchademo.captcha.service; import com.thcb.captchademo.captcha.domain.captcha; import com.thcb.captchademo.captcha.utils.captchautils; import org.springframework.beans.factory.annotation.autowired; import org.springframework.data.redis.core.stringredistemplate; import org.springframework.data.redis.core.valueoperations; import org.springframework.stereotype.service; import java.awt.image.bufferedimage; import java.util.uuid; import java.util.concurrent.timeunit; /** * 滑动验证码service * * @author thcb * @date 2023-05-25 */ @service public class captchaservice { /** * 拼图验证码允许偏差 **/ private static integer allow_deviation = 3; @autowired private stringredistemplate stringredistemplate; /** * 校验验证码 * * @param imagekey * @param imagecode * @return boolean **/ public string checkimagecode(string imagekey, string imagecode) { valueoperations<string, string> ops = stringredistemplate.opsforvalue(); string key = "imagecode:" + imagekey; string text = ops.get(key); if (text == null || text.equals("")) { return "验证码已失效"; } // 根据移动距离判断验证是否成功 if (math.abs(integer.parseint(text) - integer.parseint(imagecode)) > allow_deviation) { return "验证失败,请控制拼图对齐缺口"; } // 验证成功,删除redis缓存 stringredistemplate.delete(key); return null; } /** * 缓存验证码,有效期1分钟 * * @param key * @param code **/ public void saveimagecode(string key, string code) { valueoperations<string, string> ops = stringredistemplate.opsforvalue(); ops.set("imagecode:" + key, code, 60, timeunit.seconds); } /** * 获取验证码拼图(生成的抠图和带抠图阴影的大图及抠图坐标) **/ public object getcaptcha(captcha captcha) { //参数校验 captchautils.checkcaptcha(captcha); //获取画布的宽高 int canvaswidth = captcha.getcanvaswidth(); int canvasheight = captcha.getcanvasheight(); //获取阻塞块的宽高/半径 int blockwidth = captcha.getblockwidth(); int blockheight = captcha.getblockheight(); int blockradius = captcha.getblockradius(); //获取资源图 bufferedimage canvasimage = captchautils.getbufferedimage(captcha.getplace()); //调整原图到指定大小 canvasimage = captchautils.imageresize(canvasimage, canvaswidth, canvasheight); //随机生成阻塞块坐标 int blockx = captchautils.getnoncebyrange(blockwidth, canvaswidth - blockwidth - 10); int blocky = captchautils.getnoncebyrange(10, canvasheight - blockheight + 1); //阻塞块 bufferedimage blockimage = new bufferedimage(blockwidth, blockheight, bufferedimage.type_4byte_abgr); //新建的图像根据轮廓图颜色赋值,源图生成遮罩 captchautils.cutbytemplate(canvasimage, blockimage, blockwidth, blockheight, blockradius, blockx, blocky); // 移动横坐标 string noncestr = uuid.randomuuid().tostring().replaceall("-", ""); // 缓存 saveimagecode(noncestr, string.valueof(blockx)); //设置返回参数 captcha.setnoncestr(noncestr); captcha.setblocky(blocky); captcha.setblocksrc(captchautils.tobase64(blockimage, "png")); captcha.setcanvassrc(captchautils.tobase64(canvasimage, "png")); return captcha; } }
2.6 滑动验证码controller
controller层有两个方法,一个是获取验证码的方法,一个是验证码校验的方法。
package com.thcb.captchademo.captcha.controller; import com.thcb.captchademo.captcha.domain.captcha; import com.thcb.captchademo.captcha.domain.loginvo; import com.thcb.captchademo.captcha.service.captchaservice; import com.thcb.captchademo.captcha.utils.ajaxresult; import org.springframework.beans.factory.annotation.autowired; import org.springframework.web.bind.annotation.postmapping; import org.springframework.web.bind.annotation.requestbody; import org.springframework.web.bind.annotation.requestmapping; import org.springframework.web.bind.annotation.restcontroller; /** * 滑动验证码controller * * @author thcb * @date 2023-05-25 */ @restcontroller @requestmapping("/captcha") public class captchacontroller { @autowired private captchaservice captchaservice; @postmapping("/slidecaptchaimage") public ajaxresult getcaptcha() { return ajaxresult.success(captchaservice.getcaptcha(new captcha())); } @postmapping(value = "/login") public ajaxresult login(@requestbody loginvo loginvo) { // 验证码校验 string msg = captchaservice.checkimagecode(loginvo.getnoncestr(), loginvo.getvalue()); if (msg != null && !msg.equals("")) { return ajaxresult.error(msg); } return ajaxresult.success(); } }
3 总结
滑动验证码功能不算复杂,可以和项目当前已有的验证码共存,调用不同的接口,返回不同类型的验证码,当然这个就根据项目具体情况确定了。
以上就是spring框架实现滑动验证码功能的代码示例的详细内容,更多关于spring滑动验证码功能的资料请关注代码网其它相关文章!
发表评论