js+java實(shí)現(xiàn)登錄滑動(dòng)圖片驗(yàn)證
最新需要公司要求在不改變?cè)瓉?lái)的登錄邏輯的情況下,將原來(lái)的驗(yàn)證碼登錄的形式改成滑動(dòng)圖片的形式!下面是做出來(lái)的效果:

實(shí)現(xiàn)思路:所有的圖片數(shù)據(jù),驗(yàn)證全部由后端來(lái)做。前端調(diào)用接口,后端會(huì)返回兩張經(jīng)過(guò)base64加密的圖片信息,分別是背景圖片和滑塊圖片,前端滑動(dòng)滑塊以后將X方向的滑動(dòng)距離傳回后端做驗(yàn)證,驗(yàn)證成功以后再做后續(xù)的登錄邏輯驗(yàn)證,以下是完整的過(guò)程:
獲取背景圖,我這邊是在FTP上放了10張圖片,隨機(jī)獲取一張。
@LogAnnotation(description = "web獲取滑動(dòng)圖片信息")
@ApiOperation(value = "web獲取滑動(dòng)圖片信息", httpMethod = "POST", response = Result.class, notes = "")
@RequiresPermissions(value = "code/picture/msg")
@RequestMapping(value = "code/picture/msg", method = RequestMethod.POST)
@ResponseBody
public Response getPictureCode(HttpServletRequest request) {
Map<String, Object> pictureMap = new HashMap<>();
try {
//隨機(jī)獲取需要切成的圖片形狀
Integer templateNum = new Random().nextInt(10) + 1;
String randomStr = String.valueOf(templateNum);
if(templateNum < 10){
randomStr = "0"+randomStr;
}
InputStream tempInputStream = FileUtils.downloadFtpFile(Constant.POCTURE_CHECK_PATH,randomStr+".jpg");
//根據(jù)源圖片和摳圖形狀生成新的圖片信息以流的形式返回到前端
pictureMap = VerifyImageUtil.getVerifyImage(tempInputStream);
//將剪裁好了以后的圖片信息以當(dāng)前時(shí)間戳為key,存入redis(這一步是為了對(duì)圖片信息做過(guò)期處理,根據(jù)自己需求做)
String tempTime = String.valueOf(System.currentTimeMillis());
redisUtils.hset(Constant.REDIS_LOGIN_PICTURE_CODE, tempTime,pictureMap.get("locationX").toString(),60);
pictureMap.put("tempTime",tempTime);
//移出隨機(jī)生成的摳圖位置坐標(biāo),不返回給前端
pictureMap.remove("locationX");
return new ObjectResponse<Map<String, Object>>(pictureMap);
}catch(Exception e){
logger.error("code/picture/msg", e);
return new FailedResponse();
}
}
FTP下載方法代碼
public static FTPClient ftpLogin(){
String ip = p.getProperty("ftp的ip");
String username = p.getProperty("ftp的user");
String password = p.getProperty("ftp的pwd");
int ftpPort = Integer.parseInt(p.getProperty("ftp的port"));
FTPClient ftpClient = new FTPClient();
try {
ftpClient.connect(ip , ftpPort );// 連接FTP服務(wù)器
ftpClient.login(username , password );// 登陸FTP服務(wù)器
if (!FTPReply.isPositiveCompletion(ftpClient.getReplyCode())) {
ftpClient.disconnect();
}
} catch (SocketException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return ftpClient;
}
public static InputStream downloadFtpFile(String ftpPath, String fileName) {
FTPClient ftpClient = ftpLogin();
try {
ftpClient.setControlEncoding("UTF-8"); // 中文支持
ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
ftpClient.enterLocalPassiveMode();
ftpClient.changeWorkingDirectory(ftpPath);
InputStream inputStream = ftpClient.retrieveFileStream(new String(fileName.getBytes("UTF-8"), "ISO-8859-1"));
//ftpClient.completePendingCommand();
return inputStream;
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
ftpClient.logout();
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
摳圖工具類(lèi)VerifyImageUtil
public class VerifyImageUtil {
/**
* 源文件寬度
*/
private static int ORI_WIDTH = 296;
/**
* 源文件高度
*/
private static int ORI_HEIGHT = 182;
/**
* 模板圖寬度
*/
private static int CUT_WIDTH = 50;
/**
* 模板圖高度
*/
private static int CUT_HEIGHT = 50;
/**
* 摳圖凸起圓心
*/
private static int circleR = 6;
/**
* 摳圖內(nèi)部矩形填充大小
*/
private static int RECTANGLE_PADDING = 6;
/**
* 摳圖的邊框?qū)挾?
*/
private static int SLIDER_IMG_OUT_PADDING = 1;
/**
* 根據(jù)傳入的路徑生成指定驗(yàn)證碼圖片
*
* @param filePath
* @return
* @throws IOException
*/
public static Map<String, Object> getVerifyImage(InputStream filePath) throws IOException {
BufferedImage srcImage = ImageIO.read(filePath);
int locationX = CUT_WIDTH + new Random().nextInt(srcImage.getWidth() - CUT_WIDTH * 2);
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);
Map<String, Object> resultMap = new HashMap<>();
//放入背景圖的加密信息
resultMap.put("srcImage",getImageBASE64(srcImage));
//放入滑塊圖的加密信息
resultMap.put("markImage",getImageBASE64(markImage));
//放入摳圖位置的X方向的信息,用于驗(yàn)證滑塊位置是否正確
resultMap.put("locationX",locationX);
//放入摳圖位置的Y方向的信息,用于前端控制定位信息
resultMap.put("locationY",locationY);
return resultMap;
}
/**
* 生成隨機(jī)滑塊形狀
* <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像素隨機(jī)
double x1 = RECTANGLE_PADDING + (CUT_WIDTH - 2 * RECTANGLE_PADDING) / 2.0 - 5 + random.nextInt(10);
//y 矩形上邊界半徑-1像素移動(dòng)
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++) {
//矩形區(qū)域
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;
}
//凸出區(qū)域
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;
}
}
//凹進(jìn)區(qū)域
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++) {
//四個(gè)正方形邊角處理
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;
}
/**
* 裁剪區(qū)塊
* 根據(jù)生成的滑塊形狀,對(duì)原圖和裁剪塊進(jìn)行變色處理
* @param oriImage 原圖
* @param targetImage 裁剪圖
* @param blockImage 滑塊
* @param x 裁剪點(diǎn)x
* @param y 裁剪點(diǎn)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);
// 原圖中對(duì)應(yīng)位置變色處理
if (rgbFlg == 1) {
//摳圖上復(fù)制對(duì)應(yīng)顏色值
targetImage.setRGB(i,j, rgb_ori);
//原圖對(duì)應(yīng)位置顏色變化
oriImage.setRGB(_x, _y, rgb_ori & 0x363636);
} 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);
}
}
}
}
/**
* 隨機(jī)獲取一張圖片對(duì)象
* @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();
}
/**
* 將圖片轉(zhuǎn)換為BASE64
* @param image
* @return
* @throws IOException
*/
public static String getImageBASE64(BufferedImage image) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ImageIO.write(image,"png",out);
//轉(zhuǎn)成byte數(shù)組
byte[] bytes = out.toByteArray();
BASE64Encoder encoder = new BASE64Encoder();
//生成BASE64編碼
return encoder.encode(bytes);
}
/**
* 將BASE64字符串轉(zhuǎn)換為圖片
* @param base64String
* @return
*/
public static BufferedImage base64StringToImage(String base64String) {
try {
BASE64Decoder decoder=new BASE64Decoder();
byte[] bytes1 = decoder.decodeBuffer(base64String);
ByteArrayInputStream bais = new ByteArrayInputStream(bytes1);
return ImageIO.read(bais);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
前端收到請(qǐng)求成功返回信息以后,對(duì)數(shù)據(jù)做解析

// 設(shè)置圖片的src屬性
$("#背景圖的ID").attr("src", "data:image/png;base64," + result.datas.srcImage);
$("#滑塊圖的ID").attr("src", "data:image/png;base64," + result.datas.markImage);
$("#滑塊圖的ID").css("top", result.datas.locationY);
/* 初始化按鈕拖動(dòng)事件 */
// 鼠標(biāo)點(diǎn)擊事件
$("#滑塊拖動(dòng)條").mousedown(function(e) {
e = e || window.event;
// 鼠標(biāo)在滑塊按下切換滑塊背景
var left = e.pageX;//記錄鼠標(biāo)按下時(shí)的坐標(biāo) X軸值
var num = 0;
// 鼠標(biāo)移動(dòng)事件
document.onmousemove = function(e) {
var nowLeft = e.pageX;
num = nowLeft-left;
if(num <= 0){
num = 0;
}else if(num >= 251){
num= 251;
}
$("#滑塊拖動(dòng)條").css("width", (42+num) + "px");
$("#滑塊拖動(dòng)條圖片").css("left", (num) + "px");
$("#滑塊拖動(dòng)條").css("background-color", "#3c55ff");
};
// 鼠標(biāo)松開(kāi)事件
document.onmouseup = function(e) {
left = 0;
document.onmousemove = null;
document.onmouseup = null;
//松開(kāi)以后進(jìn)行后續(xù)的驗(yàn)證......
};
});
以上就是完整的過(guò)程了.
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- JavaScript+html實(shí)現(xiàn)前端頁(yè)面滑動(dòng)驗(yàn)證
- js實(shí)現(xiàn)滑動(dòng)滑塊驗(yàn)證登錄
- js實(shí)現(xiàn)html滑動(dòng)圖片拼圖驗(yàn)證
- JS實(shí)現(xiàn)滑動(dòng)拼圖驗(yàn)證功能完整示例
- js+canvas實(shí)現(xiàn)滑動(dòng)拼圖驗(yàn)證碼功能
- js登錄滑動(dòng)驗(yàn)證的實(shí)現(xiàn)(不滑動(dòng)無(wú)法登陸)
- 使用 Node.js 模擬滑動(dòng)拼圖驗(yàn)證碼操作的示例代碼
- js插件實(shí)現(xiàn)圖片滑動(dòng)驗(yàn)證碼
- JS實(shí)現(xiàn)PC手機(jī)端和嵌入式滑動(dòng)拼圖驗(yàn)證碼三種效果
- JavaScript+html實(shí)現(xiàn)前端頁(yè)面滑動(dòng)驗(yàn)證(2)
相關(guān)文章
Windows下使用Graalvm將Springboot應(yīng)用編譯成exe大大提高啟動(dòng)和運(yùn)行效率(推薦)
這篇文章主要介紹了Windows下使用Graalvm將Springboot應(yīng)用編譯成exe大大提高啟動(dòng)和運(yùn)行效率,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-02-02
Springboot支持Emoji表情的實(shí)現(xiàn)方法
本文主要介紹了Springboot 支持Emoji 表情,本篇的實(shí)現(xiàn)方式是僅需后端處理,具有一定的參考價(jià)值,需要的朋友可以參考一下。2021-07-07
Java的帶GUI界面猜數(shù)字游戲的實(shí)現(xiàn)示例
這篇文章主要介紹了Java的帶GUI界面猜數(shù)字游戲的實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-12-12
IDEA在Maven項(xiàng)目中使用本地jar包的方法
我們?cè)谀玫脚f項(xiàng)目的時(shí)候,經(jīng)常會(huì)遇到一種情況,就是這個(gè)項(xiàng)目的maven中依賴(lài)了一個(gè)本地的jar包,這種情況就需要引入這個(gè)jar包,所以本文給大家介紹了IDEA在Maven項(xiàng)目中使用本地jar包的方法,需要的朋友可以參考下2024-04-04

