拦截器 之 用户登录判断

spring boot 拦截器的实现需要有两步:

拦截器

自定义一个拦截器

package com.example.demo.common;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

@Component
public class LoginInterceptor implements HandlerInterceptor { // 继承这个接口表示当前是一个自定义拦截器

    // 生成重写代码,此方法表示在目标方法前调用,返回的是一个 boolean 类型的
    // 返回 true 表示拦截器校验成功,正常执行
    // 返回 false 表示拦截器校验失败,不会往下执行
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 判断用户是否登录
        HttpSession session = request.getSession(false);
        if (session != null && session.getAttribute("session_userinfo") != null) {
            // 说明用户已经登陆
            return true;
        }
        // 用户没登陆就设置一个 401 状态码,不然就是一个空白页面
        response.setStatus(401);
        return false;
    }
}

将拦截器设置到配置项中,并设定拦截规则

package com.example.demo.config;

import com.example.demo.common.LoginInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class LoginConfig implements WebMvcConfigurer { // 使用这个接口中的方法把自定义拦截器添加到项目配置中

    @Autowired
    private LoginInterceptor loginInterceptor;

    // 添加拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)  // registry 是一个注册器,往里面添加自定义的拦截器
                .addPathPatterns("/**")  // 所有的 url 都会进行拦截判断
                .excludePathPatterns("/user/login")  // 设置不进行拦截的 url
                .excludePathPatterns("/user/reg");

    }
}

运行结果:

拦截器实现原理

实现源码

        所有的 Controller 执⾏都会通过⼀个调度器 DispatcherServlet 来实现,而所有⽅法都会执行 DispatcherServlet 中的 doDispatch 调度方法。从源码可以看出在开始执行 Controller 之前,会先调用 预处理方法 applyPreHandle, 在 applyPreHandle 中会获取所有的拦截器 HandlerInterceptor 并执⾏拦截器中的 preHandle ⽅法,这样就会前⾯定义的拦截器对应上了,此时⽤户登录权限的验证⽅法就会执⾏,这就是拦截器的实现原理。

统一异常处理

        在写代码的时候经常会遇到各种错误,但每个地方都写一个异常处理就不太合适,或者直接不管就让程序报错也不合适,相对较好的办法就是:先保证程序不会直接报错,然后再返回一些信息给前端。因此就需要专门创建个处理异常的类。

        统⼀异常处理使⽤的是 @ControllerAdvice + @ExceptionHandler 来实现的,@ControllerAdvice 表示控制器通知类,@ExceptionHandler 是异常处理器,两个结合表示当出现异常的时候执⾏某个通知,也就是执⾏某个⽅法事件,具体实现代码如下:
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.HashMap;

@ControllerAdvice  // 这个注解表示当前类会监控所有的异常,并且根据项目启动而启动
@ResponseBody
public class ResponseAdvice  {  // 继承这个接口表示对数据进行加工(而不是返回一个数据)

    @ExceptionHandler(NullPointerException.class)  // 定义异常的类型,空指针异常
    public Object NullPointerException(NullPointerException e) {
        HashMap<String, Object> result = new HashMap<>();
        result.put("code", -1);
        result.put("msg ", "空指针:" + e.getMessage());
        result.put("data", null);
        return result;
    }

    @ExceptionHandler(Exception.class)  // 默认的异常处理,当具体的异常匹配不到就会执行
    public Object Exception(Exception e) {
        HashMap<String, Object> result = new HashMap<>();
        result.put("code", -1);
        result.put("msg ", "Exception:" + e.getMessage());
        result.put("data", null);
        return result;
    }

}

运行结果:

统一数据返回格式

        主要作用:降低前端和后端的沟通成本、有利于项⽬统⼀数据的维护和修改。
        统⼀的数据返回格式可以使用 @ControllerAdvice + ResponseBodyAdvice 的⽅式实现,具体实现代码如下:
package com.example.demo.common;

import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import java.util.HashMap;

@ControllerAdvice
public class MyResponseBodyAdvice implements ResponseBodyAdvice {  // 继承这个接口表示对数据进行加工(而不是返回一个数据)

    // 是否执行 beforeBodyWrite 方法,true = 执行,重写返回结果
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true;
    }

    // 执行数据的重写的方法,这个是保底的做法,保证不会抛异常,但是重写的返回结果都是千篇一律的
    // 假设如果数据是 HashMap 格式的,那么就认为数据统一了是正确的,否则就重写成 HashMap 的格式
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {

        // 如果原始数据 body 是 HashMap 格式的话
        if (body instanceof HashMap) {
            return body;
        }

        // 不是的话就把原始数据重写成 HashMap 格式
        HashMap<String, Object> result = new HashMap<>();
        result.put("code", 200);
        result.put("msg ", "修改了数据");
        result.put("data", body);  // body 是原始的返回值
        return result;
    }
}

运行结果:

但是当返回值为 String 并且需要重写数据的时候:

        这是因为:1. 当返回的是 String 类型的时候,2. 在统一数据返回之前会先将 String 转换成 HashMap,3. 然后再将 HashMap 转换成 application / json 字符串传给前端。

        出错的原因发生在第 3 步:转换的时候会对原 body 的类型进行判断:1. String 类型,会使用 StringHttpMessageConverter 进行类型转换、2. 非 String 类型,会使用 HttpMessageConverter 进行类型转换。

        因此解决方案就有两种解决方案:

1. 将 StringHttpMessageConverter 从配置文件中去掉

@Configuration
public class MyConfig implements WebMvcConfigurer {
     // 移除 StringHttpMessageConverter
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.removeIf(converter -> converter instanceof StringHttpMessageConverter);
    }
}

2. 在统一数据重写时,单独处理,返回一个 String 字符串而不是 HashMap

// 不是的话就把原始数据重写成 HashMap 格式
        HashMap<String, Object> result = new HashMap<>();
        result.put("code", 200);
        result.put("msg ", "修改了数据");
        result.put("data", body);  // body 是原始的返回值
        if (body instanceof String) {
            // 把对象转成 json 再进行返回
            return objectMapper.writeValueAsString(result);
        }
        return result;

运行结果:

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值