【Java】SpringBoot的统⼀用户登录权限验证、统⼀异常处理与统⼀数据返回格式


Spring Boot 统⼀功能处理也就是前面学习过的AOP 的实战。接下来实现三种统一的功能处理。

统一用户登录权限验证

原来实现一个用户登录权限验证的功能存在一些缺点:
1.每个方法中都要单独写用户登录验证的⽅法,即使封装成公共用法,也⼀样要传参调用和在方法中进行判断。
2. 添加控制器越多,调用用户登录验证的方法也越多,这样就增加了后期的修改成本和维护成本。
3. 这些用用户登录验证的方法和接下来要实现的业务几乎没有任何关联,但每个方法中都要写⼀遍。

此时我们想到的第⼀个实现方案是 Spring AOP 前置通知或环绕通知来实现。但是这里依旧存在一些问题:
1.获取参数复杂
2. 我们要对⼀部分方法进行拦截,而另⼀部分方法不拦截,如注册方法和登录方法是不拦截的,这样的话排除方法的规则很难定义,甚至没办法定义

Spring 拦截器

对于以上问题 Spring 中提供了具体的实现拦截器:HandlerInterceptor,拦截器的实现分为以下两个步骤:

  1. 创建自定义拦截器,实现 HandlerInterceptor 接口的 preHandle(执行具体方法之前的预处理)方法
  2. 将自定义拦截器加入 WebMvcConfigurer 的 addInterceptors 方法中

1.自定义拦截器:实现HandlerInterceptor

@Component
public class LoginInterceptor implements HandlerInterceptor{
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //判断是否登录
        HttpSession session = request.getSession(false);
        if(session != null && session.getAttribute("username") != null){
            //通过,不进行拦截
            return true;
        }
        response.setStatus(401);
        return false;
    }
}

2.将自定义拦截器加入到系统配置:实现WebMvcConfigurer
将上面的自定义拦截器加⼊到系统配置信息中:

@Configuration
public class AppConfig implements WebMvcConfigurer {
    @Autowired
    private  LoginInterceptor loginInterceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**") //拦截所有URL
                .excludePathPatterns("/user/login") //排除登录功能
                .excludePathPatterns("/user/reg") ; //排除注册功能

    }
}

其中:
addPathPatterns:表示需要拦截的 URL,** 表示拦截任意方法(也就是所有⽅法)。
excludePathPatterns:表示需要排除的 URL。
以上拦截规则可以拦截项⽬中的 URL,还可以拦截静态文件(图⽚文件、JS 和 CSS 等文件)。

定义Controller并运行

@Slf4j
@RestController
@RequestMapping("user")
public class TestController {
    //获取用户信息
    @RequestMapping("/getInfo")
    public String getInfo(){
        log.info("get info...");
        return "get info.....";
    }
    //注册
    @RequestMapping("/reg")
    public String reg(){
        log.info("reg...");
        return  "reg....";
    }
    //登录
    @RequestMapping("/login")
    public String login(){
        log.info("login...");
        return "login.....";
    }
}

在这里插入图片描述

此外,还可以通过重写configurePathMatch方法来在添加前缀,在访问时补全URL,代码如下:

@Configuration
public class AppConfig implements WebMvcConfigurer {
    @Autowired
    private  LoginInterceptor loginInterceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**") //拦截所有URL
                .excludePathPatterns("/api/user/login") //排除登录功能
                .excludePathPatterns("/api/user/reg") ; //排除注册功能

    }

    // 所有的接⼝添加 api 前缀
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        configurer.addPathPrefix("api", c -> true);
    }
}

拦截器的实现原理

在这里插入图片描述

实现原理源码分析

所有的 Controller 执⾏都会通过⼀个调度器 DispatcherServlet 来实现,这⼀点可以从 Spring Boot 控制台的打印信息看出:
在这里插入图片描述
而所有方法都会执行 DispatcherServlet 中的 doDispatch 调度方法,doDispatch 部分源码如下:
在这里插入图片描述
从上述源码可以看出在开始执行 Controller 之前,会先调用预处理⽅法 applyPreHandle,⽽applyPreHandle方法的实现源码如下:
在这里插入图片描述
在 applyPreHandle 中会获取所有的拦截器 HandlerInterceptor 并执行拦截器中的 preHandle 方法,这与我们自定义的拦截器以及重写的 preHandle对应起来了。

通过上面的源码分析,可以看出Spring 中的拦截器也是通过动态代理和环绕通知的思想实现的,大体的调用流程如下:
在这里插入图片描述

统一异常处理

统⼀异常处理使用的是 @ControllerAdvice + @ExceptionHandler 来实现的,@ControllerAdvice 表示控制器通知类,@ExceptionHandler 是异常处理器,两个结合表示当出现异常的时候执行某个通知,也就是执行某个方法事件。

@ResponseBody
@ControllerAdvice
public class ErrorHandler {
    @ExceptionHandler
    public Object error(Exception e){
        HashMap<String,Object> result = new HashMap<>();
        result.put("success",0);
        result.put("code",-1);
        result.put("msg","内部异常");
        return result;
    }

    @ExceptionHandler
    public Object error(ArithmeticException e){
        HashMap<String,Object> result = new HashMap<>();
        result.put("success",0);
        result.put("code",-2);
        result.put("msg","算数异常");
        return result;
    }

    @ExceptionHandler
    public Object error(NullPointerException e){
        HashMap<String,Object> result = new HashMap<>();
        result.put("success",0);
        result.put("code",-3);
        result.put("msg","空指针异常");
        return result;
    }
}

以上方法表示,如果出现了异常就返回给前端⼀个 HashMap 的对象,其中包含的字段如代码中定义的那样。
定义Controller:

@RestController
public class ExceptionController {
    @RequestMapping("/test1")
    public boolean test1(){
        int a = 10/0;
        return true;
    }
    @RequestMapping("/test2")
    public boolean test2(){
        String str = null;
        System.out.println(str.length());
        return true;
    }
    @RequestMapping("/test3")
    public String test3(){
        throw new RuntimeException("test3手动创建异常");
    }
}

运行结果: 当有多个异常通知时,匹配顺序为从子类到父类向上依次匹配
在这里插入图片描述

统一数据返回格式

统⼀的数据返回格式可以使用 @ControllerAdvice + ResponseBodyAdvice 的方式实现。

@ControllerAdvice
public class ResponseHandler implements ResponseBodyAdvice {
    /**
     * 内容是否需要重写(通过此⽅法可以选择性部分控制器和⽅法进⾏重写)
     * 返回 true 表示重写
     */
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true;
    }

    /**
     * ⽅法返回之前调⽤此⽅法
     */
    @SneakyThrows
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        // 构造统⼀返回对象
        HashMap<String,Object> result = new HashMap<>();
        result.put("success",1);
        result.put("data",body);
        result.put("errMsg","");
        //如果返回结果是字符串,存在java.util.HashMap -> java.lang.String 转换的异常
        // 此时对字符串进行特殊处理
        if(body instanceof String){
            ObjectMapper mapper = new ObjectMapper();
            return mapper.writeValueAsString(result);
        }
        return result;
    }
}

在这里插入图片描述


继续加油~
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值