为了处理强制登陆问题,我们使用了session机制存储登陆状态
这里我们学习更简单的拦截方法拦截器,统⼀拦截所有的请求,并进行Session校验
拦截器是Spring框架提供的核心功能之一,主要用来拦截用户的请求,在指定方法前后,根据业务需要执行预先设定的代码
也就是说,允许开发人员提前预定义一些逻辑,在用户的请求响应前后执行,也可以在用户请求前阻止 其执行. 在拦截器当中,开发人员可以在应用程序中做一些通用性的操作,比如通过拦截器来拦截前端发来的 请求,判断Session中是否有登录用户的信息,如果有就可以放行,如果没有就进行拦截

比如我们去银行办理业务,在办理业务前后,就可以加一些拦截操作 办理业务之前,先取号,如果带身份证了就取号成功 业务办理结束,给业务办理人员的服务进行评价。 这些就是”拦截器”做的工作。
使用步骤:
自定义拦截器:实现Handlerlnterceptor接口,并重写其所有方法
@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("LoginInterceptor 目标方法执行前执行..");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("执行postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("执行afterCompletion");
}注册配置拦截器:实现WebMvcConfigurer接口,并重写addInterceptors方法
可以注入loginInterceptor,也可以创建对象
//配置信息
@Configuration
public class WebConfig implements WebMvcConfigurer {
// @Autowired
// private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**")//对那些路径生效
.excludePathPatterns("/user/login");//排除掉哪些路径
}
}拦截路径是指我们定义的这个拦截器,对哪些请求生效
我们在注册配置拦截器的时候,通过addPathPatterns()方法指定要拦截哪些请求.也可以通过excludePathPatterns()指定不拦截哪些请求.
上述代码中,我们配置的是/**,表示拦截所有的请求。
比如用户登录校验,我们希望可以对除了登录之外所有的路径生效。
//配置信息
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**")//对哪些路径生效
.excludePathPatterns("/user/login");//排除掉哪些路径
}
}在拦截器中除了可以设置/**拦截所有资源外,还有一些常见拦截路径设置:
拦截路径 | 含义 | 举例 |
|---|---|---|
/* | 一级路径 | 能匹配/user,/book,/login,不能匹配 /user/login |
/** | 任意级路径 | 能匹配/user,/user/login,/user/reg |
/book/* | /book下的一级路径 | 能匹配/book/addBook,不能匹配/book/addBook/1,/book |
/book/** | /book下的任意级路径 | 能匹配/book,/book/addBook,/book/addBook/2,不能匹配/user/login |
以上拦截规则可以拦截此项目中的使用URL,包括静态文件(图片文件,JS和CSS等文件)

从session中获取用户信息,如果session中不存在,则返回false,并设置http状态码为401,否则返回true.
@Slf4j
//@Component
public class LoginInterceptor implements HandlerInterceptor {
//@Autowired
private ObjectMapper objectMapper = new ObjectMapper() ;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("登录拦截");
// 获取当前会话(不创建新会话)
HttpSession session = request.getSession(false);
if (session == null || session.getAttribute(Constants.SESSION_USER_KEY) == null ||
!StringUtils.hasLength((String) session.getAttribute(Constants.SESSION_USER_KEY))) {
//用户未登录
log.error("用户未登录,进行拦截");
response.setStatus(HttpStatus.UNAUTHORIZED.value());
Result result = Result.unlogin();
// 将响应体转为 JSON 并写入输出流
response.getOutputStream().write(objectMapper.writeValueAsString(result).getBytes());
response.setContentType("application/json;charset=utf-8");
return false;// 拦截请求,不进入 Controller
}
log.info("用户校验通过");
return true;// 放行请求,继续执行 Controller 方法
}
}登陆失败进行拦截:


登陆成功放行:


//配置信息
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**")//对那些路径生效
.excludePathPatterns("/user/login");//排除掉哪些路径
}
}统一的数据返回格式使用@ControllerAdvice和ResponseBodyAdvice的方式实现
@ControllerAdvice表示控制器通知类
添加类ResponseAdvice实现ResponseBodyAdvice接口,并在类上添加@ControllerAdvice注解
将所有接口返回值统一包装为 Result 格式
统一所有接口的返回格式,将任意类型的返回值(如实体对象、集合、字符串等)包装为 Result 类型(包含状态码、消息、数据的标准化结构),同时兼容已手动包装的 Result 对象和特殊的 String 类型,避免格式混乱或类型转换异常。
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice<Object> {
@Autowired
private ObjectMapper objectMapper;
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
@SneakyThrows
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
if (body instanceof Result) {
return body;
}
if (body instanceof String) {
return objectMapper.writeValueAsString(Result.sucess(body));
}
return Result.sucess(body);
}
}body 进行统一包装:
添加统一数据返回格式前后对比
body 已是 Result 类型(手动包装的响应),直接返回,避免重复包装;
body 是 String 类型:因 Spring 对 String 默认使用 StringHttpMessageConverter 处理,直接返回 Result 会导致类型不匹配,故通过 objectMapper 将 Result 序列化为 JSON 字符串;
User、List<User> 等):统一用 Result.success(body) 包装为标准化格式(包含状态码、消息、数据)。

统一异常处理使用的是@ControllerAdvice+@ExceptionHandler来实现的,
@ControllerAdvice表示控制器通知类,@ExceptionHandler是异常处理器,
两个结合表示当出现异常的时候执行某个通知,也就是执行某个方法事件
通过 @ControllerAdvice 和 @ExceptionHandler 注解捕获应用中抛出的各类异常,统一返回标准化响应
@Slf4j
@ControllerAdvice
@ResponseBody
public class ExceptionAdvice {
@ExceptionHandler
public Object handler(Exception e){
log.error("发生异常, e: ", e);
return Result.fail();
}
// @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler
public Object handler(ArrayIndexOutOfBoundsException e){
log.error("发生异常, e:{} ", e.getMessage());
return Result.fail("发生数组越界异常");
}
@ExceptionHandler
public Object handler(NullPointerException e){
log.error("发生异常, e: ", e);
return Result.fail("发生NullPointerException 异常");
}
@ResponseStatus(HttpStatus.NOT_FOUND)
@ExceptionHandler
public Object handler(NoResourceFoundException e){
log.error("发生异常, e: {}, path:{}", e.getDetailMessageCode(), e.getResourcePath());
return Result.fail("发生NoResourceFoundException 异常");
}
}类名,方法名和返回值可以自定义
接口返回为数据时,需要加@ResponseBody注解