在进行Springboot项目开发的时候如何把每次请求都要验证的用户进行提取拦截统一处理
背景
如果不进行统一的拦截处理,其实这是一个非常痛苦的一件事情,因为每次用户请求你都要去进行用户的信息(用户信息存储在session中)的验证,代码重复,所以在本篇提供一个解决方案:
定义一个拦截器,把请求都进行统一的处理,如果Session中存在用户的信息那么就放行;如果不存在,那么就直接出现异常报错未登录。在这样的一个方案中其实还存在着一个问题,在业务逻辑中我要去获取用户的信息,那不又是很麻烦了?这里可以通过ThreadLocal解决。
为什么用ThreadLocal:当用户发起请求时,会访问我们像tomcat注册的端口,任何程序想要运行,都需要有一个线程对当前端口号进行监听,tomcat也不例外,当监听线程知道用户想要和tomcat连接连接时,那会由监听线程创建socket连接,socket都是成对出现的,用户通过socket像互相传递数据,当tomcat端的socket接受到数据后,此时监听线程会从tomcat的线程池中取出一个线程执行用户请求,在我们的服务部署到tomcat后,线程会找到用户想要访问的工程,然后用这个线程转发到工程中的controller,service,dao中,并且访问对应的DB,在用户执行完请求后,再统一返回,再找到tomcat端的socket,再将数据写回到用户端的socket,完成请求和响应通过以上讲解,我们可以得知 每个用户其实对应都是去找tomcat线程池中的一个线程来完成工作的, 使用完成后再进行回收,既然每个请求都是独立的,所以在每个用户去访问我们的工程时,我们可以使用threadlocal来做到线程隔离,每个线程操作自己的一份数据
定义一个ThreadLocal线程工具类
便于对线程内部的值进行处理。
public class UserHolder {
public static final ThreadLocal<User> userThreadLocal = new ThreadLocal<>();
public static void setValue(User user){
userThreadLocal.set(user);
}
public static User getValue(){
return userThreadLocal.get();
}
public static void clear(){
userThreadLocal.remove();
}
}
定义拦截器
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
// 获取 session
HttpSession session = request.getSession();
// 检查用户是否已登录
if (session.getAttribute("user") == null) {
// 用户未登录,进行相关处理
throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR);
}
// 从 session 中获取用户数据
User user = (User) session.getAttribute("user");
// 将用户数据存储到 ThreadLocal 中,以便在整个请求周期内访问
UserHolder.setValue(user);
// 进行其他逻辑验证,根据需求自行添加
return true; // 允许请求继续执行
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
// 在请求处理之后执行,可以对 ModelAndView 进行修改
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
// 在请求完成之后执行,用于资源清理等操作
// 清理 ThreadLocal 中的用户数据,防止内存泄漏
UserHolder.clear();
}
}
让拦截器生效
通过配置让拦截器生效
@Configuration
public class MvcConfig implements WebMvcConfigurer {
public void addInterceptors(InterceptorRegistry registry) {
// 添加拦截器
// excludePath 就是排除在外被拦截的路径
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**")
.excludePathPatterns(
"/user/login"
);
}
}
然后就可以啦
测试
测试Controller
@RestController
@RequestMapping("/user")
public class UserController {
@Resource
private UserService userService;
@PostMapping("/login")
public BaseResponse<User> login(HttpSession session){
System.out.println("Hello");
Random random = new Random(new Date().getTime()) ;
User user = new User(random.nextLong(), "123","123454","123");
session.setAttribute("user",user);
return ResultUtils.success(user);
}
@GetMapping("/get")
public BaseResponse<User> get(){
return ResultUtils.success(UserHolder.getValue());
}
}
测试结果
开始没有登录
进行登录
再次获取get请求就可以了
这里我出现一个问题我一直调试了很久,md,就是在配置拦截器的时候添加路径首先把所有路径进行拦截,然后放行/user/login就好,我的这个项目在配置文件中给所有的路径首先加了一个/api这样的前缀,然后我在拦截路径的时候都加了api,这个其实是不用加的,直接上路径就好了,spring自动会加。