記錄一次滑動拼圖驗證碼的實現流程
由於資源有限,實現原理比較簡單。沒有使用任何的第三方jar。只是用了原生的 Java api。
驗證是否通過的原理是,判斷小圖的結束坐標,與摳圖位置的坐標是否吻合。
主要難點就是,小圖的生成,目前只對小圖增加了白色的邊框,沒有做陰影及邊緣模糊處理。
1、隨機獲取一張地圖
2、在一定范圍內隨機生成一個坐標點用於摳圖
3、構建小圖方形矩陣,隨機選取兩個邊添加凸起,創建圖層
4、將底圖的顏色拷貝到小圖中
5、給底圖添加灰色水印
6、返回小圖的隨機位置,以及地圖和小圖的base64圖片
准備工作: 10張地圖、redis(摳圖位置的坐標和驗證碼的其他用戶驗證的相關信息放到了redis中)。
缺點:
圖片效果有待優化,摳圖的代碼自於網絡,比較簡單;后期應考慮用開源的api來處理,替代原生api, 小圖應該增加陰影和邊緣虛化處理,讓客戶看起來更容易。
驗證算法過於簡單。適用於項目前期。算法是驗證碼的核心,網絡上也有很多驗證算法,但是安全性不高實現,方式比較復雜。有算法工程師的可以考慮加入識別算法。
入口方法:
public DubboResult<JigsawImageDTO> getJigsawImage() {
String fileName = getBaseImageFileName();
String jigsawId = UUID.randomUUID().toString();
logger.info("fileName={}", fileName);
Map<String, String> result = generateJigsaw(fileName);
//放入緩存
CoordinateDTO slideImageTargetCoordinate = new CoordinateDTO();
slideImageTargetCoordinate.setX(Integer.valueOf(result.get("location_x")));
slideImageTargetCoordinate.setY(Integer.valueOf(result.get("location_y")));
JigsawImageRedisInfoDTO jigsawImageRedisInfoDTO = new JigsawImageRedisInfoDTO();
jigsawImageRedisInfoDTO.setSlideImageCoordinate(slideImageTargetCoordinate);
jigsawImageRedisInfoDTO.setJigsawId(jigsawId);
jigsawImageRedisInfoDTO.setSuccess(Boolean.FALSE);
RedisUtil.set(getRedieKey(jigsawId), JSON.toJSONString(jigsawImageRedisInfoDTO), JIGSAW_VALID_TIME);
//拼接返回值
JigsawImageDTO jigsawImageDTO = new JigsawImageDTO();
jigsawImageDTO.setJigsawId(jigsawId);
jigsawImageDTO.setBaseImage(result.get("bigImgBase64Str"));
jigsawImageDTO.setSlideImage(result.get("smallImgBase46Str"));
//int randomX = new Random().nextInt(Integer.valueOf(slideImageTargetCoordinate.getX())/3);
slideImageTargetCoordinate.setX(20);
jigsawImageDTO.setSlideImageCoordinate(slideImageTargetCoordinate);
return DubboResult.correct(jigsawImageDTO);
}
private String getBaseImageFileName() {
String fileName1 = this.getClass().getClassLoader().getResource("").toString();
logger.info("filename1={}", fileName1);
String fileName = this.getClass().getResource("/").toString()
+ BASE_IMAGE_DIRECTORY
+ (new Random().nextInt(BASE_IMAGE_COUNT) + 1)
+ BASE_IMAGE_TYPE;
return fileName.substring(5, fileName.length());
}
/**
** 隨機選取一個地圖
*/
private Map<String, String> generateJigsaw(String fileName) {
try {
BufferedImage image = ImageIO.read(new File(fileName));
ByteArrayOutputStream out = new ByteArrayOutputStream();
ImageIO.write(image, "png", out);
Map<String, String> result = CaptchaUtils.createImage(new File(fileName));
logger.info("result:" + result);
return result;
} catch (Exception e) {
logger.error("獲取圖片失敗", e);
}
return null;
}
圖片處理工具類:
package com.isesol.jigsaw.util;
import org.apache.commons.lang.math.RandomUtils;
import sun.misc.BASE64Encoder;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
public class CaptchaUtils {
static int targetWidth = 50;//小圖長
static int targetHeight = 50;//小圖高
static int circleR = 7;//半徑
private static int[][] getBlockDataV2() {
int[][] data = new int[targetWidth][targetHeight];
double po = Math.pow(circleR, 2);
//隨機生成兩個圓的坐標
//在4個方向上 隨機找到2個方向添加凸出
//凸出1
int face1 = RandomUtils.nextInt(3);
//凸出2
int face2;
//使凸出1 與 凸出2不在同一個方向
while (true) {
face2 = RandomUtils.nextInt(3);
if (face1 != face2) {
break;
}
}
int center1X = getCenterLocation(face1).get("x");
int center1Y = getCenterLocation(face1).get("y");
int center2X = getCenterLocation(face2).get("x");
int center2Y = getCenterLocation(face2).get("y");
//圓的標准方程 (x-a)²+(y-b)²=r²,標識圓心(a,b),半徑為r的圓
//計算需要的小圖輪廓,用二維數組來表示,二維數組有兩張值,0和1,其中0表示沒有顏色,1有顏色
for (int i = 0; i < targetWidth; i++) {
for (int j = 0; j < targetHeight; j++) {
data[i][j] = 0;
double d1 = Math.pow(i - center1X, 2) + Math.pow(j - center1Y, 2);
double d2 = Math.pow(i - center2X, 2) + Math.pow(j - center2Y, 2);
//創建中間的方形區域
if ((i >= circleR && i <= targetWidth - circleR
&& j >= circleR
&& j <= targetHeight - circleR)) {
data[i][j] = 1;
}
//創建兩個凸起
if (d1 <= po || d2 <= po) {
data[i][j] = 1;
}
//優化 凸起頂點部分效果
if(i == (targetWidth/2- 1) && j == 0){
data[i][j] = 0;
}
if(i == (targetWidth - 1) && j == (targetHeight/2 - 1)){
data[i][j] = 0;
}
if(i == (targetWidth/2- 1) && j == targetHeight - 1){
data[i][j] = 0;
}
if(i == 0 && j == (targetHeight/2 - 1)){
data[i][j] = 0;
}
}
}
return data;
}
/**
* 根據圓的方向獲取圓心坐標
*
* @param face
* @return
*/
private static Map<String, Integer> getCenterLocation(int face) {
Map<String, Integer> map = new HashMap<>(2);
//上
if (0 == face) {
map.put("x", targetWidth / 2 - 1);
map.put("y", circleR);
//左
} else if (1 == face) {
map.put("x", circleR);
map.put("y", targetHeight / 2 - 1);
//下
} else if (2 == face) {
map.put("x", targetWidth / 2 - 1);
map.put("y", targetHeight - circleR - 1);
//右
} else if (3 == face) {
map.put("x", targetWidth - circleR - 1);
map.put("y", targetHeight / 2 - 1);
}
return map;
}
private static void cutByTemplate1(BufferedImage oriImage, BufferedImage targetImage, int[][] templateImage, int x, int y) {
int[][] martrix = new int[3][3];
int[] values = new int[9];
BufferedImage waterImage = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_4BYTE_ABGR);
//創建shape區域
for (int i = 0; i < targetWidth; i++) {
for (int j = 0; j < targetHeight; j++) {
int rgb = templateImage[i][j];
// 原圖中對應位置變色處理
int rgb_ori = oriImage.getRGB(x + i, y + j);
if (rgb == 1) {
waterImage.setRGB(i, j, Color.BLACK.getRGB());
targetImage.setRGB(i, j, rgb_ori);
} else {
//這里把背景設為透明
targetImage.setRGB(i, j, rgb_ori & 0x00ffffff);
waterImage.setRGB(i, j, rgb_ori & 0x00ffffff);
}
int xLength = templateImage[1].length;
int yLength = templateImage.length;
//防止數組越界判斷
if (i == (xLength - 1) || j == (yLength - 1)) {
continue;
}
//描邊處理,,取帶像素和無像素的界點,判斷該點是不是臨界輪廓點,如果是設置該坐標像素是白色
if ((templateImage[i][j] == 1 && templateImage[i + 1][j] == 0) || (templateImage[i][j] == 1 && templateImage[i][j + 1] == 0)
|| (templateImage[i][j] == 1 && templateImage[i - 1][j] == 0) || (templateImage[i][j] == 1 && templateImage[i][j - 1] == 0)) {
targetImage.setRGB(i, j, Color.white.getRGB());
waterImage.setRGB(i, j, Color.white.getRGB());
//oriImage.setRGB(x + i, y + j, Color.white.getRGB());
}
}
}
addWatermark(oriImage, waterImage, x, y, 0.8F);
}
private static BufferedImage addWatermark(BufferedImage imageBuffer, BufferedImage smallImage, int x, int y, float alpha){
Graphics2D graphics2D = imageBuffer.createGraphics();
graphics2D.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha));
graphics2D.drawImage(smallImage, x, y, null);
graphics2D.dispose(); //釋放
return imageBuffer;
}
public static Map<String, String> createImage(File file) {
Map<String, String> resultMap = new HashMap<>();
try {
BufferedImage oriImage = ImageIO.read(file);
Random random = new Random();
//X軸距離右端targetWidth Y軸距離底部targetHeight以上
int widthRandom = random.nextInt(targetWidth / 2) + oriImage.getWidth() / 2 + targetWidth;
int heightRandom = random.nextInt(targetHeight) + targetHeight / 4;
BufferedImage targetImage = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_4BYTE_ABGR);
cutByTemplate1(oriImage, targetImage, getBlockDataV2(), widthRandom, heightRandom);
//BufferedImage aaa = addaa(targetImage, 20, 20, Color.red, 0.3f);
resultMap.put("bigImgBase64Str", getImageBASE64(oriImage));//大圖
//System.out.println(getImageBASE64(oriImage));
resultMap.put("smallImgBase46Str", getImageBASE64(targetImage));//小圖
//System.out.println(getImageBASE64(targetImage));
resultMap.put("location_x", String.valueOf(widthRandom));
resultMap.put("location_y", String.valueOf(heightRandom));
} catch (Exception e) {
e.printStackTrace();
} finally {
return resultMap;
}
}
/**
* @param image
*/
public static String getImageBASE64(BufferedImage image) throws IOException {
byte[] imagedata = null;
ByteArrayOutputStream bao = new ByteArrayOutputStream();
ImageIO.write(image, "png", bao);
imagedata = bao.toByteArray();
BASE64Encoder encoder = new BASE64Encoder();
String BASE64IMAGE = encoder.encodeBuffer(imagedata).trim();
BASE64IMAGE = BASE64IMAGE.replaceAll("\r|\n", ""); //刪除 \r\n
return BASE64IMAGE;
}
public static void main(String[] args) {
Map<String, Object> resultMap = new HashMap<>();
//讀取本地路徑下的圖片,隨機選一條
File file = new File("D:\\intellj_text_prj\\boot-test\\src\\main\\resources\\static\\captcha_image_01.png");
createImage(file);
resultMap.remove("xWidth");
resultMap.put("errcode", 0);
resultMap.put("errmsg", "success");
}
}