java实现图片滑动验证(包含前端代码)
前言
1、下面是一个效果展示;
2、先抱怨一下,在博客上面的抄袭真的非常严重,为了实现一个图片滑动验证,我搜索了挺久的资料,不过内容翻来覆去就是同样的内容,千篇一律,作者还各不相同;内容相同我就不多说了,毕竟能解决问题就行,然而恰恰相反,这些东西都没有为我实质性地解决问题。可能图片验证是一个需要前后台同时交互的功能吧,从业的人员大部分都是偏向后台或者偏向前台的,所以写出来的博客都不能完整阐述整个流程,下面是我自己实践完成的内容,记录一下,供各位参阅斧正。
注:由于使用到的控件和工具较多,有许多地方做了省略,这里只做核心流程的记录。
一、后端图片裁剪与生成
首先是一个图片处理工具VerifyImageUtil.class,它主要的作用是生成两张图片:一张被扣除了一部分的原始图片;一张抠出来图片。两两结合,可以组成一张完整的图片。原始图片(target目录)提供了20张,规格都是590*360的;抠图需要的模板图(template目录)有4张,规格都是93*360的(图片等各种资源会在文末给出)。将图片资源导入到我们项目的静态资源路径下(你也可以通过其他方式存储它们),我这边是SpringBoot的项目,所以就放在resource下的static目录下了:
下面是 VerifyImageUtil.class
packagecom.mine.risk.util;
importorg.apache.commons.lang.StringUtils;
importjavax.imageio.ImageIO;
importjavax.imageio.ImageReadParam;
importjavax.imageio.ImageReader;
importjavax.imageio.stream.ImageInputStream;
importjava.awt.*;
importjava.awt.image.BufferedImage;
importjava.io.ByteArrayOutputStream;
importjava.io.File;
importjava.io.FileInputStream;
importjava.io.InputStream;
importjava.text.NumberFormat;
importjava.util.HashMap;
importjava.util.Iterator;
importjava.util.Map;
importjava.util.Random;
/**
*滑块验证工具类
*@author:spirit
*@date:Createdin10:572019/9/05
*/
publicclassVerifyImageUtil{
/**源文件宽度*/
privatestaticfinalintORI_WIDTH=590;
/**源文件高度*/
privatestaticfinalintORI_HEIGHT=360;
/**抠图坐标x*/
privatestaticintX;
/**抠图坐标y*/
privatestaticintY;
/**模板图宽度*/
privatestaticintWIDTH;
/**模板图高度*/
privatestaticintHEIGHT;
publicstaticintgetX(){
returnX;
}
publicstaticintgetY(){
returnY;
}
/**
*根据模板切图
*@paramtemplateFile模板文件
*@paramtargetFile目标文件
*@paramtemplateType模板文件类型
*@paramtargetType目标文件类型
*@return切图map集合
*@throwsException异常
*/
publicstaticMappictureTemplatesCut(FiletemplateFile,FiletargetFile,StringtemplateType,StringtargetType)throwsException{
MappictureMap=newHashMap<>(2);
if(StringUtils.isEmpty(templateType)||StringUtils.isEmpty(targetType)){
thrownewRuntimeException("filetypeisempty");
}
InputStreamtargetIs=newFileInputStream(targetFile);
//模板图
BufferedImageimageTemplate=ImageIO.read(templateFile);
WIDTH=imageTemplate.getWidth();
HEIGHT=imageTemplate.getHeight();
//随机生成抠图坐标
generateCutoutCoordinates();
//最终图像
BufferedImagenewImage=newBufferedImage(WIDTH,HEIGHT,imageTemplate.getType());
Graphics2Dgraphics=newImage.createGraphics();
graphics.setBackground(Color.white);
intbold=5;
//获取感兴趣的目标区域
BufferedImagetargetImageNoDeal=getTargetArea(X,Y,WIDTH,HEIGHT,targetIs,targetType);
//根据模板图片抠图
newImage=dealCutPictureByTemplate(targetImageNoDeal,imageTemplate,newImage);
//设置“抗锯齿”的属性
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
graphics.setStroke(newBasicStroke(bold,BasicStroke.CAP_BUTT,BasicStroke.JOIN_BEVEL));
graphics.drawImage(newImage,0,0,null);
graphics.dispose();
//新建流。
ByteArrayOutputStreamos=newByteArrayOutputStream();
//利用ImageIO类提供的write方法,将bi以png图片的数据模式写入流。
ImageIO.write(newImage,"png",os);
byte[]newImages=os.toByteArray();
pictureMap.put("newImage",newImages);
//源图生成遮罩
BufferedImageoriImage=ImageIO.read(targetFile);
byte[]oriCopyImages=dealOriPictureByTemplate(oriImage,imageTemplate,X,Y);
pictureMap.put("oriCopyImage",oriCopyImages);
System.out.println("X="+X+";y="+Y);
returnpictureMap;
}
/**
*抠图后原图生成
*@paramoriImage原始图片
*@paramtemplateImage模板图片
*@paramx坐标X
*@paramy坐标Y
*@return添加遮罩层后的原始图片
*@throwsException异常
*/
privatestaticbyte[]dealOriPictureByTemplate(BufferedImageoriImage,BufferedImagetemplateImage,intx,
inty)throwsException{
//源文件备份图像矩阵支持alpha通道的rgb图像
BufferedImageoriCopyImage=newBufferedImage(oriImage.getWidth(),oriImage.getHeight(),BufferedImage.TYPE_4BYTE_ABGR);
//源文件图像矩阵
int[][]oriImageData=getData(oriImage);
//模板图像矩阵
int[][]templateImageData=getData(templateImage);
//copy源图做不透明处理
for(inti=0;i>8));
intb=(0xff&(rgb>>16));
//无透明处理
rgb=r+(g<<8)+(b<<16)+(255<<24);
oriCopyImage.setRGB(i,j,rgb);
}
}
for(inti=0;i>8));
intb=(0xff&(rgb_ori>>16));
rgb_ori=r+(g<<8)+(b<<16)+(150<<24);
oriCopyImage.setRGB(x+i,y+j,rgb_ori);
}else{
//donothing
}
}
}
//新建流
ByteArrayOutputStreamos=newByteArrayOutputStream();
//利用ImageIO类提供的write方法,将bi以png图片的数据模式写入流
ImageIO.write(oriCopyImage,"png",os);
//从流中获取数据数组
returnos.toByteArray();
}
/**
*根据模板图片抠图
*@paramoriImage原始图片
*@paramtemplateImage模板图片
*@return扣了图片之后的原始图片
*/
privatestaticBufferedImagedealCutPictureByTemplate(BufferedImageoriImage,BufferedImagetemplateImage,
BufferedImagetargetImage)throwsException{
//源文件图像矩阵
int[][]oriImageData=getData(oriImage);
//模板图像矩阵
int[][]templateImageData=getData(templateImage);
//模板图像宽度
for(inti=0;iimageReaderList=ImageIO.getImageReadersByFormatName(fileType);
ImageReaderimageReader=imageReaderList.next();
//获取图片流
ImageInputStreamiis=ImageIO.createImageInputStream(ois);
//输入源中的图像将只按顺序读取
imageReader.setInput(iis,true);
ImageReadParamparam=imageReader.getDefaultReadParam();
Rectanglerec=newRectangle(x,y,targetWidth,targetHeight);
param.setSourceRegion(rec);
returnimageReader.read(0,param);
}
/**
*生成图像矩阵
*@parambufferedImage图片流
*@return图像矩阵
*/
privatestaticint[][]getData(BufferedImagebufferedImage){
int[][]data=newint[bufferedImage.getWidth()][bufferedImage.getHeight()];
for(inti=0;i
有了工具类,就可以开始生成图片内容了,我这边直接在Spring的控制器生成内容并返回
@RequestMapping("createImgValidate")
@ResponseBody
publicMessagecreateImgValidate(SmsVerificationCodeVovo){
try{
IntegertemplateNum=newRandom().nextInt(4)+1;
IntegertargetNum=newRandom().nextInt(20)+1;
FiletemplateFile=ResourceUtils.getFile("classpath:static/images/validate/template/"+templateNum+".png");
FiletargetFile=ResourceUtils.getFile("classpath:static/images/validate/target/"+targetNum+".jpg");
MappictureMap=VerifyImageUtil.pictureTemplatesCut(templateFile,targetFile,
ConstString.IMAGE_TYPE_PNG,ConstString.IMAGE_TYPE_JPG);
//将生成的偏移位置信息设置到redis中
Stringkey=ConstString.WEB_VALID_IMAGE_PREFIX+vo.getTelephone();
booleanverified=redisUtil.exists(key);
if(verified){
redisUtil.del(key);
}
redisUtil.set(key,(VerifyImageUtil.getX()+67)+"",SmsUtil.VALID_IMAGE_TIMEOUT);
returnResponseUtil.success(pictureMap);
}catch(Exceptione){
e.printStackTrace();
returnResponseUtil.info(ResponseEnum.BUSINESS_ERROR);
}
}
基本的逻辑是从静态资源中随机加载一张target图片和一张template图片,放到图片处理工具中,处理并返回我们需要的两张图片,生成图片以后,就可以直接返回这个Map了,它会以base64的方式返回到浏览器端。在这里,偏移的位置信息属于敏感内容,它会参与前台传入偏移量的对比校验,所以我这里存到了redis中,返回的内容也就是Map,只不过我用了一个自定义的返回辅助方法(有兴趣的人也可以找我要这些辅助工具)。
二、前端展示图片
首先还是需要在SpringBoot对应的控制器中,加入生成视图的代码(我做图片滑动验证主要为了在发送手机验证码之前做校验,所以有一个手机号的参数)。
/**
*跳转到图片验证界面
*@return图片验证界面
*/
@RequestMapping("imgValidate")
publicStringtoImgValidate(ModelMapmap,Stringtelephone){
map.addAttribute("telephone",telephone);
return"component/imageValidate";
}
之后便是我们的HTML页码代码:imageValidate.html
图片验证