說明:因為重新整理的圖示和點選圖檔文字的時候需要在圖檔上生成圖示,為了美觀,是以我引入Font Awesome圖示庫,如果你需要的話需引入該圖示庫方可使用
先上驗證碼效果圖:
步驟1:前端
html代碼
<div>
<!--每點選一次拼接記錄對應坐标-->
<input name="imgCheckInfo" id="imgCheckInfo" type="text"></input>
<!--生成img圖像-->
<div class="img" id="dv" >
<img id="showCheckImg" alt="點互式驗證"/>
</div>
<!--生成重新整理圖示-->
<i id="freshIcon" class=" pull-left fa fa-refresh" onclick="refreshImg()"></i>
<!--輸出提示資訊-->
<span class="pull-left" id="imgCheckText"></span>
</div>
css
<style>
html,body{margin:0px;height:100%;}
#dv{position:relative;width:100%;height:100%;margin:0px auto;}
#imgCheckInfo{display:none}
.img .marker{position:absolute;}
#showCheckImg{width:100%;height:100%}
#freshIcon{color:#3464ff;font-size: 1.5em}
#imgCheckText{font-size:0.6em;color:#ff4546}
</style>
js
//記錄點選漢字次數
var checkClickNum=0;
$(document).ready(function() {
refreshImg();
//觸發滑鼠點選圖檔事件
document.getElementById('dv').onclick = function (e) {
checkClickNum++;
//當點選次數小于等于3時,進行建立标記以及儲存坐标位置
if(checkClickNum<=3){
e = e || window.event;var x = e.offsetX || e.layerX;var y = e.offsetY || e.layerY;
createMarker(x, y);
saveCheckDataInfo(x,y);
}
//如果到第三次點選時,進行發送資料到背景進行處理
if(checkClickNum===3){
var imgCheckInfo='['+$("#imgCheckInfo")[0].innerText.substring(1)+']';
$.ajax({
url: "/ImgCheck/checkImg",
type: "POST",
data: { "imgCheckInfo": imgCheckInfo},
success: function(data) {
if(data.code=="200"){
//将提示資訊進行傳回
var a=$("#imgCheckText");
a.html(data.msg);
a[0].style.display=null;
}
}
});
}
}
});
//重新整理圖檔
function refreshImg(){
checkClickNum=0;
$("#imgCheckText")[0].style.display="none";
$("#imgCheckInfo")[0].innerText="";
$("i").remove(".marker");
$("#showCheckImg").attr("src","/ImgCheck/getImg?r="+Math.random());
}
//點選時建立一個圖示标記
function createMarker(x, y) {
var i = document.createElement('i');
i.className = 'marker fa fa-check-circle '; i.style.left = x-10 + 'px'; i.style.top = y-10 + 'px';
i.style.fontSize="1.5em";
i.style.color="#FFFFFF";
document.getElementById('dv').appendChild(i);
}
//儲存點選圖檔的資料
function saveCheckDataInfo(x, y) {
var imgCheckInfo=$("#imgCheckInfo");
var text=imgCheckInfo[0].innerText;
text+=',['+x+','+y+']'
imgCheckInfo[0].innerText=text;
}
2:後端
controller
import com.vdobs.utils.JsonResult;
import com.vdobs.utils.TouchVerificationUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.HashMap;
import java.util.Random;
@Controller
@RequestMapping("/ImgCheck")
public class ImgCheckController {
private final static Logger logger = LoggerFactory.getLogger(ImgCheckController.class);
private static Random random = new Random();
@ResponseBody
@RequestMapping("/getImg")
public void getImg(HttpServletResponse response, HttpServletRequest request) throws IOException{
HashMap<String,Object> hashMap=TouchVerificationUtil.createImg(6,3,100,555,18,Color.white,"/static/system/login/img/test.png");
request.getSession().setAttribute("correctCheckCode",hashMap.get("correctCheckCode"));
ImageIO.write((BufferedImage)hashMap.get("BufferedImage"), "PNG", response.getOutputStream()); //将圖檔輸出
}
@ResponseBody
@PostMapping("/checkImg")
public JsonResult checkImg(HttpServletRequest request,String imgCheckInfo) {
String correctCheckCode=(String)request.getSession().getAttribute("correctCheckCode");
if (TouchVerificationUtil.checkImg(correctCheckCode,imgCheckInfo)) { //若前端上傳的坐标在session中記錄的坐标的一定範圍内則驗證成功
return new JsonResult("200","驗證成功!");
} else {
return new JsonResult("200","驗證失敗,請重新重新整理再次驗證!");
}
}
}
封裝的img驗證調用類
import com.alibaba.fastjson.JSONArray;
import org.springframework.util.StringUtils;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Random;
public class TouchVerificationUtil {
private static Random random = new Random();
/**
*
* @param showNum 展示多少個文字
* @param checkNum 驗證幾個文字
* @param height 圖像高度
* @param width 圖像寬度
* @param fontSize 文字大小
* @param fontColor 文字顔色
* @param defaultBackGroundImg 背景圖檔路徑
* @return
*/
public static HashMap<String,Object> createImg(int showNum,int checkNum,int height,int width,int fontSize,Color fontColor,String defaultBackGroundImg){
HashMap<String,Object> hashMap=new HashMap<>();
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D g = (Graphics2D) image.getGraphics();
// 讀取本地圖檔,做背景圖檔
// URL resource = getClass().getResource("/system/login/img/" + (random.nextInt(4) + 1) + ".png");
URL resource = TouchVerificationUtil.class.getResource(defaultBackGroundImg);
try {
g.drawImage(ImageIO.read(new File(resource.getPath())), 0, fontSize, width, height, null); //将背景圖檔從高度30開始
} catch (IOException e) {
System.out.println(e.getMessage());;
}
g.setColor(Color.WHITE); //設定顔色
g.drawRect(0, 0, width - 1, height - 1); //畫邊框
g.setFont(new Font("宋體", Font.BOLD, fontSize)); //設定字型
String[] target = new String[checkNum]; // 用于記錄文字
StringBuilder correctCheckCode=new StringBuilder(16);
for (int i = 0; i < showNum; i++) { //随機産生8個文字,坐标,顔色都不同
g.setColor(TouchVerificationUtil.getRandColor(100, 160));
String str = TouchVerificationUtil.getRandomChineseChar();
int tempWidth=random.nextInt(width);
int tempHeight=random.nextInt(height);
tempWidth = tempWidth>width-fontSize*3?tempWidth-fontSize*3:tempWidth+fontSize;
tempHeight = tempHeight>height-fontSize*3?(int)(tempHeight-fontSize*1.5):(int)(tempHeight+fontSize*1.5);
if (i<checkNum) {
target[i] = str; //記錄文字
correctCheckCode.append(",[").append(tempWidth).append(",").append(tempHeight).append("]");
}
g.drawString(str, tempWidth, tempHeight);
}
g.setColor(fontColor);
//設定字型類型、字型大小、字型樣式
g.drawString("請按順序點選:" + Arrays.toString(target).replaceAll("[\\[\\]\\s]",""), 0, fontSize);
//redisTemplate.opsForValue().set("XXX:" + MD5Utils.encrypt(id), x + ":" + y, 5, TimeUnit.MINUTES);
//5.釋放資源
g.dispose();
hashMap.put("BufferedImage",image);
System.out.println(correctCheckCode);
hashMap.put("correctCheckCode","["+correctCheckCode.substring(1)+"]");
return hashMap;
}
/**
*
* @param correctCheckCode 正确的坐标碼
* @param imgCheckInfo 前端傳到背景的坐标碼
* @return
*/
public static Boolean checkImg(String correctCheckCode, String imgCheckInfo) {
Boolean result=true;
if (StringUtils.isEmpty(imgCheckInfo)) {
return false;
}
JSONArray imgCheckInfos=JSONArray.parseArray(imgCheckInfo);
JSONArray correctCheckCodes=JSONArray.parseArray(correctCheckCode);
if (imgCheckInfos.size()!=3) {
return false;
}
for (int i=0;i<imgCheckInfos.size()&&result;i++){
Integer[] correctCheck=correctCheckCodes.getObject(i,Integer[].class);
Integer[] imgCheck=imgCheckInfos.getObject(i,Integer[].class);
int cx=correctCheck[0];
int cy=correctCheck[1];
int x=imgCheck[0];
int y=imgCheck[1];
result=cx- 22 < x && x < cx + 22 && cy - 22 < y && y < cy + 22;
}
return result;
}
/**
* 随機産生顔色
*/
public static Color getRandColor(int fc, int bc) {
if (fc > 255)
fc = 255;
if (bc > 255)
bc = 255;
int r = fc + random.nextInt(bc - fc);
int g = fc + random.nextInt(bc - fc);
int b = fc + random.nextInt(bc - fc);
return new Color(r, g, b);
}
/**
* 随機産生漢字
*/
public static String getRandomChineseChar() {
String str = null;
int heightPos; // 定義高低位
int lowPos;
Random random = new Random();
heightPos = (176 + Math.abs(random.nextInt(39))); //擷取高位值
lowPos = (161 + Math.abs(random.nextInt(93))); //擷取低位值
byte[] b = new byte[2];
b[0] = (new Integer(heightPos).byteValue());
b[1] = (new Integer(lowPos).byteValue());
try {
str = new String(b, "GBk"); //轉成中文
} catch (UnsupportedEncodingException e) {
System.out.println(e.getMessage());;
}
return str;
}
}
/**
* 統一傳回對象
* @author shengwu ni
* @param <T>
*/
public class JsonResult<T> {
private T data;
private String code;
private String msg;
/**
* 若沒有資料傳回,預設狀态碼為200,提示資訊為:操作成功!
*/
public JsonResult() {
this.code = "200";
this.msg = "操作成功!";
}
/**
* 若沒有資料傳回,可以人為指定狀态碼和提示資訊
* @param code
* @param msg
*/
public JsonResult(String code, String msg) {
this.code = code;
this.msg = msg;
}
/**
* 有資料傳回時,狀态碼為200,預設提示資訊為:操作成功!
* @param data
*/
public JsonResult(T data) {
this.data = data;
this.code = "200";
this.msg = "操作成功!";
}
/**
* 有資料傳回,狀态碼為0,人為指定提示資訊
* @param data
* @param code
* @param msg
*/
public JsonResult(T data,String code, String msg) {
this.data = data;
this.code = code;
this.msg = msg;
}
/**
* 有資料傳回,狀态碼為0,人為指定提示資訊
* @param data
* @param msg
*/
public JsonResult(T data, String msg) {
this.data = data;
this.code = "0";
this.msg = msg;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
最後:說一下該驗證碼的缺點:1,容易字型重疊,2:偶爾文字超出顯示,3:當頁面進行縮放時會導緻坐标不對應由此驗證失敗
解決方案:第一個問題:代碼記錄生成的坐标值進行比較,相差較近的進行加減随機數或再次生成可以解決
第二個問題:設定的img大小以及字型大小都會影響文字的位置顯示,需要多輪測試得到最優方案
第三個問題:擷取頁面伸縮占比,将目前頁面的點選位置+目前位置*(1-占比)進行算出
以上解決方案都是閉着眼吹牛,請不要相信。。。。。。