当前位置:   article > 正文

springboot全局异常拦截+统一接口返回格式(包括过滤器、拦截器中的异常)_springboot 拦截器返回结果

springboot 拦截器返回结果

环境

  • Java17
  • spring boot v-3.1.3

描述

为了不让后端莫名其妙的错误返回给用户,为了提高可维护性和可扩展性...为了少写两行代码,懒得打字直接搞代码

统一接口出参格式,统一异常处理

直接先展示成品

  1. import io.swagger.v3.oas.annotations.Operation;
  2. import io.swagger.v3.oas.annotations.tags.Tag;
  3. import org.springframework.web.bind.annotation.GetMapping;
  4. import org.springframework.web.bind.annotation.RequestMapping;
  5. import org.springframework.web.bind.annotation.RestController;
  6. import java.util.Arrays;
  7. import java.util.List;
  8. /**
  9. * @author: dzg
  10. */
  11. @RestController
  12. @RequestMapping("test")
  13. @Tag(name = "test控制器")
  14. public class Test2Controller {
  15. @GetMapping("test1")
  16. @Operation(summary = "无特殊返回")
  17. public Result<Object> test1(){
  18. //模拟调用方法,成功了默认返回成功,有问题直接方法直接被全局异常拦截
  19. test1s();
  20. return Result.success();
  21. }
  22. @GetMapping("test2")
  23. @Operation(summary = "要返回一些数据")
  24. public Result<List<String>> test2(){
  25. //模拟调用方法,成功了直接返回,有问题直接方法直接被全局异常拦截
  26. return Result.success(test2s());
  27. }
  28. @GetMapping("test3")
  29. @Operation(summary = "测试异常拦截")
  30. public Result<Object> test3(){
  31. //模拟调用方法,成功了直接返回,有问题直接方法直接被全局异常拦截
  32. return Result.success();
  33. }
  34. @GetMapping("test4")
  35. @Operation(summary = "测试异常拦截")
  36. public Result<Object> test4(){
  37. if(true){
  38. throw new CustomException(ResultCode.CODE_MISSING_PARAMETER);
  39. }
  40. //模拟调用方法,成功了直接返回,有问题直接方法直接被全局异常拦截
  41. return Result.success();
  42. }
  43. public static void test1s(){
  44. System.out.println(5/0);
  45. }
  46. public static List<String> test2s(){
  47. return Arrays.asList("H", "e", "l", "l", "o");
  48. }
  49. }

test1结果:

  1. {
  2. "code": 500,
  3. "msg": "操作失败",
  4. "data": null
  5. }

test2结果:

  1. {
  2. "code": 200,
  3. "msg": "操作成功",
  4. "data": [
  5. "H",
  6. "e",
  7. "l",
  8. "l",
  9. "o"
  10. ]
  11. }

这种只有一两行代码的Controller看着不清爽吗?报错也不会抛出来奇奇怪怪的东西

统一出参Code码

  1. public enum ResultCode {
  2. /**
  3. * 成功状态码
  4. */
  5. CODE_SUCCESS(200, "操作成功"),
  6. /**
  7. * 违规请求
  8. */
  9. CODE_METHOD_NOT_ALLOWED(405, "违规请求"),
  10. /**
  11. * 无效请求状态码
  12. */
  13. CODE_INVALID_REQUEST(400, "无效请求"),
  14. /**
  15. * 操作失败
  16. */
  17. CODE_ERROR(500, "操作失败"),
  18. /**
  19. * 未登录,请登录后再次访问
  20. */
  21. CODE_NOT_LOGIN(401, "未登录,请登录后再次访问");
  22. /**
  23. * 等等等...
  24. */
  25. /**
  26. * 状态码
  27. */
  28. private final Integer code;
  29. /**
  30. * msg消息
  31. */
  32. private String msg;
  33. ResultCode(Integer code, String msg) {
  34. this.code = code;
  35. this.msg = msg;
  36. }
  37. public Integer getCode() {
  38. return this.code;
  39. }
  40. public String getCodeStr(){
  41. return getCode().toString();
  42. }
  43. public String getMsg() {
  44. return this.msg;
  45. }
  46. }

统一出参类Reslut

  1. import com.dzg.demo.common.menu.ResultCode;
  2. import io.swagger.v3.oas.annotations.media.Schema;
  3. /**
  4. * @Author: dzg
  5. * @Date: 2023/9/18 17:14
  6. * @Describe:
  7. */
  8. @Schema(description = "统一出参类Result")
  9. public class Result<T> {
  10. /**
  11. * 状态码
  12. */
  13. @Schema(description = "状态码", defaultValue = "200")
  14. private int code;
  15. /**
  16. * 描述信息
  17. */
  18. @Schema(description = "描述信息", defaultValue = "操作成功")
  19. private String msg;
  20. /**
  21. * 携带出参
  22. */
  23. @Schema(description = "携带出参")
  24. private T data;
  25. /**
  26. * 返回code
  27. *
  28. * @return str
  29. */
  30. public int getCode() {
  31. return this.code;
  32. }
  33. public Result<T> setCode(int code) {
  34. this.code = code;
  35. return this;
  36. }
  37. /**
  38. * 给msg赋值,连缀风格
  39. */
  40. public Result<T> setMsg(String msg) {
  41. this.msg = msg;
  42. return this;
  43. }
  44. public String getMsg() {
  45. return this.msg;
  46. }
  47. /**
  48. * 给data赋值,连缀风格
  49. */
  50. public Result<T> setData(T data) {
  51. this.data = data;
  52. return this;
  53. }
  54. /**
  55. * 将data还原为指定类型并返回
  56. */
  57. public T getData() {
  58. return data;
  59. }
  60. public Result(){}
  61. public Result(ResultCode resultCode) {
  62. setCode(resultCode.getCode());
  63. setMsg(resultCode.getMsg());
  64. }
  65. public Result(int code, String msg, T data) {
  66. setCode(code);
  67. setMsg(msg);
  68. setData(data);
  69. }
  70. /**
  71. * 返回成功
  72. *
  73. * @return
  74. */
  75. public static <T> Result<T> success() {
  76. return new Result<>(ResultCode.CODE_SUCCESS);
  77. }
  78. /**
  79. * 返回成功
  80. *
  81. * @return
  82. */
  83. public static <T> Result<T> success(T data) {
  84. return new Result<>(ResultCode.CODE_SUCCESS.getCode(), ResultCode.CODE_SUCCESS.getMsg(), data);
  85. }
  86. /**
  87. * 返回失败
  88. *
  89. * @return
  90. */
  91. public static <T> Result<T> error() {
  92. return new Result<>(ResultCode.CODE_ERROR);
  93. }
  94. public static <T> Result<T> error(ResultCode resultCode){
  95. return error(resultCode.getCode(), resultCode.getMsg());
  96. }
  97. public static <T> Result<T> error(int code, String msg) {
  98. return new Result<>(code, msg, null);
  99. }
  100. }

自定义异常

  1. import cn.hutool.core.text.CharSequenceUtil;
  2. import com.dzg.demo.common.menu.ResultCode;
  3. import java.io.Serial;
  4. /**
  5. * @author: dzg
  6. * @date: 2023/9/18 17:12
  7. * @describe: 自定义异常类
  8. */
  9. public class CustomException extends RuntimeException {
  10. @Serial
  11. private static final long serialVersionUID = 6552388948084405686L;
  12. /**
  13. * 异常错误码
  14. */
  15. private int code;
  16. /**
  17. * 给一个空构造器
  18. */
  19. public CustomException() {
  20. super(ResultCode.CODE_ERROR.getMsg());
  21. this.code = ResultCode.CODE_ERROR.getCode();
  22. }
  23. public CustomException(ResultCode resultCode) {
  24. super(resultCode != null ? resultCode.getMsg() : ResultCode.CODE_ERROR.getMsg());
  25. this.code = resultCode != null ? resultCode.getCode() : ResultCode.CODE_ERROR.getCode();
  26. }
  27. /**
  28. * 自定义提示,但是通用code码为500
  29. * @param msg 提示
  30. */
  31. public CustomException(String msg) {
  32. super(msg);
  33. this.code = ResultCode.CODE_ERROR.getCode();
  34. }
  35. public CustomException(ResultCode resultCode, String msg) {
  36. super(CharSequenceUtil.format(resultCode.getMsg(), msg));
  37. this.code = resultCode.getCode();
  38. }
  39. /**
  40. * httpclient5文章中拦截responseCode使用
  41. */
  42. public CustomException(HttpStatus httpStatus) {
  43. super(httpStatus != null ? "远程请求失败: "+httpStatus.getReasonPhrase() : "远程请求失败: "+ResultCode.CODE_ERROR.getMsg());
  44. this.code = httpStatus != null ? httpStatus.value() : ResultCode.CODE_ERROR.getCode();
  45. }
  46. public int getCode() {
  47. return code;
  48. }
  49. public void setCode(int code) {
  50. this.code = code;
  51. }
  52. }

全局异常拦截

  1. import jakarta.servlet.http.HttpServletRequest;
  2. import jakarta.servlet.http.HttpServletResponse;
  3. import jakarta.validation.ConstraintViolationException;
  4. import org.slf4j.Logger;
  5. import org.slf4j.LoggerFactory;
  6. import org.springframework.validation.FieldError;
  7. import org.springframework.web.HttpRequestMethodNotSupportedException;
  8. import org.springframework.web.bind.MethodArgumentNotValidException;
  9. import org.springframework.web.bind.MissingServletRequestParameterException;
  10. import org.springframework.web.bind.annotation.ExceptionHandler;
  11. import org.springframework.web.bind.annotation.ModelAttribute;
  12. import org.springframework.web.bind.annotation.ResponseBody;
  13. import org.springframework.web.bind.annotation.RestControllerAdvice;
  14. import java.util.Objects;
  15. /**
  16. * @author: dzg
  17. * @date: 2023/9/18 17:12
  18. * @describe: 全局异常拦截
  19. */
  20. @RestControllerAdvice
  21. public class GlobalExceptionConfig {
  22. private static final Logger log = LoggerFactory.getLogger(GlobalExceptionConfig.class);
  23. /**
  24. * 在当前类每个方法进入之前触发的操作
  25. *
  26. * @param request
  27. */
  28. @ModelAttribute
  29. public void get(HttpServletRequest request) {
  30. }
  31. /**
  32. * 参数校验的异常(post)
  33. *
  34. * @param ex
  35. * @return
  36. */
  37. @ResponseBody
  38. @ExceptionHandler(MethodArgumentNotValidException.class)
  39. public Result<Object> methodArgumentNotValidException(HttpServletRequest request, MethodArgumentNotValidException ex) {
  40. FieldError fieldError = ex.getBindingResult().getFieldError();
  41. log.error("参数校验失败(POST):{}", Objects.requireNonNull(fieldError).getDefaultMessage());
  42. return Result.error(ResultCode.CODE_INVALID_REQUEST.getCode(), fieldError.getDefaultMessage());
  43. }
  44. /**
  45. * 参数校验的异常(get)
  46. *
  47. * @param ex
  48. * @return
  49. */
  50. @ResponseBody
  51. @ExceptionHandler(ConstraintViolationException.class)
  52. public Result<Object> constraintViolationException(HttpServletRequest request, ConstraintViolationException ex) {
  53. log.error("参数校验失败(GET):{}", ex.getMessage());
  54. return Result.error(ResultCode.CODE_INVALID_REQUEST.getCode(), ex.getMessage());
  55. }
  56. /**
  57. * 全局异常拦截(拦截项目中的所有异常)
  58. *
  59. * @param e
  60. * @param request
  61. * @param response
  62. * @return
  63. * @throws Exception
  64. */
  65. @ResponseBody
  66. @ExceptionHandler(Exception.class)
  67. public Result<Object> handlerException(Exception e, HttpServletRequest request, HttpServletResponse response){
  68. return errorHandle(request, e);
  69. }
  70. /**
  71. * 异常处理 -这里拦截后就不会继续向上抛了
  72. *
  73. * @param e
  74. * @return
  75. */
  76. public static Result<Object> errorHandle(HttpServletRequest request, Exception e) {
  77. // 输出异常防止不展示调试时困难
  78. e.printStackTrace();
  79. Result<Object> result;
  80. // 不同异常返回不同状态码
  81. if (e instanceof CustomException cx) {
  82. result = customExceptionHandle(cx);
  83. } else if (e instanceof HttpRequestMethodNotSupportedException) {
  84. result = Result.error(ResultCode.CODE_METHOD_NOT_ALLOWED);
  85. } else if (e instanceof MissingServletRequestParameterException) {
  86. result = Result.error(ResultCode.CODE_MISSING_PARAMETER);
  87. } else {
  88. // 返回给前端 // 普通(未知)异常 , 输出:500 + 异常信息
  89. result = Result.error(e.getMessage());
  90. }
  91. return result;
  92. }
  93. /**
  94. * 自定义异常处理
  95. *
  96. * @param exception
  97. * @return
  98. */
  99. private static Result<Object> customExceptionHandle(CustomException exception) {
  100. //可以处理特殊的业务
  101. return Result.error(exception.getCode(), exception.getMessage());
  102. }
  103. }

此时到这里就可以拦截任意controller异常了,但是有个问题,那就是不能拦掉过滤器中的自定义异常或者其他异常..大致思路就是创建一个过滤器优先级最高,然后捕获异常并处理。

过滤器捕获异常

  1. import com.alibaba.fastjson2.JSONObject;
  2. import jakarta.servlet.*;
  3. import jakarta.servlet.http.HttpServletRequest;
  4. import jakarta.servlet.http.HttpServletResponse;
  5. import org.springframework.http.HttpStatus;
  6. import org.springframework.web.context.support.SpringBeanAutowiringSupport;
  7. import java.io.IOException;
  8. import java.io.OutputStream;
  9. import java.nio.charset.StandardCharsets;
  10. /**
  11. * @author: dzg
  12. * @date: 2023/9/18 23:26
  13. * @describe:
  14. */
  15. public class CommonFilter implements Filter {
  16. @Override
  17. public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
  18. // 请求进入时间
  19. HttpServletResponse httpServletResponse = (HttpServletResponse) response;
  20. HttpServletRequest httpServletRequest = (HttpServletRequest) request;
  21. try {
  22. chain.doFilter(request, httpServletResponse);
  23. //请求耗时
  24. if (HttpStatus.OK.value() != httpServletResponse.getStatus()) {
  25. responseError(httpServletRequest, httpServletResponse, HttpStatus.resolve(httpServletResponse.getStatus()));
  26. }
  27. } catch (Exception e) {
  28. //请求耗时
  29. Throwable error = (e.getCause() != null ? e.getCause() : e);
  30. error.printStackTrace();
  31. responseError(httpServletRequest, httpServletResponse, error);
  32. }
  33. }
  34. /**
  35. * 主要用来处理 @{@link GlobalExceptionConfig} 拦截不到的异常
  36. *
  37. * @param httpServletResponse
  38. * @param error
  39. * @throws IOException
  40. */
  41. public static void responseError(HttpServletRequest request, HttpServletResponse httpServletResponse, Throwable error) throws IOException {
  42. String errorMsg = JSONObject.toJSONString(GlobalExceptionConfig.errorHandle(request, (Exception) error));
  43. responseError(request, httpServletResponse, errorMsg);
  44. }
  45. public static void responseError(HttpServletRequest request, HttpServletResponse httpServletResponse, ResultCode resultCode) throws IOException {
  46. String errorMsg = JSONObject.toJSONString(Result.error(resultCode));
  47. responseError(request, httpServletResponse, errorMsg);
  48. }
  49. public static void responseError(HttpServletRequest request, HttpServletResponse httpServletResponse, HttpStatus httpStatus) throws IOException {
  50. String errorMsg = JSONObject.toJSONString(Result.error(httpStatus));
  51. responseError(request, httpServletResponse, errorMsg);
  52. }
  53. public static void responseError(HttpServletRequest request, HttpServletResponse httpServletResponse, String msg) throws IOException {
  54. httpServletResponse.setContentType("application/json; charset=utf-8");
  55. httpServletResponse.setCharacterEncoding(StandardCharsets.UTF_8.toString());
  56. OutputStream out = httpServletResponse.getOutputStream();
  57. out.write(msg.getBytes(StandardCharsets.UTF_8));
  58. out.flush();
  59. }
  60. @Override
  61. public void init(FilterConfig filterConfig) throws ServletException {
  62. SpringBeanAutowiringSupport.processInjectionBasedOnServletContext(this, filterConfig.getServletContext());
  63. }
  64. @Override
  65. public void destroy() {
  66. }
  67. }

过滤器测试类

  1. import jakarta.servlet.*;
  2. import jakarta.servlet.http.HttpServletRequest;
  3. import java.io.IOException;
  4. /**
  5. * @Author: dzg
  6. * @Date: 2023/9/18 23:04
  7. * @Describe:
  8. */
  9. public class TestFilter implements Filter {
  10. @Override
  11. public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
  12. HttpServletRequest request = (HttpServletRequest) servletRequest;
  13. if (request.getRequestURI().contains("/test3")){
  14. // 测试异常
  15. throw new CustomException(ResultCode.CODE_CUSTOM_EXCEPTION);
  16. }
  17. filterChain.doFilter(servletRequest, servletResponse);
  18. }
  19. }

注册过滤器

  1. import org.springframework.boot.web.servlet.FilterRegistrationBean;
  2. import org.springframework.context.annotation.Bean;
  3. import org.springframework.context.annotation.Configuration;
  4. import org.springframework.core.Ordered;
  5. /**
  6. * @author: dzg
  7. * @date: 2023/9/18 23:26
  8. * @describe: 注册过滤器类
  9. */
  10. @Configuration
  11. public class FilterRegister {
  12. @Bean
  13. public CommonFilter commonFilter(){
  14. return new CommonFilter();
  15. }
  16. /**
  17. * 通用过滤器---设定优先级最高
  18. * @return
  19. */
  20. @Bean
  21. public FilterRegistrationBean<CommonFilter> commonFilterRegistration() {
  22. FilterRegistrationBean<CommonFilter> registration = new FilterRegistrationBean<>();
  23. registration.setFilter(commonFilter());
  24. registration.setName("commonFilter");
  25. //此处尽量小,要比其他Filter靠前
  26. registration.setOrder(Ordered.HIGHEST_PRECEDENCE);
  27. return registration;
  28. }
  29. @Bean
  30. public TestFilter testFilter(){
  31. return new TestFilter();
  32. }
  33. @Bean
  34. public FilterRegistrationBean<TestFilter> testFilterRegistration() {
  35. FilterRegistrationBean<TestFilter> registration = new FilterRegistrationBean<>();
  36. registration.setFilter(testFilter());
  37. registration.setName("testFilter");
  38. registration.setOrder(-1);
  39. return registration;
  40. }
  41. }

到此就结束了,过滤器拦截异常的方法其实是可以代替全局异常类且比全局异常类可拦截异常范围更广,自己选就行了。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/菜鸟追梦旅行/article/detail/460433
推荐阅读
相关标签
  

闽ICP备14008679号