说明:因为刷新的图标和点击图片文字的时候需要在图片上生成图标,为了美观,所以我引入Font Awesome图标库,如果你需要的话需引入该图标库方可使用
先上验证码效果图:
步骤1:前端
html代码
<div>
<!--每点击一次拼接记录对应坐标-->
<input name="imgCheckInfo" id="imgCheckInfo" type="text"></input>
<!--生成img图像-->
<div class="img" id="dv" >
<img id="showCheckImg" alt="点互式验证"/>
</div>
<!--生成刷新图标-->
<i id="freshIcon" class=" pull-left fa fa-refresh" onclick="refreshImg()"></i>
<!--输出提示信息-->
<span class="pull-left" id="imgCheckText"></span>
</div>
css
<style>
html,body{margin:0px;height:100%;}
#dv{position:relative;width:100%;height:100%;margin:0px auto;}
#imgCheckInfo{display:none}
.img .marker{position:absolute;}
#showCheckImg{width:100%;height:100%}
#freshIcon{color:#3464ff;font-size: 1.5em}
#imgCheckText{font-size:0.6em;color:#ff4546}
</style>
js
//记录点击汉字次数
var checkClickNum=0;
$(document).ready(function() {
refreshImg();
//触发鼠标点击图片事件
document.getElementById('dv').onclick = function (e) {
checkClickNum++;
//当点击次数小于等于3时,进行创建标记以及保存坐标位置
if(checkClickNum<=3){
e = e || window.event;var x = e.offsetX || e.layerX;var y = e.offsetY || e.layerY;
createMarker(x, y);
saveCheckDataInfo(x,y);
}
//如果到第三次点击时,进行发送数据到后台进行处理
if(checkClickNum===3){
var imgCheckInfo='['+$("#imgCheckInfo")[0].innerText.substring(1)+']';
$.ajax({
url: "/ImgCheck/checkImg",
type: "POST",
data: { "imgCheckInfo": imgCheckInfo},
success: function(data) {
if(data.code=="200"){
//将提示信息进行返回
var a=$("#imgCheckText");
a.html(data.msg);
a[0].style.display=null;
}
}
});
}
}
});
//刷新图片
function refreshImg(){
checkClickNum=0;
$("#imgCheckText")[0].style.display="none";
$("#imgCheckInfo")[0].innerText="";
$("i").remove(".marker");
$("#showCheckImg").attr("src","/ImgCheck/getImg?r="+Math.random());
}
//点击时创建一个图标标记
function createMarker(x, y) {
var i = document.createElement('i');
i.className = 'marker fa fa-check-circle '; i.style.left = x-10 + 'px'; i.style.top = y-10 + 'px';
i.style.fontSize="1.5em";
i.style.color="#FFFFFF";
document.getElementById('dv').appendChild(i);
}
//保存点击图片的数据
function saveCheckDataInfo(x, y) {
var imgCheckInfo=$("#imgCheckInfo");
var text=imgCheckInfo[0].innerText;
text+=',['+x+','+y+']'
imgCheckInfo[0].innerText=text;
}
2:后端
controller
import com.vdobs.utils.JsonResult;
import com.vdobs.utils.TouchVerificationUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.HashMap;
import java.util.Random;
@Controller
@RequestMapping("/ImgCheck")
public class ImgCheckController {
private final static Logger logger = LoggerFactory.getLogger(ImgCheckController.class);
private static Random random = new Random();
@ResponseBody
@RequestMapping("/getImg")
public void getImg(HttpServletResponse response, HttpServletRequest request) throws IOException{
HashMap<String,Object> hashMap=TouchVerificationUtil.createImg(6,3,100,555,18,Color.white,"/static/system/login/img/test.png");
request.getSession().setAttribute("correctCheckCode",hashMap.get("correctCheckCode"));
ImageIO.write((BufferedImage)hashMap.get("BufferedImage"), "PNG", response.getOutputStream()); //将图片输出
}
@ResponseBody
@PostMapping("/checkImg")
public JsonResult checkImg(HttpServletRequest request,String imgCheckInfo) {
String correctCheckCode=(String)request.getSession().getAttribute("correctCheckCode");
if (TouchVerificationUtil.checkImg(correctCheckCode,imgCheckInfo)) { //若前端上传的坐标在session中记录的坐标的一定范围内则验证成功
return new JsonResult("200","验证成功!");
} else {
return new JsonResult("200","验证失败,请重新刷新再次验证!");
}
}
}
封装的img验证调用类
import com.alibaba.fastjson.JSONArray;
import org.springframework.util.StringUtils;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Random;
public class TouchVerificationUtil {
private static Random random = new Random();
/**
*
* @param showNum 展示多少个文字
* @param checkNum 验证几个文字
* @param height 图像高度
* @param width 图像宽度
* @param fontSize 文字大小
* @param fontColor 文字颜色
* @param defaultBackGroundImg 背景图片路径
* @return
*/
public static HashMap<String,Object> createImg(int showNum,int checkNum,int height,int width,int fontSize,Color fontColor,String defaultBackGroundImg){
HashMap<String,Object> hashMap=new HashMap<>();
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D g = (Graphics2D) image.getGraphics();
// 读取本地图片,做背景图片
// URL resource = getClass().getResource("/system/login/img/" + (random.nextInt(4) + 1) + ".png");
URL resource = TouchVerificationUtil.class.getResource(defaultBackGroundImg);
try {
g.drawImage(ImageIO.read(new File(resource.getPath())), 0, fontSize, width, height, null); //将背景图片从高度30开始
} catch (IOException e) {
System.out.println(e.getMessage());;
}
g.setColor(Color.WHITE); //设置颜色
g.drawRect(0, 0, width - 1, height - 1); //画边框
g.setFont(new Font("宋体", Font.BOLD, fontSize)); //设置字体
String[] target = new String[checkNum]; // 用于记录文字
StringBuilder correctCheckCode=new StringBuilder(16);
for (int i = 0; i < showNum; i++) { //随机产生8个文字,坐标,颜色都不同
g.setColor(TouchVerificationUtil.getRandColor(100, 160));
String str = TouchVerificationUtil.getRandomChineseChar();
int tempWidth=random.nextInt(width);
int tempHeight=random.nextInt(height);
tempWidth = tempWidth>width-fontSize*3?tempWidth-fontSize*3:tempWidth+fontSize;
tempHeight = tempHeight>height-fontSize*3?(int)(tempHeight-fontSize*1.5):(int)(tempHeight+fontSize*1.5);
if (i<checkNum) {
target[i] = str; //记录文字
correctCheckCode.append(",[").append(tempWidth).append(",").append(tempHeight).append("]");
}
g.drawString(str, tempWidth, tempHeight);
}
g.setColor(fontColor);
//设置字体类型、字体大小、字体样式
g.drawString("请按顺序点击:" + Arrays.toString(target).replaceAll("[\\[\\]\\s]",""), 0, fontSize);
//redisTemplate.opsForValue().set("XXX:" + MD5Utils.encrypt(id), x + ":" + y, 5, TimeUnit.MINUTES);
//5.释放资源
g.dispose();
hashMap.put("BufferedImage",image);
System.out.println(correctCheckCode);
hashMap.put("correctCheckCode","["+correctCheckCode.substring(1)+"]");
return hashMap;
}
/**
*
* @param correctCheckCode 正确的坐标码
* @param imgCheckInfo 前端传到后台的坐标码
* @return
*/
public static Boolean checkImg(String correctCheckCode, String imgCheckInfo) {
Boolean result=true;
if (StringUtils.isEmpty(imgCheckInfo)) {
return false;
}
JSONArray imgCheckInfos=JSONArray.parseArray(imgCheckInfo);
JSONArray correctCheckCodes=JSONArray.parseArray(correctCheckCode);
if (imgCheckInfos.size()!=3) {
return false;
}
for (int i=0;i<imgCheckInfos.size()&&result;i++){
Integer[] correctCheck=correctCheckCodes.getObject(i,Integer[].class);
Integer[] imgCheck=imgCheckInfos.getObject(i,Integer[].class);
int cx=correctCheck[0];
int cy=correctCheck[1];
int x=imgCheck[0];
int y=imgCheck[1];
result=cx- 22 < x && x < cx + 22 && cy - 22 < y && y < cy + 22;
}
return result;
}
/**
* 随机产生颜色
*/
public static Color getRandColor(int fc, int bc) {
if (fc > 255)
fc = 255;
if (bc > 255)
bc = 255;
int r = fc + random.nextInt(bc - fc);
int g = fc + random.nextInt(bc - fc);
int b = fc + random.nextInt(bc - fc);
return new Color(r, g, b);
}
/**
* 随机产生汉字
*/
public static String getRandomChineseChar() {
String str = null;
int heightPos; // 定义高低位
int lowPos;
Random random = new Random();
heightPos = (176 + Math.abs(random.nextInt(39))); //获取高位值
lowPos = (161 + Math.abs(random.nextInt(93))); //获取低位值
byte[] b = new byte[2];
b[0] = (new Integer(heightPos).byteValue());
b[1] = (new Integer(lowPos).byteValue());
try {
str = new String(b, "GBk"); //转成中文
} catch (UnsupportedEncodingException e) {
System.out.println(e.getMessage());;
}
return str;
}
}
/**
* 统一返回对象
* @author shengwu ni
* @param <T>
*/
public class JsonResult<T> {
private T data;
private String code;
private String msg;
/**
* 若没有数据返回,默认状态码为200,提示信息为:操作成功!
*/
public JsonResult() {
this.code = "200";
this.msg = "操作成功!";
}
/**
* 若没有数据返回,可以人为指定状态码和提示信息
* @param code
* @param msg
*/
public JsonResult(String code, String msg) {
this.code = code;
this.msg = msg;
}
/**
* 有数据返回时,状态码为200,默认提示信息为:操作成功!
* @param data
*/
public JsonResult(T data) {
this.data = data;
this.code = "200";
this.msg = "操作成功!";
}
/**
* 有数据返回,状态码为0,人为指定提示信息
* @param data
* @param code
* @param msg
*/
public JsonResult(T data,String code, String msg) {
this.data = data;
this.code = code;
this.msg = msg;
}
/**
* 有数据返回,状态码为0,人为指定提示信息
* @param data
* @param msg
*/
public JsonResult(T data, String msg) {
this.data = data;
this.code = "0";
this.msg = msg;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
最后:说一下该验证码的缺点:1,容易字体重叠,2:偶尔文字超出显示,3:当页面进行缩放时会导致坐标不对应由此验证失败
解决方案:第一个问题:代码记录生成的坐标值进行比较,相差较近的进行加减随机数或再次生成可以解决
第二个问题:设置的img大小以及字体大小都会影响文字的位置显示,需要多轮测试得到最优方案
第三个问题:获取页面伸缩占比,将当前页面的点击位置+当前位置*(1-占比)进行算出
以上解决方案都是闭着眼吹牛,请不要相信。。。。。。