1 验证码
1.1 功能概述
- 接口文档
{ "msg": "操作成功", "code": 200, "data": { "uuid": "b71fafb1a91b4961afb27372bd3af77c", "captcha": "data:image/png;base64,iVBORw0KGgoAAAA", "code": "nrew" } }- 技术栈选用
验证码存储方案对比(为何放弃数据库、Cookie、Session)
1.数据库存储
- 核心问题:验证码是高频、临时的轻量数据,数据库(如 MySQL)主打持久化存储,写入 / 读取需走磁盘 IO,性能远低于内存存储;且验证码有效期仅 2 分钟,频繁读写数据库会增加不必要的性能开销,还需额外写定时清理过期验证码的逻辑,徒增复杂度。
- 简单总结:太重、太慢,没必要用持久化存储存临时验证码。
2.Cookie 存储
- 核心问题:Cookie 存储在客户端浏览器,可被篡改、伪造,验证码是安全校验数据,存客户端完全无安全性;且 Cookie 有存储大小限制(约 4KB),虽验证码文本小,但违背 “安全数据服务端存储” 的原则。
- 简单总结:不安全,数据能被客户端修改,失去验证码校验意义。
3.Session 存储
- 核心问题:Session 基于服务器内存(或容器存储),分布式部署场景下(多台服务器),Session 无法共享,用户请求落到不同服务器时会校验失败;且 Session 默认过期时间较长(通常 30 分钟),手动设置 2 分钟过期需额外配置,还会占用服务器内存,服务器重启后 Session 丢失,验证码直接失效。
- 简单总结:分布式环境不兼容,服务器重启易丢失,内存占用不灵活。
Redis 选用原因及简单介绍
1.为什么选 Redis
- 高性能:基于内存读写,速度比数据库快几个量级,适配验证码高频读写场景;
- 过期策略:原生支持设置 key 的过期时间,自动清理,无需手动维护;
- 分布式友好:Redis 可独立部署,多台应用服务器都能访问,解决 Session 共享问题;
- 轻量灵活:仅存储临时的验证码键值对,资源占用极低。
2.Redis 简单介绍
代码中用到的工具类 / 包介绍
1.SpecCaptcha(验证码生成)
作用:
- 快速生成图片验证码(包含验证码文本 + 图片 Base64 编码),无需手动处理图片绘制、编码;
核心用法:
- new SpecCaptcha(宽度, 高度, 验证码位数) 创建实例,text() 获取验证码文本,toBase64() 转为 Base64 编码(方便前端直接展示图片),具体示例见代码。
导入依赖:
<!-- 验证码 --> <dependency> <groupId>com.github.whvcse</groupId> <artifactId>easy-captcha</artifactId> <version>${captcha.version}</version> </dependency>2.IdUtil(UUID 生成,通常是 Hutool 工具包)
作用
- Hutool 工具包中的 ID 工具类,simpleUUID() 生成无横线的 UUID(如b71fafb1a91b4961afb27372bd3af77c),替代手动写 UUID 生成逻辑,简化代码;
优势:
- 封装了各种 ID 生成规则(UUID、雪花 ID 等),开箱即用,避免重复造轮子。
导入依赖
<!--工具包--> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>${hutool.version}</version> </dependency>3. RedisTemplate(Spring Data Redis)
作用:
- Spring 框架封装的 Redis 操作模板,简化 Redis 的连接、读写操作;
核心用法:
- opsForValue() 操作字符串类型数据,set(key, value, 过期时间, 时间单位) 存入带过期时间的键值对,无需手动处理 Redis 连接、序列化等底层逻辑。
导入依赖
<!--redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>yml配置文件
spring: redis: host: 127.0.0.1 port: 6379 database: 0- 这是 Spring Boot 的 Redis 配置前缀,所有 Redis 相关配置都嵌套在这个层级下,是 Spring 框架约定的固定格式,框架会自动识别该前缀下的配置项并初始化 Redis 连接。
- Redis 服务器的 IP 地址:127.0.0.1表示连接本地的 Redis 服务(本机部署的 Redis);如果 Redis 部署在其他服务器,这里要改对应 IP(如192.168.1.100)。
- Redis 服务的端口号:6379 是 Redis 的默认端口,若安装 Redis 时修改过端口(比如 6380),需对应修改这里。
- Redis 的数据库编号:
- Redis 默认有 16 个逻辑数据库(编号 0-15),无需手动创建,通过编号区分不同用途的数据集;
- 配置database: 0表示使用第 0 个数据库存储数据(如验证码的 uuid 和 code);
- 作用:可按业务隔离数据(比如验证码用 0 库、用户令牌用 1 库),避免不同业务数据混在一起。
4. Result(自定义响应类)
作用:
- 项目自定义的统一响应体类,保证接口返回格式统一(匹配文档的msg/code/data结构)。
实现
- 对于Result类的的实现主要有两种方式。
实现方式 | 核心特点 |
继承 HashMap | 基于 Map 结构,通过 put 方法动态添加字段,灵活但无类型约束,依赖键值对操作 |
泛型类(POJO) | 固定字段(code/message/data 等),通过泛型约束数据类型,结构规范、类型安全 |
5.TimeUnit(java.util.concurrent)
作用
- Java 并发包下的时间单位枚举类,用于明确指定时间单位(如秒、分钟、小时),配合 RedisTemplate 设置过期时间,避免 “硬编码数字 + 注释说明单位” 的不规范写法。
核心用法
- 代码中120, TimeUnit.SECONDS表示 “120 秒”,RedisTemplate 会根据该枚举自动解析时间单位,确保过期时间的语义清晰、不易出错(比如不会把 120 秒误理解为 120 毫秒)。
1.2 代码实现
Result
package com.qcby.community_sp.util; import java.util.HashMap; public class Result extends HashMap<String,Object> { public static Result ok(){ Result result = new Result(); result.put("code", 200); result.put("msg", "操作成功"); return result; } public static Result error(String msg){ Result result = new Result(); result.put("code", 500); result.put("msg", msg); return result; } @Override public Result put(String key, Object value) { super.put(key, value); return this; } }LoginController
package com.qcby.community_sp.controller; import cn.hutool.core.util.IdUtil; import cn.hutool.crypto.SecureUtil; import com.qcby.community_sp.util.Result; import com.wf.captcha.SpecCaptcha; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import java.util.HashMap; import java.util.concurrent.TimeUnit; //登录相关业务功能的Controller //统一回调方式,全部返回json到前端 @RestController public class LoginController { @Autowired private RedisTemplate redisTemplate; /** * 获取验证码 * @return */ @GetMapping("/captcha") public Result getCaptcha() { //生成一个验证码 SpecCaptcha specCaptcha = new SpecCaptcha(130,48,4); //获取工具类生成的验证码图片的验证码,并转成大写 String code = specCaptcha.text().toUpperCase(); //用uuid生成一个唯一的key(Redis里面的key),用来在Redis里面存取验证码 String uuid = IdUtil.simpleUUID(); //向Redis里面存数据 redisTemplate.opsForValue().set(uuid,code,120, TimeUnit.SECONDS); HashMap<String,String> res = new HashMap<>(); res.put("uuid",uuid); res.put("captcha",specCaptcha.toBase64()); res.put("code",code); return Result.ok().put("data",res); } }RedisTemplate 中的 opsForValue () 介绍
- opsForValue () 核心定义
- 核心判断逻辑
业务场景 | 对应 Redis 数据结构 | RedisTemplate 操作入口 |
单键单值(如 uuid→验证码) | 字符串(String) | opsForValue() |
单键多字段(如 user→name/age) | 哈希(Hash) | opsForHash() |
有序可重复列表(如消息队列) | 列表(List) | opsForList() |
- 单值键值对 → 用 opsForValue ()
- 多字段 / 多值结构 → 选 opsForHash ()/opsForList () 等对应方法
- Key 的类型说明
- Value 的类型说明
- 代码场景对应