天天看点

springboot文字图片点击验证码

说明:因为刷新的图标和点击图片文字的时候需要在图片上生成图标,为了美观,所以我引入Font Awesome图标库,如果你需要的话需引入该图标库方可使用

先上验证码效果图:

springboot文字图片点击验证码
springboot文字图片点击验证码
springboot文字图片点击验证码

步骤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-占比)进行算出

以上解决方案都是闭着眼吹牛,请不要相信。。。。。。

继续阅读