在大多数系统中都会有一个防止机器爆破使用某个功能接口,比如有登录、发送短信、下载导出等等,这些功能通常比较敏感的并且发生频率不允许太高,而为了防止机器循环爆破或者用户误触,所以需要在发生功能事件做一个防机器拦截,而如今大多数是使用拖动滑块图片验证来实现其目地的;拖动滑块图片验证中滑块每次所需正确位置完全随机,背景图片也将随机刷新,每次滑错都将重置滑块正确位置和背景图片,使得机器无法自动识别并且暴力破解,至此实现了其功能。
本文章将用Java语言实现拖动滑块图片验证
1、静态文件
首先准备好静态文件放在项目的resource静态目录,需要的背景图、滑块交互js、样式css、以及ui组件的图片
2、渲染滑块界面接口
该接口需要返回4个参数纵轴坐标Y(本功能只修改横坐标)、背景图imgBg、背景抠图imgBtn、以及标识tokenId;
/**
* 验证码生成
* @return
* @throws IOException
*/
Result verifyCodeInit() throws IOException;
业务具体实现:
@Override
public Result verifyCodeInit() throws IOException {
Map<String,Object> result = new HashMap<>();
/*本地缓存实现*/
List<byte[]> imgList = null;
// VerifyEntity verify = redisTemplate.get( KEY_VALIDATE_IMG,VerifyEntity.class);
// if (!ObjectUtil.ObjectIsNull(verify)) {
// imgList = verify.getImgList();
// }
//
// if (CollectionUtils.isEmpty(imgList)) {
// imgList = initFileByte();
// VerifyEntity verifyEntity = new VerifyEntity();
// verifyEntity.setImgList(imgList);
// redisTemplate.set(KEY_VALIDATE_IMG, verifyEntity);
// }
//加载内存中的图片
imgList = DEFAULT_IMG_LIST;
//随机取出一张验证图
Random ra = new Random();
int rd = ra.nextInt(imgList.size() - 1);
byte[] targetIs = imgList.get(rd);
//生成验证码
PuzzleCaptcha puzzleCaptcha = new PuzzleCaptcha(targetIs);
puzzleCaptcha.setImageQuality(Image.SCALE_AREA_AVERAGING);
puzzleCaptcha.run();
//抠块图
String imgBtn = ImageConvertUtil.toDataUri(puzzleCaptcha.getVacancy(), "png");
//背景图
String imgBg = ImageConvertUtil.toDataUri(puzzleCaptcha.getArtwork(), "png");
String token = UUID.randomUUID().toString().replaceAll("-", "");
Map<String, Object> cacheObj = new HashMap<>(5);
cacheObj.put("token", token);
//偏移量
cacheObj.put("X", puzzleCaptcha.getX());
cacheObj.put("Y", puzzleCaptcha.getY());
//验证起始时间
cacheObj.put("time", System.currentTimeMillis());
//保存验证状态
cacheObj.put("verifyCount", 0);
//保存2分钟
redisKit.set(KEY_VALIDATE_TOKEN+":"+token, cacheObj, 120);
result.put("imgBtn", imgBtn);
result.put("imgBg", imgBg);
result.put("tokenId", token);
result.put("Y", puzzleCaptcha.getY());
return Result.me().success().setData(result);
}
初始化背景图片的方法:
private static List<byte[]> DEFAULT_IMG_LIST;
static {
try {
DEFAULT_IMG_LIST = initFileByte();
//未加载到图片或缓存被外部清除
if (DEFAULT_IMG_LIST == null || DEFAULT_IMG_LIST.size() < 1) {
//重新加载
DEFAULT_IMG_LIST = initFileByteNew();
}
} catch (IOException e) {
System.out.println("加载背景图失败");
}
}
/**
* 读取文件字节流
*/
private static List<byte[]> initFileByte() throws IOException {
//图片存储路径在resource/static/verify_imgs包下
String imgPath = "META-INF/static/app/images/verify_imgs";
URL url = Thread.currentThread().getContextClassLoader().getResource(imgPath);
if (url != null) {
String protocol = url.getProtocol();
System.out.println("protocol ======> " + protocol);
return "jar".equalsIgnoreCase(protocol) ? initJarImg(url) : initClassImg(url);
}
return null;
}
/**
* 加载项目资源图片文件
*
* @param url
* @return
*/
private static List<byte[]> initClassImg(URL url) throws IOException {
if (url != null) {
File fileDir = new File(url.getPath());
File[] fs = fileDir.listFiles();
if (fs != null && fs.length > 0) {
List<byte[]> byteList = new ArrayList<>();
byte[] bytes;
for (File f : fs) {
bytes = IOUtils.toByteArray(new FileInputStream(f));
byteList.add(bytes);
}
return byteList;
}
}
return null;
}
/**
* 加载jar包资源图片文件
*
* @param url
* @return
*/
private static List<byte[]> initJarImg(URL url) throws IOException {
DSFLog.logDebug(url);
if (url != null) {
DSFLog.info("再执行读取jar包文件方法 url="+url);
String jarPath = url.getPath().substring(0, url.getPath().indexOf("!/") + 2);
URL jarUrl = new URL("jar:" + jarPath);
JarURLConnection jarCon = (JarURLConnection) jarUrl.openConnection();
if (jarCon != null) {
DSFLog.info("JarURLConnection 成功"+jarCon);
JarFile jarFile = jarCon.getJarFile();
Enumeration<JarEntry> jarEntrys = jarFile.entries();
byte[] bytes;
List<byte[]> byteList = new ArrayList<>();
while (jarEntrys.hasMoreElements()) {
JarEntry entry = jarEntrys.nextElement();
String name = entry.getName();
//打包jar后的图片资源地址
if (name.startsWith("META-INF/classes/static/verify_imgs") && name.contains(".jpg")) {
bytes = IOUtils.toByteArray(ImgValidationController.class.getClassLoader().getResourceAsStream(name));
byteList.add(bytes);
}
}
return byteList;
}else{
DSFLog.info("JarURLConnection 失败"+jarCon);
}
}
return null;
}
/**
* 读取文件字节流
*/
private static List<byte[]> initFileByteNew() throws IOException {
//图片存储路径在resource/static/verify_imgs包下
List<byte[]> allImg = new ArrayList<>();;
for (int i = 1; i <= 10; i++) {
try {
String imgUrl = VerifyServiceImpl.class.getResource("/").getPath().concat("templates/verify_imgs/").concat(String.valueOf(i)).concat(".jpg");
InputStream inputStream = new FileInputStream(imgUrl);
byte[] imgByte = convert2ByteArray(inputStream) ;
allImg.add(imgByte);
}catch (Exception e){
String imgUrl = "templates/verify_imgs/" + i + ".jpg";
InputStream inputStream = VerifyServiceImpl.class.getClassLoader().getResourceAsStream(imgUrl);
byte[] imgByte = convert2ByteArray(inputStream) ;
allImg.add(imgByte);
}
}
return allImg;
}
PuzzleCaptcha实体类
public class PuzzleCaptcha {
/** 默认宽度,用来计算阴影基本长度 */
private static final int DEFAULT_WIDTH = 350;
/** 随机数 */
private static final Random RANDOM = new Random();
/** 蒙版 */
private static Color color = new Color(108, 104, 220, 204);
/** alpha通道过滤器 */
private static InvertAlphaFilter alphaFilter = new InvertAlphaFilter();
/** 边距 */
private static int margin = 50;
/** 生成图片的宽度 */
private int width = DEFAULT_WIDTH;
/** 生成图片高度 */
private int height = 160;
/** x轴的坐标,由算法决定 */
private int x;
/** y轴的坐标,由算法决定 */
private int y;
/** 拼图长宽 */
private int vwh = 10 * 3;
/** 原图 */
private Image image;
/** 大图 */
private Image artwork;
/** 小图 */
private Image vacancy;
/** 是否注重速度 */
private boolean isFast = true;
/** 小图描边颜色 */
private Color vacancyBorderColor = new Color(250, 252, 200, 255);
/** 小图描边线条的宽度 */
private float vacancyBorderWidth = 3f;
/** 主图描边的颜色 */
private Color artworkBorderColor;
/** 主图描边线条的宽度 */
private float artworkBorderWidth = 5f;
/**
* 最高放大倍数,合理的放大倍数可以使图像平滑且提高渲染速度
* 当isFast为false时,此属性生效
* 放大倍数越高,生成的图像越平滑,受原始图片大小的影响。
*/
private double maxRatio = 2;
/**
* 画质
*
* @see Image#SCALE_DEFAULT
* @see Image#SCALE_FAST
* @see Image#SCALE_SMOOTH
* @see Image#SCALE_REPLICATE
* @see Image#SCALE_AREA_AVERAGING
*/
private int imageQuality = Image.SCALE_DEFAULT;
/**
* 从文件中读取图片
*
* @param file
*/
// public PuzzleCaptcha(File file) {
// try {
// image = ImageIO.read(new ByteArrayInputStream(bytes));
// } catch (IOException var2) {
// throw new IORuntimeException(var2);
// }
// }
/**
* 从文件中读取图片,请使用绝对路径,使用相对路径会相对于ClassPath
*
* @param imageFilePath
*/
// public PuzzleCaptcha(String imageFilePath) {
// image = ImgUtil.read(imageFilePath);
// }
/**
* 从{@link Resource}中读取图片
*
* @param resource
*/
// public PuzzleCaptcha(Resource resource) {
// image = ImgUtil.read(resource);
// }
/**
* 从流中读取图片
*
* @param imageStream
*/
// public PuzzleCaptcha(InputStream imageStream) {
// image = ImgUtil.read(imageStream);
// }
/**
* 从图片流中读取图片
*
* @param imageStream
*/
// public PuzzleCaptcha(ImageInputStream imageStream) {
// image = ImgUtil.read(imageStream);
// }
/**
* 加载图片
*
* @param image
*/
// public PuzzleCaptcha(Image image) {
// this.image = image;
// }
/**
* 加载图片
*
* @param bytes
*/
public PuzzleCaptcha(byte[] bytes) {
try {
this.image = ImageIO.read(new ByteArrayInputStream(bytes));
} catch (IOException var2) {
throw new IORuntimeException(var2);
}
try {
this.image = ImageIO.read(new ByteArrayInputStream(bytes));
} catch (IOException var2) {
throw new IORuntimeException(var2);
}
}
/**
* 生成随机x、y坐标
*/
private void init() {
if (x == 0 || y == 0) {
this.x = random(vwh, this.width - vwh - margin);
this.y = random(margin, this.height - vwh - margin);
}
}
/**
* 执行
*/
public void run() {
init();
// 缩略图
Image thumbnail;
GeneralPath path;
int realW = image.getWidth(null);
int realH = image.getHeight(null);
int w = realW, h = realH;
double wScale = 1, hScale = 1;
// 如果原始图片比执行的图片还小,则先拉伸再裁剪
boolean isFast = this.isFast || w < this.width || h < this.height;
if (isFast) {
// 缩放,使用平滑模式
thumbnail = image.getScaledInstance(width, height, imageQuality);
path = paintBrick(1, 1);
w = this.width;
h = this.height;
} else {
// 缩小到一定的宽高,保证裁剪的圆润
boolean flag = false;
if (realW > width * maxRatio) {
// 不超过最大倍数且不超过原始图片的宽
w = Math.min((int) (width * maxRatio), realW);
flag = true;
}
if (realH > height * maxRatio) {
h = Math.min((int) (height * maxRatio), realH);
flag = true;
}
if (flag) {
// 若放大倍数生效,则缩小图片至最高放大倍数,再进行裁剪
thumbnail = image.getScaledInstance(w, h, imageQuality);
} else {
thumbnail = image;
}
hScale = NumberUtil.div(h, height);
wScale = NumberUtil.div(w, width);
path = paintBrick(wScale, hScale);
}
// 创建阴影过滤器
float radius = 5 * ((float) w / DEFAULT_WIDTH) * (float) wScale;
int left = 1;
ShadowFilter shadowFilter = new ShadowFilter(radius, 2 * (float) wScale, -1 * (float) hScale, 0.8f);
// 创建空白的图片
BufferedImage artwork = translucent(new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB));
BufferedImage localVacancy = translucent(new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB));
// 画小图
Graphics2D vg = localVacancy.createGraphics();
// 抗锯齿
vg.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// 设置画图路径范围
vg.setClip(path);
// 将区域中的图像画到小图中
vg.drawImage(thumbnail, null, null);
//描边
if (vacancyBorderColor != null) {
vg.setColor(vacancyBorderColor);
vg.setStroke(new BasicStroke(vacancyBorderWidth));
vg.draw(path);
}
// 释放图像
vg.dispose();
// 画大图
// 创建画笔
Graphics2D g = artwork.createGraphics();
// 抗锯齿
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// 画上图片
g.drawImage(thumbnail, null, null);
// 设置画图路径范围
g.setClip(path);
// 填充缺口透明度 颜色混合,不透明在上
g.setComposite(AlphaComposite.SrcAtop);
// 填充一层白色的透明蒙版,透明度越高,白色越深 alpha:0-255
g.setColor(color);
g.fill(path);
//描边
if (artworkBorderColor != null) {
g.setColor(artworkBorderColor);
g.setStroke(new BasicStroke(artworkBorderWidth));
g.draw(path);
}
// 画上基于小图的内阴影,先反转alpha通道,然后创建阴影
g.drawImage(shadowFilter.filter(alphaFilter.filter(localVacancy, null), null), null, null);
// 释放图像
g.dispose();
// 裁剪掉多余的透明背景
localVacancy = ImageUtils.getSubimage(localVacancy, (int) (x * wScale - left), 0, (int) Math.ceil(path.getBounds().getWidth() + radius) + left, h);
if (isFast) {
// 添加阴影
this.vacancy = shadowFilter.filter(localVacancy, null);
this.artwork = artwork;
} else {
// 小图添加阴影
localVacancy = shadowFilter.filter(localVacancy, null);
// 大图缩放
this.artwork = artwork.getScaledInstance(width, height, imageQuality);
// 缩放时,需要加上阴影的宽度,再除以放大比例
this.vacancy = localVacancy.getScaledInstance((int) ((path.getBounds().getWidth() + radius) / wScale), height, imageQuality);
}
}
/**
* 绘制拼图块的路径
*
* @param xScale x轴放大比例
* @param yScale y轴放大比例
* @return
*/
private GeneralPath paintBrick(double xScale, double yScale) {
double x = this.x * xScale;
double y = this.y * yScale;
// 直线移动的基础距离
double hMoveL = vwh / 3f * yScale;
double wMoveL = vwh / 3f * xScale;
GeneralPath path = new GeneralPath();
path.moveTo(x, y);
path.lineTo(x + wMoveL, y);
// 上面的圆弧正东方向0°,顺时针负数,逆时针正数
path.append(arc(x + wMoveL, y - hMoveL / 2, wMoveL, hMoveL, 180, -180), true);
path.lineTo(x + wMoveL * 3, y);
path.lineTo(x + wMoveL * 3, y + hMoveL);
// 右边的圆弧
path.append(arc(x + wMoveL * 2 + wMoveL / 2, y + hMoveL, wMoveL, hMoveL, 90, -180), true);
path.lineTo(x + wMoveL * 3, y + hMoveL * 3);
path.lineTo(x, y + hMoveL * 3);
path.lineTo(x, y + hMoveL * 2);
// 左边的内圆弧
path.append(arc(x - wMoveL / 2, y + hMoveL, wMoveL, hMoveL, -90, 180), true);
path.lineTo(x, y);
path.closePath();
return path;
}
/**
* 绘制圆形、圆弧或者是椭圆形
* 正东方向0°,顺时针负数,逆时针正数
*
* @param x 左上角的x坐标
* @param y 左上角的y坐标
* @param w 宽
* @param h 高
* @param start 开始的角度
* @param extent 结束的角度
* @return
*/
private Arc2D arc(double x, double y, double w, double h, double start, double extent) {
return new Arc2D.Double(x, y, w, h, start, extent, Arc2D.OPEN);
}
/**
* 透明背景
*
* @param bufferedImage
* @return
*/
private BufferedImage translucent(BufferedImage bufferedImage) {
Graphics2D g = bufferedImage.createGraphics();
bufferedImage = g.getDeviceConfiguration().createCompatibleImage(bufferedImage.getWidth(), bufferedImage.getHeight(), Transparency.TRANSLUCENT);
g.dispose();
return bufferedImage;
}
/**
* 随机数
*
* @param min
* @param max
* @return
*/
private static int random(int min, int max) {
return RANDOM.nextInt((max - min) + 1) + min;
}
public static Color getColor() {
return color;
}
public static void setColor(Color color) {
PuzzleCaptcha.color = color;
}
public static InvertAlphaFilter getAlphaFilter() {
return alphaFilter;
}
public static void setAlphaFilter(InvertAlphaFilter alphaFilter) {
PuzzleCaptcha.alphaFilter = alphaFilter;
}
public static int getMargin() {
return margin;
}
public static void setMargin(int margin) {
PuzzleCaptcha.margin = margin;
}
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
public int getVwh() {
return vwh;
}
public void setVwh(int vwh) {
this.vwh = vwh;
}
public Image getImage() {
return image;
}
public void setImage(Image image) {
this.image = image;
}
public Image getArtwork() {
return artwork;
}
public void setArtwork(Image artwork) {
this.artwork = artwork;
}
public Image getVacancy() {
return vacancy;
}
public void setVacancy(Image vacancy) {
this.vacancy = vacancy;
}
public boolean isFast() {
return isFast;
}
public void setFast(boolean fast) {
isFast = fast;
}
public Color getVacancyBorderColor() {
return vacancyBorderColor;
}
public void setVacancyBorderColor(Color vacancyBorderColor) {
this.vacancyBorderColor = vacancyBorderColor;
}
public float getVacancyBorderWidth() {
return vacancyBorderWidth;
}
public void setVacancyBorderWidth(float vacancyBorderWidth) {
this.vacancyBorderWidth = vacancyBorderWidth;
}
public Color getArtworkBorderColor() {
return artworkBorderColor;
}
public void setArtworkBorderColor(Color artworkBorderColor) {
this.artworkBorderColor = artworkBorderColor;
}
public float getArtworkBorderWidth() {
return artworkBorderWidth;
}
public void setArtworkBorderWidth(float artworkBorderWidth) {
this.artworkBorderWidth = artworkBorderWidth;
}
public double getMaxRatio() {
return maxRatio;
}
public void setMaxRatio(double maxRatio) {
this.maxRatio = maxRatio;
}
public int getImageQuality() {
return imageQuality;
}
public void setImageQuality(int imageQuality) {
this.imageQuality = imageQuality;
}
}
ImageConvertUtil工具类
public class ImageConvertUtil {
/**
* 将image对象转为base64字符串
*
* @param image
* @return
*/
public static String toBase64(Image image, String format) {
BASE64Encoder encoder = new BASE64Encoder();
return encoder.encode(toBytes(image, format));
}
/**
* 将image对象转为前端img标签识别的base64字符串
*
* @param image
* @param format
* @return
*/
public static String toDataUri(Image image, String format) {
return String.format("data:image/%s;base64,%s", format, toBase64(image, format));
}
/**
* 将image对象转为字节
*
* @param image
* @param format
* @return
*/
public static byte[] toBytes(Image image, String format) {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
try {
ImageIO.write(ImageUtils.convertImageToARGB(image), format, stream);
} catch (IOException e) {
e.printStackTrace();
}
return stream.toByteArray();
}
}
3、前端页面上加上调用
html文件
<button type="button" class="getYzm" onclick="showPopup()">获取验证码</button>
js文件
//新版获取滑动验证码 start
// 图片验证
var dataList = ["0","1"];
var codeObj={};
var options = {
dataList: dataList,
success:function(obj){
//动态验证码验证通过回调
hidePopup();
codeObj = obj;
sendCode();
},
fail: function(){
}
};
var SliderBarInfo;
// 弹窗
var overlay = document.getElementById("overlay");
function showPopup(){
if (!$("#loginName").val()) {
dsf.layer.message("请输入用户名!",false);
return;
}
// if (!$("#password").val()) {
// dsf.layer.message("请输入密码!",false);
// return;
// }
SliderBarInfo = SliderBar("slideBar", options);
overlay.classList.add('show')
}
function hidePopup(){
overlay.classList.remove('show')
}
//新版获取滑动验证码 end
此处用到的SliderBar来自slider.js,已上传至绑定资源
滑块验证通过后 发送登录接口再次验证(可以不再验证)
//获取验证码
function sendCode(piccode){
// $(".hxphone").hide();
var phone = $("#loginName").val().trim();
var flag = '';
if($(".right .tab li.active").attr("data-id") == 7){
flag = "stu";
}else if ($(".right .tab li.active").attr("data-id") == 8){
flag = "tea";
}else {
flag = "dw";
}
if(!codeDisabled){
var params = {phone:phone,tokenId:codeObj.tokenId,x:codeObj.x,y:codeObj.y,flag:flag};
codeDisabled = true;
$(".getYzm").attr("disabled","disabled");
dsf.http.request(dsf.url.getWebPath("com/teas/sendsms/sendcode"),params, "GET")
.done(function (res) {
if (res.success) {
top.layer.closeAll();
yzmid = res.data;
dsf.layer.message("验证码已发送,请注意查收",true);
$(".getYzm").text(second+"s后重新获取");
getSjhm(params);
codetimer=setTimeout(function(){
startTime();
},1000);
} else {
dsf.layer.message(res.message);
codeDisabled = false;
$(".getYzm").removeAttr("disabled");
loadrcode();
}
})
.error(function (err) {
dsf.layer.message("发送失败,请联系管理员!");
codeDisabled = false;
$(".getYzm").removeAttr("disabled");
})
.always(function () {
})
.exec();
}
}
4、检查滑块位置是否正确
/**
* 验证码验证
* @param tokenId 验证码id
* @param x
* @param y
* @return
*/
Result checkVerifyCode(String tokenId, Integer x, Integer y);
@Override
public Result checkVerifyCode(String tokenId, Integer x, Integer y) {
JSONObject message = new JSONObject();
int code;
String resultStr;
double time = 0.00;
if (StringUtils.isEmpty(tokenId) || x == null || y == null) {
message.put("code", 0);
// message.put("message", "请求参数错误:参数不能为空");
message.put("message", "验证不通过,请重试!");
return Result.me().error().setData(message).setMessage("验证不通过,请重试!");
}
Map<String, Object> cacheObj = (Map<String, Object>) redisKit.get(KEY_VALIDATE_TOKEN+":"+tokenId);
if (null == cacheObj) {
code = -1;
resultStr = "验证码超期,请重新请求!";
} else {
int sX = (Integer) cacheObj.get("X");
int sY = (Integer) cacheObj.get("Y");
int sStatus = (Integer) cacheObj.get("verifyCount");
if (sY != y) {
code = 0;
// resultStr = "请求参数错误:位置信息不正确!";
resultStr = "验证不通过,请重试!";
} else {
if (Math.abs(sX - x) <= CHECK_SCOPE) {
code = 1;
resultStr = "验证通过!";
time = System.currentTimeMillis() - (Long) cacheObj.get("time");
//更新验证状态,存储60s
cacheObj.put("verifyCount", sStatus + 1);
// redisTemplate.set(KEY_VALIDATE_TOKEN+":"+tokenId, cacheObj, 60);
//存储新token
String token = UUID.randomUUID().toString().replaceAll("-", "");
cacheObj.put("token",token);
redisKit.set(KEY_VALIDATE_TOKEN+":"+token, cacheObj, 120);
message.put("tokenId", token);
} else {
code = 2;
resultStr = "验证不通过,请重试!";
}
}
//删除旧token
redisKit.del(KEY_VALIDATE_TOKEN+":"+tokenId);
}
message.put("time", NumberUtil.div(time * 1.00, 1000.00, 2));
message.put("code", code);
message.put("message", resultStr);
if (code == 1){
return Result.me().success().setData(message);
}else{
return Result.me().error().setData(message).setMessage(resultStr);
}
}
5、业务层全部代码
/**
* <p>
* 验证码信息 服务实现类
* </p>
*/
@Service
public class VerifyServiceImpl implements VerifyService {
@Autowired
private RedisTemplate redisTemplate;
@Autowired
RedisKit redisKit;
/**
* 验证码缓存KEY
*/
private static final String KEY_VERIFY_CODE = "KEY_VERIFY_CODE";
/**
* Token Cache Key
*/
private static final String KEY_VALIDATE_TOKEN = "KEY_VALIDATE_TOKEN";
/**
* Img Cache Key
*/
private static final String KEY_VALIDATE_IMG = "KEY_VALIDATE_IMG";
/**
* 校验误差范围 px
*/
private static final Integer CHECK_SCOPE = 10;
private static List<byte[]> DEFAULT_IMG_LIST;
static {
try {
DEFAULT_IMG_LIST = initFileByte();
//未加载到图片或缓存被外部清除
if (DEFAULT_IMG_LIST == null || DEFAULT_IMG_LIST.size() < 1) {
//重新加载
DEFAULT_IMG_LIST = initFileByteNew();
}
} catch (IOException e) {
System.out.println("加载背景图失败");
}
}
@Override
public Result verifyCodeInit() throws IOException {
Map<String,Object> result = new HashMap<>();
/*本地缓存实现*/
List<byte[]> imgList = null;
// VerifyEntity verify = redisTemplate.get( KEY_VALIDATE_IMG,VerifyEntity.class);
// if (!ObjectUtil.ObjectIsNull(verify)) {
// imgList = verify.getImgList();
// }
//
// if (CollectionUtils.isEmpty(imgList)) {
// imgList = initFileByte();
// VerifyEntity verifyEntity = new VerifyEntity();
// verifyEntity.setImgList(imgList);
// redisTemplate.set(KEY_VALIDATE_IMG, verifyEntity);
// }
//加载内存中的图片
imgList = DEFAULT_IMG_LIST;
//随机取出一张验证图
Random ra = new Random();
int rd = ra.nextInt(imgList.size() - 1);
byte[] targetIs = imgList.get(rd);
//生成验证码
PuzzleCaptcha puzzleCaptcha = new PuzzleCaptcha(targetIs);
puzzleCaptcha.setImageQuality(Image.SCALE_AREA_AVERAGING);
puzzleCaptcha.run();
//抠块图
String imgBtn = ImageConvertUtil.toDataUri(puzzleCaptcha.getVacancy(), "png");
//背景图
String imgBg = ImageConvertUtil.toDataUri(puzzleCaptcha.getArtwork(), "png");
String token = UUID.randomUUID().toString().replaceAll("-", "");
Map<String, Object> cacheObj = new HashMap<>(5);
cacheObj.put("token", token);
//偏移量
cacheObj.put("X", puzzleCaptcha.getX());
cacheObj.put("Y", puzzleCaptcha.getY());
//验证起始时间
cacheObj.put("time", System.currentTimeMillis());
//保存验证状态
cacheObj.put("verifyCount", 0);
//保存2分钟
redisKit.set(KEY_VALIDATE_TOKEN+":"+token, cacheObj, 120);
result.put("imgBtn", imgBtn);
result.put("imgBg", imgBg);
result.put("tokenId", token);
result.put("Y", puzzleCaptcha.getY());
return Result.me().success().setData(result);
}
@Override
public Result checkVerifyCode(String tokenId, Integer x, Integer y) {
JSONObject message = new JSONObject();
int code;
String resultStr;
double time = 0.00;
if (StringUtils.isEmpty(tokenId) || x == null || y == null) {
message.put("code", 0);
// message.put("message", "请求参数错误:参数不能为空");
message.put("message", "验证不通过,请重试!");
return Result.me().error().setData(message).setMessage("验证不通过,请重试!");
}
Map<String, Object> cacheObj = (Map<String, Object>) redisKit.get(KEY_VALIDATE_TOKEN+":"+tokenId);
if (null == cacheObj) {
code = -1;
resultStr = "验证码超期,请重新请求!";
} else {
int sX = (Integer) cacheObj.get("X");
int sY = (Integer) cacheObj.get("Y");
int sStatus = (Integer) cacheObj.get("verifyCount");
if (sY != y) {
code = 0;
// resultStr = "请求参数错误:位置信息不正确!";
resultStr = "验证不通过,请重试!";
} else {
if (Math.abs(sX - x) <= CHECK_SCOPE) {
code = 1;
resultStr = "验证通过!";
time = System.currentTimeMillis() - (Long) cacheObj.get("time");
//更新验证状态,存储60s
cacheObj.put("verifyCount", sStatus + 1);
// redisTemplate.set(KEY_VALIDATE_TOKEN+":"+tokenId, cacheObj, 60);
//存储新token
String token = UUID.randomUUID().toString().replaceAll("-", "");
cacheObj.put("token",token);
redisKit.set(KEY_VALIDATE_TOKEN+":"+token, cacheObj, 120);
message.put("tokenId", token);
} else {
code = 2;
resultStr = "验证不通过,请重试!";
}
}
//删除旧token
redisKit.del(KEY_VALIDATE_TOKEN+":"+tokenId);
}
message.put("time", NumberUtil.div(time * 1.00, 1000.00, 2));
message.put("code", code);
message.put("message", resultStr);
if (code == 1){
return Result.me().success().setData(message);
}else{
return Result.me().error().setData(message).setMessage(resultStr);
}
}
/**
* 读取文件字节流
*/
private static List<byte[]> initFileByte() throws IOException {
//图片存储路径在resource/static/verify_imgs包下
String imgPath = "META-INF/static/app/images/verify_imgs";
URL url = Thread.currentThread().getContextClassLoader().getResource(imgPath);
if (url != null) {
String protocol = url.getProtocol();
System.out.println("protocol ======> " + protocol);
return "jar".equalsIgnoreCase(protocol) ? initJarImg(url) : initClassImg(url);
}
return null;
}
/**
* 读取文件字节流
*/
private static List<byte[]> initFileByteNew() throws IOException {
//图片存储路径在resource/static/verify_imgs包下
DSFLog.info("在执行自己写的获取图片方法");
List<byte[]> allImg = new ArrayList<>();;
for (int i = 1; i <= 10; i++) {
try {
String imgUrl = VerifyServiceImpl.class.getResource("/").getPath().concat("templates/verify_imgs/").concat(String.valueOf(i)).concat(".jpg");
InputStream inputStream = new FileInputStream(imgUrl);
byte[] imgByte = convert2ByteArray(inputStream) ;
allImg.add(imgByte);
}catch (Exception e){
String imgUrl = "templates/verify_imgs/" + i + ".jpg";
InputStream inputStream = VerifyServiceImpl.class.getClassLoader().getResourceAsStream(imgUrl);
byte[] imgByte = convert2ByteArray(inputStream) ;
allImg.add(imgByte);
}
}
DSFLog.info("执行自己写的获取图片方法完成,图片数量:"+allImg.size());
return allImg;
}
/**
* inputStream转为byte[]
*
* @param inStream
* @return byte[]
* @throws IOException
*/
public static byte[] convert2ByteArray(InputStream inStream) throws IOException {
ByteArrayOutputStream swapStream = new ByteArrayOutputStream();
byte[] buff = new byte[1024];
int rc = 0;
while ((rc = inStream.read(buff, 0, 1024)) > 0) {
swapStream.write(buff, 0, rc);
}
byte[] in_b = swapStream.toByteArray();// in_b为转换之后的结果
return in_b;
}
/**
* 加载项目资源图片文件
*
* @param url
* @return
*/
private static List<byte[]> initClassImg(URL url) throws IOException {
if (url != null) {
File fileDir = new File(url.getPath());
File[] fs = fileDir.listFiles();
if (fs != null && fs.length > 0) {
List<byte[]> byteList = new ArrayList<>();
byte[] bytes;
for (File f : fs) {
bytes = IOUtils.toByteArray(new FileInputStream(f));
byteList.add(bytes);
}
return byteList;
}
}
return null;
}
/**
* 加载jar包资源图片文件
*
* @param url
* @return
*/
private static List<byte[]> initJarImg(URL url) throws IOException {
DSFLog.logDebug(url);
if (url != null) {
DSFLog.info("再执行读取jar包文件方法 url="+url);
String jarPath = url.getPath().substring(0, url.getPath().indexOf("!/") + 2);
URL jarUrl = new URL("jar:" + jarPath);
JarURLConnection jarCon = (JarURLConnection) jarUrl.openConnection();
if (jarCon != null) {
DSFLog.info("JarURLConnection 成功"+jarCon);
JarFile jarFile = jarCon.getJarFile();
Enumeration<JarEntry> jarEntrys = jarFile.entries();
byte[] bytes;
List<byte[]> byteList = new ArrayList<>();
while (jarEntrys.hasMoreElements()) {
JarEntry entry = jarEntrys.nextElement();
String name = entry.getName();
//打包jar后的图片资源地址
if (name.startsWith("META-INF/classes/static/verify_imgs") && name.contains(".jpg")) {
bytes = IOUtils.toByteArray(ImgValidationController.class.getClassLoader().getResourceAsStream(name));
byteList.add(bytes);
}
}
return byteList;
}else{
DSFLog.info("JarURLConnection 失败"+jarCon);
}
}
return null;
}
}