• SpringBoot统一功能处理


    关于SpringBoot统一处理。我们要实现的目标有三个:

    1. 统一用户登录权限验证
    2. 统一数据格式返回
    3. 统一异常处理

    1. 用户登录权限校验

    1.1 最初用户登录验证

    1. @RestController
    2. @RequestMapping("/user")
    3. public class UserController {
    4. /**
    5. * 某⽅法 1
    6. */
    7. @RequestMapping("/m1")
    8. public Object method(HttpServletRequest request) {
    9. return processRequest(request);
    10. }
    11. /**
    12. * 某⽅法 2
    13. */
    14. @RequestMapping("/m2")
    15. public Object method2(HttpServletRequest request) {
    16. return processRequest(request);
    17. }
    18. // Common logic for method and method2
    19. private Object processRequest(HttpServletRequest request) {
    20. // 有 session 就获取,没有不会创建
    21. HttpSession session = request.getSession(false);
    22. if (session != null && session.getAttribute("userinfo") != null) {
    23. // 说明已经登录,业务处理
    24. return true;
    25. } else {
    26. // 未登录
    27. return false;
    28. }
    29. }
    30. // 其他⽅法...
    31. }

    从上述代码可以看出来,每一个方法中都有相同的用户登录验证权限,它的缺点就是:

    1. 每一个方法中都要单独写用户登陆验证的方法,即使封装成公共方法,也一样要传参调用和在方法中进行判断
    2. 添加的控制器越多,调用用户的方法也越多,这样就会增加后期修改成本和维护成本
    3. 这些用户登录验证的方法合计下来掩饰显得业务几乎没有任何关联,但是每一个都要写一遍

    所以提供一个公共的AOP方法进行统一用户登录权限验证是很有用的

    1.2 Spring AOP用户统一登录验证的问题

    说到统⼀的⽤户登录验证,我们想到的第⼀个实现⽅案是 Spring AOP 前置通知或环绕通知来实现,具 体实现代码如下:

    1. import org.aspectj.lang.ProceedingJoinPoint;
    2. import org.aspectj.lang.annotation.*;
    3. import org.springframework.stereotype.Component;
    4. @Aspect
    5. @Component
    6. public class UserAspect {
    7. // 定义切点⽅法 controller 包下、⼦孙包下所有类的所有⽅法
    8. @Pointcut("execution(* com.example.demo.controller..*.*(..))")
    9. public void pointcut() {
    10. }
    11. // 前置⽅法
    12. @Before("pointcut()")
    13. public void doBefore() {
    14. // Your code for before advice
    15. }
    16. // 环绕⽅法
    17. @Around("pointcut()")
    18. public Object doAround(ProceedingJoinPoint joinPoint) {
    19. Object obj = null;
    20. System.out.println("Around ⽅法开始执⾏");
    21. try {
    22. // 执⾏拦截⽅法
    23. obj = joinPoint.proceed();
    24. } catch (Throwable throwable) {
    25. throwable.printStackTrace();
    26. }
    27. System.out.println("Around ⽅法结束执⾏");
    28. return obj;
    29. }
    30. }

    如果要在以上 Spring AOP 的切⾯中实现⽤户登录权限效验的功能,有以下两个问题:

    1. 没办法获取到 HttpSession 对象。
    2. 我们要对⼀部分⽅法进⾏拦截,⽽另⼀部分⽅法不拦截,如注册⽅法和登录⽅法是不拦截的,这样 的话排除⽅法的规则很难定义,甚⾄没办法定义。

    如果我们需要解决这样的问题,我们就需要用到一种新的方法Spring拦截器

    1.3 Spring拦截器

    Spring提供了具体的实现拦截器:HandlerInterceptor,拦截器的实现分为以下两步:

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

    1.3.1 自定义拦截器

    接下来使⽤代码来实现⼀个⽤户登录的权限效验,⾃定义拦截器是⼀个普通类,具体实现代码如下:

    1. public class UserInterceptor implements HandlerInterceptor {
    2. /**
    3. * 返回 true代表拦截器验证成功,继续执行后续方法
    4. * false代表拦截器验证失败,不会执行后续目标方法
    5. */
    6. @Override
    7. public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
    8. Object handler) throws Exception {
    9. //业务方法
    10. HttpSession session = request.getSession(false);
    11. if(session!=null && session.getAttribute(AppVar.SESSION_KEY)!=null){
    12. //用户已经登陆
    13. return true;
    14. }
    15. return false;
    16. }
    17. }

     1.3.2 将自定义拦截器加入到系统配置

    将上⼀步中的⾃定义拦截器加⼊到系统配置信息中,具体实现代码如下:

    1. @Configuration
    2. public class AppConfig implements WebMvcConfigurer {
    3. @Autowired
    4. public UserInterceptor userInterceptor;
    5. @Override
    6. public void addInterceptors(InterceptorRegistry registry) {
    7. registry.addInterceptor(userInterceptor)
    8. .addPathPatterns("/**")//拦截所有请求
    9. .excludePathPatterns("/login.html")
    10. .excludePathPatterns("/reg.html")
    11. .excludePathPatterns("/css/**")
    12. .excludePathPatterns("editor.md/**")
    13. .excludePathPatterns("/image/**")
    14. .excludePathPatterns("/user/reg")
    15. .excludePathPatterns("/user/getnum")
    16. ;
    17. }
    18. }

    addPathPatterns:表示需要拦截的URL,“**”表示拦截任意方法

    excludePathPatterns:表示需要排除的URL

    此时我们调用方法试一下:

    这里被成功拦截,我们在执行拦截功能的时候可以打印一下日志发现:

    成功拦截 

    当我们访问login页面便可以正常访问

    2. 统一异常处理

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

    1. @RestControllerAdvice
    2. public class ExceptionAdvice {
    3. @ExceptionHandler(NullPointerException.class)
    4. public ResultAjax doNullPointerException(NullPointerException e){
    5. ResultAjax resultAjax = new ResultAjax();
    6. resultAjax.setCode(-1);
    7. resultAjax.setMsg("空指针异常:"+e.getMessage());
    8. resultAjax.setData(null);
    9. return resultAjax;
    10. }
    11. @ExceptionHandler(Exception.class)
    12. public ResultAjax doException(Exception e){
    13. ResultAjax resultAjax = new ResultAjax();
    14. resultAjax.setCode(-1);
    15. resultAjax.setMsg("异常:"+e.getMessage());
    16. resultAjax.setData(null);
    17. return resultAjax;
    18. }
    19. }

    当我们执行此代码的时候就会给前端页面返回一个异常

     3. 统一数据返回格式

    3.1 为什么需要统一数据返回格式

    统⼀数据返回格式的优点有很多,⽐如以下⼏个:

    1. ⽅便前端程序员更好的接收和解析后端数据接⼝返回的数据。
    2. 降低前端程序员和后端程序员的沟通成本,按照某个格式实现就⾏了,因为所有接⼝都是这样返回 的。
    3. 有利于项⽬统⼀数据的维护和修改。
    4. 有利于后端技术部⻔的统⼀规范的标准制定,不会出现稀奇古怪的返回内容。

    3.2 统一数据返回格式的实现

    1. @ControllerAdvice
    2. public class ResponseAdvice implements ResponseBodyAdvice {
    3. /**
    4. * true才会调用beforeBodyWrite
    5. * @param returnType
    6. * @param converterType
    7. * @return
    8. */
    9. @Autowired
    10. private ObjectMapper objectMapper;
    11. @Override
    12. public boolean supports(MethodParameter returnType, Class converterType) {
    13. return true;
    14. }
    15. @Override
    16. public Object beforeBodyWrite(Object body, MethodParameter returnType,
    17. MediaType selectedContentType,
    18. Class selectedConverterType,
    19. ServerHttpRequest request, ServerHttpResponse response) {
    20. if(body instanceof ResultAjax){
    21. return body;
    22. }
    23. if(body instanceof String){
    24. ResultAjax resultAjax = ResultAjax.succ(body);
    25. try {
    26. return objectMapper.writer().writeValueAsString(resultAjax);
    27. } catch (JsonProcessingException e) {
    28. e.printStackTrace();
    29. }
    30. }
    31. return ResultAjax.succ(body);
    32. }
    33. }

    返回对象:

    1. package com.example.demo.common;
    2. import com.sun.prism.PresentableState;
    3. import lombok.Data;
    4. @Data
    5. public class ResultAjax {
    6. private int code; //状态码
    7. private String msg; //状态码描述信息
    8. private Object data; //返回数据
    9. /**
    10. * 返回成功
    11. */
    12. public static ResultAjax succ(Object data){
    13. ResultAjax resultAjax = new ResultAjax();
    14. resultAjax.setCode(200);
    15. resultAjax.setMsg("");
    16. resultAjax.setData(data);
    17. return resultAjax;
    18. }
    19. public static ResultAjax succ(String msg,Object data){
    20. ResultAjax resultAjax = new ResultAjax();
    21. resultAjax.setCode(200);
    22. resultAjax.setMsg(msg);
    23. resultAjax.setData(data);
    24. return resultAjax;
    25. }
    26. public static ResultAjax fail(int code,String msg){
    27. ResultAjax resultAjax = new ResultAjax();
    28. resultAjax.setCode(code);
    29. resultAjax.setMsg(msg);
    30. resultAjax.setData(null);
    31. return resultAjax;
    32. }
    33. public static ResultAjax fail(int code,String msg,Object data){
    34. ResultAjax resultAjax = new ResultAjax();
    35. resultAjax.setCode(code);
    36. resultAjax.setMsg(msg);
    37. resultAjax.setData(data);
    38. return resultAjax;
    39. }
    40. }

    有些人会好奇问什么需要单独判断String呢

    我们可以给前端返回一个String验证一下

    报错了

    为什么呢?是因为我们执行到这里的时候string渲染引擎没有加载完成,我们直接要返回一个json对象,就会报错

  • 相关阅读:
    零基础如何自学网络安全,基于就业前景全方位讲解,包教包会
    解决word之间复制公式时,公式编辑器变成图片
    kubernetes-v1.23.3 部署 MySQL-5.7.31
    浅谈 —— AAA认证(认证+授权)详解+配置
    【ROS2要素】xml、GDF、URDF的关系
    前端的强缓存和协商缓存
    libcurl库使用
    JDBC学习
    消费电子 SIC462ED SIC463ED DC/DC 稳压器 参数 应用
    Leetcode 73 矩阵置0
  • 原文地址:https://blog.csdn.net/loss_rose777/article/details/134489569