短信登陆
约 5716 字大约 19 分钟
2025-06-26
1.1 导入黑马点评项目
1.1.1 导入 SQL、后端项目和前端工程
略
启动项目后,在浏览器访问:http://localhost:8081/shop-type/list ,如果可以看到数据则证明运行没有问题
1.1.2 有关当前模型
在典型的 Web 应用架构中,客户端(如手机 App 或 Web 浏览器)通过 HTTP 协议与 Nginx 服务器进行通信。Nginx 在此架构中扮演着至关重要的角色:
- 反向代理与负载均衡: Nginx 可以作为反向代理服务器,接收客户端的请求,并将这些请求分发到后端的 Tomcat 服务器集群。通过负载均衡算法,Nginx 可以有效地将流量分散到不同的 Tomcat 实例上,避免单点过载,提高系统的整体并发处理能力。例如,一个 4 核 8G 的 Tomcat 服务器在优化后可能只能处理 1000 左右的并发,但通过 Nginx 的负载均衡,可以将上万的并发请求分摊到多台 Tomcat 服务器上。
- 静态资源服务器: Nginx 能够直接处理静态资源(如 HTML、CSS、JavaScript、图片等),无需将这些请求转发到 Tomcat 服务器。这种动静分离的架构可以显著降低 Tomcat 的压力,提高静态资源的访问速度。
- Lua 脚本支持: Nginx 支持 Lua 脚本,允许开发者在 Nginx 层面直接处理一些业务逻辑,例如,绕过 Tomcat 直接访问 Redis 缓存,从而提高性能。
在后端,Tomcat 服务器负责处理 Java Web 应用的动态请求。为了应对高并发场景,通常会采用以下策略:
- MySQL 集群: 将 MySQL 数据库部署为集群,提高数据库的并发处理能力和可用性。
- Redis 缓存: 引入 Redis 缓存,将热点数据缓存在内存中,减少对 MySQL 数据库的直接访问,提高数据读取性能。同时,使用 Redis 集群可以提高 Redis 服务的可用性和扩展性。
通常,企业级的 MySQL 服务器(例如,16 核或 32 核 CPU,32G 或 64G 内存,配备固态硬盘)能够支撑 4000 到 7000 左右的并发。在高并发场景下,直接访问 MySQL 容易导致服务器 CPU 和硬盘资源耗尽,甚至崩溃。因此,使用 MySQL 集群和 Redis 缓存是优化高并发应用的关键手段。
接下来,我们将讨论如何使用 Session 实现登录流程。
1.2 基于 Session 实现登录流程
传统的基于 Session 的登录流程通常包含以下几个步骤:
- 发送验证码: 用户提交手机号后,系统首先验证手机号的格式是否正确。如果手机号合法,则生成一个随机验证码,并将验证码保存在服务器端的 Session 中。然后,通过短信服务将验证码发送给用户。
- 短信验证码登录、注册: 用户输入手机号和收到的验证码,提交到服务器进行验证。服务器从 Session 中取出之前保存的验证码,与用户输入的验证码进行比较。如果验证码不一致,则登录失败。如果验证码一致,则根据手机号查询用户信息。如果用户不存在,则创建一个新的用户账号,并将用户信息保存到数据库中。无论用户是否存在,都将用户信息保存到 Session 中,以便后续的登录状态校验。
- 校验登录状态: 用户在后续的请求中,通常会通过 Cookie 携带 JSESSIONID 到服务器。服务器根据 JSESSIONID 从 Session 中获取用户信息。如果 Session 中不存在用户信息,则认为用户未登录,进行拦截。如果 Session 中存在用户信息,则将用户信息保存到 ThreadLocal 中,并放行请求。

下面我们来详细讲解如何实现发送短信验证码功能。
1.3 实现发送短信验证码功能
在实现发送短信验证码功能时,主要包含以下步骤:
1.3.1 页面流程
- 用户在前端页面输入手机号,点击“发送验证码”按钮。
- 前端将手机号发送到后端服务器。
- 后端服务器验证手机号的格式是否正确。
- 如果手机号格式不正确,返回错误信息给前端。
- 如果手机号格式正确,生成一个随机验证码。
- 将验证码保存到 Session 中。
- 调用短信服务,将验证码发送到用户手机。
- 后端返回成功信息给前端。

1.3.2 具体代码
@Override
public Result sendCode(String phone, HttpSession session) {
// 1.校验手机号
if (RegexUtils.isPhoneInvalid(phone)) {
// 2.如果不符合,返回错误信息
return Result.fail(MessageConstants.INVALID_PHONE);
}
// 3.符合,生成验证码
String code = RandomUtil.randomNumbers(6);
// 4.保存验证码到 session
session.setAttribute("code",code);
// 5.发送验证码
log.debug("发送短信验证码成功,验证码:{}", code);
// 返回ok
return Result.ok();
}
代码解释:
RegexUtils.isPhoneInvalid(phone)
: 用于校验手机号格式是否正确。RandomUtil.randomNumbers(6)
: 生成一个 6 位随机数字的验证码。session.setAttribute("code",code)
: 将验证码保存到 Session 中,key 为 "code"。
@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {
// 1. 检验手机号
String phone = loginForm.getPhone();
if (RegexUtils.isPhoneInvalid(phone)) {
// 2. 如果不符合,返回错误信息
return Result.fail(MessageConstants.INVALID_PHONE);
}
// 3. 检验验证码
Object cacheCode = session.getAttribute("code");
String code = loginForm.getCode();
if (cacheCode == null || !cacheCode.toString().equals(code)) {
return Result.fail(MessageConstants.ERROR_CODE);
}
User user = query().eq("phone", phone).one();
// 4. 判断用户是否存在
if (user == null) {
user = createUserWithPhone(phone);
}
// 5. 保存用户信息到 session
session.setAttribute("user", user);
return Result.ok();
}
private User createUserWithPhone(String phone) {
User user = new User();
user.setPhone(phone);
user.setNickName(SystemConstants.USER_NICK_NAME_PREFIX + RandomUtil.randomString(8));
save(user);
return user;
}
代码解释:
query().eq("phone", phone).one()
: 根据手机号查询用户信息,使用了 MyBatis-Plus 框架。createUserWithPhone(phone)
:如果用户不存在,则创建一个新的用户账号,并将用户信息保存到数据库中。session.setAttribute("user",user)
: 将用户信息保存到 Session 中,key 为 "user"。
实现了发送短信验证码和登录功能后,我们需要考虑如何实现登录拦截功能,保证只有登录用户才能访问特定资源。
1.4 实现登录拦截功能
Tomcat 作为 Web 服务器,其运行原理可以概括如下:
- 监听端口: Tomcat 启动时,会创建一个监听线程,监听指定的端口号(例如,8080)。
- 建立连接: 当客户端发起请求时,监听线程会创建一个 Socket 连接,用于客户端和服务器之间的数据传输。Socket 都是成对出现的,用户通过 Socket 像互相传递数据
- 线程池处理: 监听线程从 Tomcat 的线程池中取出一个线程,处理客户端的请求。
- 请求转发: 线程根据请求的 URL,将请求转发到对应的 Web 应用(WAR 包)中的 Servlet 或 Controller。
- 业务处理: Servlet 或 Controller 调用 Service 和 DAO 层,执行具体的业务逻辑,例如,访问数据库。
- 响应返回: 业务处理完成后,将响应数据返回给客户端。
- 关闭连接: Tomcat 将数据写回到用户端的 Socket,完成请求和响应
由于每个用户请求都由独立的线程处理,因此可以使用 ThreadLocal 来实现线程隔离,存储每个用户的登录信息。
ThreadLocal 原理
ThreadLocal 提供了线程局部变量,每个线程都拥有自己独立的变量副本。ThreadLocal 的 put()
和 get()
方法都首先获取当前线程,然后从线程的 ThreadLocalMap
中获取或设置变量的值。由于每个线程都有自己的 ThreadLocalMap
,因此可以实现线程隔离。
登陆拦截流程图如下:

public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//1.获取session
HttpSession session = request.getSession();
//2.获取session中的用户
Object user = session.getAttribute("user");
//3.判断用户是否存在
if(user == null){
//4.不存在,拦截,返回401状态码
response.setStatus(401);
return false;
}
//5.存在,保存用户信息到Threadlocal
UserHolder.saveUser((User)user);
//6.放行
return true;
}
}
代码解释:
preHandle()
方法在请求处理之前被调用。request.getSession()
: 获取 Session 对象。session.getAttribute("user")
: 从 Session 中获取用户信息。- 如果 Session 中不存在用户信息,则设置 HTTP 状态码为 401(Unauthorized),并返回
false
,表示拦截请求。 - 如果 Session 中存在用户信息,则将用户信息保存到 ThreadLocal 中,并返回
true
,表示放行请求。
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 登录拦截器
registry.addInterceptor(new LoginInterceptor())
.excludePathPatterns(
"/shop/**",
"/voucher/**",
"/shop-type/**",
"/upload/**",
"/blog/hot",
"/user/code",
"/user/login"
);
}
}
代码解释:
WebMvcConfigurer
接口用于配置 Spring MVC。addInterceptors()
方法用于添加拦截器。registry.addInterceptor(new LoginInterceptor())
: 添加登录拦截器。.excludePathPatterns(...)
: 指定不需要拦截的 URL 模式。
@GetMapping("/me")
public Result me(){
// 获取当前登录的用户并返回
UserDTO user = UserHolder.getUser();
return Result.ok(user);
}
代码解释:
- 登陆成功后,ThreadLocal 中即已经放入
user
,取出后返回给前端即可。
配置好拦截器后,我们需要考虑如何隐藏用户的敏感信息,防止泄露。
1.5 隐藏用户敏感信息
为了防止用户敏感信息泄露,例如密码、手机号等,在返回用户信息之前,需要将这些敏感信息进行隐藏。一种常见的做法是创建一个 UserDTO
(Data Transfer Object),该对象只包含需要返回给客户端的信息,不包含敏感信息。
login
@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {
// 1. 检验手机号
String phone = loginForm.getPhone();
if (RegexUtils.isPhoneInvalid(phone)) {
// 2. 如果不符合,返回错误信息
return Result.fail(SystemConstants.INVALID_PHONE);
}
// 3. 检验验证码
Object cacheCode = session.getAttribute("code");
String code = loginForm.getCode();
if (cacheCode == null || !cacheCode.toString().equals(code)) {
return Result.fail(SystemConstants.ERROR_CODE);
}
User user = query().eq("phone", phone).one();
// 4. 判断用户是否存在
if (user == null) {
user = createUserWithPhone(phone);
}
// 5. 保存用户信息到 session
session.setAttribute("user", user);
session.setAttribute("user", BeanUtils.copyProperties(user,UserDTO.class));
return Result.ok();
}
preHandle
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//1.获取session
HttpSession session = request.getSession();
//2.获取session中的用户
Object user = session.getAttribute("user");
//3.判断用户是否存在
if(user == null){
//4.不存在,拦截,返回401状态码
response.setStatus(401);
return false;
}
//5.存在,保存用户信息到Threadlocal
UserHolder.saveUser((User)user);
UserHolder.saveUser((UserDTO) user);
//6.放行
return true;
}
}
UserHolder
public class UserHolder {
private static final ThreadLocal<UserDTO> tl = new ThreadLocal<>();
public static void saveUser(UserDTO user){ // [!code word:UserDTO
tl.set(user);
}
public static UserDTO getUser(){ // [!code word:UserDTO
return tl.get();
}
public static void removeUser(){
tl.remove();
}
}
代码解释:
- 修改 UserHolder 中存储的用户信息类型为 UserDTO。
隐藏用户敏感信息后,我们需要考虑 Session 共享问题,确保在集群环境下,用户在不同服务器之间切换时,登录状态仍然有效。
1.6 Session 共享问题
在集群环境下,每个 Tomcat 服务器都拥有自己的 Session 存储空间。如果用户第一次访问第一台 Tomcat 服务器,并将用户信息保存到该服务器的 Session 中,当用户第二次访问第二台 Tomcat 服务器时,第二台服务器无法获取到第一台服务器 Session 中的用户信息,导致登录状态失效。
早期的解决方案是 Session 拷贝,即每当任意一台服务器的 Session 数据发生变化时,都将数据同步到其他所有服务器。但是,这种方案存在以下问题:
- 服务器压力过大: 每台服务器都需要存储完整的 Session 数据,占用大量内存空间。
- 数据同步延迟: Session 数据在服务器之间拷贝时,可能存在延迟,导致数据不一致。
为了解决这些问题,通常采用基于 Redis 的 Session 共享方案。将 Session 数据存储到 Redis 中,由于 Redis 数据是共享的,因此可以避免 Session 共享问题。

接下来,我们将讨论如何使用 Redis 代替 Session,实现登录流程。
1.7 Redis 代替 Session 的业务流程
使用 Redis 代替 Session 实现登录流程,主要包含以下几个步骤:
1.7.1 设计 key 的结构
首先,我们需要选择 Redis 的数据结构来存储用户信息。常用的选择有两种:
- String: 将用户信息序列化成字符串,存储在 String 类型的 value 中。
KEY | VALUE |
---|---|
heima:user:1 | "{name:"Jack", age:21}" |
heima:user:2 | "{name:"Rose", age:18}" |
- Hash: 将用户信息的各个字段分别存储在 Hash 类型的 field 中。
KEY | VALUE | VALUE |
---|---|---|
KEY | field | value |
heima:user:1 | name | Jack |
heima:user:1 | age | 21 |
heima:user:2 | name | Rose |
heima:user:2 | age | 18 |
1.7.2 设计 key 的具体细节
在使用 Redis 时,我们需要设计 key 的命名规则。key 需要满足以下两个条件:
- 唯一性: 每个用户的 key 必须是唯一的。
- 方便携带: key 需要能够方便地在客户端和服务器之间传递。
因此,在发送短信验证码阶段,可将 key 设为手机号。在客户端发起获取验证码的请求时,只需要提交手机号;服务端可直接用这个手机号从 Redis 中取/存验证码。
但是,在检验登陆状态时,我们需要一个存在本地的 key 发送到服务端,以判断是否需要重新登陆。如果一直用手机号做 key,用户在每次携带 key(比如 Cookie、HTTP header 或者请求参数)来登陆验证时,可能会将手机号暴露出来。因此我们可将 key 换成一个随机生成的 token 。即使 token 泄露,也只是一次性凭证;我们可以很快作废它,而不必动到用户的手机号或其他敏感信息。
1.7.3 整体访问流程
用户登录时,服务器验证手机号和验证码是否一致。
如果验证成功,则根据手机号查询用户信息。如果用户不存在,则创建一个新的用户。
将用户信息存储到 Redis 中,并生成一个 Token 作为 Redis 的 key。
客户端在后续的请求中携带 Token。
服务器接收到请求后,从 Redis 中根据 Token 获取用户信息。
如果 Redis 中不存在该 Token 对应的用户信息,则拦截请求。
如果 Redis 中存在该 Token 对应的用户信息,则将用户信息保存到 ThreadLocal 中,并放行请求。
接下来,我们将通过代码示例,演示如何基于 Redis 实现短信登录。
1.8 基于 Redis 实现短信登录
1.8.1 修改发送短信验证码
@Resource
private StringRedisTemplate stringRedisTemplate;
@Override
public Result sendCode(String phone, HttpSession session) {
// 1.校验手机号
if (RegexUtils.isPhoneInvalid(phone)) {
// 2.如果不符合,返回错误信息
return Result.fail(MessageConstants.INVALID_PHONE);
}
// 3.符合,生成验证码
String code = RandomUtil.randomNumbers(6);
// 4.保存验证码到 redis
session.setAttribute("code",code);
stringRedisTemplate.opsForValue().set(
RedisConstants.LOGIN_CODE_KEY + phone, code,
RedisConstants.LOGIN_CODE_TTL, TimeUnit.MINUTES
);
// 5.发送验证码
log.debug("发送短信验证码成功,验证码:{}", code);
// 返回ok
return Result.ok();
}
代码解释:
- 引入
StringRedisTemplate
来操作 Redis。 - 将验证码保存在 Redis 中,键为
RedisConstants.LOGIN_CODE_KEY + phone
,值为验证码,并设置过期时间为RedisConstants.LOGIN_CODE_TTL
分钟。
@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {
// 1. 检验手机号
String phone = loginForm.getPhone();
if (RegexUtils.isPhoneInvalid(phone)) {
// 2. 如果不符合,返回错误信息
return Result.fail(MessageConstants.INVALID_PHONE);
}
// 3. 检验验证码
Object cacheCode = session.getAttribute("code");
String cacheCode = stringRedisTemplate.opsForValue().get(RedisConstants.LOGIN_CODE_KEY + phone);
String code = loginForm.getCode();
if (cacheCode == null || !cacheCode.toString().equals(code)) {
if (cacheCode == null || !cacheCode.equals(code)) {
return Result.fail(MessageConstants.ERROR_CODE);
}
User user = query().eq("phone", phone).one();
// 4. 判断用户是否存在
if (user == null) {
user = createUserWithPhone(phone);
}
// 5. 保存用户信息到 session
session.setAttribute("user", user);
// 5. 保存用户信息到 redis
// 5.1 随机生成 token
String token = UUID.randomUUID().toString(true);
// 5.2 将 User 对象转成 HashMap
UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(),
CopyOptions.create()
.setIgnoreNullValue(true)
.setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString()));
// 5.3 存储
String key = RedisConstants.LOGIN_USER_KEY + token;
stringRedisTemplate.opsForHash().putAll(key, userMap);
// 5.4 设置 token 有效期
stringRedisTemplate.expire(key, RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);
// 6 返回 token
return Result.ok();
return Result.ok(token);
}
代码解释:
- 用户认证通过后,不再将用户信息存储到
HttpSession
中,而是:- 生成一个随机的 token (UUID)。
- 将
User
对象转换为UserDTO
,然后将UserDTO
转换为 HashMap (这样可以更灵活地存储和读取用户信息,并避免序列化问题)。 - 将用户信息 (HashMap) 存储到 Redis 中,键为
RedisConstants.LOGIN_USER_KEY + token
。 - 设置 token 的过期时间为
RedisConstants.LOGIN_USER_TTL
分钟。
- 返回生成的 token。
1.8.2 修改检验登陆状态
public class LoginInterceptor implements HandlerInterceptor {
private StringRedisTemplate stringRedisTemplate;
public LoginInterceptor(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 1.获取session
HttpSession session = request.getSession();
// 2.获取session中的用户
Object user = session.getAttribute("user");
// 3.判断用户是否存在
if(user == null){
//4.不存在,拦截,返回401状态码
response.setStatus(401);
return false;
}
// 5.存在,保存用户信息到Threadlocal
UserHolder.saveUser((User)user);
// 1. 获取 token
String token = request.getHeader("authorization");
if (StrUtil.isBlank(token)) {
response.setStatus(401);
return false;
}
// 2. 基于 token 获取 user
String key = RedisConstants.LOGIN_USER_KEY + token;
Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key);
// 3. 判断 user 是否存在
if (userMap.isEmpty()) {
// 4. 不存在则返回错误码,并拦截
response.setStatus(401);
return false;
}
// 5. 将查询到的 HashMap 转换成 UserDTO
UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
// 6. 存在,保存用户信息到 ThreadLocal
UserHolder.saveUser(userDTO);
// 7. 刷新 token 有效期
stringRedisTemplate.expire(key, RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);
// 8. 放行
return true;
}
}
代码解释:
- 通过构造函数接收
StringRedisTemplate
实例,以便与 Redis 交互。 - 从请求头 (Authorization) 中获取
token
。 - 如果
token
为空,则拦截请求,返回 401 状态码。 - 根据
token
从 Redis 中获取用户信息。 - 如果 Redis 中不存在该
token
对应的用户信息,则拦截请求,返回 401 状态码。 - 将从 Redis 获取的
userMap
转换为UserDTO
对象。 - 将
userDTO
保存到 ThreadLocal 中。 - 刷新
token
在 Redis 中的过期时间,保持用户登录状态。
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Resource
private StringRedisTemplate stringRedisTemplate;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 登录拦截器
registry.addInterceptor(new LoginInterceptor())
registry.addInterceptor(new LoginInterceptor(stringRedisTemplate))
.excludePathPatterns(
"/shop/**",
"/voucher/**",
"/shop-type/**",
"/upload/**",
"/blog/hot",
"/user/code",
"/user/login"
);
}
}
代码解释:
- 将
StringRedisTemplate
注入到LoginInterceptor
中,使得拦截器可以正常访问 Redis。这体现了 Spring 的依赖注入原则。
实现了基于 Redis 的短信登录后,我们需要解决登录状态刷新问题,确保用户在一段时间内保持登录状态。
1.9 解决状态登录刷新问题
1.9.1 初始方案思路总结
一个常见的方案是使用拦截器,拦截特定的 URL 路径,并在拦截器中刷新 Token 的有效期。但是,这种方案存在一个问题:如果用户访问了不需要拦截的 URL 路径,那么拦截器就不会生效,导致 Token 的有效期无法刷新。

1.9.2 优化方案
为了解决上述问题,可以采用以下优化方案:
- 创建一个拦截器,拦截所有的 URL 路径。
- 在拦截器中,首先从 Redis 中根据 Token 获取用户信息。
- 如果 Redis 中存在该 Token 对应的用户信息,则刷新 Token 的有效期,并将用户信息保存到 ThreadLocal 中。
- 如果 Redis 中不存在该 Token 对应的用户信息,则放行请求,交由后续的拦截器处理。
- 创建第二个拦截器,该拦截器只负责判断 ThreadLocal 中是否存在用户信息,如果不存在,则拦截请求。

token
拦截器public class RefreshTokenInterceptor implements HandlerInterceptor {
private StringRedisTemplate stringRedisTemplate;
public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 1.获取请求头中的token
String token = request.getHeader("authorization");
if (StrUtil.isBlank(token)) {
return true;
}
// 2.基于TOKEN获取redis中的用户
String key = LOGIN_USER_KEY + token;
Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key);
// 3.判断用户是否存在
if (userMap.isEmpty()) {
return true;
}
// 5.将查询到的hash数据转为UserDTO
UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
// 6.存在,保存用户信息到 ThreadLocal
UserHolder.saveUser(userDTO);
// 7.刷新token有效期
stringRedisTemplate.expire(key, LOGIN_USER_TTL, TimeUnit.MINUTES);
// 8.放行
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 移除用户
UserHolder.removeUser();
}
}
代码解释:
- 当
ThreadLocal
中含有用户信息时,刷新token
,其他情况全部放行。
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 1.判断是否需要拦截(ThreadLocal中是否有用户)
if (UserHolder.getUser() == null) {
// 没有,需要拦截,设置状态码
response.setStatus(401);
// 拦截
return false;
}
return true;
}
}
代码解释:
- 如果 ThreadLocal 中不存在用户信息,则设置 HTTP 状态码为 401,并返回
false
,表示拦截请求。
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Resource
private StringRedisTemplate stringRedisTemplate;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 登陆拦截器
registry.addInterceptor(new LoginInterceptor(stringRedisTemplate))
registry.addInterceptor(new LoginInterceptor())
.excludePathPatterns(
"/shop/**",
"/voucher/**",
"/shop-type/**",
"/upload/**",
"/blog/hot",
"/user/code",
"/user/login"
);
).order(1);
// token 拦截器
registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).addPathPatterns("/**").order(0);
}
}
代码解释:
.order(1)
: 设置拦截器的执行顺序,数字越小,优先级越高。
通过以上优化方案,可以有效地解决登录状态刷新问题,确保用户在一段时间内保持登录状态。