基于Hutool的验证码功能完整技术文档
📋 目录
- 功能概述
- 技术架构
- 环境依赖
- 代码结构分析
- 后端实现详解
- 前端实现详解
- 完整实现步骤
- 配置说明
- API接口文档
- 常见问题解决
- 扩展建议
🎯 功能概述
本项目实现了一个完整的图形验证码功能,具备以下特性:
核心功能
- 验证码生成:使用Hutool工具类生成图形验证码
- 验证码验证:支持用户输入验证,验证后自动失效
- 验证码刷新:支持手动刷新获取新验证码
- 自定义参数:支持自定义验证码宽度、高度、字符数、干扰线数量
技术特点
- 安全性:验证码ID与答案分离存储,前端无法获取答案
- 时效性:验证码具有过期时间(默认5分钟)
- 易用性:前端组件化,开箱即用
- 响应式:支持移动端和PC端适配
🏗️ 技术架构
┌─────────────────┐ HTTP请求 ┌─────────────────┐
│ 前端 Vue3 │ ──────────────→ │ 后端 Spring │
│ │ │ │
│ - 验证码组件 │ ←────────────── │ - Controller │
│ - 登录页面 │ JSON响应 │ - Service │
│ - 状态管理 │ │ - Cache │
└─────────────────┘ └─────────────────┘│ ││ │▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ 浏览器存储 │ │ 内存缓存 │
│ │ │ │
│ - 验证码图片 │ │ - 验证码答案 │
│ - 验证码ID │ │ - 过期时间 │
└─────────────────┘ └─────────────────┘
核心组件说明
- 前端组件:Vue3 + Composition API + Tailwind CSS
- 后端服务:Spring Boot + Hutool工具类
- 缓存机制:内存缓存(可扩展为Redis)
- 数据传输:RESTful API + JSON格式
🛠️ 环境依赖
后端依赖
<!-- Spring Boot Starter Web -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency><!-- Hutool工具类 -->
<dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.22</version>
</dependency><!-- 日志依赖 -->
<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId>
</dependency>
前端依赖
{"vue": "^3.3.0","vue-router": "^4.2.0","tailwindcss": "^3.3.0","naive-ui": "^2.34.0"
}
📁 代码结构分析
后端代码结构
cn.jbolt.admin.user/
├── controller/
│ └── CaptchaController.java # 验证码控制器
├── service/
│ ├── CaptchaService.java # 验证码服务接口
│ └── impl/
│ └── CaptchaServiceImpl.java # 验证码服务实现
└── util/└── cache/└── CaptchaCache.java # 验证码缓存工具类
前端代码结构
src/
├── components/
│ └── verificationCode.vue # 验证码组件
├── views/
│ └── login/
│ └── index.vue # 登录页面
└── stores/└── user.js # 用户状态管理
⚙️ 后端实现详解
1. 控制器层(CaptchaController.java)
@RestController
@RequestMapping("/captcha")
@Uncheck // 免登录验证注解
public class CaptchaController {@Autowiredprivate CaptchaService captchaService;// 生成验证码(使用默认参数)@GetMapping("/generate")public Result generateCaptcha() {return captchaService.generateCaptcha();}// 生成自定义验证码@GetMapping("/generate/custom")public Result generateCustomCaptcha(@RequestParam(defaultValue = "200") int width,@RequestParam(defaultValue = "100") int height,@RequestParam(defaultValue = "4") int codeCount,@RequestParam(defaultValue = "20") int lineCount) {return captchaService.generateCaptcha(width, height, codeCount, lineCount);}// 验证验证码@PostMapping("/verify")public Result verifyCaptcha(@RequestParam String captchaId, @RequestParam String code) {return captchaService.verifyCaptcha(captchaId, code);}// 刷新验证码@GetMapping("/refresh")public Result refreshCaptcha() {return captchaService.refreshCaptcha();}
}
关键点说明:
@Uncheck
:确保验证码接口无需登录即可访问- 参数验证:使用
@RequestParam
设置默认值 - 统一返回:使用
Result
包装返回结果
2. 服务层(CaptchaServiceImpl.java)
@Service
public class CaptchaServiceImpl implements CaptchaService {private static final Logger logger = LoggerFactory.getLogger(CaptchaServiceImpl.class);// 默认配置常量private static final int DEFAULT_WIDTH = 200;private static final int DEFAULT_HEIGHT = 100;private static final int DEFAULT_CODE_COUNT = 4;private static final int DEFAULT_LINE_COUNT = 20;private static final int DEFAULT_EXPIRE_MINUTES = 5;@Overridepublic Result generateCaptcha(int width, int height, int codeCount, int lineCount) {try {// 1. 使用Hutool生成验证码LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(width, height, codeCount, lineCount);// 2. 生成唯一IDString captchaId = IdUtil.simpleUUID();// 3. 获取验证码答案和图片String code = lineCaptcha.getCode();String imageBase64 = lineCaptcha.getImageBase64Data();// 4. 存入缓存boolean cached = CaptchaCache.putCaptcha(captchaId, code, DEFAULT_EXPIRE_MINUTES, TimeUnit.MINUTES);if (!cached) {logger.error("验证码缓存失败: captchaId={}", captchaId);return Result.error("验证码生成失败,请重试");}// 5. 构建返回结果(不包含答案)Map<String, Object> result = new HashMap<>();result.put("captchaId", captchaId);result.put("captchaImage", imageBase64);logger.info("验证码生成成功: captchaId={}", captchaId);return Result.success(result, "验证码生成成功");} catch (Exception e) {logger.error("生成验证码失败", e);return Result.error("验证码生成失败,请重试");}}@Overridepublic Result verifyCaptcha(String captchaId, String inputCode) {// 1. 参数校验if (StrUtil.isBlank(captchaId)) {return Result.error("验证码ID不能为空");}if (StrUtil.isBlank(inputCode)) {return Result.error("验证码不能为空");}try {// 2. 验证验证码(验证成功后自动移除)boolean isValid = CaptchaCache.verifyCaptcha(captchaId, inputCode.trim(), true);if (isValid) {logger.info("验证码验证成功: captchaId={}", captchaId);return Result.success(true, "验证码验证成功");} else {logger.warn("验证码验证失败: captchaId={}, inputCode={}", captchaId, inputCode);return Result.error("验证码错误或已过期");}} catch (Exception e) {logger.error("验证验证码时发生异常: captchaId={}, inputCode={}", captchaId, inputCode, e);return Result.error("验证码验证失败,请重试");}}
}
核心技术点:
- Hutool验证码生成:
CaptchaUtil.createLineCaptcha()
- UUID生成:
IdUtil.simpleUUID()
确保ID唯一性 - Base64编码:
getImageBase64Data()
获取图片数据 - 缓存管理:自定义缓存类管理验证码生命周期
- 安全设计:返回结果不包含验证码答案
3. 缓存管理(CaptchaCache.java)
public class CaptchaCache {private static final Map<String, CaptchaInfo> CACHE = new ConcurrentHashMap<>();// 验证码信息内部类private static class CaptchaInfo {private final String code;private final long expireTime;public CaptchaInfo(String code, long expireTime) {this.code = code;this.expireTime = expireTime;}public boolean isExpired() {return System.currentTimeMillis() > expireTime;}}// 存储验证码public static boolean putCaptcha(String captchaId, String code, int timeout, TimeUnit timeUnit) {long expireTime = System.currentTimeMillis() + timeUnit.toMillis(timeout);CACHE.put(captchaId, new CaptchaInfo(code, expireTime));return true;}// 验证验证码public static boolean verifyCaptcha(String captchaId, String inputCode, boolean removeAfterVerify) {CaptchaInfo info = CACHE.get(captchaId);if (info == null || info.isExpired()) {CACHE.remove(captchaId); // 清理过期数据return false;}boolean isValid = info.code.equalsIgnoreCase(inputCode);if (removeAfterVerify) {CACHE.remove(captchaId); // 验证后移除}return isValid;}
}
🎨 前端实现详解
1. 验证码组件(verificationCode.vue)
<template><div><div class="flex space-x-4"><!-- 验证码输入框 --><div class="flex-1"><div class="relative"><!-- 图标 --><div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none z-10"><svg class="h-5 w-5" :class="iconClass" fill="currentColor"><!-- SVG路径 --></svg></div><!-- 输入框 --><inputid="captcha":value="modelValue"@input="$emit('update:modelValue', $event.target.value)"type="text"class="input-class":class="inputClass"placeholder="验证码"@focus="onFocus"@blur="onBlur"@mouseenter="onMouseEnter"@mouseleave="onMouseLeave"></div></div><!-- 验证码图片显示区域 --><divclass="w-32 h-12 relative overflow-hidden rounded-lg cursor-pointer"@click="refreshCaptcha":class="{ 'animate-pulse': loading || isFocused }"><!-- 加载状态 --><div v-if="loading" class="loading-spinner"><svg class="animate-spin h-6 w-6 text-gray-400"><!-- 加载动画SVG --></svg></div><!-- 验证码图片 --><imgv-else-if="captchaImage":src="captchaImage"alt="验证码"class="absolute inset-0 w-full h-full object-cover"@error="onImageError"/><!-- 默认占位符 --><div v-else class="placeholder-content">点击获取</div><!-- 刷新提示 --><div class="refresh-overlay">点击刷新</div></div></div></div>
</template><script>
import { computed } from 'vue';export default {name: 'CaptchaComponent',props: {modelValue: String, // 输入值error: String, // 错误信息captchaImage: String, // 验证码图片loading: Boolean, // 加载状态focusField: String, // 焦点字段hoverField: String // 悬停字段},emits: ['update:modelValue', 'refresh', 'focus', 'blur', 'mouseenter', 'mouseleave'],setup(props, { emit }) {// 计算属性const isFocused = computed(() => props.focusField === 'captcha');const isHovered = computed(() => props.hoverField === 'captcha');const iconClass = computed(() => [props.error ? 'text-red-500' : (isFocused.value ? 'text-green-500' : 'text-gray-400')]);const inputClass = computed(() => [props.error ? 'ring-red-500 focus:ring-red-500' :(isFocused.value ? 'shadow-lg ring-[#4ab27d] ring-2' :(isHovered.value ? 'ring-[#4ab27d]/50' : 'ring-gray-200/70'))]);// 事件处理const onFocus = () => emit('focus');const onBlur = () => emit('blur');const onMouseEnter = () => emit('mouseenter');const onMouseLeave = () => emit('mouseleave');const refreshCaptcha = () => emit('refresh');const onImageError = () => {console.warn('验证码图片加载失败');emit('refresh'); // 自动重新获取};return {isFocused,isHovered,iconClass,inputClass,onFocus,onBlur,onMouseEnter,onMouseLeave,refreshCaptcha,onImageError};}
}
</script>
组件特点:
- 响应式设计:支持焦点、悬停状态变化
- 错误处理:自动重试加载失败的验证码
- 用户体验:点击图片刷新,加载动画提示
- 样式动态:根据状态动态调整样式类
2. 登录页面集成(index.vue)
<script setup>
import { ref, reactive, onMounted, watch } from 'vue';
import { JBoltApi } from "@/service/request/index.js";
import CaptchaComponent from './cnps/verificationCode.vue';// 验证码相关状态
const captcha = reactive({id: '', // 验证码IDimage: '', // 验证码图片Base64loading: false // 验证码加载状态
});// 系统配置
const config = reactive({validateCodeEnable: false, // 是否启用验证码
});// 获取验证码
function loadCaptcha() {if (!config.validateCodeEnable) return;captcha.loading = true;JBoltApi.tryGet('/captcha/generate').then((res) => {captcha.id = res.data.captchaId;captcha.image = res.data.captchaImage;console.debug('验证码加载成功:', captcha.id);}).catch((error) => {console.error('获取验证码失败:', error);message.error('获取验证码失败,请重试');}).finally(() => {captcha.loading = false;});
}// 刷新验证码
function refreshCaptcha() {if (!config.validateCodeEnable) return;captcha.loading = true;form.value.captcha = ''; // 清空输入errors.captcha = ''; // 清空错误JBoltApi.tryGet('/captcha/refresh').then((res) => {captcha.id = res.data.captchaId;captcha.image = res.data.captchaImage;console.debug('验证码刷新成功:', captcha.id);}).catch((error) => {console.error('刷新验证码失败:', error);message.error('刷新验证码失败');}).finally(() => {captcha.loading = false;});
}// 获取系统配置
function loadConfig() {JBoltApi.tryGet('/admin/globalConfig/getByKeys?keys=jaOLT_LOGIN_USE_CAPTURE').then((res) => {config.validateCodeEnable = res.data.jaOLT_LOGIN_USE_CAPTURE;// 如果开启了验证码,则加载验证码if (config.validateCodeEnable) {loadCaptcha();}}).catch((error) => {console.error('获取系统配置失败:', error);// 使用默认配置config.validateCodeEnable = true;loadCaptcha();});
}// 登录处理
function handleLogin() {if (!validateForm()) return;loading.value = true;const loginData = {userName: form.value.username,password: processPassword(form.value.password),};// 如果开启了验证码,添加验证码信息if (config.validateCodeEnable) {loginData.captchaCode = form.value.captcha;loginData.captchaId = captcha.id;}JBoltApi.tryPost("/auth/login", loginData).then((res) => {// 登录成功处理auth.login(res.data.user, res.data.token);router.push("/jboltai");}).catch((error) => {console.error('登录失败:', error);// 登录失败时刷新验证码if (config.validateCodeEnable) {refreshCaptcha();}}).finally(() => {loading.value = false;});
}// 生命周期
onMounted(() => {loadConfig();
});// 监听配置变化
watch(() => config.validateCodeEnable, (enabled) => {if (enabled && !captcha.image && !captcha.loading) {loadCaptcha();}
});
</script><template><!-- 验证码组件使用 --><captcha-componentv-if="config.validateCodeEnable"v-model="form.captcha":error="errors.captcha":captcha-image="captcha.image":loading="captcha.loading":focus-field="focusField":hover-field="hoverField"@refresh="refreshCaptcha"@focus="focusField = 'captcha'"@blur="focusField = ''"@mouseenter="hoverField = 'captcha'"@mouseleave="hoverField = ''"/>
</template>
🚀 完整实现步骤
步骤1:创建后端缓存工具类
// src/main/java/cn/jbolt/util/cache/CaptchaCache.java
package cn.jbolt.util.cache;import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;public class CaptchaCache {private static final Map<String, CaptchaInfo> CACHE = new ConcurrentHashMap<>();private static class CaptchaInfo {private final String code;private final long expireTime;public CaptchaInfo(String code, long expireTime) {this.code = code;this.expireTime = expireTime;}public boolean isExpired() {return System.currentTimeMillis() > expireTime;}public String getCode() {return code;}}/*** 存储验证码*/public static boolean putCaptcha(String captchaId, String code, int timeout, TimeUnit timeUnit) {long expireTime = System.currentTimeMillis() + timeUnit.toMillis(timeout);CACHE.put(captchaId, new CaptchaInfo(code, expireTime));// 清理过期数据cleanExpiredCache();return true;}/*** 验证验证码*/public static boolean verifyCaptcha(String captchaId, String inputCode, boolean removeAfterVerify) {CaptchaInfo info = CACHE.get(captchaId);if (info == null || info.isExpired()) {CACHE.remove(captchaId);return false;}boolean isValid = info.getCode().equalsIgnoreCase(inputCode.trim());if (removeAfterVerify) {CACHE.remove(captchaId);}return isValid;}/*** 清理过期缓存*/private static void cleanExpiredCache() {CACHE.entrySet().removeIf(entry -> entry.getValue().isExpired());}/*** 获取缓存大小(用于监控)*/public static int getCacheSize() {cleanExpiredCache();return CACHE.size();}
}
步骤2:创建服务接口
// src/main/java/cn/jbolt/admin/user/service/CaptchaService.java
package cn.jbolt.admin.user.service;import cn.jbolt.util.Result;public interface CaptchaService {/*** 生成验证码(默认参数)*/Result generateCaptcha();/*** 生成验证码(自定义参数)*/Result generateCaptcha(int width, int height, int codeCount, int lineCount);/*** 验证验证码*/Result verifyCaptcha(String captchaId, String inputCode);/*** 刷新验证码*/Result refreshCaptcha();
}
步骤3:实现服务类
// src/main/java/cn/jbolt/admin/user/service/impl/CaptchaServiceImpl.java
// [完整代码见上文服务层实现]
步骤4:创建控制器
// src/main/java/cn/jbolt/admin/user/controller/CaptchaController.java
// [完整代码见上文控制器层实现]
步骤5:创建前端验证码组件
<!-- src/components/verificationCode.vue -->
<!-- [完整代码见上文前端组件实现] -->
步骤6:在登录页面集成
<!-- src/views/login/index.vue -->
<!-- [完整代码见上文登录页面集成] -->
⚙️ 配置说明
1. 验证码参数配置
// 在 CaptchaServiceImpl.java 中修改默认配置
private static final int DEFAULT_WIDTH = 200; // 图片宽度
private static final int DEFAULT_HEIGHT = 100; // 图片高度
private static final int DEFAULT_CODE_COUNT = 4; // 验证码字符数
private static final int DEFAULT_LINE_COUNT = 20; // 干扰线数量
private static final int DEFAULT_EXPIRE_MINUTES = 5; // 过期时间(分钟)
2. 系统配置开关
在数据库配置表中添加:
-- 验证码开关配置
INSERT INTO sys_config (config_key, config_value, description)
VALUES ('jaOLT_LOGIN_USE_CAPTURE', 'true', '登录是否启用验证码');-- 版权信息配置
INSERT INTO sys_config (config_key, config_value, description)
VALUES ('SYSTEM_COPYRIGHT_COMPANY', 'JBoltAI技术有限公司', '版权信息');
3. 前端配置
// 在 index.vue 中配置开发模式调试
const isDev = ref(process.env.NODE_ENV === 'development');// API接口配置
const API_BASE_URL = {development: 'http://localhost:8080',production: 'https://api.yourdomian.com'
}[process.env.NODE_ENV];
📚 API接口文档
1. 生成验证码
接口地址: GET /captcha/generate
请求参数: 无
响应示例:
{"code": 200,"msg": "验证码生成成功","data": {"captchaId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890","captchaImage": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAABkCAYA..."}
}
2. 生成自定义验证码
接口地址: GET /captcha/generate/custom
请求参数:
参数名 | 类型 | 必填 | 默认值 | 说明 |
---|---|---|---|---|
width | int | 否 | 200 | 图片宽度 |
height | int | 否 | 100 | 图片高度 |
codeCount | int | 否 | 4 | 验证码字符数 |
lineCount | int | 否 | 20 | 干扰线数量 |
请求示例:
GET /captcha/generate/custom?width=250&height=80&codeCount=5&lineCount=30
3. 验证验证码
接口地址: POST /captcha/verify
请求参数:
参数名 | 类型 | 必填 | 说明 |
---|---|---|---|
captchaId | String | 是 | 验证码ID |
code | String | 是 | 用户输入的验证码 |
请求示例:
{"captchaId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890","code": "ABCD"
}
响应示例:
{"code": 200,"msg": "验证码验证成功","data": true
}
4. 刷新验证码
接口地址: GET /captcha/refresh
请求参数: 无
响应示例: 同生成验证码接口
❗ 常见问题解决
1. 验证码图片不显示
可能原因:
- 后端接口未正确返回Base64数据
- 前端图片src格式错误
- 网络请求失败
解决方案:
// 1. 检查返回的Base64格式
console.log('验证码图片数据:', captcha.image);// 2. 确保Base64格式正确
if (captcha.image && !captcha.image.startsWith('data:image/')) {captcha.image = 'data:image/png;base64,' + captcha.image;
}// 3. 添加图片加载错误处理
const onImageError = () => {console.warn('验证码图片加载失败,正在重新获取...');refreshCaptcha();
};
2. 验证码验证失败
可能原因:
- 验证码已过期
- 大小写不匹配
- 验证码ID与输入不对应
解决方案:
// 在验证时添加详细日志
@Override
public Result verifyCaptcha(String captchaId, String inputCode) {logger.info("验证码验证开始: captchaId={}, inputCode={}", captchaId, inputCode);// 添加更详细的验证逻辑CaptchaInfo info = CaptchaCache.getCaptcha(captchaId);if (info == null) {logger.warn("验证码不存在: captchaId={}", captchaId);return Result.error("验证码不存在或已过期");}if (info.isExpired()) {logger.warn("验证码已过期: captchaId={}", captchaId);CaptchaCache.removeCaptcha(captchaId);return Result.error("验证码已过期,请刷新");}// 忽略大小写比较boolean isValid = info.getCode().equalsIgnoreCase(inputCode.trim());logger.info("验证码验证结果: captchaId={}, isValid={}", captchaId, isValid);return isValid ? Result.success(true) : Result.error("验证码错误");
}
3. 内存缓存占用过多
解决方案:
// 添加定时清理任务
@Component
public class CaptchaCacheCleanTask {@Scheduled(fixedRate = 60000) // 每分钟执行一次public void cleanExpiredCache() {int beforeSize = CaptchaCache.getCacheSize();CaptchaCache.cleanExpiredCache();int afterSize = CaptchaCache.getCacheSize();if (beforeSize != afterSize) {logger.info("清理过期验证码缓存: {} -> {}", beforeSize, afterSize);}}
}
4. 前端组件状态同步问题
解决方案:
// 使用 watch 监听配置变化
watch(() => config.validateCodeEnable, (enabled) => {console.debug('验证码配置变化:', enabled);if (enabled && !captcha.image && !captcha.loading) {console.debug('配置启用后自动加载验证码');loadCaptcha();}
});// 添加组件状态调试
watch(() => captcha, (newCaptcha) => {console.debug('验证码状态变化:', {hasImage: !!newCaptcha.image,captchaId: newCaptcha.id,loading: newCaptcha.loading});
}, { deep: true });
🔧 扩展建议
1. 升级为Redis缓存
@Service
public class RedisCaptchaService {@Autowiredprivate RedisTemplate<String, String> redisTemplate;private static final String CAPTCHA_PREFIX = "captcha:";public boolean putCaptcha(String captchaId, String code, int timeout, TimeUnit timeUnit) {String key = CAPTCHA_PREFIX + captchaId;redisTemplate.opsForValue().set(key, code, timeout, timeUnit);return true;}public boolean verifyCaptcha(String captchaId, String inputCode, boolean removeAfterVerify) {String key = CAPTCHA_PREFIX + captchaId;String storedCode = redisTemplate.opsForValue().get(key);if (storedCode == null) {return false;}boolean isValid = storedCode.equalsIgnoreCase(inputCode.trim());if (removeAfterVerify) {redisTemplate.delete(key);}return isValid;}
}
2. 添加验证码类型扩展
public enum CaptchaType {LINE, // 线性验证码CIRCLE, // 圆圈验证码 SHEAR, // 扭曲验证码GIF // 动态验证码
}@Service
public class EnhancedCaptchaService {public Result generateCaptcha(CaptchaType type, int width, int height, int codeCount) {AbstractCaptcha captcha;switch (type) {case LINE:captcha = CaptchaUtil.createLineCaptcha(width, height, codeCount, 20);break;case CIRCLE:captcha = CaptchaUtil.createCircleCaptcha(width, height, codeCount, 20);break;case SHEAR:captcha = CaptchaUtil.createShearCaptcha(width, height, codeCount, 4);break;case GIF:captcha = CaptchaUtil.createGifCaptcha(width, height, codeCount);break;default:captcha = CaptchaUtil.createLineCaptcha(width, height, codeCount, 20);}// 其他逻辑保持不变return generateCaptchaResult(captcha);}
}
3. 添加验证码统计功能
@Component
public class CaptchaMetrics {private final AtomicLong generateCount = new AtomicLong(0);private final AtomicLong verifyCount = new AtomicLong(0);private final AtomicLong successCount = new AtomicLong(0);public void recordGenerate() {generateCount.incrementAndGet();}public void recordVerify(boolean success) {verifyCount.incrementAndGet();if (success) {successCount.incrementAndGet();}}public Map<String, Object> getMetrics() {Map<String, Object> metrics = new HashMap<>();metrics.put("generateCount", generateCount.get());metrics.put("verifyCount", verifyCount.get());metrics.put("successCount", successCount.get());metrics.put("successRate", verifyCount.get() > 0 ? (double) successCount.get() / verifyCount.get() : 0);return metrics;}
}
4. 前端增强功能
<script setup>
// 添加键盘事件支持
const handleKeydown = (event) => {if (event.key === 'Enter') {handleLogin();} else if (event.key === 'F5' || (event.ctrlKey && event.key === 'r')) {event.preventDefault();refreshCaptcha();}
};// 添加验证码倒计时
const countdown = ref(0);
const startCountdown = () => {countdown.value = 300; // 5分钟倒计时const timer = setInterval(() => {countdown.value--;if (countdown.value <= 0) {clearInterval(timer);refreshCaptcha(); // 自动刷新}}, 1000);
};// 添加无障碍支持
const speakCaptcha = () => {if ('speechSynthesis' in window) {const utterance = new SpeechSynthesisUtterance('请输入验证码');speechSynthesis.speak(utterance);}
};onMounted(() => {window.addEventListener('keydown', handleKeydown);
});onUnmounted(() => {window.removeEventListener('keydown', handleKeydown);
});
</script>
📝 总结
本技术文档详细介绍了基于Hutool工具类实现的完整验证码功能,包含:
- 完整的技术栈:Spring Boot + Hutool + Vue3 + Tailwind CSS
- 安全的设计:验证码ID与答案分离,过期自动清理
- 良好的用户体验:响应式设计,自动刷新,错误处理
- 可扩展性:支持Redis缓存,多种验证码类型,统计功能
- 详细的实现步骤:从零开始的完整实现指南
通过这个文档,即使是技术小白也能够:
- 理解验证码功能的完整架构
- 按步骤实现所有功能
- 解决常见问题
- 根据需要进行功能扩展
希望这个文档能够帮助您快速掌握验证码功能的开发!