基于Redis的Spring Boot登录方案设计与实践
发布时间:2026/9/18 4:03:02 锦皓数字建站

1. 整体方案设计为什么登录功能绕不开 Redis做后端开发这些年我接手过好几个项目的登录模块从最早的 Tomcat Session 到后来的 JWT再到今天要聊的 Redis 方案可以说每一种都有它的适用场景但基于 Redis 实现登录功能是目前分布式场景下最主流的做法没有之一。先说一个最典型的痛点以前单体应用直接用 HttpSession 存用户状态浏览器 Cookie 里带一个 JSESSIONID 就能识别用户。听起来挺省事但一旦服务扩容到多台机器用户第一次请求落到 A 机器上第二次请求被负载均衡转发到 B 机器B 机器上没有这个用户的 Session用户就被迫重新登录。为了解决这个问题过去常用的是 Session 黏滞Sticky Session或者 Session 广播同步前者把负载均衡的路由策略绑死做不到真正的高可用后者在节点多的时候同步开销大得惊人一个用户登录要把 Session 发给所有节点机器一多基本就废了。Redis 这套方案解决的核心问题就是把用户的登录状态从应用服务器的内存中抽离出来集中存储到一个所有应用节点都能访问的独立中间件里。这样无论请求落到哪台机器上只要去 Redis 里查一下这个用户的会话数据就能确认身份彻底解开了服务无状态和登录有状态之间的矛盾。同时 Redis 本身是内存级读写单实例 QPS 动辄十万级别就算用户量上去了登录校验的性能开销也基本可以忽略再加上 Redis 天然支持 key 过期登录 token 的过期时间直接映射成 Redis 的 TTL系统主动帮你把会话清理掉省去一堆定时任务。这个方案适合谁来参考我觉得只要你做的是 Web 后端或者哪怕只是自己写点项目练手都值得花时间把 Redis 登录这套搞透。它不复杂但里面涉及的 key 设计、序列化选择、过期策略、并发控制这些问题几乎涵盖了 Redis 在业务场景中 80% 的常用姿势。弄懂一个登录功能你对 Redis 的理解会从会用命令上升到能设计合理方案的层面。2. 技术选型拆解Redis 数据类型、序列化与 Key 设计的底层逻辑2.1 为什么存登录态选 String 而不是 Hash在聊 Redis 存登录态之前得先搞清楚 Redis 有哪些数据类型因为不同数据结构和业务场景的匹配度完全不一样。Redis 最基础的是 String然后是 Hash、List、Set、ZSet另外还有 Bitmap、HyperLogLog、Geo 这些扩展类型。登录功能里用到最多的就是 String 和 Hash 这两种。我见过很多新手一上来就用 Hash 存用户信息字段是 username、userId、avatar 一长串塞进去觉得这样结构清晰。但登录态的存储其实和普通的用户资料缓存不是一个场景。登录态的访问特征是一次性读取整份数据我要校验这个 token 是否有效只需要确认它对应的数据存在并且取出 userId 就够了。如果按字段分别读 Hash 的话虽然也可以但在序列化机制和对象转换上会多绕一层。相比之下String 存的就是一个完整的 JSON 字符串set 的时候序列化进去get 的时候反序列化出来整个过程清爽利落。再有String 类型的操作是最原子、最底层的Redis 对 String 的读写性能也是所有类型里最好的。登录校验是每次请求都会触发的操作属于高频路径用 String 能省一丁点序列化和命令解析的损耗。一次请求省 0.1 毫秒听起来不多但你的系统一天扛几千万请求的时候这个差距就体现出来了。当然 Hash 也不是不能用如果你的登录态里要频繁单独更新某个字段比如更新用户的最后活跃时间Hash 可以做到只更新一个 field而 String 需要把整个 JSON 取出来反序列化再改再写回去这种情况下 Hash 更合适。但从大多数业务场景来看登录态是一次性写入、整体读取String 就是最优解。2.2 序列化方案选择JDK、Jackson 还是 FastjsonRedis 存的只能是字节或字符串Java 对象的存储必然要经过序列化。这里有个特别值得说的坑我见过不少项目因为序列化方案没选对后面排查问题的时候欲哭无泪。Spring Data Redis 默认提供的序列化器是 JdkSerializationRedisSerializer你如果不去改它直接往 RedisTemplate 里 set 一个对象产生的是一串带类型信息的二进制数据。数据能存也能读项目也能跑但是你在 Redis Desktop Manager 里看到的是一堆类似\xAC\xED\x00\x05t\x00...的乱码想手动查一条数据看看对不对根本没法看。再有一点JDK 序列化要求对象实现Serializable接口而且每次改对象字段序列化版本号变了可能就反序列化失败。这种方案在生产环境和调试体验上都很差个人强烈建议换掉。比较靠谱的搭配是 key 用 StringRedisSerializervalue 用 GenericJackson2JsonRedisSerializer。key 用 String 序列化是为了保证可读性红框搜索的时候能直接看到login:token:xxx这样的 keyvalue 用 Jackson 序列化则是把对象存成规范可读的 JSON 字符串调试的时候一眼就能看出内容对不对。这里要注意一点GenericJackson2JsonRedisSerializer 会在 JSON 里加上class字段记录类型信息反序列化的时候能自动还原成对应的对象。如果你用的是不带类型信息的普通 Jackson 配置读出来默认是 LinkedHashMap强转成自定义类型会直接报 ClassCastException这个坑我在下面的问题排查部分还会详细说。Fastjson 我不是很想推荐虽然它的性能确实不错但历史上有过几次安全漏洞而且社区活跃度不如 Jackson。Spring Boot 默认整合的就是 Jackson没必要为了这点性能差异引入一个额外依赖。2.3 Key 的命名规范和过期时间的测算思路Redis 的 key 是全局平铺的没有库表的概念所以命名规范特别重要。我见过一个项目把所有 Redis 数据都用一个单词做 key比如直接存user:1001结果后来加了验证码功能key 叫code:xxx再后来加了商品缓存key 叫goods:xxx。当时看着挺清晰但等 key 的数量上万之后Redis 那个键空间看着跟垃圾场一样谁也不敢删谁也不知道哪些 key 在哪个业务里用着。推荐的命名方式是业务前缀:功能模块:标识比如登录态的 key 应该是login:token:uuid验证码应该是login:code:userId商品缓存应该是product:detail:1001。这样当你用 Redis Desktop Manager 按前缀筛选的时候同一类业务的 key 都聚在一起排查问题非常方便。而且通过 key 前缀能很直观地看出业务归属后面的维护成本低很多。过期时间这块TTL 的设计要结合业务需求。如果做完登录功能用户能一直挂着那 token 的过期时间就应该和 Session 超时时间保持一致一般是 30 分钟活跃用户会自动续期如果是那种安全级别比较高的后台管理系统可以把过期时间缩短到 15 分钟配合续期机制来平衡体验和安全性。理论上Redis 的 TTL 精确度很高实测下来 key 过期会在时间到之后的极短时间内被清理掉不会出现明显的误差问题。设置过期时间的时候要注意TTL 的单位是秒设置的时候不要把这个搞混了比如你要 30 分钟应该写 1800而不是 30。我个人的习惯是登录 token 过期时间设置成 30 分钟活跃用户每次请求接口的时候校验通过后顺手把 TTL 重新刷新回 1800 秒这样用户只要一直操作就不会掉线但是超过半小时没有任何操作token 就自动失效了。这个逻辑实现起来就是一行expire(key, 1800)的事但产品体验和纯固定过期时间相比差别非常明显。3. 核心功能实操基于 Spring Boot 完整实现 Redis 登录3.1 环境准备和依赖引入用 Spring Boot 集成 Redis 已经算是后端开发的基操了。第一步当然是确保 Redis 服务本身是活的如果你用的 Windows 开发环境直接去官网下载 Redis 的 Windows 版本或者用 WSL 装一个都行生产环境强烈建议用 Docker 来部署命令非常简单docker run -d --name redis-server -p 6379:6379 redis:7.0 --requirepass 你的密码在 pom.xml 里引入依赖Spring Boot 2.x 和 3.x 都适用dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependency dependency groupIdorg.apache.commons/groupId artifactIdcommons-pool2/artifactId /dependencycommons-pool2 是 Redis 连接池的依赖不加的话 Lettuce 也能跑但是连接是每次新建的高并发下性能会受影响。这个依赖加上去在 application.yml 里配置一下连接池参数spring: data: redis: host: localhost port: 6379 password: 你的密码 lettuce: pool: max-active: 8 max-idle: 8 min-idle: 0Spring Boot 2.x 里的配置前缀是spring.redis3.x 改成了spring.data.redis这个注意一下配置不生效的时候八成就是前缀写错了。3.2 RedisTemplate 配置让可读性提升十倍这一步可以说是全篇的核心实操配置对了后面的开发顺风顺水配置错了后面查数据看到乱码心态直接炸裂。我给出一个经过多轮调整后的标准配置类Configuration public class RedisConfig { Bean public RedisTemplateString, Object redisTemplate(RedisConnectionFactory factory) { RedisTemplateString, Object template new RedisTemplate(); template.setConnectionFactory(factory); // key 使用 String 序列化 StringRedisSerializer stringSerializer new StringRedisSerializer(); // value 使用 Jackson 泛型序列化 GenericJackson2JsonRedisSerializer jsonSerializer new GenericJackson2JsonRedisSerializer(); template.setKeySerializer(stringSerializer); template.setHashKeySerializer(stringSerializer); template.setValueSerializer(jsonSerializer); template.setHashValueSerializer(jsonSerializer); template.afterPropertiesSet(); return template; } }几个容易忽略的细节setHashKeySerializer和setHashValueSerializer也要设置不然后续你一旦用到 Hash 结构key 或者 value 就会被默认的 JDK 序列化处理又是一堆乱码。afterPropertiesSet()这行是让配置尽快生效的有些版本不调用也能工作但建议加上。配置完成之后往 Redis 里写入一条数据用可视化工具看到的效果类似KeyValueTTLlogin:token:9f8d0c2a-1b3e-4a5c-9d7e-8f1a2b3c4d5e{id:1001,username:zhangsan,role:admin,loginTime:1699000000000}1799这种效果才算配置到位key 可读value 可读排查问题的时候只需要一眼扫过去就能判断登录态是否写入成功。3.3 登录接口的完整实现校验、生成 Token、写入过期键核心登录逻辑是这样的用户提交用户名密码校验通过后生成一个 UUID 作为 token把用户信息序列化后写入 Redis设置过期时间然后把 token 返回给前端。前端后续每次请求在 Header 里带上这个 token后端校验存在性来确定用户身份。Service public class LoginService { Autowired private RedisTemplateString, Object redisTemplate; Autowired private UserMapper userMapper; private static final String LOGIN_TOKEN_PREFIX login:token:; private static final long TOKEN_EXPIRE_TIME 1800; // 30分钟 public LoginResponse login(LoginRequest request) { // 1. 校验用户名密码实际项目中密码需要 BCrypt 加密存储这里简化 User user userMapper.selectByUsername(request.getUsername()); if (user null || !user.getPassword().equals(DigestUtils.md5DigestAsHex(request.getPassword().getBytes()))) { throw new BusinessException(用户名或密码错误); } // 2. 生成一次性 token String token UUID.randomUUID().toString().replace(-, ); // 3. 构建登录用户信息对象 LoginUser loginUser new LoginUser( user.getId(), user.getUsername(), user.getRole(), System.currentTimeMillis() ); // 4. 写入 Redis 并设置过期时间 String key LOGIN_TOKEN_PREFIX token; redisTemplate.opsForValue().set(key, loginUser, TOKEN_EXPIRE_TIME, TimeUnit.SECONDS); // 5. 返回 token 给前端 return new LoginResponse(token, TOKEN_EXPIRE_TIME); } public void logout(String token) { String key LOGIN_TOKEN_PREFIX token; redisTemplate.delete(key); } public LoginUser getLoginUser(String token) { String key LOGIN_TOKEN_PREFIX token; Object value redisTemplate.opsForValue().get(key); if (value null) { return null; } // 反序列化时GenericJackson2JsonRedisSerializer 会自动还原类型 // 这里建议直接强转如果类型信息丢失的话需要手动 convert return (LoginUser) value; } }写这段代码的时候有几个点需要重点提示第一登录用户的密码绝对不能存进 Redis。登录态的关键作用只是标记这个用户已认证你存 userId 和 username 就够了。一旦把密码这种敏感信息缓存到 Redis即使 Redis 有密码保护内存 dump 或者日志泄漏造成的风险也是不可接受的。第二UUID 生成 token 是我们最常用的方式但业务要求更高的时候可以考虑用 JWT 代替。JWT 的优点是服务端校验不需要查 Redistoken 本身携带了用户信息用密钥验签就行。但 JWT 也有特别明显的缺点无法主动失效你没办法让一个已经签发的 JWT 立即过期除非自己维护一个黑名单这又变相回到了 Redis 存储。所以登录态这种需要能主动踢人的场景Redis 随机 token 是更简单可靠的方案。第三getLoginUser里的强转在配置了 GenericJackson2JsonRedisSerializer 的前提下是没有问题的因为 JSON 里有class信息反序列化出来就是 LoginUser 类型。如果你用了不带类型信息的序列化器这里会抛 ClassCastException。下面会详细说这个坑。3.4 登录校验拦截器每个受保护接口的守门员有了登录接口还得有校验登录态的拦截器。定义一个 HandlerInterceptor对所有需要认证的接口做拦截从 Header 里取 token查 Redis查不到就直接返回 401查到了就把用户信息塞进 ThreadLocal 里供后续业务代码使用。Component public class LoginInterceptor implements HandlerInterceptor { Autowired private LoginService loginService; private static final ThreadLocalLoginUser USER_HOLDER new ThreadLocal(); Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 放行预检请求 if (OPTIONS.equalsIgnoreCase(request.getMethod())) { return true; } String token request.getHeader(Authorization); if (token null || token.isEmpty()) { response.setStatus(401); response.setContentType(application/json;charsetUTF-8); response.getWriter().write({\code\:401,\message\:\未登录或登录已过期\}); return false; } LoginUser loginUser loginService.getLoginUser(token); if (loginUser null) { response.setStatus(401); response.setContentType(application/json;charsetUTF-8); response.getWriter().write({\code\:401,\message\:\未登录或登录已过期\}); return false; } // 登录成功顺手刷新过期时间实现滑动续期 loginService.refreshTokenExpire(token); // 用户信息放入 ThreadLocal业务层直接从 ThreadLocal 获取 USER_HOLDER.set(loginUser); return true; } Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // 请求结束后必须移除否则线程池复用会导致用户信息串号 USER_HOLDER.remove(); } public static LoginUser getCurrentUser() { return USER_HOLDER.get(); } }拦截器里的refreshTokenExpire实现就一行public void refreshTokenExpire(String token) { String key LOGIN_TOKEN_PREFIX token; Long ttl redisTemplate.getExpire(key, TimeUnit.SECONDS); if (ttl ! null ttl 0) { redisTemplate.expire(key, TOKEN_EXPIRE_TIME, TimeUnit.SECONDS); } }注意一点afterCompletion里的ThreadLocal.remove()千万不能省。Web 容器处理请求用的线程池是复用的如果不清理 ThreadLocal下一个请求复用这个线程的时候getCurrentUser()拿到的是上一个用户的信息这种串号 bug 极其隐蔽查起来特别费劲。3.5 注册登录 WebAuthn 的进阶扩展方向最近 WebAuthn 相关的热度很高热词里也有 webauthn实现自定义登录/注册功能 springboot。如果你觉得传统账号密码登录太老套想接生物识别或者硬件安全密钥可以把 WebAuthn 和 Redis 结合起来用。WebAuthn 是一种无密码认证协议浏览器通过公钥加密的方式生成一对密钥服务端保存公钥用户登录的时候用私钥签名挑战值来验证身份。Redis 在这个场景下的作用主要有两个一个是保存注册阶段的挑战值 challenge设置短 TTL比如 2 分钟防止重放攻击另一个仍然是会话管理认证通过之后签发一个随机 token 存 Redis逻辑和账号密码登录完全一样。也就是说不管你前端认证方式怎么换后端登录态的存储和校验始终可以统一收敛到 Redis 这一层。我实际调研过的做法是用 Spring Security 的 WebAuthn 集成库处理认证协议拿到认证结果之后组装 LoginUser 对象剩下的 token 生成、Redis 写入、过期管理直接复用现有的 LoginService。这样既保留了生物识别的高级感又不破坏整体架构的简洁性。这块内容如果你感兴趣可以单独深入这里先埋个伏笔。4. 常见问题与排查实录序列化陷阱、缓存穿透与并发控制4.1 反序列化报 ClassCastException 的根治方法这是我在群里回答了几百遍的问题几乎每过一段时间就有人贴一段报错来问java.lang.ClassCastException: class java.util.LinkedHashMap cannot be cast to class com.example.LoginUser我前面已经提到了这个问题的根源是序列化器选择不对。你可能在 RedisTemplate 里用了Jackson2JsonRedisSerializer注意不是 Generic 版本这个序列化器虽然输出的是 JSON但里面没有类型信息。存进去的时候是 LoginUser读出来的时候 Jackson 只知道它是一个 Object具体是什么类型它不知道默认给你还原成了 LinkedHashMap。你一看代码明明是 LoginUser结果运行起来偏偏报 LinkedHashMap 转不过去满脸问号。解决办法有两个。最省事的是换成GenericJackson2JsonRedisSerializer它会自动在 JSON 里写入一个class字段记录类型全路径。存的时候是{class:com.example.LoginUser,id:1001,...}读的时候 Jackson 看到class就知道该转成 LoginUser强转自然就成功了。另一个办法是手动做类型转换LoginUser loginUser objectMapper.convertValue(value, LoginUser.class);这个方案要求你自己维护一个 ObjectMapper代码量多一点灵活度也高一些。我的建议是一般业务项目直接用 Generic 版本就够了省心最重要。还有个隐藏细节如果你用 GenericJackson2JsonRedisSerializerJSON 里会多一个class字段这会增加一点存储空间但一个登录态对象撑死也就多几十个字节完全可以忽略。如果实在在意空间可以在配置 ObjectMapper 时关闭默认类型信息用 activateDefaultTyping 的方式做更精细的控制但这是优化阶段的活别在一开始就背上这个包袱。4.2 Redis 里看到乱码 key 的补救方案很多人在开发过程中发现 Redis 里出现了一堆\xac\xed\x00\x05t\x00开头的 key或者 value 是二进制乱码异常难看。这是默认的 JdkSerializationRedisSerializer 在搞鬼。如果你刚发现这个问题而且你的 Redis 里存的数据都是登录态这类可以被主动失效的数据最简单的补救方法是直接清空该业务前缀下所有 key然后修改 RedisTemplate 配置后重新启动应用。但如果你的数据里已经有不可再生的业务数据那只能通过临时写工具类的方式把数据读出来反序列化再重新以 String/JSON 的格式写回去工作量会大一些。我个人的建议是在项目开发初期就把 RedisConfig 的序列化方案定下来别等数据多了再改。而且还应该顺手配一个单元测试往 Redis 里写入一条数据读出来断言类型正确key 和 value 的可读性一目了然。这种测试写一次能受益一整个项目周期。4.3 缓存穿透与并发重复登录Redis 登录场景的防护细节登录场景里的缓存穿透和普通缓存场景还不一样。我这里说的不是那种恶意攻击的穿透而是业务上常见的场景大量用户同时使用同一个账号登录或者同一个用户短时间内疯狂点击登录按钮。如果你不加任何防护每一次点击都会执行一次密码校验然后生成一个新的 token 写入 Redis。用户那边可能还没感觉到什么Redis 里的无效 key 已经堆了一堆。初级的优化是在前端按钮上加 loading 或者禁用置灰这个属于体验优化不解决后端实质问题。后端层面可以对同一个用户 ID 的登录请求做并发控制。用 Redis 的分布式锁机制key 设计成login:lock:userId设置 1-2 秒的过期时间用setIfAbsent实现Boolean locked redisTemplate.opsForValue().setIfAbsent(lockKey, 1, 2, TimeUnit.SECONDS); if (Boolean.FALSE.equals(locked)) { throw new BusinessException(登录请求处理中请勿重复提交); }这只是最基础的姿势系统学习的话可以去搜 Redis 分布式锁、Redisson 相关的文章尤其注意看 Redisson 的看门狗机制和锁的可重入设计能帮你避免死锁和锁误删的问题。这也解释了为什么 Redis 相关面试题里登录、缓存、分布式锁总是被串在一起问因为它们在实际系统中本质就是一个整体。另一个常见问题是防暴力破解。用户密码连续输错 N 次就锁定一段时间这个也可以依赖 Redis 轻松实现。设计一个 keylogin:fail:username每次密码错误就increment并设置过期时间如果计数达到上限就拒绝登录。这里要注意的是 key 的过期时间是在第一次写入时设置的increment操作不会改变 TTL如果你需要每次输错都重新计时需要在 increment 之后手动调用expire。这个细节很多人容易忽略结果就是锁定窗口比预期短很多。4.4 缓存雪崩和 Key 集中过期的避坑经验缓存雪崩说的是大量 key 在同一时间段集中过期导致请求直接穿透到数据库。登录 token 一般不会直接打数据库但如果你的登录态 token 是每天凌晨统一时间点批量生成的TTL 又完全一样很有可能出现凌晨某个时刻大量用户的登录态同时失效引发一波重新登录风暴。避免的方法不复杂一是过期时间加一个随机抖动比如基础 1800 秒实际设置的时候加上RandomUtils.nextInt(0, 300)秒的偏移这样每个用户的过期时间有前有后不会整齐划一二是真出现了集中过期的情况可以在校验逻辑里做一个短期缓存或者降级策略避免大量请求同时重建会话。Redis 的 TTL 机制在实现上并不是到期立即删除。Redis 同时有主动删除惰性删除和被动删除定时扫描两种策略一个 key 到了过期时间并不会立刻从内存中消失而是等再次被访问或者后台的循环扫描发现了才删掉。所以如果你在 key 刚过期的瞬间去读可能会读到还在内存中但已经逻辑失效的数据吗不会Redis 的惰性删除会在 get 的时候判断 TTL 是否已到到了的话直接返回 nil 并删除 key所以业务上不会读到已过期的数据。但你要额外关注的是内存占用大量已经过期但还没有被访问和扫描到的 key 会霸占内存这也是为什么 Redis 配置里要有 maxmemory 和淘汰策略的原因之一合理配置才能防止内存爆掉。4.5 Redis 面试常考题登录功能背后的原理延伸把所有核心内容过了一遍之后你会发现基于 Redis 实现登录功能其实把 Redis 的几个面试高频考点都覆盖了Redis 的持久化机制RDB 和 AOF键值对在重启后是否还能恢复登录态这里有个取舍登录态丢了最多重新登录所以可以考虑关闭持久化或使用 AOF 的较短刷盘周期Redis 的过期删除策略惰性删除加定期删除Redis 的内存淘汰策略内存满了之后是淘汰最久未使用的 key 还是随机淘汰Redis 的线程模型为什么单线程还能这么快以及 Redis 的分布式锁实现。把这些原理和登录功能关联起来理解比死记硬背面试题要牢靠得多。从数据可靠性角度说Redis 挂着的时候所有用户登录态瞬间全部失效应用层面需要有一个兜底方案。轻量级做法是在 Redis 不可用的时候业务可以降级为直接放行或者通过数据库短暂查询用户信息来重建会话。稳妥的做法是给 Redis 做高可用部署比如主从架构加哨兵。现在热词里也有 docker安装redis主从如果你们公司对登录服务的可用性要求非常高直接上主从加哨兵的架构会比单节点稳很多。Redis 主从复制的原理是主节点把写操作追加到内存缓冲区从节点通过 SYNC 命令同步全量数据之后增量拉取主节点的命令流。整个过程对应用层无感知属于一个比较成熟的运维方案。结尾的个人经验如果让我总结这套基于 Redis 登录方案里最值钱的几条体会我首先会强调序列化方案一定要在项目启动的第一天就选对这是后面所有调试体验的地基其次key 命名和过期时间的设计要当成接口文档一样重视因为 Redis 不像数据库有清晰的表结构它更像一个巨大的内存字典命名就是你对抗混乱的最有力武器最后登录功能虽然看着简单但它几乎是每个系统里第一个被攻击者盯上的模块任何一步都不能抱有侥幸心理。后来的实际项目里我又在这个登录方案的基础上扩展了多端登录互踢同一账号新登录后旧 token 直接删除、用户在线状态统计用 ZSet 记录活跃用户和时间戳以及基于 Redis 的短信验证码限流。这些都是把登录功能做深做扎实的延伸方向。如果你正打算从零开始搭建或者重构登录模块这套 Redis 方案应该能帮你少踩不少弯路希望上面的实操细节和踩坑记录对你有用。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。