📋 目录
🎯 功能概述
本项目实现了一个完整的图形验证码功能,具备以下特性:
核心功能
- 验证码生成:使用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 {
@Autowired
private 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;
@Override
public Result generateCaptcha(int width, int height, int codeCount, int lineCount) {
try {
// 1. 使用Hutool生成验证码
LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(
width, height, codeCount, lineCount);
// 2. 生成唯一ID
String 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("验证码生成失败,请重试");
}
}
@Override
public 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>
<!-- 输入框 -->
<input
id="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>
<!-- 验证码图片显示区域 -->
<div
class="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>
<!-- 验证码图片 -->
<img
v-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: '', // 验证码ID
image: '', // 验证码图片Base64
loading: 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-component
v-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 {
@Autowired
private 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缓存,多种验证码类型,统计功能
- 详细的实现步骤:从零开始的完整实现指南
通过这个文档,即使是技术小白也能够:
- 理解验证码功能的完整架构
- 按步骤实现所有功能
- 解决常见问题
- 根据需要进行功能扩展
希望这个文档能够帮助您快速掌握验证码功能的开发!