From 664cae654867b528ea339f187ebe290cb04d6fcf Mon Sep 17 00:00:00 2001
From: xyc <jc_xiong@hotmail.com>
Date: 星期四, 27 八月 2026 18:41:21 +0800
Subject: [PATCH] docs: 关机前交接——运行状态、待验证项、外置配置要点

---
 traffic-audit-server/src/main/java/com/trafficaudit/security/captcha/CaptchaService.java |  444 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 444 insertions(+), 0 deletions(-)

diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/security/captcha/CaptchaService.java b/traffic-audit-server/src/main/java/com/trafficaudit/security/captcha/CaptchaService.java
new file mode 100644
index 0000000..ebb972c
--- /dev/null
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/security/captcha/CaptchaService.java
@@ -0,0 +1,444 @@
+package com.trafficaudit.security.captcha;
+
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Random;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * 琛屼负楠岃瘉鐮侊細鎸夋彁绀洪『搴忕偣鍑荤珛浣撳浘褰€��
+ * 鏈嶅姟绔洿鎺ョ敓鎴愭瘡涓浘褰㈢殑绛夎酱娴嬪嚑浣曢潰锛堝杈瑰舰/妞渾/璺緞锛夛紝鍓嶇鍙礋璐f覆鏌擄紝
+ * 鍝嶅簲涓笉鍖呭惈浠讳綍鍥惧舰绫诲瀷鍚嶇О锛岀瓟妗堝彧淇濆瓨鍦ㄦ湇鍔$銆�
+ */
+@Service
+public class CaptchaService {
+
+    public static final int CANVAS_W = 340;
+    public static final int CANVAS_H = 148;
+
+    private static final int SHAPE_COUNT = 6;
+    private static final int ANSWER_COUNT = 4;
+    private static final long TTL_MILLIS = 5 * 60 * 1000L;
+    private static final int MAX_FAIL = 3;
+    private static final char[] ID_CHARS = "ABCDEFGHJKMNPQRSTUVWXYZ23456789".toCharArray();
+
+    private static final Map<String, String> KIND_CN = new LinkedHashMap<>();
+
+    static {
+        KIND_CN.put("CUBE", "姝f柟浣�");
+        KIND_CN.put("BOX", "闀挎柟浣�");
+        KIND_CN.put("CYLINDER", "鍦嗘煴");
+        KIND_CN.put("CONE", "鍦嗛敟");
+        KIND_CN.put("SPHERE", "鐞�");
+        KIND_CN.put("TRI_PRISM", "涓夋1鏌�");
+        KIND_CN.put("PYRAMID", "閲戝瓧濉�");
+        KIND_CN.put("HEX_PRISM", "鍏1鏌�");
+    }
+
+    private static final String[] KINDS = KIND_CN.keySet().toArray(new String[0]);
+    private static final String[] PATTERNS = {"dots", "grid", "lines", "rings", "cross", "tri"};
+
+    private final Map<String, Challenge> store = new ConcurrentHashMap<>();
+    private final Random random = new Random();
+
+    public enum VerifyResult { OK, WRONG, NOT_FOUND, RETRY_LIMIT }
+
+    public enum ConsumeResult { OK, WRONG, NOT_FOUND, NOT_VERIFIED }
+
+    private static class Challenge {
+        String id;
+        List<String> answerKinds;
+        Map<String, String> idToKind;
+        boolean verified;
+        long expireAt;
+        int failCount;
+    }
+
+    /** 鐢熸垚涓�缁勬寫鎴橈紝杩斿洖鍙洿鎺ヤ笅鍙戝墠绔殑 JSON 缁撴瀯銆� */
+    public Map<String, Object> generate() {
+        boolean darkBg = random.nextBoolean();
+        int bgHue = random.nextInt(360);
+        int bgSat = 30 + random.nextInt(40);
+        int bgLight = darkBg ? 14 + random.nextInt(16) : 74 + random.nextInt(16);
+        String pattern = PATTERNS[random.nextInt(PATTERNS.length)];
+        int patHue = (bgHue + 120 + random.nextInt(200)) % 360;
+
+        int shapeSat = 55 + random.nextInt(30);
+        int shapeLight = darkBg ? 60 + random.nextInt(18) : 28 + random.nextInt(16);
+        int shapeHue = (bgHue + 30 + random.nextInt(300)) % 360;
+
+        // 绛旀搴忓垪锛氶殢鏈哄彇 ANSWER_COUNT 涓笉閲嶅绫诲瀷锛涘叾浣欎綅缃敤鍓╀綑绫诲瀷濉厖
+        List<String> pool = new ArrayList<>(Arrays.asList(KINDS));
+        Collections.shuffle(pool, random);
+        List<String> answerKinds = new ArrayList<>(pool.subList(0, ANSWER_COUNT));
+        List<String> allKinds = new ArrayList<>();
+        for (int i = 0; i < SHAPE_COUNT; i++) {
+            if (i < ANSWER_COUNT) {
+                allKinds.add(answerKinds.get(i));
+            } else {
+                allKinds.add(pool.get(ANSWER_COUNT + random.nextInt(pool.size() - ANSWER_COUNT)));
+            }
+        }
+        Collections.shuffle(allKinds, random);
+
+        int cols = 3, rows = 2;
+        List<Map<String, Object>> shapes = new ArrayList<>();
+        Map<String, String> idToKind = new HashMap<>();
+        for (int i = 0; i < SHAPE_COUNT; i++) {
+            int col = i % cols, row = i / cols;
+            double x = CANVAS_W * (col + 0.5) / cols + (random.nextDouble() * 24 - 12);
+            double y = CANVAS_H * (row + 0.5) / rows + (random.nextDouble() * 20 - 10);
+            double size = 0.6 + random.nextDouble() * 0.35;
+            double rotate = (random.nextInt(5) - 2) * 6 + (random.nextInt(5) - 2);
+            String kind = allKinds.get(i);
+            String id = randomId();
+            idToKind.put(id, kind);
+            shapes.add(buildShape(kind, id, x, y, size, rotate,
+                    (shapeHue + random.nextInt(40) - 20 + 360) % 360, shapeSat, shapeLight));
+        }
+
+        Challenge ch = new Challenge();
+        ch.id = randomId();
+        ch.answerKinds = answerKinds;
+        ch.idToKind = idToKind;
+        ch.expireAt = System.currentTimeMillis() + TTL_MILLIS;
+        store.put(ch.id, ch);
+
+        Map<String, Object> data = new LinkedHashMap<>();
+        data.put("captchaId", ch.id);
+        List<String> prompt = new ArrayList<>();
+        for (String k : answerKinds) {
+            prompt.add(KIND_CN.get(k));
+        }
+        data.put("prompt", prompt);
+
+        Map<String, Object> bg = new LinkedHashMap<>();
+        bg.put("hue", bgHue);
+        bg.put("sat", bgSat);
+        bg.put("light", bgLight);
+        bg.put("pattern", pattern);
+        bg.put("patternHue", patHue);
+        bg.put("patternSat", 50);
+        bg.put("patternLight", 60);
+        bg.put("patternOpacity", 0.22);
+        bg.put("patternSize", 9 + random.nextInt(8));
+        data.put("background", bg);
+        data.put("shapes", shapes);
+        return data;
+    }
+
+    /** 鏍¢獙鐐瑰嚮椤哄簭锛涢�氳繃鍚庢爣璁板凡璁よ瘉锛岀櫥褰曟椂娑堣垂銆� */
+    public VerifyResult verify(String captchaId, List<String> clickIds) {
+        Challenge ch = getLive(captchaId);
+        if (ch == null) {
+            return VerifyResult.NOT_FOUND;
+        }
+        if (match(ch, clickIds)) {
+            ch.verified = true;
+            return VerifyResult.OK;
+        }
+        ch.failCount++;
+        if (ch.failCount >= MAX_FAIL) {
+            store.remove(ch.id);
+            return VerifyResult.RETRY_LIMIT;
+        }
+        return VerifyResult.WRONG;
+    }
+
+    /** 鐧诲綍鏃舵秷璐归獙璇佺爜锛氫竴娆℃�э紝鏍¢獙绛旀骞跺垹闄ゆ寫鎴樸�� */
+    public ConsumeResult consume(String captchaId, List<String> clickIds) {
+        Challenge ch = getLive(captchaId);
+        if (ch == null) {
+            return ConsumeResult.NOT_FOUND;
+        }
+        store.remove(ch.id);
+        if (!ch.verified) {
+            return ConsumeResult.NOT_VERIFIED;
+        }
+        if (!match(ch, clickIds)) {
+            return ConsumeResult.WRONG;
+        }
+        return ConsumeResult.OK;
+    }
+
+    private Challenge getLive(String captchaId) {
+        if (captchaId == null || captchaId.isEmpty()) {
+            return null;
+        }
+        Challenge ch = store.get(captchaId);
+        if (ch == null) {
+            return null;
+        }
+        if (ch.expireAt < System.currentTimeMillis()) {
+            store.remove(ch.id);
+            return null;
+        }
+        return ch;
+    }
+
+    private boolean match(Challenge ch, List<String> clickIds) {
+        if (clickIds == null || clickIds.isEmpty() || clickIds.size() != ch.answerKinds.size()) {
+            return false;
+        }
+        List<String> got = new ArrayList<>();
+        for (String id : clickIds) {
+            String kind = ch.idToKind.get(id);
+            if (kind == null) {
+                return false;
+            }
+            got.add(kind);
+        }
+        return ch.answerKinds.equals(got);
+    }
+
+    private String randomId() {
+        StringBuilder sb = new StringBuilder(6);
+        for (int i = 0; i < 6; i++) {
+            sb.append(ID_CHARS[random.nextInt(ID_CHARS.length)]);
+        }
+        return sb.toString();
+    }
+
+    // ==================== 绛夎酱娴嬪嚑浣曠敓鎴� ====================
+
+    private Map<String, Object> buildShape(String kind, String id, double x, double y, double size,
+                                           double rotate, int hue, int sat, int light) {
+        ShapeArt art;
+        switch (kind) {
+            case "CUBE": art = cube(hue, sat, light); break;
+            case "BOX": art = box(hue, sat, light); break;
+            case "CYLINDER": art = cylinder(hue, sat, light); break;
+            case "CONE": art = cone(hue, sat, light); break;
+            case "SPHERE": art = sphere(hue, sat, light); break;
+            case "TRI_PRISM": art = triPrism(hue, sat, light); break;
+            case "PYRAMID": art = pyramid(hue, sat, light); break;
+            case "HEX_PRISM": art = hexPrism(hue, sat, light); break;
+            default: art = cube(hue, sat, light);
+        }
+        Map<String, Object> m = new LinkedHashMap<>();
+        m.put("id", id);
+        m.put("x", x);
+        m.put("y", y);
+        m.put("size", size);
+        m.put("rotate", rotate);
+        m.put("hitW", art.bbox[0]);
+        m.put("hitH", art.bbox[1]);
+        m.put("faces", art.faces);
+        return m;
+    }
+
+    private static class ShapeArt {
+        List<Map<String, Object>> faces;
+        double[] bbox; // {w, h} 鏈棆杞湭缂╂斁鏃剁殑鍖呭洿鐩�
+
+        ShapeArt(List<Map<String, Object>> faces, double w, double h) {
+            this.faces = faces;
+            this.bbox = new double[]{w, h};
+        }
+    }
+
+    private static Map<String, Object> poly(String pts, String fill, String stroke) {
+        Map<String, Object> f = new LinkedHashMap<>();
+        f.put("type", "poly");
+        f.put("pts", pts);
+        f.put("fill", fill);
+        f.put("stroke", stroke);
+        f.put("sw", 1.2);
+        return f;
+    }
+
+    private static Map<String, Object> ellipse(double cx, double cy, double rx, double ry,
+                                               String fill, String stroke, double opacity) {
+        Map<String, Object> f = new LinkedHashMap<>();
+        f.put("type", "ellipse");
+        f.put("cx", cx);
+        f.put("cy", cy);
+        f.put("rx", rx);
+        f.put("ry", ry);
+        f.put("fill", fill);
+        f.put("stroke", stroke);
+        f.put("sw", opacity > 0 ? 0 : 1.2);
+        f.put("opacity", opacity);
+        return f;
+    }
+
+    private static Map<String, Object> path(String d, String fill, String stroke) {
+        Map<String, Object> f = new LinkedHashMap<>();
+        f.put("type", "path");
+        f.put("d", d);
+        f.put("fill", fill);
+        f.put("stroke", stroke);
+        f.put("sw", 1.2);
+        return f;
+    }
+
+    private static String hsl(int h, int s, int l) {
+        return "hsl(" + h + "," + s + "%," + l + "%)";
+    }
+
+    private static Map<String, Object> stop(double offset, String color) {
+        Map<String, Object> m = new LinkedHashMap<>();
+        m.put("o", offset);
+        m.put("c", color);
+        return m;
+    }
+
+    private static String shade(int h, int s, int l, int delta) {
+        int v = Math.max(6, Math.min(94, l + delta));
+        return hsl(h, s, v);
+    }
+
+    private static String fmt(double d) {
+        return String.format(Locale.ROOT, "%.1f", d);
+    }
+
+    private static String pts(double... p) {
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < p.length; i += 2) {
+            if (sb.length() > 0) {
+                sb.append(' ');
+            }
+            sb.append(fmt(p[i])).append(',').append(fmt(p[i + 1]));
+        }
+        return sb.toString();
+    }
+
+    private static ShapeArt cube(int h, int s, int l) {
+        double side = 26, d = 13, cy = d / 2;
+        String fillTop = shade(h, s, l, 18);
+        String fillFront = hsl(h, s, l);
+        String fillRight = shade(h, s, l, -14);
+        String stroke = shade(h, s, l, -30);
+        List<Map<String, Object>> faces = new ArrayList<>();
+        faces.add(poly(pts(-side / 2, -cy, side / 2, -cy, side / 2 + d, -cy - d, -side / 2 + d, -cy - d), fillTop, stroke));
+        faces.add(poly(pts(side / 2, -cy, side / 2, -cy + side, side / 2 + d, -cy + side - d, side / 2 + d, -cy - d), fillRight, stroke));
+        faces.add(poly(pts(-side / 2, -cy, side / 2, -cy, side / 2, -cy + side, -side / 2, -cy + side), fillFront, stroke));
+        return new ShapeArt(faces, side + d, side + d);
+    }
+
+    private static ShapeArt box(int h, int s, int l) {
+        double w = 34, height = 22, d = 14, cy = d / 2;
+        String fillTop = shade(h, s, l, 18);
+        String fillFront = hsl(h, s, l);
+        String fillRight = shade(h, s, l, -14);
+        String stroke = shade(h, s, l, -30);
+        List<Map<String, Object>> faces = new ArrayList<>();
+        faces.add(poly(pts(-w / 2, -cy, w / 2, -cy, w / 2 + d, -cy - d, -w / 2 + d, -cy - d), fillTop, stroke));
+        faces.add(poly(pts(w / 2, -cy, w / 2, -cy + height, w / 2 + d, -cy + height - d, w / 2 + d, -cy - d), fillRight, stroke));
+        faces.add(poly(pts(-w / 2, -cy, w / 2, -cy, w / 2, -cy + height, -w / 2, -cy + height), fillFront, stroke));
+        return new ShapeArt(faces, w + d, height + d);
+    }
+
+    private static ShapeArt cylinder(int h, int s, int l) {
+        double r = 18, height = 34;
+        String fillSide = hsl(h, s, l);
+        String fillTop = shade(h, s, l, 18);
+        String stroke = shade(h, s, l, -30);
+        List<Map<String, Object>> faces = new ArrayList<>();
+        String side = "M" + fmt(-r) + "," + fmt(-height / 2)
+                + " L" + fmt(-r) + "," + fmt(height / 2)
+                + " A" + fmt(r) + "," + fmt(r * 0.45) + ",0,0,0," + fmt(r) + "," + fmt(height / 2)
+                + " L" + fmt(r) + "," + fmt(-height / 2)
+                + " A" + fmt(r) + "," + fmt(r * 0.45) + ",0,0,0," + fmt(-r) + "," + fmt(-height / 2) + " Z";
+        faces.add(path(side, fillSide, stroke));
+        faces.add(ellipse(0, -height / 2, r, r * 0.45, fillTop, stroke, 1));
+        return new ShapeArt(faces, r * 2, height + r * 0.9);
+    }
+
+    private static ShapeArt cone(int h, int s, int l) {
+        double r = 20, height = 38;
+        double apexY = -height;
+        double cy = (-height + r * 0.45) / 2;
+        String fillBase = shade(h, s, l, -14);
+        String fillSide = hsl(h, s, l);
+        String stroke = shade(h, s, l, -30);
+        List<Map<String, Object>> faces = new ArrayList<>();
+        faces.add(ellipse(0, -cy, r, r * 0.45, fillBase, stroke, 1));
+        String side = "M" + fmt(0) + "," + fmt(apexY - cy)
+                + " L" + fmt(-r) + "," + fmt(-cy)
+                + " A" + fmt(r) + "," + fmt(r * 0.45) + ",0,0,1," + fmt(r) + "," + fmt(-cy) + " Z";
+        faces.add(path(side, fillSide, stroke));
+        return new ShapeArt(faces, r * 2, height + r * 0.45);
+    }
+
+    private static ShapeArt sphere(int h, int s, int l) {
+        double r = 21;
+        String stroke = shade(h, s, l, -30);
+        List<Map<String, Object>> faces = new ArrayList<>();
+        Map<String, Object> grad = new LinkedHashMap<>();
+        grad.put("id", "g");
+        grad.put("cx", 0.35);
+        grad.put("cy", 0.32);
+        grad.put("r", 0.85);
+        List<Map<String, Object>> stops = new ArrayList<>();
+        stops.add(stop(0, shade(h, s, l, 26)));
+        stops.add(stop(0.5, hsl(h, s, l)));
+        stops.add(stop(1, shade(h, s, l, -20)));
+        grad.put("stops", stops);
+        Map<String, Object> f = new LinkedHashMap<>();
+        f.put("type", "radial");
+        f.put("cx", 0.0);
+        f.put("cy", 0.0);
+        f.put("r", r);
+        f.put("grad", grad);
+        f.put("stroke", stroke);
+        f.put("sw", 1.2);
+        faces.add(f);
+        faces.add(ellipse(-r * 0.38, -r * 0.45, r * 0.3, r * 0.2, "#ffffff", "none", 0.65));
+        faces.add(ellipse(r * 0.25, r * 0.45, r * 0.55, r * 0.2, shade(h, s, l, -32), "none", 0.45));
+        return new ShapeArt(faces, r * 2, r * 2);
+    }
+
+    private static ShapeArt triPrism(int h, int s, int l) {
+        double w = 34, height = 30, depth = 12;
+        double cy = (depth * 0.5 + height) / 2;
+        String fillTop = shade(h, s, l, 18);
+        String fillRight = hsl(h, s, l);
+        String fillLeft = shade(h, s, l, -14);
+        String stroke = shade(h, s, l, -30);
+        List<Map<String, Object>> faces = new ArrayList<>();
+        faces.add(poly(pts(0, -cy + depth * 0.5, w / 2, -cy, -w / 2, -cy), fillTop, stroke));
+        faces.add(poly(pts(0, -cy + depth * 0.5, w / 2, -cy, w / 2, -cy + height, 0, -cy + depth * 0.5 + height), fillRight, stroke));
+        faces.add(poly(pts(0, -cy + depth * 0.5, -w / 2, -cy, -w / 2, -cy + height, 0, -cy + depth * 0.5 + height), fillLeft, stroke));
+        return new ShapeArt(faces, w, depth * 0.5 + height);
+    }
+
+    private static ShapeArt pyramid(int h, int s, int l) {
+        double half = 26, height = 34;
+        double cy = (-height + half) / 2;
+        String fillBase = shade(h, s, l, -20);
+        String fillRight = hsl(h, s, l);
+        String fillLeft = shade(h, s, l, -10);
+        String stroke = shade(h, s, l, -30);
+        List<Map<String, Object>> faces = new ArrayList<>();
+        faces.add(poly(pts(0, -cy, half, -cy + half * 0.5, 0, -cy + half, -half, -cy + half * 0.5), fillBase, stroke));
+        faces.add(poly(pts(0, -cy - height, 0, -cy, -half, -cy + half * 0.5), fillLeft, stroke));
+        faces.add(poly(pts(0, -cy - height, 0, -cy, half, -cy + half * 0.5), fillRight, stroke));
+        return new ShapeArt(faces, half * 2, height + half);
+    }
+
+    private static ShapeArt hexPrism(int h, int s, int l) {
+        double r = 22, height = 28, ry = r * 0.433;
+        double cy = ry;
+        String fillTop = shade(h, s, l, 18);
+        String fillR = hsl(h, s, l);
+        String fillF = shade(h, s, l, -12);
+        String fillL = shade(h, s, l, -22);
+        String stroke = shade(h, s, l, -30);
+        List<Map<String, Object>> faces = new ArrayList<>();
+        faces.add(poly(pts(r, -cy, r * 0.5, -cy + ry, -r * 0.5, -cy + ry, -r, -cy,
+                -r * 0.5, -cy - ry, r * 0.5, -cy - ry), fillTop, stroke));
+        faces.add(poly(pts(r, -cy, r * 0.5, -cy + ry, r * 0.5, -cy + ry + height, r, -cy + height), fillR, stroke));
+        faces.add(poly(pts(r * 0.5, -cy + ry, -r * 0.5, -cy + ry, -r * 0.5, -cy + ry + height, r * 0.5, -cy + ry + height), fillF, stroke));
+        faces.add(poly(pts(-r * 0.5, -cy + ry, -r, -cy, -r, -cy + height, -r * 0.5, -cy + ry + height), fillL, stroke));
+        return new ShapeArt(faces, r * 2, ry * 2 + height);
+    }
+}
\ No newline at end of file

--
Gitblit v1.9.1