本文實現類 12306 行為驗證碼:服務端從分類目錄隨機抽取 8 張圖片合成一張大圖,隨機指定一類作為驗證目標;前端展示大圖,用戶點擊選擇目標圖片,提交點擊坐標;後端根據坐標映射判斷是否命中目標類別。
tips[8]。tips 中隨機選一個 tip,記錄該類別在 8 張圖中的位置(1-8),存入 Session。BufferedImage.setRGB 將 8 張 200x200 小圖拼成 800x400 大圖(兩行四列),輸出 JPEG 流。x||y 序列。public static List<Object> getEightImages() {
List<String> paths = new ArrayList<>();
File[] finalImages = new File[8];
String[] tips = new String[8];
for (int i = 0; i < 8; i++) {
File secDir = getRandomSubDir(); // 隨機二級目錄
tips[i] = secDir.getName();
File img = getRandomFile(secDir);
img = dropSameImage(img, paths, tips, i); // 去重
paths.add(img.getPath());
finalImages[i] = img;
}
return Arrays.asList(finalImages, tips);
}
public static List<Object> getLocation(String[] tips) {
String tip = tips[new Random().nextInt(8)]; // 隨機目標類別
List<Object> loc = new ArrayList<>();
loc.add(tip);
for (int i = 0; i < 8; i++) if (tip.equals(tips[i])) loc.add(i + 1); // 1-based
return loc;
}
public static void mergeImage(File[] imgs, HttpServletResponse resp) throws IOException {
BufferedImage big = new BufferedImage(800, 400, BufferedImage.TYPE_INT_BGR);
for (int i = 0; i < 8; i++) {
BufferedImage src = ImageIO.read(imgs[i]);
int[] rgb = src.getRGB(0, 0, src.getWidth(), src.getHeight(), null, 0, src.getWidth());
if (i < 4) big.setRGB(i * 200, 0, 200, 200, rgb, 0, 200);
else big.setRGB((i - 4) * 200, 200, 200, 200, rgb, 0, 200);
}
ImageIO.write(big, "jpg", resp.getOutputStream());
}
List<Object> obj = ImageHelper.getEightImages();
File[] imgs = (File[]) obj.get(0);
String[] tips = (String[]) obj.get(1);
List<Object> loc = ImageHelper.getLocation(tips);
session.setAttribute("tip", loc.get(0)); // 目標類別
loc.remove(0);
session.setAttribute("locations", loc); // 目標位置列表
ImageHelper.mergeImage(imgs, response);
// 點擊記錄坐標
$('#bigPic').click(e => {
let x = e.offsetX - 400; // 原點調整
let y = 200 - e.offsetY;
coords.push(x + '||' + y);
// 添加標記 div...
});
// 刷新驗證碼(AJAX + Blob)
function refresh() {
fetch('/identify').then(r => r.blob()).then(blob => {
bigPic.src = URL.createObjectURL(blob);
});
}
public List<Integer> isPass(String coords) {
List<Integer> clicked = new ArrayList<>();
for (String xy : coords.split(",")) {
int x = Integer.parseInt(xy.split("\\|\\|")[0]);
int y = Integer.parseInt(xy.split("\\|\\|")[1]);
// 根據 200x200 區域判斷圖片編號 1-8
if (x > -75 && x <= 0) {
if (y > -75 && y <= 0) clicked.add(8);
else if (y >= -150 && y <= -75) clicked.add(4);
} else if (x > -150 && x <= -75) { ... }
// ... 其餘區域
}
return clicked;
}
// 對比 clicked 與 session.locations