SpringBoot统一功能处理

发布于:2025-08-02 ⋅ 阅读:(20) ⋅ 点赞:(0)

1、拦截器

在完成强制登录这一功能时,后端程序需要根据Session来判断用户是否登录,但是实现方法是很麻烦的:1、需要修改每个接口的处理逻辑   2、需要修改每个接口的返回结果。 3、接口定义修改,前端代码也需要跟着修改。

有没有一种简单的方法能统一拦截所有的请求,并进行Session校验呢?

这就需要我们的拦截器出马了~~

快速上手拦截器

什么是拦截器

拦截器是Spring提供的核心功能之一,主要用来拦截用户请求,在指定方法前后,根据业务需要执行预先设定的代码。也就是说,允许开发人员提前预定义一些逻辑,在用户的请求响应前后执行,也可以在用户请求前阻止其执行。

在拦截器中,开发人员可以在应用程序中做一些通用性的操作,比如拦截前端发来的请求,判断Session中是否有登录用户的信息,如果有就放行,没有就进行拦截。

拦截器的使用主要分为两步:

1、定义拦截器

2、注册配置拦截器

自定义拦截器

自定义拦截器需要实现HandlerInterceptor接口,并重写方法。

@Slf4j
@Component
public class StringInterceptor implements HandlerInterceptor{
    //请求执行前的逻辑
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            log.info("目标方法执行前执行");
            return true;
    }
    //请求执行后的逻辑
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
            log.info("目标方法执行后执行");
    }
    //视图渲染后的逻辑
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            log.info("视图渲染后执行");
    }
}
  • preHandle():目标方法执行前执行,返回true:继续执行后续操作;返回false:中断后续操作。
  • postHandle():目标方法执行后执行
  • afterCompletion():视图渲染完成后执行,最后执行(后端开发现在几乎不涉及视图了,暂不了解)
注册配置拦截器

注册配置拦截器需要实现WebMvcConfigurer接口并重写addInterceptors方法。

@Configuration
public class Webconfig implements WebMvcConfigurer {
    //自定义拦截器对象
    @Autowired
    private StringInterceptor interceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //注册自定义拦截器对象
        // addPathPatterns():设置拦截的请求路径,/**表示所有路径    excludePathPatterns()设置不拦截的路径
        registry.addInterceptor(interceptor).
                addPathPatterns("/**").
                excludePathPatterns("/test/t2");
    }
}

Controller测试类:

@Slf4j
@RestController
@RequestMapping("/test")
public class TestController {
    @RequestMapping("/t1")
    public void t1(){
      log.info("t1执行中");
    }

    @RequestMapping("/t2")
    public void t2(){
        log.info("t2执行中");
    }

    @RequestMapping("/t3")
    public void t3(){
        log.info("t3执行中");
    }
}

测试结果: 

t1: t2:t3:

可以看到拦截器没有拦截t2请求,并执行拦截器中重写的方法。 

在拦截器中除了可以设置/**拦截所有资源外,还有一些常见拦截路径设置:

拦截器执行流程

正常调用顺序:

有了拦截器后:

1、添加拦截器后,执行Controller方法之前,请求会先被拦截住并执行preHandle()方法, 这个方法需要返回一个boolean类型的值。如果返回true,则表示放行,继续访问Controller中的方法。如果返回false,则不会放行,不会执行Controller中的方法。

2、controller当中的方法执行完毕后,再回过头来执行postHandle这个方法以及afterCompletion()方法,执行完毕之后,最终给浏览器响应数据。

2、统一返回类型 

在开发环境中,我们的方法往往有许多不同的返回类型。如果返回的类型不同,前端就要使用各种不同的类型进行接收,这样会增加前端开发人员的工作量。SpringBoot为我们提供了统一返回类型的功能实现。

快速入门

统一数据返回类型需要使用@ControllerAdvice(标识控制器通知类,这里的通知并不是我们日常生活中的通知,而是某些功能的实现)并实现ResponseBodyAdvice接口。

@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
    @Autowired
    private ObjectMapper mapper;
    //什么请求需要处理      true——所有请求都处理       false——所有请求都不处理
    @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) {
       if(body instanceof Result){
           return body;
       }
       if(body instanceof String){
           //如果是字符串,转成json字符串再返回,否则可能会产生类型转换异常
            return mapper.writeValueAsString(Result.success(body));
       }
        return Result.success(body);
    }
}

supports方法:判断是否要执行beforeBodyWrite方法。true为执行,flase不执行。提供该方法可以选择那些类或那些方法的response要进行处理,其他的不进行处理。

从returnType获取类名和方法名:

 //获取执行的类
        Class<?> declaringClass = returnType.getMethod().getDeclaringClass();
        //获取执行的方法
        Method method = returnType.getMethod();

beforeBodyWritr方法:对response方法进行具体操作处理

测试相关类及方法: 

Controller:

@Slf4j
@RequestMapping("/book")
@RestController
public class BookController {
    @Autowired
    private BookSeverice bookSeverice;

    //根据id查询图书
    @RequestMapping("/queryBookById")
    public BookInfo queryBookById(Integer bookId) {
        log.info("根据id查询图书信息,id:" + bookId);
        return bookSeverice.queryBookById(bookId);
    }
}

Service: 

@Service
public class BookSeverice {
    @Autowired
    private BookMapper mapper;


    //根据id查询图书
    public BookInfo queryBookById(Integer bookId) {
       BookInfo bookInfo =  mapper.queryById(bookId);
       bookInfo.setStatusCN(BookStatus.getDescByCode(bookInfo.getStatus()).getDesc());
       return bookInfo;
    }
}

mapper: 

    @Select("select * from book_info where id = #{id}")
    BookInfo queryById(Integer id);

 Result:

@Data
public class Result<T> {
    private ResultStatus code;//业务码     不是HTTP状态码   200——成功   -2 失败    -1未登录
    private String errMsg;//错误信息        如果业务成功,errMsg为空
    private T data;

    public static<T> Result success(T data){
        Result result = new Result<>();
        result.setCode(ResultStatus.SUCCESS);
        result.setData(data);
        return result;
    }
    public static <T> Result noLogin(){
            Result result = new Result<>();
            result.setCode(ResultStatus.NOLOGIN);
            result.setErrMsg("用户未登录");
            return result;
        }

    public  static Result fail(String msg){
        Result result = new Result<>();
        result.setCode(ResultStatus.FAIL);
        result.setErrMsg(msg);
        return result;
    }
    public  static Result fail(String msg,ResultStatus resultStatus){
        Result result = new Result<>();
        result.setCode(resultStatus);
        result.setErrMsg(msg);
        return result;
    }

  }

业务码枚举类:


public enum ResultStatus {
    SUCCESS(200),
    FAIL(-1),
    NOLOGIN(-2);

    ResultStatus(int code) {
        this.code = code;
    }

    private int code;

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }
}

Bookinfo:


@Data
public class BookInfo {
    private Integer id;
    private String bookName;
    private String author;
    private Integer count;
    private BigDecimal price;
    private String publish;
    private Integer status;//1-正常   2-不可借阅
    private String  statusCN ;
    private Date createTime;
    private Date updateTime;
}

添加统一数据返回格式之前: 

添加统一数据返回格式之后: 

优点:

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

3、统一异常处理 

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

代码如下:

@ResponseBody
@ControllerAdvice
@Slf4j
public class ExceptionAdvice {
    /**
     * 统一异常处理
     * @param e
     * @return
     */
    @ExceptionHandler
    public Result handleException(Exception e){
        //建议打印日志
        log.error("e: "+e);
        return Result.fail("发生内部错误");
    }
}

因为接口返回的是数据,所以需要加上@ResponseBody注解。

以上代码表示,如果代码出现Exception异常(包括它的子类),就返回一个Result对象,Result类代码:


@Data
public class Result<T> {
    private ResultStatus code;//业务码     不是HTTP状态码   200——成功   -2 失败    -1未登录
    private String errMsg;//错误信息        如果业务成功,errMsg为空
    private T data;

    public static<T> Result success(T data){
        Result result = new Result<>();
        result.setCode(ResultStatus.SUCCESS);
        result.setData(data);
        return result;
    }
    public static <T> Result noLogin(){
            Result result = new Result<>();
            result.setCode(ResultStatus.NOLOGIN);
            result.setErrMsg("用户未登录");
            return result;
        }

    public  static Result fail(String msg){
        Result result = new Result<>();
        result.setCode(ResultStatus.FAIL);
        result.setErrMsg(msg);
        return result;
    }
    public  static Result fail(String msg,ResultStatus resultStatus){
        Result result = new Result<>();
        result.setCode(resultStatus);
        result.setErrMsg(msg);
        return result;
    }

  }

我们还可以针对不同的异常返回不同的结果: 

@ResponseBody
@ControllerAdvice
@Slf4j
public class ExceptionAdvice {
    /**
     * 统一异常处理
     * @param e
     * @return
     */
    @ExceptionHandler
    public Result handleException(Exception e){
        //建议打印日志
        log.error("e: "+e);
        return Result.fail("发生内部错误");
    }

   @ExceptionHandler
   public Object handler(NullPointerException e) {
     return Result.fail("发⽣NullPointerException:"+e.getMessage());
    
    }
    
    @ExceptionHandler
     public Object handler(ArithmeticException e) {
     return Result.fail("发⽣ArithmeticException:"+e.getMessage());
    }
}

模拟异常的代码 

@RequestMapping("/test")
@RestController
public class TestController {
    @RequestMapping("/t1")
    public String t1(){
        return "string";
    }

    @RequestMapping("/t2")
    public Integer t2(){
        int a = 10/0;//抛出算数异常
        return 1;
    }

    @RequestMapping("/t3")
    public Boolean t3(){
        String a = null;
        System.out.println(a.length());//抛出空指针异常
        return true;
    }
}

t2: 

t3: 

完!!! 


网站公告

今日签到

点亮在社区的每一天
去签到