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滑动验证码功能的资料请关注代码网其它相关文章!
发表评论