js+java實現登錄滑動圖片驗證功能

最新需要公司要求在不改變原來的登錄邏輯的情況下,將原來的驗證碼登錄的形式改成滑動圖片的形式!下面是做出來的效果:

實現思路:所有的圖片數據,驗證全部由後端來做。前端調用接口,後端會返回兩張經過base64加密的圖片信息,分別是背景圖片和滑塊圖片,前端滑動滑塊以後將X方向的滑動距離傳回後端做驗證,驗證成功以後再做後續的登錄邏輯驗證,以下是完整的過程:

獲取背景圖,我這邊是在FTP上放了10張圖片,隨機獲取一張。

@LogAnnotation(description = "web獲取滑動圖片信息")
	@ApiOperation(value = "web獲取滑動圖片信息", 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 {
			//隨機獲取需要切成的圖片形狀
			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");
			//根據源圖片和摳圖形狀生成新的圖片信息以流的形式返回到前端
			pictureMap = VerifyImageUtil.getVerifyImage(tempInputStream);
			//將剪裁好了以後的圖片信息以當前時間戳爲key,存入redis(這一步是爲了對圖片信息做過期處理,根據自己需求做)
			String tempTime = String.valueOf(System.currentTimeMillis());
			redisUtils.hset(Constant.REDIS_LOGIN_PICTURE_CODE, tempTime,pictureMap.get("locationX").toString(),60);
			pictureMap.put("tempTime",tempTime);
			//移出隨機生成的摳圖位置座標,不返回給前端
			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服務器  
            ftpClient.login(username , password );// 登陸FTP服務器  
            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;
    }  

摳圖工具類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;
	/**
	 * 摳圖內部矩形填充大小
	 */
	private static int RECTANGLE_PADDING = 6;
	/**
	 * 摳圖的邊框寬度
	 */
	private static int SLIDER_IMG_OUT_PADDING = 1;


	/**
	 * 根據傳入的路徑生成指定驗證碼圖片
	 *
	 * @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方向的信息,用於驗證滑塊位置是否正確
		resultMap.put("locationX",locationX);
        //放入摳圖位置的Y方向的信息,用於前端控制定位信息
		resultMap.put("locationY",locationY);
		return resultMap;
	}


	/**
	 * 生成隨機滑塊形狀
	 * <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, 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);
				}
			}

		}
	}


	/**
	 * 隨機獲取一張圖片對象
	 * @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編碼
		return encoder.encode(bytes);
	}

	/**
	 * 將BASE64字符串轉換爲圖片
	 * @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;
	}
}

前端收到請求成功返回信息以後,對數據做解析

// 設置圖片的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);
				/* 初始化按鈕拖動事件 */
				// 鼠標點擊事件
				$("#滑塊拖動條").mousedown(function(e) {
					e = e || window.event;
					// 鼠標在滑塊按下切換滑塊背景
					var left = e.pageX;//記錄鼠標按下時的座標 X軸值
					var num = 0;
					// 鼠標移動事件
					document.onmousemove = function(e) {
						var nowLeft = e.pageX;
						num = nowLeft-left;
						if(num <= 0){
							num = 0;
						}else if(num >= 251){
							num= 251;
						}
						$("#滑塊拖動條").css("width", (42+num) + "px");
						$("#滑塊拖動條圖片").css("left", (num) + "px");
						$("#滑塊拖動條").css("background-color", "#3c55ff");
					};
					// 鼠標鬆開事件
					document.onmouseup = function(e) {
						left = 0;
						document.onmousemove = null;
						document.onmouseup = null;
                        //鬆開以後進行後續的驗證......
						
					};
				});

以上就是完整的過程了,歡迎交流!

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章