生成包含隨機字符的圖片驗證碼,支持配置寬高、字符集、干擾線、字體、顏色,輸出到 OutputStream(通常為 response.getOutputStream()),並返回驗證碼字符串供 Session 存儲校驗。
package com.example.util;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Random;
import javax.imageio.ImageIO;
public class CaptchaUtil {
/** 字符集:數字 + 大小寫字母,排除易混淆字符 */
private static final char[] CHAR_SET = "23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz".toCharArray();
private static final Random RANDOM = new Random();
/** 默認配置 */
private static final int DEFAULT_WIDTH = 100;
private static final int DEFAULT_HEIGHT = 36;
private static final int DEFAULT_LENGTH = 4;
private static final int DEFAULT_FONT_SIZE = 24;
private static final Color DEFAULT_BG_COLOR = new Color(0xF0, 0xF0, 0xF0);
private static final Color DEFAULT_BORDER_COLOR = Color.BLACK;
/**
* 生成驗證碼圖片並寫入輸出流
* @param os 輸出流(如 response.getOutputStream())
* @param width 圖片寬度
* @param height 圖片高度
* @param length 驗證碼字符長度
* @return 生成的驗證碼字符串
*/
public static String generate(OutputStream os, int width, int height, int length) {
if (width <= 0) width = DEFAULT_WIDTH;
if (height <= 0) height = DEFAULT_HEIGHT;
if (length <= 0) length = DEFAULT_LENGTH;
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D g = image.createGraphics();
// 抗鋸齒
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
// 背景
g.setColor(DEFAULT_BG_COLOR);
g.fillRect(0, 0, width, height);
// 邊框
g.setColor(DEFAULT_BORDER_COLOR);
g.drawRect(0, 0, width - 1, height - 1);
// 干擾線
drawInterferenceLines(g, width, height, 3);
// 生成隨機字符
StringBuilder code = new StringBuilder(length);
for (int i = 0; i < length; i++) {
code.append(CHAR_SET[RANDOM.nextInt(CHAR_SET.length)]);
}
// 繪製字符(隨機顏色、字體、旋轉、位置)
drawChars(g, code.toString(), width, height);
g.dispose();
try {
ImageIO.write(image, "JPEG", os);
} catch (IOException e) {
throw new RuntimeException("驗證碼圖片輸出失敗", e);
}
return code.toString();
}
/** 重載方法:使用默認尺寸 */
public static String generate(OutputStream os) {
return generate(os, DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_LENGTH);
}
private static void drawInterferenceLines(Graphics2D g, int w, int h, int count) {
g.setStroke(new BasicStroke(1.5f));
for (int i = 0; i < count; i++) {
g.setColor(randomColor(160, 200));
int x1 = RANDOM.nextInt(w);
int y1 = RANDOM.nextInt(h);
int x2 = RANDOM.nextInt(w);
int y2 = RANDOM.nextInt(h);
g.drawLine(x1, y1, x2, y2);
}
}
private static void drawChars(Graphics2D g, String code, int w, int h) {
int charWidth = w / code.length();
for (int i = 0; i < code.length(); i++) {
char c = code.charAt(i);
// 隨機字體
Font font = new Font(getRandomFontName(), Font.BOLD | Font.ITALIC, DEFAULT_FONT_SIZE);
g.setFont(font);
// 隨機顏色
g.setColor(randomColor(20, 130));
// 隨機旋轉角度 (-30° ~ +30°)
double theta = (RANDOM.nextDouble() - 0.5) * Math.PI / 3;
// 字符中心坐標
int x = i * charWidth + charWidth / 2;
int y = h / 2 + DEFAULT_FONT_SIZE / 3;
// 仿射變換:平移→旋轉→平移回
g.translate(x, y);
g.rotate(theta);
g.drawString(String.valueOf(c), -g.getFontMetrics().stringWidth(String.valueOf(c)) / 2, 0);
g.rotate(-theta);
g.translate(-x, -y);
}
}
private static String getRandomFontName() {
String[] fonts = {"Arial", "Verdana", "Tahoma", "Courier New", "Georgia", "Comic Sans MS"};
return fonts[RANDOM.nextInt(fonts.length)];
}
private static 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);
}
}
<%@ page contentType="image/jpeg" pageEncoding="UTF-8" import="com.example.util.CaptchaUtil" %>
<%
response.setHeader("Pragma", "No-cache");
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expires", 0);
String code = CaptchaUtil.generate(response.getOutputStream());
session.setAttribute("captcha_code", code.toLowerCase()); // 忽略大小寫存儲
%>
// 提交時
String input = request.getParameter("captcha");
String real = (String) session.getAttribute("captcha_code");
if (input != null && real != null && input.toLowerCase().equals(real)) {
// 通過
}
BufferedImage.TYPE_INT_ARGB、預生成字體緩存、關閉抗鋸齒(低端機)。封裝可配置、可擴展的 CaptchaUtil 類,配合無狀態設計(不依賴 Servlet API),便於單元測試與多項目復用。現代項目建議接入成熟驗證碼服務(如 Google reCAPTCHA、hCaptcha、阿里云驗證碼)以應對機器學習破解。