场景重现
当一个boot程序开启拦截器,那么每次拦截请求都需要通过
mysql
查询用户信息,这样会给服务器带来很大的负担,此时可以使用redis
作为中间件,缓存登录信息
优点:
redis
内存读写,速度快
没使用redis
缓存前
使用 redis
缓存后
环境准备
- springboot
- redis
- mysql
springboot 的 application.yaml
如下:
data:
redis:
host: localhost
port: 6379
password: ********
lettuce:
pool:
max-active: 10
max-idle: 10
min-idle: 1
time-between-eviction-runs: 10s
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/xxx?useSSL=false&serverTimezone=UTC
username: hmdp
password: 2fjR8hnYzEh4Rik6
jackson:
default-property-inclusion: non_null # JSON处理时忽略非空字段
mybatis-plus:
type-aliases-package: com.hmdp.entity # 别名扫描包
logging:
level:
com.hmdp: debug
实现步骤
1. 在原本的业务代码中加入如下逻辑代码
@Resource
private StringRedisTemplate stringRedisTemplate;
@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {
// 1. 校验手机号和验证码
.......
// 2. 从Redis中获取验证码并校验
String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);
String code = loginForm.getCode();
if (cacheCode == null || !cacheCode.equals(code)) {
// 不一致,错误
return Result.fail("验证码错误");
}
// 3. 根据手机号查询用户
......
// 5. 存入 redis
// 5.1 生成随机token String token = UUID.randomUUID().toString(true);
// 5.2 将 User 对象转换为DTO
UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(),
CopyOptions.create()
.setIgnoreNullValue(true)
.setFieldValueEditor((s, o) -> o.toString()));
// 5.3 存储
String tokenKey = LOGIN_USER_KEY + token;
stringRedisTemplate.opsForHash().putAll(tokenKey, userMap);
// 5.4 设置过期时间
stringRedisTemplate.expire(tokenKey, LOGIN_USER_TTL, TimeUnit.MINUTES);
return Result.ok(token);
}
2.添加拦截器
public class RefreshInterceptor implements HandlerInterceptor {
private final StringRedisTemplate stringRedisTemplate;
public RefreshInterceptor(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 (StringUtils.isBlank(token)) {
return true;
}
// 2.从TOKEN获取redis中的用户
Map<Object, Object> userMap = stringRedisTemplate.opsForHash()
.entries(RedisConstants.LOGIN_USER_KEY + token);
// 3.判断用户是否存在
if (userMap.isEmpty()) {
return true;
}
// 5.将查询到的Hash数据转换为DTO对象
UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
// 6. 存在,将信息保存到ThreadLocal
UserHolder.saveUser(userDTO);
// 7. 刷新 token 有效期
stringRedisTemplate.expire(RedisConstants.LOGIN_USER_KEY + token, RedisConstants.LOGIN_CODE_TTL, TimeUnit.MINUTES);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 移除用户
UserHolder.removeUser();
}
}
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 1. 判断是否需要拦截
if (UserHolder.getUser() == null) {
// 没有,需要拦截
response.setStatus(401);
return false;
}
// 有用户,放行
return true;
}
}
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Resource
private StringRedisTemplate stringRedisTemplate;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 登录拦截器
registry.addInterceptor(new LoginInterceptor()).excludePathPatterns(
"/shop/**",
"/voucher/**",
"/shop-type/**",
"/upload/**",
"/blog/hot",
"/user/code",
"/user/login"
).order(1);
// 刷新拦截器
registry.addInterceptor(new RefreshInterceptor(stringRedisTemplate)).addPathPatterns("/**").order(0);
}
}