【redis实战】使用Redis实现图片验证码
目录1 验证码1.1 功能概述验证码存储方案对比为何放弃数据库、Cookie、Session1.数据库存储2.Cookie 存储3.Session 存储Redis 选用原因及简单介绍1.为什么选 Redis2.Redis 简单介绍代码中用到的工具类 / 包介绍1.SpecCaptcha验证码生成作用核心用法导入依赖2.IdUtilUUID 生成通常是 Hutool 工具包作用优势导入依赖3. RedisTemplateSpring Data Redis作用核心用法导入依赖yml配置文件4. Result自定义响应类作用实现5.TimeUnitjava.util.concurrent作用核心用法1.2 代码实现ResultLoginControllerRedisTemplate 中的 opsForValue () 介绍1 验证码1.1 功能概述想在登录的时候实现一个验证码功能用户登录时需要先看验证码是否有效和正确都满足了后端才会去数据库检查用户密码是否正确。接口文档urlGET /captcha参数无返回{ msg: 操作成功, code: 200, data: { uuid: b71fafb1a91b4961afb27372bd3af77c, captcha: data:image/png;base64,iVBORw0KGgoAAAA, code: nrew } }技术栈选用使用Redis存储验证码并使用一些工具包快速生成验证码。验证码存储方案对比为何放弃数据库、Cookie、Session1.数据库存储核心问题验证码是高频、临时的轻量数据数据库如 MySQL主打持久化存储写入 / 读取需走磁盘 IO性能远低于内存存储且验证码有效期仅 2 分钟频繁读写数据库会增加不必要的性能开销还需额外写定时清理过期验证码的逻辑徒增复杂度。简单总结太重、太慢没必要用持久化存储存临时验证码。2.Cookie 存储核心问题Cookie 存储在客户端浏览器可被篡改、伪造验证码是安全校验数据存客户端完全无安全性且 Cookie 有存储大小限制约 4KB虽验证码文本小但违背 “安全数据服务端存储” 的原则。简单总结不安全数据能被客户端修改失去验证码校验意义。3.Session 存储核心问题Session 基于服务器内存或容器存储分布式部署场景下多台服务器Session 无法共享用户请求落到不同服务器时会校验失败且 Session 默认过期时间较长通常 30 分钟手动设置 2 分钟过期需额外配置还会占用服务器内存服务器重启后 Session 丢失验证码直接失效。简单总结分布式环境不兼容服务器重启易丢失内存占用不灵活。Redis 选用原因及简单介绍1.为什么选 Redis高性能基于内存读写速度比数据库快几个量级适配验证码高频读写场景过期策略原生支持设置 key 的过期时间自动清理无需手动维护分布式友好Redis 可独立部署多台应用服务器都能访问解决 Session 共享问题轻量灵活仅存储临时的验证码键值对资源占用极低。2.Redis 简单介绍Redis 是一款内存型键值对数据库主打高性能、高可用支持字符串、哈希、列表等多种数据结构常用作缓存、临时数据存储如验证码、令牌核心特点是 “快”内存操作、“灵活”丰富的过期策略、数据结构适配各类临时、高频访问的场景。代码中用到的工具类 / 包介绍1.SpecCaptcha验证码生成作用快速生成图片验证码包含验证码文本 图片 Base64 编码无需手动处理图片绘制、编码核心用法new SpecCaptcha(宽度, 高度, 验证码位数) 创建实例text() 获取验证码文本toBase64() 转为 Base64 编码方便前端直接展示图片具体示例见代码。导入依赖!-- 验证码 -- dependency groupIdcom.github.whvcse/groupId artifactIdeasy-captcha/artifactId version${captcha.version}/version /dependency2.IdUtilUUID 生成通常是 Hutool 工具包作用Hutool 工具包中的 ID 工具类simpleUUID() 生成无横线的 UUID如b71fafb1a91b4961afb27372bd3af77c替代手动写 UUID 生成逻辑简化代码优势封装了各种 ID 生成规则UUID、雪花 ID 等开箱即用避免重复造轮子。导入依赖!--工具包-- dependency groupIdcn.hutool/groupId artifactIdhutool-all/artifactId version${hutool.version}/version /dependency3. RedisTemplateSpring Data Redis作用Spring 框架封装的 Redis 操作模板简化 Redis 的连接、读写操作核心用法opsForValue() 操作字符串类型数据set(key, value, 过期时间, 时间单位) 存入带过期时间的键值对无需手动处理 Redis 连接、序列化等底层逻辑。导入依赖!--redis -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependencyyml配置文件spring: redis: host: 127.0.0.1 port: 6379 database: 01.spring: redis:这是 Spring Boot 的 Redis 配置前缀所有 Redis 相关配置都嵌套在这个层级下是 Spring 框架约定的固定格式框架会自动识别该前缀下的配置项并初始化 Redis 连接。2.host: 127.0.0.1Redis 服务器的 IP 地址127.0.0.1表示连接本地的 Redis 服务本机部署的 Redis如果 Redis 部署在其他服务器这里要改对应 IP如192.168.1.100。3.port: 6379Redis 服务的端口号6379 是 Redis 的默认端口若安装 Redis 时修改过端口比如 6380需对应修改这里。4.database: 0Redis 的数据库编号Redis 默认有 16 个逻辑数据库编号 0-15无需手动创建通过编号区分不同用途的数据集配置database: 0表示使用第 0 个数据库存储数据如验证码的 uuid 和 code作用可按业务隔离数据比如验证码用 0 库、用户令牌用 1 库避免不同业务数据混在一起。4. Result自定义响应类作用项目自定义的统一响应体类保证接口返回格式统一匹配文档的msg/code/data结构。实现对于Result类的的实现主要有两种方式。实现方式核心特点继承 HashMap基于 Map 结构通过put方法动态添加字段灵活但无类型约束依赖键值对操作泛型类POJO固定字段code/message/data 等通过泛型约束数据类型结构规范、类型安全企业开发中统一响应体首选泛型 POJO 式 Result 类核心原因是类型安全、结构规范、序列化稳定适配绝大多数固定返回结构的业务场景如验证码接口继承 HashMap 的方式仅用于字段需动态增减的特殊临时场景正式项目极少使用。5.TimeUnitjava.util.concurrent作用Java 并发包下的时间单位枚举类用于明确指定时间单位如秒、分钟、小时配合 RedisTemplate 设置过期时间避免 “硬编码数字 注释说明单位” 的不规范写法。核心用法代码中120, TimeUnit.SECONDS表示 “120 秒”RedisTemplate 会根据该枚举自动解析时间单位确保过期时间的语义清晰、不易出错比如不会把 120 秒误理解为 120 毫秒。1.2 代码实现Resultpackage com.qcby.community_sp.util; import java.util.HashMap; public class Result extends HashMapString,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; } }LoginControllerpackage 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生成一个唯一的keyRedis里面的key用来在Redis里面存取验证码 String uuid IdUtil.simpleUUID(); //向Redis里面存数据 redisTemplate.opsForValue().set(uuid,code,120, TimeUnit.SECONDS); HashMapString,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 () 核心定义opsForValue() 是 RedisTemplate 针对 Redis 字符串String类型 数据的操作入口核心用于操作「单键单值」的键值对结构一个 key 对应一个 value与 Java 中 Value 的具体数据类型无关仅取决于 Redis 底层存储的数据结构类型。核心判断逻辑业务场景对应 Redis 数据结构RedisTemplate 操作入口单键单值如 uuid→验证码字符串StringopsForValue()单键多字段如 user→name/age哈希HashopsForHash()有序可重复列表如消息队列列表ListopsForList()简单记单值键值对 → 用 opsForValue ()多字段 / 多值结构 → 选 opsForHash ()/opsForList () 等对应方法Key 的类型说明Redis 底层 Key 是字节数组RedisTemplate 默认序列化为 String 类型实际开发中Redis 的 Key 几乎都用 String如 uuid可读性、兼容性最优。Value 的类型说明opsForValue().set(key, value) 中 Value 可传任意 Java 类型String/Integer/ 自定义对象等RedisTemplate 会自动序列化后存储无需因 Value 类型切换 opsForXXX 方法。即如果Value是HashMap或者List用opsForValue()也可以技术上可行但是不规范。代码场景对应上面的验证码代码中uuidkey→ codevalue 是典型「单键单值」结构因此用 opsForValue() 匹配 Redis String 类型搭配 TimeUnit.SECONDS 设置 120 秒过期时间是标准且规范的写法。