图形滑动验证码JAVA实现【前后端结合】

一、为什么要验证码?

        防暴力破解的课题一直是信息安全的重点,漏洞攻击更是开发者的噩梦。。。。

        如果没有经历过这个噩梦,请感受一下被信安部门支配的恐惧。(一大堆漏洞给扫出来,您修代码吧)

二、怎么实现的?

        前端:HTML+JS+CSS   开源组件:Layui

        后端:Java  后端框架:SpringBoot

        思路:

  • 后端随机读取一张图片,利用算法随机“抠图”,返回base64码值到前端 
  • 前端读取到base64,转码生成图片,底部搭配上“滑块”进行拼图匹配
  • 后端取到匹配的误差值,计算,返回前端验证,是否成功

手机模式看看效果:

滑动验证码

三、代码示例

1、Java工具类:class VerifyImageUtil 

用于生成滑动验证码的两个要素

  •  根据原图片裁剪出
  •  裁剪块blockImage;
  •  与裁剪后的原图targetImage
  •  并返回左上角坐标(X,Y)
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Random;


public class VerifyImageUtil {

    /**
     * 源文件宽度
     */
    private static int ORI_WIDTH = 200;
    /**
     * 源文件高度
     */
    private static int ORI_HEIGHT = 116;
    /**
     * 模板图宽度
     */
    private static int CUT_WIDTH = 50;
    /**
     * 模板图高度
     */
    private static int CUT_HEIGHT = 50;
    /**
     * 抠图凸起圆心
     */
    private static int circleR = 5;
    /**
     * 抠图内部矩形填充大小
     */
    private static int RECTANGLE_PADDING = 8;
    /**
     * 抠图的边框宽度
     */
    private static int SLIDER_IMG_OUT_PADDING = 1;


    /**
     * 根据传入的路径生成指定验证码图片
     *
     * @param filePath
     * @return
     * @throws IOException
     */
    public static VerifyImage getVerifyImage(String filePath) throws IOException {
        BufferedImage srcImage = ImageIO.read(new File(filePath));

        int locationX = CUT_WIDTH + new Random().nextInt(srcImage.getWidth() - CUT_WIDTH * 3);
        int locationY = CUT_HEIGHT + new Random().nextInt(srcImage.getHeight() - CUT_HEIGHT) / 2;
        BufferedImage markImage = new BufferedImage(CUT_WIDTH,CUT_HEIGHT,BufferedImage.TYPE_4BYTE_ABGR);
        int[][] data = getBlockData();
        cutImgByTemplate(srcImage, markImage, data, locationX, locationY);
        return new VerifyImage(getImageBASE64(srcImage),getImageBASE64(markImage),locationX,locationY,srcImage.getWidth(),srcImage.getHeight());
    }


    /**
     * 生成随机滑块形状
     * <p>
     * 0 透明像素
     * 1 滑块像素
     * 2 阴影像素
     * @return int[][]
     */
    private static int[][] getBlockData() {
        int[][] data = new int[CUT_WIDTH][CUT_HEIGHT];
        Random random = new Random();
        //(x-a)²+(y-b)²=r²
        //x中心位置左右5像素随机
        double x1 = RECTANGLE_PADDING + (CUT_WIDTH - 2 * RECTANGLE_PADDING) / 2.0 - 5 + random.nextInt(10);
        //y 矩形上边界半径-1像素移动
        double y1_top = RECTANGLE_PADDING - random.nextInt(3);
        double y1_bottom = CUT_HEIGHT - RECTANGLE_PADDING + random.nextInt(3);
        double y1 = random.nextInt(2) == 1 ? y1_top : y1_bottom;


        double x2_right = CUT_WIDTH - RECTANGLE_PADDING - circleR + random.nextInt(2 * circleR - 4);
        double x2_left = RECTANGLE_PADDING + circleR - 2 - random.nextInt(2 * circleR - 4);
        double x2 = random.nextInt(2) == 1 ? x2_right : x2_left;
        double y2 = RECTANGLE_PADDING + (CUT_HEIGHT - 2 * RECTANGLE_PADDING) / 2.0 - 4 + random.nextInt(10);

        double po = Math.pow(circleR, 2);
        for (int i = 0; i < CUT_WIDTH; i++) {
            for (int j = 0; j < CUT_HEIGHT; j++) {
                //矩形区域
                boolean fill;
                if ((i >= RECTANGLE_PADDING && i < CUT_WIDTH - RECTANGLE_PADDING)
                        && (j >= RECTANGLE_PADDING && j < CUT_HEIGHT - RECTANGLE_PADDING)) {
                    data[i][j] = 1;
                    fill = true;
                } else {
                    data[i][j] = 0;
                    fill = false;
                }
                //凸出区域
                double d3 = Math.pow(i - x1, 2) + Math.pow(j - y1, 2);
                if (d3 < po) {
                    data[i][j] = 1;
                } else {
                    if (!fill) {
                        data[i][j] = 0;
                    }
                }
                //凹进区域
                double d4 = Math.pow(i - x2, 2) + Math.pow(j - y2, 2);
                if (d4 < po) {
                    data[i][j] = 0;
                }
            }
        }
        //边界阴影
        for (int i = 0; i < CUT_WIDTH; i++) {
            for (int j = 0; j < CUT_HEIGHT; j++) {
                //四个正方形边角处理
                for (int k = 1; k <= SLIDER_IMG_OUT_PADDING; k++) {
                    //左上、右上
                    if (i >= RECTANGLE_PADDING - k && i < RECTANGLE_PADDING
                            && ((j >= RECTANGLE_PADDING - k && j < RECTANGLE_PADDING)
                            || (j >= CUT_HEIGHT - RECTANGLE_PADDING - k && j < CUT_HEIGHT - RECTANGLE_PADDING +1))) {
                        data[i][j] = 2;
                    }

                    //左下、右下
                    if (i >= CUT_WIDTH - RECTANGLE_PADDING + k - 1 && i < CUT_WIDTH - RECTANGLE_PADDING + 1) {
                        for (int n = 1; n <= SLIDER_IMG_OUT_PADDING; n++) {
                            if (((j >= RECTANGLE_PADDING - n && j < RECTANGLE_PADDING)
                                    || (j >= CUT_HEIGHT - RECTANGLE_PADDING - n && j <= CUT_HEIGHT - RECTANGLE_PADDING ))) {
                                data[i][j] = 2;
                            }
                        }
                    }
                }

                if (data[i][j] == 1 && j - SLIDER_IMG_OUT_PADDING > 0 && data[i][j - SLIDER_IMG_OUT_PADDING] == 0) {
                    data[i][j - SLIDER_IMG_OUT_PADDING] = 2;
                }
                if (data[i][j] == 1 && j + SLIDER_IMG_OUT_PADDING > 0 && j + SLIDER_IMG_OUT_PADDING < CUT_HEIGHT && data[i][j + SLIDER_IMG_OUT_PADDING] == 0) {
                    data[i][j + SLIDER_IMG_OUT_PADDING] = 2;
                }
                if (data[i][j] == 1 && i - SLIDER_IMG_OUT_PADDING > 0 && data[i - SLIDER_IMG_OUT_PADDING][j] == 0) {
                    data[i - SLIDER_IMG_OUT_PADDING][j] = 2;
                }
                if (data[i][j] == 1 && i + SLIDER_IMG_OUT_PADDING > 0 && i + SLIDER_IMG_OUT_PADDING < CUT_WIDTH && data[i + SLIDER_IMG_OUT_PADDING][j] == 0) {
                    data[i + SLIDER_IMG_OUT_PADDING][j] = 2;
                }
            }
        }
        return data;
    }

    /**
     * 裁剪区块
     * 根据生成的滑块形状,对原图和裁剪块进行变色处理
     * @param oriImage    原图
     * @param targetImage 裁剪图
     * @param blockImage  滑块
     * @param x           裁剪点x
     * @param y           裁剪点y
     */
    private static void cutImgByTemplate(BufferedImage oriImage, BufferedImage targetImage, int[][] blockImage, int x, int y) {
        for (int i = 0; i < CUT_WIDTH; i++) {
            for (int j = 0; j < CUT_HEIGHT; j++) {
                int _x = x + i;
                int _y = y + j;
                int rgbFlg = blockImage[i][j];
                int rgb_ori = oriImage.getRGB(_x, _y);
                // 原图中对应位置变色处理
                if (rgbFlg == 1) {
                    //抠图上复制对应颜色值
                    targetImage.setRGB(i,j, rgb_ori);
                    //原图对应位置颜色变化
                    oriImage.setRGB(_x, _y, Color.LIGHT_GRAY.getRGB());
                } else if (rgbFlg == 2) {
                    targetImage.setRGB(i, j, Color.WHITE.getRGB());
                    oriImage.setRGB(_x, _y, Color.GRAY.getRGB());
                }else if(rgbFlg == 0){
                    //int alpha = 0;
                    targetImage.setRGB(i, j, rgb_ori & 0x00ffffff);
                }
            }

        }
    }


    /**
     * 随机获取一张图片对象
     * @param path
     * @return
     * @throws IOException
     */
    public static BufferedImage getRandomImage(String path) throws IOException {
        File files = new File(path);
        File[] fileList = files.listFiles();
        List<String> fileNameList = new ArrayList<>();
        if (fileList!=null && fileList.length!=0){
            for (File tempFile:fileList){
                if (tempFile.isFile() && tempFile.getName().endsWith(".jpg")){
                    fileNameList.add(tempFile.getAbsolutePath().trim());
                }
            }
        }
        Random random = new Random();
        File imageFile = new File(fileNameList.get(random.nextInt(fileNameList.size())));
        return ImageIO.read(imageFile);
    }

    /**
     * 将IMG输出为文件
     * @param image
     * @param file
     * @throws Exception
     */
    public static void writeImg(BufferedImage image, String file) throws Exception {
        byte[] imagedata = null;
        ByteArrayOutputStream bao=new ByteArrayOutputStream();
        ImageIO.write(image,"png",bao);
        imagedata = bao.toByteArray();
        FileOutputStream out = new FileOutputStream(new File(file));
        out.write(imagedata);
        out.close();
    }

    /**
     * 将图片转换为BASE64
     * @param image
     * @return
     * @throws IOException
     */
    public static String getImageBASE64(BufferedImage image) throws IOException {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        ImageIO.write(image,"png",out);
        //转成byte数组
        byte[] bytes = out.toByteArray();
        BASE64Encoder encoder = new BASE64Encoder();
        //生成BASE64编码
        //String showBase64 = encoder.encode(bytes);

        String showBase64 =  Base64.getEncoder().encodeToString(bytes);
//        System.out.println("showBase64:\n"+showBase64+"\n");
        return showBase64;
    }

    /**
     * 将BASE64字符串转换为图片
     * @param base64String
     * @return
     */
    public static BufferedImage base64StringToImage(String base64String) {
        try {
            BASE64Decoder decoder=new BASE64Decoder();
            byte[] bytes1 = decoder.decodeBuffer(base64String);
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes1);
            return ImageIO.read(byteArrayInputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

}

2、实体类:VerifyImage

public class VerifyImage {
    String srcImage;    //原图Base64码值
    String cutImage;    //滑块Base64
    Integer XPosition;  //取滑块的坐标点 (X,Y)
    Integer YPosition;
    Integer srcImageWidth;
    Integer srcImageHeight;

    public VerifyImage(String srcImage, String cutImage, Integer XPosition, Integer YPosition, Integer srcImageWidth, Integer srcImageHeight) {
        this.srcImage = srcImage;
        this.cutImage = cutImage;
        this.XPosition = XPosition;
        this.YPosition = YPosition;
        this.srcImageWidth = srcImageWidth;
        this.srcImageHeight = srcImageHeight;
    }


    public void setCutImage(String cutImage) {
        this.cutImage = cutImage;
    }

    public void setXPosition(Integer XPosition) {
        this.XPosition = XPosition;
    }

    public void setYPosition(Integer YPosition) {
        this.YPosition = YPosition;
    }

    public void setSrcImage(String srcImage) {
        this.srcImage = srcImage;
    }

    public String getSrcImage() {
        return srcImage;
    }

    public Integer getSrcImageWidth() {
        return srcImageWidth;
    }

    public void setSrcImageWidth(Integer srcImageWidth) {
        this.srcImageWidth = srcImageWidth;
    }

    public Integer getSrcImageHeight() {
        return srcImageHeight;
    }

    public void setSrcImageHeight(Integer srcImageHeight) {
        this.srcImageHeight = srcImageHeight;
    }

    public String getCutImage() {
        return cutImage;
    }

    public Integer getXPosition() {
        return XPosition;
    }

    public Integer getYPosition() {
        return YPosition;
    }


    @Override
    public String toString() {
        return "VerifyImage{" +
                "srcImage='" + srcImage + '\'' +
                ", cutImage='" + cutImage + '\'' +
                ", XPosition=" + XPosition +
                ", YPosition=" + YPosition +
                '}';
    }
}

3、JavaController:class VerifyController

生成验证码的调用接口:/check/getImgSwipe

校验误差值的调用接口:/check/rstImgSwipe

import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.*;

import javax.annotation.Resource;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpSession;


import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;


@Controller
@RequestMapping(value = "/check")
public class UserController extends BaseController {

	//获取滑动验证码的图片
	@RequestMapping(value="/getImgSwipe",method=RequestMethod.GET)
	@ResponseBody
	public PageData getImgSwipe() {
		PageData pd = new PageData();
		String path = "";
		int index = 0;
		Random ran = new Random();
		HttpSession session = this.getCurrentSession();
		try {
			session.setAttribute("imgSwipeSuccess","false");
			//随机获取verifyImages文件夹下的某一张图片
			String url = this.getClass().getResource("/").toString().substring(5);
			String resources = "static/images/verifyImages";
			path = url + resources;
			File file = new File(path);
			File [] files = file.listFiles();

			index = ran.nextInt(files.length-1);	//随机下标
			System.out.println(files[index].getPath());

			VerifyImage img = VerifyImageUtil.getVerifyImage(files[index].getPath());
			pd.put("SrcImage",img.getSrcImage());
			pd.put("CutImage",img.getCutImage());
//			pd.put("XPosition",img.getXPosition());
			pd.put("YPosition",img.getYPosition());
			pd.put("SrcImageWidth",img.getSrcImageWidth());
			pd.put("SrcImageHeight",img.getSrcImageHeight());
			session.setAttribute("XPosition",img.getXPosition());

		}catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return pd;
	}

	//返回前端滑动验证码参数
	@RequestMapping(value="/rstImgSwipe",method=RequestMethod.POST)
	@ResponseBody
	public PageData rstImgSwipe() {
		PageData pd = this.getPageData();
		Float moveEnd_X =  Float.valueOf(pd.getString("moveEnd_X")) ;
		Float wbili = Float.valueOf(pd.getString("wbili"));
		HttpSession session = this.getCurrentSession();
		Integer XPosition = Integer.parseInt(session.getAttribute("XPosition").toString());

		Float xResult = XPosition * wbili;
		System.out.println("mx:"+moveEnd_X+"\nmp:"+XPosition+"\nwbili:"+wbili+"\nxRst:"+xResult);
		PageData pd1 = new PageData();
		if(Math.abs(xResult-moveEnd_X)<10){	//设置误差像素

			pd1.put("success",true);
			session.setAttribute("imgSwipeSuccess","ture");
			return pd1;
		}else {
			pd1.put("success",false);
			session.setAttribute("imgSwipeSuccess","false");
			return pd1;
		}
	}
}

4、check_verify.js

document.write('<script src="./js/img_ver.js" type="text/javascript" charset="utf-8"></script>');

layui.use(['layer', 'form', 'element'], function () {
    window.layer = layui.layer;
    window.form = layui.form;
    window.element = layui.element;
});

var imgBase64 = ''
// 登陆前清空Session
sessionStorage.clear();
//图形验证码获取func
function VerifyImg(){
	$.ajax({
	    url: "/check/getImg",
	    data: {},
	    cache:false,
            type: "GET",
	    success: function (data) {
            console.log(data)
            imgBase64 = data["img"];
            console.log(imgBase64);
            document.getElementById("img").setAttribute("src","data:image/png;base64,"+imgBase64);
            return imgBase64;
		},
	    error: function(){
	    	alert("error");
	    }
	});	
}



/*图形验证码获取与弹出*/
function layerVerifyCodeImg(userPhone){
    // console.log(imgBase64);
    layer.open({
        id: 3,
        offset: 'auto',
        height:'200px',
        type: 1,
        title: '图形验证',

        content:
        '<div class="verBox" style="height: 220px;width: 300px;text-align:center">'
        +'<div id="imgVer" style="display:inline-block;"></div>'
        +'</div>'
        ,
        //btn: ['确认'],
        btnAlign: 'c', //按钮居中
        success:function(){
            $.ajax({
                url:  "/check/getImgSwipe",
                data: {},
                cache:false,
                type: "GET",
                // async:false,
                success: function (data) {
                    console.log(data)
                    var SrcImage = "data:image/jpg;base64,"+data.SrcImage
                    var CutImage = "data:image/jpg;base64,"+data.CutImage
                    var YPosition = data.YPosition
                    var SrcImageWidth  = data.SrcImageWidth;
                    var SrcImageHeight = data.SrcImageHeight;
                    //console.log(imgBase64);

                    imgVer({
                        el:'$("#imgVer")',
                        width:'220',
                        height:'116',
                        SrcImage:SrcImage,
                        CutImage:CutImage,
                        YPosition:YPosition,
                        SrcImageWidth:SrcImageWidth,
                        SrcImageHeight:SrcImageHeight,
                        userPhone:userPhone,

                        success:function () {
                            //验证成功可自定义后续逻辑
                        },
                        error:function () {
                            //alert('错误什么都不执行')
                        }
                    });

                },
                error: function(){
                    alert("error");
                }
            });	

            
        }
    })

}


var InterValObj; //timer变量,控制时间
var count = 60; //间隔函数,1秒执行
var curCount = 0;//当前剩余秒数

/*短信验证码弹出层 */
function layerVerifyCode(phoneNumber) {
    var phoneNumberDecode = phoneNumber;
    // console.log(phoneNumberDecode);
    var fillNum;
    //判断是11位手机号码
    if(checkLegal(phoneNumberDecode)){
        fillNum = phoneNumberDecode;
    }else{
        fillNum='';
    }
    console.log(fillNum);

    layer.open({
        id: 2,
        type: 1,
        title: '请输入验证码',
        area: ['auto', 'auto'],
        content:
        '<form class="layui-form" style="margin-top:30px;padding-right:30px;">'
            + '<div class="layui-form-item">'
                + '<label class="layui-form-label">手机号码</label>'
                + '<div class="layui-input-block">'
                    + '<input id="user_phone_verify" class="layui-input" maxlength="11" placeholder="请输入手机号" oninput="clearNum(this)" value='+phoneNumberDecode+'>'
                + '</div>'
            +'</div>'
            +'<div class="layui-form-item" style="margin-top:10px;display:inline" >'
                + '<label class="layui-form-label">验证码</label>'
                + '<div class="layui-input-block">'
                    + '<input id="user_verify" class="layui-input" maxlength="6" placeholder="请输入验证码" oninput="clearNum(this)" value="" style="width: 120px;float:left;">'
                    + '<input id="btnSendCode" type="button" value="获取" onclick="sendMessage()" class="layui-btn layui-btn-sm layui-btn-danger" autocomplete="off" style="float:left;margin:5px" />'//验证码input + button发送 调用sendMessage()
                + '</div>'
            + '</div>'
        + '</form>'
        ,
        btn: ['确认'],
        btnAlign: 'c', //按钮居中
        btn1: function (index, layero) {
            chcek_confirm();
        }
    })
    //限制反复点击弹窗的按钮可用
    if(curCount > 0){
        $("#btnSendCode").attr("disabled", "true");
        console.log('disabled');
    }else if(curCount == 0){
        console.log('enabled');
    }else{
        console.log('error');
    }
}


/*发送验证码 */
function sendMessage() {
    if(curCount == 0){
        curCount = count;   //设置button效果,开始计时,防止关闭重置
    }

    $("#btnSendCode").attr("disabled", "true");
    $("#btnSendCode").val(curCount + "s");
    InterValObj = window.setInterval(SetRemainTime, 1000); //启动计时器,1秒执行一次
    var userPhone = $("#user_phone_verify").val();
    /*向后台发送处理数据*/
    console.log(userPhone);
    $.ajax({
        url: "/code/sendCode",
        data: { MOBILE_NUMBER: encode(userPhone) },
        type: "POST",
        cache: false,
        success: function (data) {
            if (decode(data).success) {
                layer.msg(decode(data).data);
            } else {
                layer.msg(decode(data).errorMessage);
            }
        }
    })
}
/*timer处理函数*/
function SetRemainTime() {
    if (curCount == 0) {                
        window.clearInterval(InterValObj);//停止计时器
        $("#btnSendCode").removeAttr("disabled");//启用按钮
        $("#btnSendCode").val("重新获取");
    }
    else {
        curCount--;
        $("#btnSendCode").val(curCount + "s");
    }
}

function chcek_confirm () {
    var phoneNumber = document.getElementById('user_phone_verify');
    if (phoneNumber.value.length != 11) {
        layer.alert("手机号不足11位");
    } else if (!checkLegal(phoneNumber.value)) {
        layer.alert("手机号输入不正确");
    } else {
        checkVerifyCode(phoneNumber.value,$("#user_verify").val());
    }
}

5.img_ver.js


function detectmob() {
    if (navigator.userAgent.match(/Android/i)
        || navigator.userAgent.match(/webOS/i)
        || navigator.userAgent.match(/iPhone/i)
        || navigator.userAgent.match(/iPad/i)
        || navigator.userAgent.match(/iPod/i)
        || navigator.userAgent.match(/BlackBerry/i)
        || navigator.userAgent.match(/Windows Phone/i)
    ) {
        return true;
    }
    else {
        return false;
    }
}
var userPhone
function imgVer(Config) {
    userPhone = Config.userPhone
    var el = eval(Config.el);
    var w = Config.width;
    var h = Config.height;
    var SrcImage = Config.SrcImage
    var CutImage = Config.CutImage
    var YPosition = Config.YPosition
    // var imgLibrary = Config.img;
    var SrcImageHeight = Config.SrcImageHeight;
    var SrcImageWidth = Config.SrcImageWidth;
    var wbili = w / SrcImageWidth;
    var hbili = h / SrcImageHeight;
    console.log(wbili + '\n' + hbili);

    var PL_Size = 48;
    var padding = 20;
    var MinN_X = padding + PL_Size;
    var MaxN_X = w - padding - PL_Size - PL_Size / 6;
    var MaxN_Y = padding;
    var MinN_Y = h - padding - PL_Size - PL_Size / 6;

    function RandomNum(Min, Max) {
        var Range = Max - Min;
        var Rand = Math.random();
        if (Math.round(Rand * Range) == 0) {
            return Min + 1;
        } else if (Math.round(Rand * Max) == Max) {
            return Max - 1;
        } else {
            var num = Min + Math.round(Rand * Range) - 1;
            return num;
        }
    }
    var imgSrc = SrcImage;
    var X = '1';
    var Y = '1';


    console.log('PrintLocation');
    console.log("X:" + X + "\n" + "Y:" + Y);
    var left_Num = 0;
    var html = '<div style="position:relative;padding:16px 16px 28px;border:1px solid #ddd;background:#f2ece1;border-radius:16px;">';
    html += '<div style="position:relative;overflow:hidden;width:' + w + 'px;">';
    html += '<div style="position:relative;width:' + w + 'px;height:' + h + 'px;">';
    html += '<img id="scream" src="' + imgSrc + '" style="width:' + w + 'px;height:' + h + 'px;">';
    html += '<canvas id="puzzleBox" width="' + w + '" height="' + h + '" style="position:absolute;left:0;top:0;z-index:22;"></canvas>';
    html += '</div>';
    html += '<div class="puzzle-lost-box" style="position:absolute;width:' + w + 'px;height:' + h + 'px;top:0;left:' + left_Num + 'px;z-index:111;">';
    html += '<canvas id="puzzleShadow" width="' + w + '" height="' + h + '" style="position:absolute;left:0;top:0;z-index:22;"></canvas>';
    html += '<canvas id="puzzleLost" width="' + w + '" height="' + h + '" style="position:absolute;left:0;top:0;z-index:33;"></canvas>';
    html += '</div>';
    html += '<p class="ver-tips"></p>';
    html += '</div>';
    html += '<div class="re-btn"><a></a></div>';
    html += '</div>';
    html += '<br>';
    html += '<div style="position:relative;width:' + w + 'px;margin:auto;touch-action: pan-y">';    //设置触摸区域,防止左滑返回
    html += '<div style="border:1px solid #c3c3c3;border-radius:24px;background:#ece4dd;box-shadow:0 1px 1px rgba(12,10,10,0.2) inset;">';
    html += '<p style="font-size:12px;color: #486c80;line-height:28px;margin:0;text-align:right;padding-right:22px;">按住左边滑块,拖动完成上方拼图</p>';
    html += '</div>';
    html += '<div class="slider-btn"></div>';
    html += '</div>';
    el.html(html);


    var c_l = document.getElementById("puzzleLost");
    //var c_s = document.getElementById("puzzleShadow");
    var ctx_l = c_l.getContext("2d");
    //var ctx_s = c_s.getContext("2d");
    var img = new Image();
    img.src = CutImage;
    img.onload = function () {
        // ctx_l.drawImage(img, 0, YPosition);
        ctx_l.drawImage(img, 0, YPosition * hbili, 50 * wbili, 50 * hbili);
    }

    var moveStart = '';

    if (detectmob()) {  //Mobile端
        document.getElementsByClassName('slider-btn')[0].addEventListener("touchstart", function (e) {
            e = e || window.event;
            $(this).css({ "background-position": "0 -216px" });
            moveStart = e.touches[0].pageX;
        }, false);
        var moveEnd;
        document.addEventListener("touchmove", function (e) {
            e = e || window.event;
            var moveX = e.touches[0].pageX;
            var d = moveX - moveStart;
            if (moveStart == '') {
            } else {
                if (d < 0 || d > (w - 2 * padding)) {
                } else {
                    $(".slider-btn").css({ "left": d + 'px', "transition": "inherit" });
                    $("#puzzleLost").css({ "left": d + 'px', "transition": "inherit" });
                    $("#puzzleShadow").css({ "left": d + 'px', "transition": "inherit" });
                }
            }
            moveEnd = moveX
        }, false);
        document.addEventListener("touchend", function (e) {
            e = e || window.event;
            var moveEnd_X = moveEnd - moveStart;

            if (moveStart == '') {
            } else {
                CheckResult_VerifyButton(moveEnd_X,wbili,Config);
            }
            setTimeout(function () {
                $(".slider-btn").css({ "left": '0', "transition": "left 0.5s" });
                $("#puzzleLost").css({ "left": '0', "transition": "left 0.5s" });
                $("#puzzleShadow").css({ "left": '0', "transition": "left 0.5s" });
            }, 1000);
            $(".slider-btn").css({ "background-position": "0 -84px" });
            moveStart = '';
            Refresh_VerifyButton();
            // console.log("mobile-refresh");   //mobile刷新

        }, false)
    } else {    //PC端
        $(".slider-btn").mousedown(function (e) {
            e = e || window.event;
            $(this).css({ "background-position": "0 -216px" });
            moveStart = e.pageX;
        });
        var moveEnd;
        onmousemove = function (e) {
            e = e || window.event;
            var moveX = e.pageX;
            var d = moveX - moveStart;
            if (moveStart == '') {
            } else {
                if (d < 0 || d > (w - 2 * padding)) {
                } else {
                    $(".slider-btn").css({ "left": d + 'px', "transition": "inherit" });
                    $("#puzzleLost").css({ "left": d + 'px', "transition": "inherit" });
                    $("#puzzleShadow").css({ "left": d + 'px', "transition": "inherit" });
                }
            }
            moveEnd = moveX
        };
        onmouseup = function (e) {
            e = e || window.event;
            var moveEnd_X = e.pageX - moveStart;

            if (moveStart == '') {
            } else {
                CheckResult_VerifyButton(moveEnd_X,wbili,Config);
            }
            setTimeout(function () {
                $(".slider-btn").css({ "left": '0', "transition": "left 0.5s" });
                $("#puzzleLost").css({ "left": '0', "transition": "left 0.5s" });
                $("#puzzleShadow").css({ "left": '0', "transition": "left 0.5s" });
            }, 1000);
            $(".slider-btn").css({ "background-position": "0 -84px" });
            moveStart = '';
            Refresh_VerifyButton();
            // console.log("pc-refresh"); 
        }
    }
}

function CheckResult_VerifyButton(moveEnd_X,wbili,Config) {
    $.ajax({
        url: "/check/rstImgSwipe",
        data: { moveEnd_X: encode(moveEnd_X), wbili: encode(wbili) },
        type: "POST",
        cache: false,
        success: function (data) {
            // var dataDecode = decode(data);
            if (data.success) {
                //    console.log(data);
                $(".ver-tips").html('<i style="background-position:-4px -1207px;"></i><span style="color:#42ca6b;">验证通过</span><span></span>');
                $(".ver-tips").addClass("slider-tips");
                $(".puzzle-lost-box").addClass("hidden");
                $("#puzzleBox").addClass("hidden");
                setTimeout(function () {
                    $(".ver-tips").removeClass("slider-tips");
                    imgVer(Config);
                }, 2000);
                Config.success();
            } else {
                // console.log(data);
                $(".ver-tips").html('<i style="background-position:-4px -1229px;"></i><span style="color:red;">验证失败:</span><span style="margin-left:4px;">拖动滑块将悬浮图像正确拼合</span>');
                $(".ver-tips").addClass("slider-tips");
                setTimeout(function () {
                    $(".ver-tips").removeClass("slider-tips");
                }, 2000);
                // Config.error();
            }
        }
    })
}

function Refresh_VerifyButton() {
    $(".re-btn a").unbind('click').click(function () {
        // imgVer(Config);
        $.ajax({
            url: "/check/getImgSwipe",
            data: {},
            cache: false,
            type: "GET",
            success: function (data) {
                console.log(data)
                var SrcImage = "data:image/jpg;base64," + data.SrcImage
                var CutImage = "data:image/jpg;base64," + data.CutImage
                var YPosition = data.YPosition
                var SrcImageWidth = data.SrcImageWidth;
                var SrcImageHeight = data.SrcImageHeight;
                imgVer({
                    el: '$("#imgVer")',
                    width: '220',
                    height: '116',
                    SrcImage: SrcImage,
                    CutImage: CutImage,
                    YPosition: YPosition,
                    SrcImageWidth: SrcImageWidth,
                    SrcImageHeight: SrcImageHeight,
                    userPhone: userPhone,
                    success: function () {
                        //验证成功可自定义后续逻辑
                    }
                });
            },
            error: function () {
                alert("error");
            }
        });
    })
}

6.页面

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <title>xxxxxxx</title>
    <!-- For IE top version load-->
    <!-- 双核浏览器条件下 默认用极速核 -->
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="renderer" content="webkit">
    <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width">

    <script src="https://cdn.adactive.top/static/activity/zartist/js/extra/jquery-3.3.1.min.js"></script>
    <script src="https://cdn.adactive.top/static/activity/zartist/js/common/remset.js"></script>
    <script src="https://cdn.adactive.top/static/activity/zartist/js/common/dialog.js"></script>
    <script src="https://cdn.adactive.top/static/activity/zartist/js/extra/swiper-4.4.1.min.js"></script>
    <link rel="stylesheet" href="https://cdn.adactive.top/static/activity/zartist/css/common/reactui.css?v=2.0"
        type="text/css">
    <link rel="stylesheet" href="https://cdn.adactive.top/static/activity/zartist/css/common/common.css?v=2.0"
        type="text/css">
    <link rel="stylesheet" href="https://cdn.adactive.top/static/activity/zartist/css/common/dialog.css?v=2.0"
        type="text/css">
    <link rel="stylesheet" href="https://cdn.adactive.top/static/activity/zartist/css/extra/swiper-4.4.1.min.css"
        type="text/css">
    <link rel="stylesheet" href="./css/tecent.css" type="text/css">

    <script src="./js/jquery.js"></script>
    <script src="./js/jquery.min.js"></script>
    <script src="./layui/layui.js"></script>
    <script src="./layui/layui.all.js"></script>

    <script src="./js/check_verify.js"></script>
    <link rel="stylesheet" href="./layui/css/layui.css">
    <link rel="stylesheet" href="./css/submit.css">
    <link rel="stylesheet" href="./css/checkStyle.css" type="text/css">




</head>

<body style="margin: 0 auto !important">
    <div class='main'>
        <div class="main-extra">
            <div class="main-instr insert_area">
                <!-- input框 -->
                <div class="main-list-con input-control" style="margin-left: 0.5rem;">
                    <input id="user_mobile" name="USERMOBILE" placeholder="请输入手机号或宽带号" class="main-list-con-input"
                        maxlength="32" oninput="clearNum(this)" required="required">
                </div>
            </div>

            <div class="main-instr">
                <!--点击区域-->
                <!--随便调用一下好了,检查手机号是否合法,合法就调用滑动验证码出来咯-->
                <p><img onclick="layerVerifyCodeImg()" src="images/check_phone_02.png"></p>
            </div>
        </div>
    </div>

</body>

</html>

7.checkStyle.css

.slider-btn{position:absolute;width:44px;height:44px;left:0;top:-7px;z-index:12;cursor:pointer;background-image:url(../images/checkSprite.png);background-position:0 -84px;transition:inherit}
.ver-tips{position:absolute;left:0;bottom:-22px;background:rgba(255,255,255,.9);height:22px;line-height:22px;font-size:12px;width:100%;margin:0;text-align:left;padding:0 8px;transition:all .4s}
.slider-tips{bottom:0}
.ver-tips i{display:inline-block;width:22px;height:22px;vertical-align:top;background-image:url(../images/checkSprite.png);background-position:-4px -1229px}
.ver-tips span{display:inline-block;vertical-align:top;line-height:22px;color:#455}
.active-tips{display:block}
.hidden{display:none}
.re-btn{position:absolute;left:0;bottom:0;height:28px;padding:0 16px}
.re-btn a{display:inline-block;width:14px;height:14px;margin:7px 0;background-image:url(../images/checkSprite.png);background-position:0 -1179px;cursor:pointer}
.re-btn a:hover{background-position:0 -1193px}

css里的图片checkSprite.png

8.补充BaseController.java

import com.entity.PageData;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

public class BaseController {
    public HttpServletRequest getRequest() {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();

        return request;
    }
    public HttpServletResponse getResponse() {
    	
    	HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
    	return response;
    }
   

    public PageData getPageData(){
        PageData pd = new PageData(this.getRequest());
        return pd;
    }

    public HttpSession getCurrentSession(){
        HttpSession session = this.getRequest().getSession();
        return session;
    }

//    public String getCurrentUser(){
//        return (String) getCurrentSession().getAttribute("USERCODE");	// 大写的OA账号
//    }

    public void setSessionAttribute(String str, Object obj){
        getCurrentSession().setAttribute(str,obj);
    }
    public void removeSessionAttribute(String str){
        getCurrentSession().removeAttribute(str);
    }
    public ModelAndView getModelAndView(){
        return new ModelAndView();
    }

}

9.补充PageData

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.util.DigestUtilEZ;

import javax.servlet.http.HttpServletRequest;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

public class PageData extends JSONObject {
    private static final long serialVersionUID = 1L;

    private JSONObject map = null;

    private HttpServletRequest request;

    @SuppressWarnings({ })
    public PageData(HttpServletRequest request){
        this.request = request;
        Map<?, ?> properties = request.getParameterMap();
        JSONObject returnMap = new JSONObject();
        Iterator<?> entries = properties.entrySet().iterator();
        Map.Entry entry;
        String name = "";
        String value = "";
        while (entries.hasNext()) {
            entry = (Map.Entry) entries.next();
            name = (String) entry.getKey();
            Object valueObj = entry.getValue();
            if(null == valueObj){
                value = "";
            }else if(valueObj instanceof String[]){
                String[] values = (String[])valueObj;
                for(int i=0;i<values.length;i++){
                    value = values[i] + ",";
                }
                value = value.substring(0, value.length()-1);
            }else{
                value = valueObj.toString();
            }
            if(!"_".equals(name)){    //解决一些特殊字符冲突,用了base64解密
                returnMap.put(name, DigestUtilEZ.digestString(value,DigestUtilEZ.ALGORITHM_NAME.Decode_BASE_64));
            }else{
                returnMap.put(name,value);    //无字符冲突 且明文传输情况
            }
            
        }
        map = returnMap;
    }

    public PageData(String jsonStr) {
        map = JSON.parseObject(jsonStr);
    }

    public PageData(){
        map = new JSONObject();
    }

    @Override
    public Object get(Object key) {
        return map.get(key);
    }

    public String getString(String key) {
        return map.getString(key);
    }

    @Override
    public Object put(String key, Object value) {
        return map.put(key, value);
    }
    @Override
    public Object putIfAbsent(String key, Object value) {
        return map.putIfAbsent(key, value);
    }

    @Override
    public Object remove(Object key) {
        return map.remove(key);
    }

    public void clear() {
        map.clear();
    }

    public boolean containsKey(Object key) {
        return map.containsKey(key);
    }

    public boolean containsValue(Object value) {
        return map.containsValue(value);
    }

    public Set<Map.Entry<String, Object>> entrySet() {
        return map.entrySet();
    }

    public boolean isEmpty() {
        return map.isEmpty();
    }

    public Set<String> keySet() {
        return map.keySet();
    }

    public void putAll(JSONObject t) {
        map.putAll(t);
    }

    public int size() {
        return map.size();
    }

    public Collection<Object> values() {
        return map.values();
    }

    @Override
    public String toString() {
        return JSON.toJSONString(map, SerializerFeature.WRITE_MAP_NULL_FEATURES, SerializerFeature.QuoteFieldNames,SerializerFeature.WriteNullListAsEmpty);
    }

//    public static void main(String[] args) {
//        PageData pd = new PageData();
//        pd.put("123",null);
//        System.out.println(pd);
//    }
}

10.补充DigestUtilEZ

加密解密工具类,好吧,只有base64能解,实际我只用了最后2个case

import org.apache.commons.codec.digest.DigestUtils;

import java.nio.charset.StandardCharsets;
import java.util.Base64;

public class DigestUtilEZ {
	public enum ALGORITHM_NAME {
		MD2, MD5, SHA_1, SHA_256, SHA_384, SHA_512,BASE_64,Decode_BASE_64
	}
	
	  /**
     * 编码字符串
     *
     * @param sourceStr    需要编码的字符串String
     * @param algorithmsName 算法名称(如:MD2,MD5,SHA1,SHA256,SHA384,SHA512)
     * @return
     */
	public static String digestString(String sourceStr, ALGORITHM_NAME algorithmsName) {
		String password = null;
		switch (algorithmsName) {
		case MD2:
			password = DigestUtils.md2Hex(sourceStr);
			break;
		case MD5:
			password = DigestUtils.md5Hex(sourceStr);
			break;
		case SHA_1:
			password = DigestUtils.sha1Hex(sourceStr);
			break;
		case SHA_256:
			password = DigestUtils.sha256Hex(sourceStr);
			break;
		case SHA_384:
			password = DigestUtils.sha384Hex(sourceStr);
			break;
		case SHA_512:
			password = DigestUtils.sha512Hex(sourceStr);
			break;
		case BASE_64:
			password = Base64.getEncoder()
	                .encodeToString(sourceStr.getBytes(StandardCharsets.UTF_8));
			break;
		case Decode_BASE_64:
			password = new String(Base64.getDecoder().decode(sourceStr), StandardCharsets.UTF_8);
		}
		return password;
	}
  
}

11. pom.xml依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.7.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.yourGroupName</groupId>
    <artifactId>yourArtifactName</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>
    <name>yourProjectName</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.0</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>com.tencentcloudapi</groupId>
            <artifactId>tencentcloud-sdk-java</artifactId>
            <!-- go to https://search.maven.org/search?q=tencentcloud-sdk-java and get the latest version. -->
            <!-- 请到https://search.maven.org/search?q=tencentcloud-sdk-java查询最新版本 -->
            <version>3.1.84</version>
        </dependency>

        <dependency>
            <groupId>com.github.ulisesbocchio</groupId>
            <artifactId>jasypt-spring-boot-starter</artifactId>
            <version>3.0.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>3.16</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.47</version>
        </dependency>
        <dependency>
            <groupId>com.oracle</groupId>
            <artifactId>ojdbc6</artifactId>
            <version>11.2</version>
        </dependency>
        <!-- log4j -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.13</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

以下是一个简单的 Java 图片滑动验证码的示例代码,使用 Java Swing 库实现: ```java import java.awt.*; import java.awt.event.*; import javax.swing.*; import java.util.Random; public class ImageSliderVerificationCode extends JFrame { private static final long serialVersionUID = 1L; private final int WIDTH = 400; private final int HEIGHT = 300; private final int BLOCK_SIZE = 50; private Image bgImage; private Image blockImage; private int blockX; private int blockY; private int mouseX; private int mouseY; private boolean isDragging; public ImageSliderVerificationCode() { setTitle("Image Slider Verification Code"); setSize(WIDTH, HEIGHT); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setResizable(false); setLocationRelativeTo(null); bgImage = new ImageIcon("background.jpg").getImage(); blockImage = new ImageIcon("block.png").getImage(); addMouseListener(new MouseAdapter() { public void mousePressed(MouseEvent e) { if (e.getX() >= blockX && e.getX() <= blockX + BLOCK_SIZE && e.getY() >= blockY && e.getY() <= blockY + BLOCK_SIZE) { isDragging = true; mouseX = e.getX(); mouseY = e.getY(); } } public void mouseReleased(MouseEvent e) { isDragging = false; if (blockX >= WIDTH - BLOCK_SIZE) { JOptionPane.showMessageDialog(null, "Verification passed!"); System.exit(0); } else { blockX = 0; blockY = new Random().nextInt(HEIGHT - BLOCK_SIZE); repaint(); } } }); addMouseMotionListener(new MouseMotionAdapter() { public void mouseDragged(MouseEvent e) { if (isDragging) { blockX += e.getX() - mouseX; blockY += e.getY() - mouseY; if (blockX < 0) { blockX = 0; } if (blockX > WIDTH - BLOCK_SIZE) { blockX = WIDTH - BLOCK_SIZE; } if (blockY < 0) { blockY = 0; } if (blockY > HEIGHT - BLOCK_SIZE) { blockY = HEIGHT - BLOCK_SIZE; } mouseX = e.getX(); mouseY = e.getY(); repaint(); } } }); } public void paint(Graphics g) { g.drawImage(bgImage, 0, 0, null); g.drawImage(blockImage, blockX, blockY, null); } public static void main(String[] args) { ImageSliderVerificationCode verCode = new ImageSliderVerificationCode(); verCode.setVisible(true); } } ``` 此代码将生成一个包含背景图片和滑块图片的窗口。当用户按住滑块图片并拖动它时,窗口将显示移动后的滑块图片。如果用户将滑块图片拖动到窗口的右边缘,则显示验证通过的消息并退出程序。否则,滑块图片将被重置到窗口的左侧,并随机在窗口的垂直方向上移动。
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值