server:
port: 8081
spring:
application:
name: hmdp
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/hmdb?useSSL=false&serverTimezone=UTC
username: root
password: 123456
redis:
host: red2333d.com
port: 6379
password: 123456
lettuce:
pool:
max-active: 10
max-idle: 10
min-idle: 1
time-between-eviction-runs: 10s
jackson:
default-property-inclusion: non_null # 如果对象中有null值,则不会序列化该字段 节省redis内存
mybatis-plus:
type-aliases-package: com.hmdp.entity # 别名扫描包
logging:
level:
com.hmdp: debug
项目结构
扩展之后
访问 localhost:8081/shop-type/list
启动前端
基于Session实现登录
注册登录一步完成
发送验证码
输入手机号,点击发送验证码->往后端发送请求
关于发送短信
短信验证码登录
实现登录校验拦截器
写拦截器
ThreadLocal工具类
public class UserHolder { private static final ThreadLocal<User> tl = new ThreadLocal<>(); public static void saveUser(User user){ tl.set(user); } public static User getUser(){ return tl.get(); } public static void removeUser(){ tl.remove(); } }
注册拦截器
用户信息接口
隐藏用户敏感信息
用户信息中有些信息是用不到的,而且Session中存入太多东西影响内存空间
因此在放入session时候,可以去掉一些不需要的东西
只需要UserDTO中必要属性即可
关于集群Session共享问题
Redis代替session的业务流程
前端将Token保存在浏览器
然后ajax发送请求都会检查浏览器缓存中有没有Token 有就会在请求中携带
修改验证码 登录等逻辑
修改验证码逻辑
修改登录逻辑
但是这样存在一个问题,就是Token持续刷新的问题,当用一直访问不需要登录的页面时候,也就不会经过我们的拦截器,导致token不能持续刷新(因为我们这个拦截器排除了一些路径)
那么好点的做法是,额外加一个拦截器,拦截一切路径,目的是为了做token的刷新 即使没有也放行,有就刷新token,注意,这个拦截器的目的就是为了刷新Token携带Token的话,就刷新,没有照常放行
拦截器1
package com.hmdp.interceptor;
import cn.hutool.core.bean.BeanUtil;
import com.hmdp.dto.UserDTO;
import com.hmdp.utils.RedisConstants;
import com.hmdp.utils.UserHolder;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* @author hrui
* @date 2025/1/26 5:50
*/
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.获取session中的用户
// Object user = request.getSession().getAttribute("user");
// //2.判断用户是否存在
// if(user == null){
// //3.不存在,拦截
// response.setStatus(401);
// return false;
// }
// //4.存在,保存用户信息到ThreadLocal 放行
// UserHolder.saveUser((UserDTO) user);
// return true;
String authorization = request.getHeader("authorization");
if(StringUtils.isEmpty(authorization)){
return true;
}
//取redis中的用户信息
String key = RedisConstants.LOGIN_USER_KEY + authorization;
Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key);
if(userMap.isEmpty()){
return true;
}
UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
UserHolder.saveUser(userDTO);
//刷新token有效期
stringRedisTemplate.expire(key, RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("第一个拦截器");
UserHolder.removeUser();
}
}
拦截器2
package com.hmdp.interceptor;
import cn.hutool.core.bean.BeanUtil;
import com.hmdp.dto.UserDTO;
import com.hmdp.entity.User;
import com.hmdp.utils.JwtUtil;
import com.hmdp.utils.RedisConstants;
import com.hmdp.utils.UserHolder;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* @author hrui
* @date 2025/1/26 5:50
*/
public class WebInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if(UserHolder.getUser()==null){
response.setStatus(401);
return false;
}
//有则放行
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("第二个拦截器");
UserHolder.removeUser();
}
}
注册拦截器
package com.hmdp.config;
import com.hmdp.interceptor.RefreshTokenInterceptor;
import com.hmdp.interceptor.WebInterceptor;
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 hrui
* @date 2025/1/26 6:13
*/
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new WebInterceptor())
.addPathPatterns("/**")
.excludePathPatterns(//排除路径
"/user/code",
"/user/login",
"/blog/hot",
"/shop/**",
"/shop-type/**",
"/voucher/**"
).order(1);
//默认就是拦截所有请求
registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate))
.order(0);
}
}