一、为什么要验证码?
防暴力破解的课题一直是信息安全的重点,漏洞攻击更是开发者的噩梦。。。。
如果没有经历过这个噩梦,请感受一下被信安部门支配的恐惧。(一大堆漏洞给扫出来,您修代码吧)
二、怎么实现的?
前端: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>