1 验证码

1 验证码

1.1 功能概述

  1. 接口文档
    url:GET /captcha
    参数:无
    返回:

    {
    "msg": "操作成功",
    "code": 200,
    "data": {
    "uuid": "b71fafb1a91b4961afb27372bd3af77c",
    "captcha": "data:image/png;base64,iVBORw0KGgoAAAA",
    "code": "nrew"
    }
    }

  2. 技术栈选用
    使用Redis存储验证码,并使用一些工具包快速生成验证码。

验证码存储方案对比(为何放弃数据库、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 简单介绍

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

1.spring: redis:

  • 这是 Spring Boot 的 Redis 配置前缀,所有 Redis 相关配置都嵌套在这个层级下,是 Spring 框架约定的固定格式,框架会自动识别该前缀下的配置项并初始化 Redis 连接。
    2.host: 127.0.0.1
  • Redis 服务器的 IP 地址:127.0.0.1表示连接本地的 Redis 服务(本机部署的 Redis);如果 Redis 部署在其他服务器,这里要改对应 IP(如192.168.1.100)。
    3.port: 6379
  • Redis 服务的端口号:6379 是 Redis 的默认端口,若安装 Redis 时修改过端口(比如 6380),需对应修改这里。
    4.database: 0
  • 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 等),通过泛型约束数据类型,结构规范、类型安全 |

企业开发中统一响应体首选泛型 POJO 式 Result 类,核心原因是类型安全、结构规范、序列化稳定,适配绝大多数固定返回结构的业务场景(如验证码接口);继承 HashMap 的方式仅用于字段需动态增减的特殊临时场景,正式项目极少使用。

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 () 介绍

  1. opsForValue () 核心定义
    opsForValue() 是 RedisTemplate 针对 Redis 字符串(String)类型 数据的操作入口,核心用于操作「单键单值」的键值对结构(一个 key 对应一个 value),与 Java 中 Value 的具体数据类型无关,仅取决于 Redis 底层存储的数据结构类型。
  2. 核心判断逻辑

|------------------------|---------------|--------------------|
| 业务场景 | 对应 Redis 数据结构 | RedisTemplate 操作入口 |
| 单键单值(如 uuid→验证码) | 字符串(String) | opsForValue() |
| 单键多字段(如 user→name/age) | 哈希(Hash) | opsForHash() |
| 有序可重复列表(如消息队列) | 列表(List) | opsForList() |

简单记:

  • 单值键值对 → 用 opsForValue ()
  • 多字段 / 多值结构 → 选 opsForHash ()/opsForList () 等对应方法
  1. Key 的类型说明
    Redis 底层 Key 是字节数组,RedisTemplate 默认序列化为 String 类型;
    实际开发中,Redis 的 Key 几乎都用 String(如 uuid),可读性、兼容性最优。
  2. Value 的类型说明
    opsForValue().set(key, value) 中 Value 可传任意 Java 类型(String/Integer/ 自定义对象等),RedisTemplate 会自动序列化后存储,无需因 Value 类型切换 opsForXXX 方法。即:如果Value是HashMap或者List,用opsForValue()也可以,技术上可行,但是不规范。
  3. 代码场景对应
    上面的验证码代码中,uuid(key)→ code(value) 是典型「单键单值」结构,因此用 opsForValue() 匹配 Redis String 类型,搭配 TimeUnit.SECONDS 设置 120 秒过期时间,是标准且规范的写法。
相关推荐
tuokuac2 小时前
Linux中的cd ~命令
linux·运维·服务器
wniuniu_2 小时前
ceph中的crush map
java·运维·ceph
陌路202 小时前
redis 发布订阅功能
数据库·redis·缓存
挺6的还2 小时前
3.Redis通用
redis
丁丁丁梦涛2 小时前
navicat跨服务器连接MySQL数据库
服务器·数据库·mysql
G31135422732 小时前
理解Linux和Windows的区别
linux·运维·服务器
SunnyDays10112 小时前
Java 实现 RTF 转 Word:完整技术指南
java·rtf转word
博风2 小时前
飞书知识库备份
java·飞书
寰天柚子2 小时前
服务器远程运维实战:高效管理租赁/自有服务器的全流程指南
运维·服务器·网络