feat: 增强版验证码全套实现 — 16种类型生成器/加密/风控/站点管理/平台后端/前端SDK/UI
- 核心新增: ICON_CLICK(PNG图标方案,预渲染资源)/SCRATCH/JIGSAW/CURVE_SLIDER(V1-V3)/ANGLE/CURVE_DRAW/WORD_ORDER_CLICK/PROOF_OF_WORK 等生成器 - 图标点选: classpath PNG 加载替代运行时字体渲染(Linux容器无emoji字体),提示条图作为 templateImage 返回前端 - 新增模块: crypto(AES+RSA)/obfuscator(背景乱序/噪声/扭曲)/risk(风控/IP黑名单/限流)/site(站点管理)/ml(轨迹规则引擎) - 平台后端: 站点管理/验证码API(generate/verify/secondary-verify)/统计/ML轨迹学习 - 前端SDK: TPCaptcha兼容,支持全部新型号渲染与交互 - 工具: tools/icon-render 图标预渲染工具
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* One-off tool: pre-render emoji icons to PNG assets for IconClickImageCaptchaGenerator.
|
||||
* Must run on a machine that has a color/outline emoji font (e.g. Windows Segoe UI Emoji).
|
||||
* Output: META-INF/captcha-icons/<name>.png (96x96, white backing disc + glyph).
|
||||
*
|
||||
* Usage: java tools/icon-render/RenderIcons.java <output-dir>
|
||||
*/
|
||||
public class RenderIcons {
|
||||
|
||||
private static final int CANVAS = 96;
|
||||
private static final float GLYPH_SIZE = 60f;
|
||||
|
||||
private static final Map<String, String> ICONS = new LinkedHashMap<>() {{
|
||||
// fruits
|
||||
put("apple", "\uD83C\uDF4E");
|
||||
put("banana", "\uD83C\uDF4C");
|
||||
put("cherry", "\uD83C\uDF52");
|
||||
put("grapes", "\uD83C\uDF47");
|
||||
put("lemon", "\uD83C\uDF4B");
|
||||
put("orange", "\uD83C\uDF4A");
|
||||
put("strawberry", "\uD83C\uDF53");
|
||||
put("watermelon", "\uD83C\uDF49");
|
||||
// animals
|
||||
put("dog", "\uD83D\uDC36");
|
||||
put("cat", "\uD83D\uDC31");
|
||||
put("mouse", "\uD83D\uDC2D");
|
||||
put("hamster", "\uD83D\uDC39");
|
||||
put("rabbit", "\uD83D\uDC30");
|
||||
put("fox", "\uD83E\uDD8A");
|
||||
put("bear", "\uD83D\uDC3B");
|
||||
put("panda", "\uD83D\uDC28");
|
||||
// sports
|
||||
put("soccer", "\u26BD");
|
||||
put("basketball", "\uD83C\uDFC0");
|
||||
put("tennis", "\uD83C\uDFBE");
|
||||
put("volleyball", "\uD83C\uDFD0");
|
||||
put("billiards", "\uD83C\uDFB1");
|
||||
put("pingpong", "\uD83C\uDFD3");
|
||||
put("trophy", "\uD83C\uDFC6");
|
||||
put("boxing", "\uD83E\uDD4A");
|
||||
// vehicles
|
||||
put("car", "\uD83D\uDE97");
|
||||
put("taxi", "\uD83D\uDE95");
|
||||
put("suv", "\uD83D\uDE99");
|
||||
put("bus", "\uD83D\uDE8C");
|
||||
put("racecar", "\uD83C\uDFCE");
|
||||
put("police", "\uD83D\uDE93");
|
||||
put("ambulance", "\uD83D\uDE91");
|
||||
put("firetruck", "\uD83D\uDE92");
|
||||
// tech
|
||||
put("phone", "\uD83D\uDCF1");
|
||||
put("laptop", "\uD83D\uDCBB");
|
||||
put("desktop", "\uD83D\uDDA5");
|
||||
put("printer", "\uD83D\uDDA8");
|
||||
put("keyboard", "\u2328");
|
||||
put("computer-mouse", "\uD83D\uDDB1");
|
||||
put("cd", "\uD83D\uDCBF");
|
||||
put("camera", "\uD83D\uDCF7");
|
||||
}};
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
Path outDir = Paths.get(args.length > 0 ? args[0] : ".");
|
||||
Files.createDirectories(outDir);
|
||||
|
||||
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
|
||||
Font emojiFont = pickFont(ge);
|
||||
System.out.println("Using font: " + emojiFont.getFontName());
|
||||
|
||||
int failures = 0;
|
||||
for (Map.Entry<String, String> e : ICONS.entrySet()) {
|
||||
BufferedImage img = render(e.getValue(), emojiFont);
|
||||
File out = outDir.resolve(e.getKey() + ".png").toFile();
|
||||
ImageIO.write(img, "png", out);
|
||||
if (emojiFont.canDisplayUpTo(e.getValue()) != -1) {
|
||||
System.out.println("[WARN] partial glyph coverage: " + e.getKey());
|
||||
failures++;
|
||||
}
|
||||
System.out.println("OK " + out.getName());
|
||||
}
|
||||
System.out.println("Done. failures(warn)=" + failures);
|
||||
}
|
||||
|
||||
private static Font pickFont(GraphicsEnvironment ge) {
|
||||
for (String name : new String[]{"Segoe UI Emoji", "Apple Color Emoji", "Noto Color Emoji", "EmojiOne"}) {
|
||||
for (Font f : ge.getAllFonts()) {
|
||||
if (f.getFontName().equalsIgnoreCase(name)) {
|
||||
return f.deriveFont(Font.PLAIN, GLYPH_SIZE);
|
||||
}
|
||||
}
|
||||
}
|
||||
System.out.println("[WARN] no known emoji font found, falling back to Dialog");
|
||||
return new Font(Font.DIALOG, Font.PLAIN, (int) GLYPH_SIZE);
|
||||
}
|
||||
|
||||
private static BufferedImage render(String emoji, Font font) {
|
||||
BufferedImage img = new BufferedImage(CANVAS, CANVAS, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D g = img.createGraphics();
|
||||
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
|
||||
g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
|
||||
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||
try {
|
||||
// translucent white backing disc -> keeps icons readable on any photo background
|
||||
g.setColor(new Color(255, 255, 255, 216));
|
||||
g.fillOval(2, 2, CANVAS - 5, CANVAS - 5);
|
||||
g.setColor(new Color(0, 0, 0, 46));
|
||||
g.setStroke(new BasicStroke(2f));
|
||||
g.drawOval(3, 3, CANVAS - 7, CANVAS - 7);
|
||||
|
||||
g.setColor(Color.BLACK);
|
||||
g.setFont(font);
|
||||
FontMetrics fm = g.getFontMetrics();
|
||||
int x = (CANVAS - fm.stringWidth(emoji)) / 2;
|
||||
int y = (CANVAS - fm.getHeight()) / 2 + fm.getAscent();
|
||||
g.drawString(emoji, x, y);
|
||||
} finally {
|
||||
g.dispose();
|
||||
}
|
||||
return img;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user