package cn.response;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;
import javax.imageio.ImageIO;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@SuppressWarnings("serial")
public class ValidatePicture extends HttpServlet {
private static final int WIDTH = 120;
private static final int HEIGHT = 25;
private static final int CODE_COUNT = 4; // 驗證碼長度
private static final String CHARS = // 可自行擴充字符池
"\u4e00\u4e09\u4e50\u4e89\u4f10\u5e00" + // 一三樂爭仰干
"ABCDEFGHJKLMNPQRSTUVWXYZ" + // 排除易混淆 I/O
"23456789"; // 排除 0/1
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
generateCaptcha(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
doGet(req, resp);
}
private void generateCaptcha(HttpServletRequest req, HttpServletResponse resp) throws IOException {
BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
Graphics2D g = image.createGraphics();
// 1. 背景
g.setColor(Color.WHITE);
g.fillRect(0, 0, WIDTH, HEIGHT);
// 2. 邊框
g.setColor(Color.BLUE);
g.drawRect(0, 0, WIDTH - 1, HEIGHT - 1);
// 3. 干擾線(3 條)
g.setColor(Color.GREEN);
Random rand = new Random();
for (int i = 0; i < 3; i++) {
int x1 = rand.nextInt(WIDTH);
int y1 = rand.nextInt(HEIGHT);
int x2 = rand.nextInt(WIDTH);
int y2 = rand.nextInt(HEIGHT);
g.drawLine(x1, y1, x2, y2);
}
// 4. 繪製驗證碼字符
g.setFont(new Font("微軟雅黑", Font.BOLD, 20));
g.setColor(Color.RED);
StringBuilder code = new StringBuilder();
int x = 5;
for (int i = 0; i < CODE_COUNT; i++) {
char ch = CHARS.charAt(rand.nextInt(CHARS.length()));
code.append(ch);
int degree = rand.nextInt(30) - 15; // -15° ~ +15°
g.rotate(Math.toRadians(degree), x, 20);
g.drawString(String.valueOf(ch), x, 20);
g.rotate(-Math.toRadians(degree), x, 20);
x += 28; // 字符間距
}
// 5. 存入 Session
req.getSession().setAttribute("captchaCode", code.toString());
// 6. 輸出圖片
resp.setContentType("image/jpeg");
resp.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
resp.setHeader("Pragma", "no-cache");
resp.setDateHeader("Expires", 0);
ImageIO.write(image, "jpg", resp.getOutputStream());
g.dispose();
}
}
<servlet>
<servlet-name>captcha</servlet-name>
<servlet-class>cn.response.ValidatePicture</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>captcha</servlet-name>
<url-pattern>/captcha.jpg</url-pattern>
</servlet-mapping>
<img id="captchaImg" src="/captcha.jpg" onclick="this.src='/captcha.jpg?t=' + Date.now()" style="cursor:pointer" />
<input type="text" name="captchaInput" placeholder="輸入驗證碼" />
點擊圖片即可刷新(加時間戳防快取)。
@PostMapping("/login")
public Result login(@RequestParam String username,
@RequestParam String password,
@RequestParam String captchaInput,
HttpSession session) {
String realCode = (String) session.getAttribute("captchaCode");
if (realCode == null || !realCode.equalsIgnoreCase(captchaInput)) {
return Result.error("驗證碼錯誤");
}
session.removeAttribute("captchaCode"); // 一次性驗證碼
// 後續登入邏輯...
}
wqy-zenhei),否則中文會顯示方塊。BufferedImage 對象池,或改用現成庫(kaptcha、hutool-captcha、simple-captcha)。