项目整体介绍
项目功能介绍
项目结构
该项目前后端分离架构模式,后端部署在Tomcat服务器,前端部署在Niginx服务器上,这也是现在企业开发的标准做法。PC端首先向Niginx发起请求,得到页面的静态资源,页面再通过ajax向服务端发起请求查询数据。这些数据可能来自Mysql或者Redis集群。再把查询到的数据返回给前端,前端完成渲染。
当然该项目也会考虑水平扩展能力,在单个Tomcat服务器无法承载时,水平扩展多个服务器形成可以负载均衡的集群,在多台Tomcat服务器上部署代码。
短信登录模块
导入黑马点评项目
- 创建hmdp数据库,导入sql文件
表的介绍
- 修改application.yaml文件中redis和mysql数据库的配置
server:
port: 8081
spring:
application:
name: hmdp
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/hmdp?useSSL=false&serverTimezone=UTC
username: root
password: 122045
redis:
host: 192.168.101.65
port: 6379
password: 123321
lettuce:
pool:
max-active: 10
max-idle: 10
min-idle: 1
time-between-eviction-runs: 10s
jackson:
default-property-inclusion: non_null # JSON处理时忽略非空字段
mybatis-plus:
type-aliases-package: com.hmdp.entity # 别名扫描包
logging:
level:
com.hmdp: debug
- 点击+号处,将项目配置为一个SpringBoot的项目,启动项目
- 如果配置正确,在浏览器中访问http://localhost:8081/shop-type/list会跳转到如下页面:
- 访问http://localhost:8080/会跳转到前端画面
此时,项目搭建完毕。
基于Session实现登录
业务梳理:
- 左图:当用户请求验证码时需要对用户提交的手机号进行格式校验,如果不符合格式,则需要重新提交。如果符合格式,在本地生成验证码,并将验证码保存到session中,并将验证码发送给用户。
- 中图:用户收到验证码,输入并提交手机号和验证码,首先校验验证码是否正确。如果不正确,用户重新提交。如果正确,再根据手机号查询用户,如果用户存在,将用户保存到session,如果用户不存在,创建新用户并将用户保存到数据库,再将用户保存到session。
- 右图:校验用户是否为收到验证码的用户,cookie中有sessionid,根据这个sessionid找到session并从session中获取用户,判断用户是否存在,如果存在,证明该用户曾经登陆过。因为后续业务会用到该用户的信息,所以将用户缓存到线程的本地存储ThreadLocal中,这样后续的业务就可以从ThreadLocal中获取到用户信息,并放行;如果不存在,进行拦截。
注解:ThreadLocal就是一个线程域对象,每一个请求到达微服务,都是一个独立的线程,如果没有用ThreadLocal,而是直接将用户保存到本地变量,可能会出现多线程并发修改的安全问题,而ThreadLocal会把数据保存到每一个线程的内部,在线程内部创建一个Map去保存,这样每一个线程都有自己独立的存储空间,相互之间没有干扰,规避了多线程并发修改的问题。后续的所有业务都可以从ThreadLocal中取出自己的用户信息,这就是基于Session的登录状态的校验。
发送短信验证码
流程:
- 校验手机格式
- 手机格式错误,返回错误信息
- 手机格式正确,生成验证码
- 将验证码保存到session
- 发送验证码给客户,一般公司都会有现成的服务直接调用,此处模拟即可
- 返回ok
先完善Controller接口,再写service。
package com.hmdp.service.impl;
import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.dto.Result;
import com.hmdp.entity.User;
import com.hmdp.mapper.UserMapper;
import com.hmdp.service.IUserService;
import com.hmdp.utils.RegexUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpSession;
/**
* <p>
* 服务实现类
* </p>
*
* @author 虎哥
* @since 2021-12-22
*/
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Override
public Result sendCode(String phone, HttpSession session) {
//1.校验手机号
boolean phoneInvalid = RegexUtils.isPhoneInvalid(phone);
//2.如果错误,返回错误提示
if(!phoneInvalid){
return Result.fail("手机号不符合格式");
}
//3.如果正确,生成验证码
String code = RandomUtil.randomNumbers(6);
//4.将验证码保存到session中
session.setAttribute("code", code);
//5.向用户发送验证码
log.debug("发送验证码成功,验证码为:{}",code);
return Result.ok();
}
}
短信验证码登录和校验
流程:
- 手机格式验证,错误则返回错误信息
- 校验码验证错误,返回错误信息
- 校验验证码正确,根据手机号查询用户(这里MyBatisPlus帮我们完成了sql查询)
- 如果用户不存在,创建用户并保存到数据库
- 如果用户存在,直接保存到数据库
- 将用户保存到session
- 返回成功提示
代码如下
@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {
//手机格式验证,错误则返回错误信息
if(!RegexUtils.isPhoneInvalid(loginForm.getPhone())){
return Result.fail("手机号不符合格式");
}
//校验码验证错误,返回错误信息
String code = loginForm.getCode();
Object cacheCode = session.getAttribute("code");//取出之前保存的验证码
if(cacheCode == null || !cacheCode.toString().equals(loginForm.getCode())){
return Result.fail("验证码错误");
}
//校验验证码正确,根据手机号查询用户(这里MyBatisPlus帮我们完成了sql查询)
String phone = loginForm.getPhone();
User user = query().eq("phone", phone).one();//这里查一个就是.one(),查多个就是list()
//如果用户不存在,创建用户并保存到数据库
if(user == null){
user = createUserWithPhone(phone);
}
//将用户保存到session
session.setAttribute("user", user);
//返回成功提示
return Result.ok();
}
private User createUserWithPhone(String phone){
User user = new User();
user.setPhone(phone);
//向user中插入一个随机的用户昵称
user.setNickName(USER_NICK_NAME_PREFIX + RandomUtil.randomString(10));
//如果用户存在,直接保存到数据库
save(user);
return user;
}
完成之后,输入后端生成的验证码,发现页面瞬间跳转有退出。因为我们还只是完成了发送验证码、登录以及注册的功能,还没有完成校验功能。
登录校验
存在问题:在项目中有很多的Controller,前端向UserController发送请求完成业务,但是越来越多的业务需要去校验用户的登录,这样太麻烦。那么有什么方便的办法嘛?答案就是拦截器?有了拦截器,用户的请求不会直接到Controller,而是会先到拦截器,先由拦截器判断是否应该放行让其到达Controller,我们可以把所有用户校验的流程放到拦截器里面去完成,这样所有Controller就不用再去写登录校验,而全部由拦截器完成。但是光是拦截也不够,还需要将拦截到的信息传递到Controller里面去,传递的过程中还要注意线程的安全问题吗,可以用ThreadLocal来解决。
ThreadLocal是一个线程域对象,每一个进入到Tomcat服务器的请求都是一个独立的线程,ThreadLocal会开辟一个内存空间保存对应用户,每个用户都有自己的独立线程,到了Controller之后再从ThreadLocal中取出用户就OK了。
因此,我们可以在拦截器中实现右图的功能,在SpringBoot中创建拦截器可以通过实现HandlerInterceptor接口定义,在HandlerInterceptor中有三个可以实现的方法分别是:
- preHandler:前置拦截,用于用户校验,也就是流程图中的内容,在写完拦截的逻辑之后,还需要一个拦截器的配置类用于配置哪些需要拦截,哪些不需要拦截。这个配置类要实现WebMvcConfigurer接口
- 获取用户session
- 获取session中的用户
- 判断用户是否存在
- 不存在,拦截
- 存在,将用户信息保存到ThreadLocal
- 放行
- postHandler:在controller执行之后
- afterCompletion:视图渲染之后,返回用户之前,用于销毁用户信息,避免内存泄露
首先写loginInterceptor的业务逻辑:
package com.hmdp.utils;
import com.hmdp.dto.UserDTO;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
/**
* @author Zonda
* @version 1.0
* @description TODO
* @2024/7/1 23:07
*/
public class loginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//获取用户session
HttpSession session = request.getSession();
//获取session中的用户
Object user = session.getAttribute("name");
//判断用户是否存在
if(user == null){
//不存在,拦截
response.setStatus(401);
return false;
}
//存在,将用户信息保存到ThreadLocal
UserHolder.saveUser((UserDTO) user);
//放行
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//移除用户
UserHolder.removeUser();
}
}
再配置拦截器:
package com.hmdp.config;
import com.hmdp.utils.loginInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @author Zonda
* @version 1.0
* @description TODO
* @2024/7/1 23:18
*/
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
//registry是拦截器的注册器,在这里面配置拦截器
registry.addInterceptor(new loginInterceptor())
.excludePathPatterns(
//不应该被拦截的一些功能
//"/shop/**"指所有shop有关的请求都不用拦截
"/shop/**",
"/voucher/**",
"/shop-type/**",
"/upload/**",
"/blog/hot",
"/user/code",
"/user/login"
);
}
}
完善/user/me接口,在拦截器中我们已经用UserHolder保存了用户,所以只需要从UserHolder中取并返回即可。
@GetMapping("/me")
public Result me(){
// TODO 获取当前登录的用户并返回
UserDTO user = UserHolder.getUser();
return Result.ok(user);
}
关键问题解决
为什么使用拦截器对用户进行校验,并且如果用户已经在Session中,则放行并将用户信息存储到ThreadLocal中,而不是直接使用session中的用户呢?
在这个场景中,使用拦截器对用户进行校验,并且如果用户已经在Session中,则放行并将用户信息存储到ThreadLocal中,这样做的目的可能包括以下几点:
-
性能优化:访问ThreadLocal比访问Session要快,因为ThreadLocal存储在当前线程的栈上,而Session可能需要从服务器的内存或分布式缓存中检索。这样可以减少I/O操作,提高系统响应速度。
-
减少Session的使用:如果用户信息频繁地被访问,每次访问都从Session中读取会增加不必要的I/O操作。通过将用户信息暂存到ThreadLocal,可以在请求处理过程中避免多次访问Session。
-
数据隔离:使用ThreadLocal可以在当前请求的生命周期内保持数据的隔离性,确保不同请求之间不会相互干扰。即使在多线程环境中,每个线程的ThreadLocal是独立的,不会共享数据。
-
简化请求处理:在请求处理过程中,如果需要多次使用用户信息,将用户信息存储在ThreadLocal中可以简化代码逻辑,避免在每次需要时都去Session中查询。
-
临时数据存储:ThreadLocal适合存储请求过程中的临时数据,这些数据在请求结束后就不再需要了。这样可以避免在Session中存储过多临时数据,保持Session的简洁性。
-
安全性:在某些情况下,为了安全考虑,可能不希望用户信息在整个会话期间一直存储在Session中。使用ThreadLocal可以在请求结束后立即清除敏感信息,降低安全风险。
-
控制Session的生命周期:通过将用户信息存储在ThreadLocal,可以在请求结束后立即清除,而不需要依赖Session的超时机制或手动清理,这样可以更精确地控制数据的生命周期。
-
适应性:在某些复杂的应用场景中,可能需要根据请求的不同阶段来动态地存储或更新用户信息。使用ThreadLocal可以更灵活地控制数据的存储和更新。
集群的Session共享问题
session共享问题:多台Tomcat并不共享session存储空间,当请求切换到不同tomcat服务器是导致数据丢失。session的替代方案应该满足:
- 数据共享
- 内存存储(内存读写性能高)
- key-value结构
那么就可以用Redis替代session!!!
基于Redis实现共享session登录
因为用户访问客户端,每次都会获取唯一的session,因此,可以将验证码通过key = “code”,value = 具体验证码字符串的形式存储。但是如果用redis的话,不同用户都访问同一个redis的内存空间,如果再用key = “code”,value = 具体验证码字符串的形式去存的话就会导致不同用户用相同的字符串,会有对应不上的问题。所以我们可以用手机号作为key,实现了唯一性,也利于后期获取验证码验证登录。
用户的保存也可以用redis保存,但是value需要用JSON或者Hash表保存。我们选择用Hash表保存,因为内存占用小,而且修改方便。
在之前的实现中,用于区分用户的是session,每个用户向后端发送请求会携带一个cookie,cookie中的sessionid找到对应的唯一的session,我们从session中可以获取到自己的用户信息。但是如果用redis,没有sessionid这个用户登录凭证了,可以用随机token为key存储用户数据。那么token是保存在后端的,用户在访问的时候也需要携带token去取出value,因此这个token需要被返回给客户端(浏览器)。这样在校验的时候用户携带token访问就可以获取用户信息了。
修改发送短信验证码的逻辑修改,另外要注意验证码要设置有效时间为两分钟,LOGIN_CODE_KEY是业务前缀,用于区分不同业务:
//4.将验证码保存到redis中
stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY+phone, code,LOGIN_CODE_TTL, TimeUnit.MINUTES);
下面要修改user存储的业务,在login方法中,不再是将用户存储到session中,而是以map的形式存储到redis中。key为UUID工具类随机生成的token,value为转为Map形式的UserDTO的对象,这里我们采用BeanUtil.beanToMap()方法进行bean->map的转换。但是!!我们的stringRedisTemplate类要求key和value都是String结构,UserDTO对象中却有一个字段为Long类型,最终我们是以Hash表的形式将对象存入到value中,而hash表中的filed和value都不允许是非String,所以会报错:无法将Long类型转化为String类型。所以需要对BeanUtil.beanToMap()中的CopyOptions中的setFieldValueEdito进行重写,具体如下:
Map<String, Object> beanMap = BeanUtil.beanToMap(userDTO,new HashMap<>(), CopyOptions.create().ignoreNullValue()
.setFieldValueEditor((fieldName,fieldValue)-> fieldValue.toString()));
一定要注意,在Service层中要返回token给前端:return Result.ok(token);!不然访问后端请求被拦截时,没有token会被拦截!
@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {
//手机格式验证,错误则返回错误信息
if(!RegexUtils.isPhoneInvalid(loginForm.getPhone())){
return Result.fail("手机号不符合格式");
}
//校验码验证
String code = loginForm.getCode();
//从redis中获取cacheCode
Object cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY+loginForm.getPhone());
if(cacheCode == null || !cacheCode.toString().equals(code)){
return Result.fail("验证码错误");
}
//校验验证码正确,根据手机号查询用户(这里MyBatisPlus帮我们完成了sql查询)
String phone = loginForm.getPhone();
User user = query().eq("phone", phone).one();//这里查一个就是.one(),查多个就是list()
//如果用户不存在,创建用户并保存到数据库
if(user == null){
user = createUserWithPhone(phone);
}
//将用户保存到redis中
//1.随机生成token作为登录令牌
String token = UUID.randomUUID().toString();
//2.将user对象转成map格式
UserDTO userDTO = new UserDTO();
BeanUtil.copyProperties(user, userDTO);
Map<String, Object> beanMap = BeanUtil.beanToMap(userDTO,new HashMap<>(), CopyOptions.create().ignoreNullValue()
.setFieldValueEditor((fieldName,fieldValue)-> fieldValue.toString()));
//3.将map存入redis中
String tokenKey = LOGIN_USER_KEY + token;
stringRedisTemplate.opsForHash().putAll(tokenKey,beanMap);
//4.设置token的有效期
stringRedisTemplate.expire(tokenKey,LOGIN_USER_TTL,TimeUnit.MINUTES);
//返回token
return Result.ok(token);
}
另外还有一个问题就是,我们token在redis中的有效时长不应该只在第一次登录之后计时30分钟,而是应该在每一次访问被拦截的时候都要增加重新设定为30分钟,因此我们的拦截器中preHandle方法中应该加入对于token有效时长的刷新,这里一定一定要记得,在request中获取token的方法应该是:==String token = request.getHeader(“authorization”);==通过token从redis中取出对应的hash表,再通过:UserDTO userdto = BeanUtil.fillBeanWithMap(map, new UserDTO(),方法将其转化为UserDTO的对象,存入ThreadLocal中,最终刷新token的有效时间!stringRedisTemplate.expire(key, RedisConstants.LOGIN_USER_TTL, TimeUnit.SECONDS);
具体代码如下:
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//获取请求头中的token
String token = request.getHeader("authorization");
//如果不存在token,返回401状态码
if(StringUtils.isBlank(token)){
//不存在,拦截
response.setStatus(401);
return false;
}
String key = LOGIN_USER_KEY + token;
//通过token获取用户信息
Map<Object, Object> map = stringRedisTemplate.opsForHash().entries(key);
//判断用户是否存在
if(map.isEmpty()){
//不存在,拦截
response.setStatus(401);
return false;
}
//将hashmap转为UserDto
UserDTO userdto = BeanUtil.fillBeanWithMap(map, new UserDTO(), false);
//存在,将userdto存入ThreadLocal
UserHolder.saveUser(userdto);
//刷新token的有效期
stringRedisTemplate.expire(key, RedisConstants.LOGIN_USER_TTL, TimeUnit.SECONDS);
//放行
return true;
}
那么既然在拦截器中要从redis中获取到UserDTO对象,就必须要用到StringRedisTemplate类,但是这个loginInterceptor并没有加任何注解,也就是说不在Spring的管辖范围之内,无法通过自动注入的方式定义一个StringRedisTemplate对象,那么就可以通过构造函数的方式传入参数赋值。具体代码如下:
private StringRedisTemplate stringRedisTemplate;
public loginInterceptor(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
在MvcConfig配置类中需要加入loginInterceptor拦截器对象,此时我们可以在配置类中自动注入一个StringRedisTemplate的对象,然后传到loginInterceptor中,这样就解决了loginInterceptor类自身无法自动注入的问题。相当于配置类在创建拦截器的时候给拦截器传入了一个redis操作对象stringRedisTemplate,具体代码如下:
package com.hmdp.config;
import com.hmdp.utils.loginInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @author Zonda
* @version 1.0
* @description TODO
* @2024/7/1 23:18
*/
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Autowired
StringRedisTemplate stringRedisTemplate;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//registry是拦截器的注册器,在这里面配置拦截器
registry.addInterceptor(new loginInterceptor(stringRedisTemplate))
.excludePathPatterns(
//不应该被拦截的一些功能
//"/shop/**"指所有shop有关的请求都不用拦截
"/shop/**",
"/voucher/**",
"/shop-type/**",
"/upload/**",
"/blog/hot",
"/user/code",
"/user/login"
);
}
}
最后一个小缺陷的解决
注:在拦截器中放行就是return true;拦截就是return false;
在我们的 loginInterceptor拦截器中配置类一系列不被拦截校验的请求,这些请求不会执行preHandle 方法,因此也不会更新token,如果用户一直访问的是这些请求的内容,那么token就不会更新,可能一段时间之后,token就失效了。
解决这个问题,我们可以在原有请求的基础上再加一个拦截器。因为 loginInterceptor有一些请求不被拦截,那我们新加的这个拦截器就拦截所有请求。我们在新加的这个拦截器中作刷新token有效期的工作。这样所有的请求都会刷新token有效时长
注意,在第一个拦截器中,对于token和token对应value为空的情况都不要拦截,直接放行,交给下一层去处理拦截的逻辑。而loginInterceptor只用处理是否拦截的逻辑就OK。
新增的拦截器命名为RefreshTokenInterceptor,具体代码如下:
package com.hmdp.utils;
import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.hmdp.dto.UserDTO;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import static com.hmdp.utils.RedisConstants.LOGIN_USER_KEY;
/**
* @author Zonda
* @version 1.0
* @description TODO
* @2024/7/1 23:07
*/
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 {
//获取请求头中的token
String token = request.getHeader("authorization");
if(StringUtils.isBlank(token)){
//如果为空,放行
return true;
}
String key = LOGIN_USER_KEY + token;
//通过token获取用户信息
Map<Object, Object> map = stringRedisTemplate.opsForHash().entries(key);
//判断用户是否存在
if(map.isEmpty()){
//为空直接放行
return true;
}
//将hashmap转为UserDto
UserDTO userdto = BeanUtil.fillBeanWithMap(map, new UserDTO(), false);
//存在,将userdto存入ThreadLocal
UserHolder.saveUser(userdto);
//刷新token的有效期
stringRedisTemplate.expire(key, RedisConstants.LOGIN_USER_TTL, TimeUnit.SECONDS);
//放行
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//移除用户
UserHolder.removeUser();
}
}
另一个拦截器就可以简化为只要ThreadLocal中用户为空,就拦截:
package com.hmdp.utils;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author Zonda
* @version 1.0
* @description TODO
* @2024/7/1 23:07
*/
public class loginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//判断ThreadLocal中是否有用户,没有就拦截
if(UserHolder.getUser() == null){
response.setStatus(401);
return false;
}
//有用户则放行
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
UserHolder.removeUser();
}
}
但是拦截器是有执行的先后顺序的,应该先是RefreshTokenInterceptor,再是loginInterceptor。
两种方式:
- 默认按照添加顺序执行
- 在 registry.addInterceptor().order(1);方法后面加order(1),order中的值越小优先级越高,越大优先级越小。
代码如下:
package com.hmdp.config;
import com.hmdp.utils.RefreshTokenInterceptor;
import com.hmdp.utils.loginInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @author Zonda
* @version 1.0
* @description TODO
* @2024/7/1 23:18
*/
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Autowired
StringRedisTemplate stringRedisTemplate;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//registry是拦截器的注册器,在这里面配置拦截器
registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).order(0);
//registry是拦截器的注册器,在这里面配置拦截器
registry.addInterceptor(new loginInterceptor())
.excludePathPatterns(
//不应该被拦截的一些请求
//"/shop/**"指所有shop有关的请求都不用拦截
"/shop/**",
"/voucher/**",
"/shop-type/**",
"/upload/**",
"/blog/hot",
"/user/code",
"/user/login"
).order(1);
}
}