前言
前段時間接到需求熊户,需要給登錄模塊做滑塊登錄驗證功能,要求設(shè)計盡量不復(fù)雜且能正確校驗滑動圖片驗證碼吭服。
實現(xiàn)思路
網(wǎng)上有很多實現(xiàn)方案嚷堡,我首先提出了兩個方案一個由前端解決,參考方案:https://www.cnblogs.com/xiaohuizhang/p/17175873.html
另一種由后端解決艇棕,滑塊素材圖片存儲在本地蝌戒,校驗規(guī)則存放redis
大致方案
1、通過滑塊拼圖和素材背景圖生成滑塊素材
2沼琉、生成滑塊校驗規(guī)則(滑動距離)并存入redis
3北苟、編寫校驗接口
4、定時任務(wù)清理并重新生成素材(可選)
生成素材打瘪、生成規(guī)則
從網(wǎng)上找一個拼圖素材友鼻,例如
放入本地項目resource目錄下,另外隨意找?guī)讖埶夭谋尘皥D闺骚,同樣存入resource目錄彩扔,接著通過接口裁剪生成校驗素材,素材可以for循環(huán)創(chuàng)建指定數(shù)量僻爽。要注意圖片路徑的讀取方式虫碉,tomcat服務(wù)器和docker容器讀取方式不一樣,下面代碼可以同時滿足兩種場景讀取
File bgFront;
ClassLoader classLoader = getClass().getClassLoader();
URL resource = classLoader.getResource("滑塊拼圖.png");
Path targetPath = Paths.get("滑塊拼圖.png");
try {
Files.copy(resource.openStream(), targetPath, StandardCopyOption.REPLACE_EXISTING);
bgFront = targetPath.toFile();
if (!bgFront.exists()) {
logger.error("拼圖文件不存在胸梆,素材生成失敗");
return;
}
} catch (IOException e) {
logger.error("生成拼圖出錯:", e);
return;
}
// 滑塊素材類
public class SlideMaterialInfo {
private Integer x;
private Integer y;
private String materialId;
private Long createTime;
}
// 隨機初始化滑塊拼圖缺口的位置和uuid
SlideMaterialInfo slideMaterialInfo = new SlideMaterialInfo(System.currentTimeMillis());
// x 的隨機位置
int positionX = NumberUtils.getRandomNum(80, 270);
// 隨機產(chǎn)生的Y值
int positionY = NumberUtils.getRandomNum(10, 110);
String materialId = UUIDUtil.getUUID();
slideMaterialInfo.setMaterialId(materialId);
slideMaterialInfo.setX(positionX);
slideMaterialInfo.setY(positionY);
try {
// 生成的素材上傳oss
String savePath = "指定上傳路徑";
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(buffImg, savePath, baos);
byte[] imageInByte = baos.toByteArray();
OssFileUtil.uploadFile2Oss(savePath, imageInByte);
} catch (IOException e) {
logger.error("在oss:{}路徑下創(chuàng)建圖片失敹嘏酢:", savePath, e);
}
// 匹配規(guī)則放入redis
redisTemplate.opsForSet().add("SLIDE_MATERIAL_ID_SET", materialId);
redisTemplate.opsForHash().put("SLIDE_MATERIAL_INFO_HASH_ID", JSON.toJSONString(slideMaterialInfo));
校驗接口
前端首先請求后端隨機從redis獲取一個materailId接著從oss下載圖片素材并展示,用戶滑動后調(diào)用校驗方法碰镜,主要通過前端傳遞滑動的距離和圖片大小绞惦,后端進行計算,如果滑動誤差超過指定大小則校驗失敗
private boolean check(String materailId, Integer start, Integer end, Integer slideDistance, Double zoomFactor) {
Object cacheVal = redisTemplate.opsForHash().get("SLIDE_MATERIAL_INFO_HASH_ID", materailId);
SlideMaterialInfo slideMaterialInfo = JSON.parseObject(cacheVal.toString(), SlideMaterialInfo.class);
// 計算距離
int calculateDis = end - start;
int calculateDeviation = Math.abs(calculateDis - slideDistance);
// 計算差距過大洋措,驗證失敗
if (calculateDeviation > 5) {
return false;
}
// 縮放后的距離(和圖片實際進行處理)
double zoomDis = calculateDis / zoomFactor;
// 縮放偏差
int zoomDeviation = Math.abs((int) zoomDis - slideMaterialInfo.getX());
if (zoomDeviation > 5) {
return false;
}
}
定時任務(wù)
這一步可有可無济蝉,有的項目比較簡單不需要更新滑塊,一次性生成幾百個就夠用了。我的項目里需要清理王滤,簡單描述一下贺嫂,主要是設(shè)定一個定時任務(wù)清除redis和oss已有的數(shù)據(jù),然后重新調(diào)用生成方法創(chuàng)建新的素材雁乡。需要注意的是定時任務(wù)可能會因為容器中服務(wù)多實例導(dǎo)致多線程問題第喳。因此在任務(wù)開始時,先向redis創(chuàng)建分布式鎖踱稍,當(dāng)其他服務(wù)發(fā)現(xiàn)已加鎖則不重復(fù)執(zhí)行這個任務(wù)曲饱。