天天看點

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-占比)進行算出

以上解決方案都是閉着眼吹牛,請不要相信。。。。。。

繼續閱讀