核心功能特性
- 支援自訂寬高、字元長度、字體、字號、干擾線密度、顏色範圍。
- 單例模式 + Builder 鏈式配置,執行緒安全。
- 自動存入 Session,防快取 headers,直接輸出 JPEG 流。
- 每字元隨機顏色、位置微調,提升識別難度。
完整工具類
public final class CaptchaFactory {
private static final char[] CHAR_POOL = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".toCharArray();
private static final CaptchaFactory INSTANCE = new CaptchaFactory();
// 可配置預設值
private int width = 120, height = 40, length = 4, fontSize = 24, xOffset = 20, yOffset = 28;
private String fontFamily = "Arial";
private CaptchaFactory() {}
public static CaptchaFactory getInstance() { return INSTANCE; }
// 鏈式配置方法
public CaptchaFactory size(int w, int h) { this.width = w; this.height = h; return this; }
public CaptchaFactory length(int len) { this.length = len; return this; }
public CaptchaFactory font(String family, int size) { this.fontFamily = family; this.fontSize = size; return this; }
public CaptchaFactory offset(int x, int y) { this.xOffset = x; this.yOffset = y; return this; }
/** 核心生成邏輯,返回 Map{code, image} */
public Map generate() {
BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D g = img.createGraphics();
// 背景
g.setColor(randomColor(200, 255));
g.fillRect(0, 0, width, height);
// 字體
g.setFont(new Font(fontFamily, Font.BOLD, fontSize));
// 干擾線
g.setColor(randomColor(160, 200));
for (int i = 0; i < 30; i++) {
int x1 = random.nextInt(width), y1 = random.nextInt(height);
int x2 = x1 + random.nextInt(12), y2 = y1 + random.nextInt(12);
g.drawLine(x1, y1, x2, y2);
}
// 字元
StringBuilder code = new StringBuilder();
for (int i = 0; i < length; i++) {
char ch = CHAR_POOL[random.nextInt(CHAR_POOL.length)];
code.append(ch);
g.setColor(new Color(random.nextInt(100), random.nextInt(100), random.nextInt(100)));
g.drawString(String.valueOf(ch), i * xOffset + 10, yOffset);
}
g.dispose();
return Map.of("code", code.toString(), "image", img);
}
/** 輸出至 HttpServletResponse */
public void write(HttpServletRequest req, HttpServletResponse resp) throws IOException {
Map captcha = generate();
String code = (String) captcha.get("code");
BufferedImage img = (BufferedImage) captcha.get("image");
HttpSession session = req.getSession();
session.setAttribute("captcha_code", code); // 存入 Session 驗證用
// 防快取
resp.setHeader("Pragma", "No-cache");
resp.setHeader("Cache-Control", "no-cache");
resp.setDateHeader("Expires", 0);
resp.setContentType("image/jpeg");
ImageIO.write(img, "jpeg", resp.getOutputStream());
}
private final Random random = new Random();
private Color randomColor(int min, int max) {
int r = min + random.nextInt(max - min);
int g = min + random.nextInt(max - min);
int b = min + random.nextInt(max - min);
return new Color(r, g, b);
}
}
Servlet 整合範例
@WebServlet("/captcha")
public class CaptchaServlet extends HttpServlet {
@Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
CaptchaFactory.getInstance().size(130, 45).length(5).write(req, resp);
}
}
前端使用
<img id="captchaImg" src="/captcha" onclick="this.src='/captcha?t='+Date.now()" style="cursor:pointer">
<input type="text" name="captcha" placeholder="輸入驗證碼">
後端驗證
String input = req.getParameter("captcha");
String sessionCode = (String) req.getSession().getAttribute("captcha_code");
if (sessionCode != null && sessionCode.equalsIgnoreCase(input)) {
// 驗證通過
req.getSession().removeAttribute("captcha_code"); // 一次性失效
}