Spring Boot图片验证码功能实现详解 - 从零开始到完美运行
📖 前言
大家好!今天我要和大家分享一个非常实用的功能:Spring Boot图片验证码。这个功能可以防止恶意攻击,比如暴力破解、刷票等。我们实现的是一个带有加减法运算的图片验证码,用户需要正确计算结果才能通过验证。
适合人群:Java初学者、Spring Boot新手、想要学习验证码实现的朋友
技术栈:Spring Boot 3.5.5 + Java 17 + Thymeleaf + Maven
🎯 功能预览
最终实现的效果:
- 生成随机数学表达式(如:5 + 3 = ?)
- 将表达式绘制成图片
- 用户输入计算结果
- 验证答案是否正确
- 支持刷新验证码
🛠️ 环境准备
1. 创建Spring Boot项目
首先,我们需要创建一个Spring Boot项目。我使用的是Spring Initializr创建的项目,包含以下依赖:
- Spring Web
- Thymeleaf
- Spring Data Redis
- Spring Session Data Redis
2. 项目结构
src/
├── main/
│ ├── java/
│ │ └── com/
│ │ └── example/
│ │ └── demo/
│ │ ├── Demo5Application.java
│ │ ├── config/
│ │ │ └── CorsConfig.java
│ │ ├── controller/
│ │ │ └── CaptchaController.java
│ │ ├── service/
│ │ │ ├── CaptchaService.java
│ │ │ └── MemoryCaptchaService.java
│ │ └── util/
│ │ └── CaptchaUtil.java
│ └── resources/
│ ├── application.properties
│ └── templates/
│ └── index.html
└── test/└── java/└── com/└── example/└── demo/└── CaptchaUtilTest.java
第一步:配置Maven依赖
首先,我们需要在pom.xml
中添加必要的依赖:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.5.5</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>org.example</groupId><artifactId>demo5</artifactId><version>0.0.1-SNAPSHOT</version><name>demo5</name><description>demo5</description><properties><java.version>17</java.version></properties><dependencies><!-- Spring Boot Web Starter --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- Thymeleaf模板引擎 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><!-- Redis支持 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- Spring Session Redis --><dependency><groupId>org.springframework.session</groupId><artifactId>spring-session-data-redis</artifactId></dependency><!-- 测试依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>
</project>
解释:
spring-boot-starter-web
:提供Web开发基础功能spring-boot-starter-thymeleaf
:模板引擎,用于渲染HTML页面spring-boot-starter-data-redis
:Redis数据库支持spring-session-data-redis
:将Session存储到Redis中
第二步:创建验证码工具类
这是整个功能的核心!我们创建一个工具类来生成数学表达式和绘制图片:
package com.example.demo.util;import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Base64;
import java.util.Random;/*** 验证码工具类* 用于生成数学表达式验证码图片*/
public class CaptchaUtil {/*** 生成随机数学表达式和答案* @return MathExpression 包含表达式和答案的对象*/public static MathExpression generateMathExpression() {Random random = new Random();int a = random.nextInt(10) + 1; // 1-10int b = random.nextInt(10) + 1; // 1-10// 随机决定是加法还是减法String operator;int result;if (random.nextBoolean()) {operator = "+";result = a + b;} else {operator = "-";// 确保结果不为负数if (a < b) {int temp = a;a = b;b = temp;}result = a - b;}String expression = a + " " + operator + " " + b + " = ?";return new MathExpression(expression, result);}/*** 生成验证码图片* @param expression 数学表达式* @return Base64编码的图片字符串* @throws IOException IO异常*/public static String generateCaptchaImage(String expression) throws IOException {int width = 120;int height = 40;// 创建图片对象BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);Graphics2D g = image.createGraphics();// 设置背景色为白色g.setColor(Color.WHITE);g.fillRect(0, 0, width, height);// 设置字体g.setFont(new Font("Arial", Font.BOLD, 16));// 绘制干扰线(让验证码更难被机器识别)Random random = new Random();for (int i = 0; i < 5; i++) {int x1 = random.nextInt(width);int y1 = random.nextInt(height);int x2 = random.nextInt(width);int y2 = random.nextInt(height);g.setColor(new Color(random.nextInt(256), random.nextInt(256), random.nextInt(256)));g.drawLine(x1, y1, x2, y2);}// 绘制表达式文字g.setColor(Color.BLACK);g.drawString(expression, 10, 25);g.dispose();// 将图片转换为Base64字符串ByteArrayOutputStream baos = new ByteArrayOutputStream();ImageIO.write(image, "png", baos);byte[] bytes = baos.toByteArray();return "data:image/png;base64," + Base64.getEncoder().encodeToString(bytes);}/*** 内部类用于存储表达式和结果*/public static class MathExpression {private String expression;private int result;public MathExpression(String expression, int result) {this.expression = expression;this.result = result;}public String getExpression() {return expression;}public int getResult() {return result;}}
}
代码解释:
-
generateMathExpression()
方法:- 生成两个1-10的随机数
- 随机选择加法或减法
- 确保减法结果不为负数
- 返回表达式字符串和正确答案
-
generateCaptchaImage()
方法:- 创建120x40像素的图片
- 设置白色背景
- 绘制5条随机颜色的干扰线
- 在图片上绘制数学表达式
- 将图片转换为Base64字符串返回
-
MathExpression
内部类:- 封装表达式和答案
- 提供getter方法
第三步:创建验证码服务类
我们需要两个服务类:一个基于Session,一个基于内存存储。
3.1 基于Session的验证码服务
package com.example.demo.service;import com.example.demo.util.CaptchaUtil;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;import jakarta.servlet.http.HttpSession;
import java.io.IOException;/*** 验证码服务类* 处理验证码的生成和验证逻辑*/
@Service
public class CaptchaService {/*** 生成验证码并存入session* @return Base64编码的验证码图片* @throws IOException IO异常*/public String generateCaptcha() throws IOException {CaptchaUtil.MathExpression mathExpression = CaptchaUtil.generateMathExpression();String imageBase64 = CaptchaUtil.generateCaptchaImage(mathExpression.getExpression());// 获取当前session并存储答案ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();HttpSession session = attr.getRequest().getSession();session.setAttribute("captchaAnswer", mathExpression.getResult());session.setMaxInactiveInterval(300); // 5分钟有效期return imageBase64;}/*** 验证用户输入的答案* @param userAnswer 用户输入的答案* @return 验证是否成功*/public boolean validateCaptcha(String userAnswer) {try {int answer = Integer.parseInt(userAnswer);// 获取当前session中的答案ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();HttpSession session = attr.getRequest().getSession();Integer correctAnswer = (Integer) session.getAttribute("captchaAnswer");// 添加调试信息System.out.println("用户输入答案: " + answer);System.out.println("正确答案: " + correctAnswer);System.out.println("Session ID: " + session.getId());if (correctAnswer != null && answer == correctAnswer) {// 验证成功后移除答案session.removeAttribute("captchaAnswer");System.out.println("验证成功");return true;}System.out.println("验证失败");return false;} catch (NumberFormatException e) {System.out.println("数字格式错误: " + userAnswer);return false;}}
}
3.2 基于内存的验证码服务(解决跨域问题)
package com.example.demo.service;import com.example.demo.util.CaptchaUtil;
import org.springframework.stereotype.Service;import java.io.IOException;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;/*** 基于内存的验证码服务* 解决跨域Session问题*/
@Service
public class MemoryCaptchaService {// 使用ConcurrentHashMap存储验证码,key为验证码ID,value为答案private final Map<String, Integer> captchaStorage = new ConcurrentHashMap<>();/*** 生成验证码并返回验证码ID和图片* @return CaptchaResponse 包含验证码ID和Base64图片* @throws IOException IO异常*/public CaptchaResponse generateCaptcha() throws IOException {CaptchaUtil.MathExpression mathExpression = CaptchaUtil.generateMathExpression();String imageBase64 = CaptchaUtil.generateCaptchaImage(mathExpression.getExpression());// 生成唯一验证码IDString captchaId = UUID.randomUUID().toString();// 存储答案到内存中captchaStorage.put(captchaId, mathExpression.getResult());System.out.println("生成验证码ID: " + captchaId + ", 答案: " + mathExpression.getResult());return new CaptchaResponse(captchaId, imageBase64);}/*** 验证用户输入的答案* @param captchaId 验证码ID* @param userAnswer 用户输入的答案* @return 验证是否成功*/public boolean validateCaptcha(String captchaId, String userAnswer) {try {int answer = Integer.parseInt(userAnswer);Integer correctAnswer = captchaStorage.get(captchaId);System.out.println("验证码ID: " + captchaId);System.out.println("用户输入答案: " + answer);System.out.println("正确答案: " + correctAnswer);if (correctAnswer != null && answer == correctAnswer) {// 验证成功后移除验证码captchaStorage.remove(captchaId);System.out.println("验证成功");return true;}System.out.println("验证失败");return false;} catch (NumberFormatException e) {System.out.println("数字格式错误: " + userAnswer);return false;}}/*** 验证码响应类*/public static class CaptchaResponse {private String captchaId;private String imageBase64;public CaptchaResponse(String captchaId, String imageBase64) {this.captchaId = captchaId;this.imageBase64 = imageBase64;}public String getCaptchaId() {return captchaId;}public String getImageBase64() {return imageBase64;}}
}
两种服务类的区别:
- Session服务:将答案存储在HttpSession中,适合同域访问
- 内存服务:将答案存储在内存Map中,通过唯一ID关联,解决跨域问题
第四步:创建控制器
控制器负责处理HTTP请求,连接前端和后端:
package com.example.demo.controller;import com.example.demo.service.CaptchaService;
import com.example.demo.service.MemoryCaptchaService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;import java.io.IOException;
import java.util.HashMap;
import java.util.Map;/*** 验证码控制器* 处理验证码相关的HTTP请求*/
@Controller
public class CaptchaController {@Autowiredprivate CaptchaService captchaService;@Autowiredprivate MemoryCaptchaService memoryCaptchaService;/*** 显示验证页面* @param model 模型对象* @return 页面名称*/@GetMapping("/")public String index(Model model) {return "index";}/*** 获取验证码图片(基于Session)* @return Base64编码的验证码图片*/@GetMapping("/captcha")@ResponseBodypublic ResponseEntity<String> getCaptcha() {try {String imageBase64 = captchaService.generateCaptcha();return ResponseEntity.ok(imageBase64);} catch (IOException e) {return ResponseEntity.status(500).body("生成验证码失败");}}/*** 验证答案(基于Session)* @param answer 用户输入的答案* @return 验证结果*/@PostMapping("/validate")@ResponseBodypublic ResponseEntity<String> validateCaptcha(@RequestParam String answer) {System.out.println("收到验证请求,答案: " + answer);boolean isValid = captchaService.validateCaptcha(answer);if (isValid) {return ResponseEntity.ok("验证成功");} else {return ResponseEntity.badRequest().body("验证失败");}}/*** 获取基于内存的验证码(解决跨域Session问题)* @return 包含验证码ID和图片的响应*/@GetMapping("/memory-captcha")@ResponseBodypublic ResponseEntity<Map<String, String>> getMemoryCaptcha() {try {MemoryCaptchaService.CaptchaResponse response = memoryCaptchaService.generateCaptcha();Map<String, String> result = new HashMap<>();result.put("captchaId", response.getCaptchaId());result.put("imageData", response.getImageBase64());return ResponseEntity.ok(result);} catch (IOException e) {return ResponseEntity.status(500).build();}}/*** 验证基于内存的验证码* @param captchaId 验证码ID* @param answer 用户输入的答案* @return 验证结果*/@PostMapping("/memory-validate")@ResponseBodypublic ResponseEntity<String> validateMemoryCaptcha(@RequestParam String captchaId, @RequestParam String answer) {System.out.println("收到内存验证码验证请求,ID: " + captchaId + ", 答案: " + answer);boolean isValid = memoryCaptchaService.validateCaptcha(captchaId, answer);if (isValid) {return ResponseEntity.ok("验证成功");} else {return ResponseEntity.badRequest().body("验证失败");}}
}
控制器解释:
@GetMapping("/")
:显示主页面@GetMapping("/captcha")
:获取Session验证码@PostMapping("/validate")
:验证Session验证码@GetMapping("/memory-captcha")
:获取内存验证码@PostMapping("/memory-validate")
:验证内存验证码
第五步:创建前端页面
创建一个美观的HTML页面:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>验证码演示</title><style>body {font-family: Arial, sans-serif;margin: 50px;background-color: #f5f5f5;}.container {max-width: 400px;margin: 0 auto;padding: 20px;background-color: white;border-radius: 5px;box-shadow: 0 0 10px rgba(0,0,0,0.1);}.form-group {margin-bottom: 15px;}label {display: block;margin-bottom: 5px;}input[type="text"] {width: 100%;padding: 8px;border: 1px solid #ddd;border-radius: 4px;box-sizing: border-box;}button {padding: 10px 15px;background-color: #4CAF50;color: white;border: none;border-radius: 4px;cursor: pointer;}button:hover {background-color: #45a049;}#captchaImage {margin-bottom: 10px;border: 1px solid #ddd;}#refreshCaptcha {margin-left: 10px;background-color: #2196F3;}#refreshCaptcha:hover {background-color: #1976D2;}.message {margin-top: 15px;padding: 10px;border-radius: 4px;display: none;}.success {background-color: #dff0d8;color: #3c763d;border: 1px solid #d6e9c6;}.error {background-color: #f2dede;color: #a94442;border: 1px solid #ebccd1;}.captcha-container {display: flex;align-items: center;margin-bottom: 10px;}</style>
</head>
<body><div class="container"><h2>验证码验证</h2><div class="form-group"><label>验证码:</label><div class="captcha-container"><img id="captchaImage" src="" alt="验证码"><button id="refreshCaptcha">刷新</button></div></div><div class="form-group"><label for="answer">请输入计算结果:</label><input type="text" id="answer" placeholder="请输入计算结果"></div><!-- 隐藏的验证码ID字段 --><input type="hidden" id="captchaId" value=""><button id="submitBtn">提交验证</button><div id="message" class="message"></div></div><script>document.addEventListener('DOMContentLoaded', function() {const captchaImage = document.getElementById('captchaImage');const refreshBtn = document.getElementById('refreshCaptcha');const answerInput = document.getElementById('answer');const submitBtn = document.getElementById('submitBtn');const messageDiv = document.getElementById('message');const captchaIdInput = document.getElementById('captchaId');// 加载验证码function loadCaptcha() {fetch('http://localhost:8080/memory-captcha').then(response => {if (!response.ok) {throw new Error('网络响应不正常');}return response.json();}).then(data => {captchaImage.src = data.imageData;captchaIdInput.value = data.captchaId;console.log('验证码ID:', data.captchaId);}).catch(error => {console.error('加载验证码失败:', error);showMessage('加载验证码失败,请重试', 'error');});}// 初始化加载验证码loadCaptcha();// 刷新验证码refreshBtn.addEventListener('click', function() {loadCaptcha();answerInput.value = '';messageDiv.style.display = 'none';});// 提交验证submitBtn.addEventListener('click', function() {const answer = answerInput.value.trim();const captchaId = captchaIdInput.value;if (!answer) {showMessage('请输入计算结果', 'error');return;}if (!captchaId) {showMessage('验证码已过期,请刷新', 'error');loadCaptcha();return;}// 发送验证请求const formData = new FormData();formData.append('captchaId', captchaId);formData.append('answer', answer);fetch('http://localhost:8080/memory-validate', {method: 'POST',body: formData}).then(response => {if (response.ok) {return response.text();} else {throw new Error('验证失败');}}).then(result => {showMessage('验证成功!', 'success');}).catch(error => {showMessage('验证失败,请重试', 'error');loadCaptcha(); // 刷新验证码answerInput.value = '';});});// 显示消息function showMessage(message, type) {messageDiv.textContent = message;messageDiv.className = 'message ' + type;messageDiv.style.display = 'block';}});</script>
</body>
</html>
前端功能解释:
- 页面加载时:自动获取验证码图片
- 刷新按钮:重新获取新的验证码
- 提交验证:发送用户输入的答案到后端验证
- 消息提示:显示验证成功或失败的消息
⚙️ 第六步:配置CORS和应用程序
6.1 CORS配置(解决跨域问题)
package com.example.demo.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;/*** CORS配置类* 解决跨域请求问题*/
@Configuration
public class CorsConfig implements WebMvcConfigurer {@Overridepublic void addCorsMappings(CorsRegistry registry) {registry.addMapping("/**").allowedOriginPatterns("*").allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS").allowedHeaders("*").allowCredentials(true).maxAge(3600);}@Beanpublic CorsConfigurationSource corsConfigurationSource() {CorsConfiguration configuration = new CorsConfiguration();configuration.addAllowedOriginPattern("*");configuration.addAllowedMethod("*");configuration.addAllowedHeader("*");configuration.setAllowCredentials(true);UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();source.registerCorsConfiguration("/**", configuration);return source;}
}
6.2 应用程序配置
# application.properties
spring.application.name=demo5# 服务器配置
server.port=8080# Session配置
server.servlet.session.timeout=300s# Redis配置(可选,如果不使用Redis可以注释掉)
# spring.redis.host=localhost
# spring.redis.port=6379
# spring.redis.password=
# spring.redis.database=0# 使用Redis存储Session(可选)
# spring.session.store-type=redis# 日志配置
logging.level.com.example.demo=DEBUG
第七步:创建测试类
为了确保我们的代码正常工作,我们创建一个测试类:
package com.example.demo;import com.example.demo.util.CaptchaUtil;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;/*** 验证码工具类测试*/
public class CaptchaUtilTest {@Testpublic void testGenerateMathExpression() {// 测试生成数学表达式CaptchaUtil.MathExpression expression = CaptchaUtil.generateMathExpression();assertNotNull(expression);assertNotNull(expression.getExpression());assertTrue(expression.getExpression().contains("="));assertTrue(expression.getExpression().contains("?"));assertTrue(expression.getResult() >= 0); // 结果应该非负}@Testpublic void testGenerateCaptchaImage() throws Exception {// 测试生成验证码图片String expression = "5 + 3 = ?";String imageBase64 = CaptchaUtil.generateCaptchaImage(expression);assertNotNull(imageBase64);assertTrue(imageBase64.startsWith("data:image/png;base64,"));assertTrue(imageBase64.length() > 100); // Base64字符串应该有一定长度}@Testpublic void testMathExpressionClass() {// 测试MathExpression内部类String expression = "2 + 3 = ?";int result = 5;CaptchaUtil.MathExpression mathExpression = new CaptchaUtil.MathExpression(expression, result);assertEquals(expression, mathExpression.getExpression());assertEquals(result, mathExpression.getResult());}
}
第八步:运行和测试
8.1 启动应用
- 在IDE中运行
Demo5Application.java
- 或者使用命令行:
mvn spring-boot:run
- 应用启动后访问:
http://localhost:8080
8.2 测试功能
- 页面加载:自动显示验证码图片
- 计算答案:根据图片中的数学表达式计算答案
- 输入答案:在输入框中输入计算结果
- 提交验证:点击"提交验证"按钮
- 刷新验证码:点击"刷新"按钮获取新的验证码
遇到的问题和解决方案
问题1:HttpSession无法解析
错误信息:
无法解析符号 'HttpSession'
原因:Spring Boot 3.x使用Jakarta EE,javax.servlet
包被替换为jakarta.servlet
解决方案:
// 错误的导入
import javax.servlet.http.HttpSession;// 正确的导入
import jakarta.servlet.http.HttpSession;
问题2:CORS跨域问题
错误信息:
Access to fetch at 'http://localhost:8080/captcha' from origin 'null' has been blocked by CORS policy
原因:当通过IDE预览页面时,页面运行在不同端口,浏览器阻止跨域请求
解决方案:
- 创建CORS配置类
- 使用内存验证码服务替代Session验证码
- 前端使用绝对URL请求
问题3:Session无法跨域共享
错误信息:
验证总是失败,Session中的答案为空
原因:跨域请求时Session无法正确共享
解决方案:
- 创建基于内存的验证码服务
- 使用唯一ID关联验证码和答案
- 前端传递验证码ID进行验证
问题4:Maven命令无法识别
错误信息:
mvn : 无法将"mvn"项识别为 cmdlet、函数、脚本文件或可运行程序的名称
原因:Maven没有安装或没有配置环境变量
解决方案:
- 安装Maven并配置环境变量
- 或者直接使用IDE运行应用
- 或者使用项目自带的Maven Wrapper:
./mvnw spring-boot:run
📊 功能特点总结
✅ 已实现的功能
- 数学表达式验证码:比传统字符验证码更友好
- 图片生成:自动绘制验证码图片
- 双重存储方案:支持Session和内存两种存储方式
- 跨域支持:解决了IDE预览时的跨域问题
- 美观界面:现代化的UI设计
- 调试信息:控制台输出详细的验证过程
- 自动刷新:验证失败后自动刷新验证码
🎯 技术亮点
- Base64图片编码:直接在前端显示图片
- ConcurrentHashMap:线程安全的内存存储
- UUID唯一标识:确保验证码ID的唯一性
- CORS配置:完整的跨域解决方案
- 异常处理:完善的错误处理机制
🔮 扩展功能建议
1. 添加Redis存储
// 可以扩展为使用Redis存储验证码
@Autowired
private StringRedisTemplate redisTemplate;public void storeCaptcha(String captchaId, int answer) {redisTemplate.opsForValue().set("captcha:" + captchaId, String.valueOf(answer), 5, TimeUnit.MINUTES);
}
2. 增加验证码复杂度
// 可以添加更多运算符
String[] operators = {"+", "-", "*", "/"};
// 可以增加数字范围
int a = random.nextInt(20) + 1; // 1-20
3. 添加验证码过期机制
// 可以添加定时清理过期验证码
@Scheduled(fixedRate = 60000) // 每分钟执行一次
public void cleanExpiredCaptchas() {// 清理逻辑
}
4. 增加验证码样式
// 可以添加更多干扰元素
g.setColor(new Color(random.nextInt(256), random.nextInt(256), random.nextInt(256)));
g.fillOval(random.nextInt(width), random.nextInt(height), 10, 10);
学习总结
通过这个项目,我们学会了:
- Spring Boot基础:如何创建Web应用
- 图片处理:使用Java Graphics API绘制图片
- Base64编码:图片的编码和解码
- Session管理:HttpSession的使用
- 跨域问题:CORS的配置和解决
- 前端交互:JavaScript与后端API的交互
- 异常处理:完善的错误处理机制
- 测试驱动:编写单元测试验证功能
结语
恭喜你!你已经成功实现了一个完整的Spring Boot图片验证码功能。这个项目涵盖了:
- ✅ 后端API开发
- ✅ 图片生成和处理
- ✅ 前端页面开发
- ✅ 跨域问题解决
- ✅ 异常处理
- ✅ 单元测试
这个验证码功能可以应用到实际的Web项目中,有效防止恶意攻击。希望这个教程对你有帮助!
如果你有任何问题或建议,欢迎在评论区留言讨论!
项目源码:所有代码都已经在文章中完整提供,可以直接复制使用。
运行环境:Java 17 + Spring Boot 3.5.5 + Maven
测试地址:http://localhost:8080
祝学习愉快!🚀