Springboot配置全局异常通用返回

Springboot配置全局异常通用返回

前言

前端对接了部分接口后,开始抱怨,"后端接口出参的格式总是千奇百怪,没有一个固定的格式,错误信息提示也不明朗,业务的状态码总是东一个西一个,前端这都不好做统一的管理操作",对此后端开始研究起对出参的统一封转。

约定统一的格式

通过对所有的出参都有固定的格式输出,可以让前端封装拦截器来对请求不同逻辑走向的处理。

简单的业务状态+详情出参

code 约定为业务结果码 2000成功,5000失败message 约定为错误提示信息,data 约定为返回的出参内容。对此就形成了固定的出参json格式如下:

json 复制代码
{
    "code":2000,
    "message":"成功",
    "data":{}
}

前端可以根据不同的code返回,做不同逻辑处理

  • code = 2000 , 做成功data数据内容的展示
  • code = 5000 , 做失败的提示语message提示

通过Http状态码判断

针对特殊场景的判定,可以根据Http状态码来判断,因为每个请求都有对应的http请求的状态码返回,例如200,500,401,404等常用的状态码,也能当作前端判断请求的情况的依据。

  • 2xx(成功状态码)
    • 200 (OK):请求成功
    • 201 (Created):请求成功,服务器对应资源已创建
    • 202 (Accepted):服务器接受请求,但还没处理好
    • 204 (No Content): 服务器成功处理请求,但没有内容返回
    • 205 (Reset Content): 服务器成功处理请求,要求客户端重置之前显示内容
    • 206 (Partial Content): 服务器成功处理部分请求,用于断点续传
  • 3xx(重定向状态码)
    • 301 (Moved Permanently): 请求资源已经换成新的url,望客户端请求新的地址Location的url
    • 302 (Found): 请求资源暂时换成新的url
    • 304 (Not Modified) : 服务端判断是否资源没被修改,浏览器使用本地缓存
  • 4xx(客户端错误状态码)
    • 400 (Bad Request) :客户端请求格式有问题
    • 401 (Unauthorized) : 未认证或者认证失败
    • 403 (Forbidden) : 没有对应的权限
    • 404 (Not Found): 服务器找不到对应的客户端请求的资源
    • 405 (Method Not Allowed) : 客户端请求方法不被服务端允许要求的
  • 5xx(服务端错误状态码)
    • 500 (Internal Server Error) : 服务器内部程序代码等引起的错误
    • 502 (Bad Gateway):作为网关,收到服务器无效的响应
    • 503 (Service Unavailable) : 服务器无法提供服务
    • 504 (Gateway Time - out) : 作为网关,收到服务器响应超时过慢

复杂展示的业务场景

对于单一code无法描述的场景,可以再额外增添参数来描述具体的场景情况。

  • errorDetails: 错误对象具体字段描述,在指定code为4000-4999之前的错误是有
    • field : 指定具体错误字段
    • errorMsg: 指定具体错误信息
  • timestamp(可选) : 记录请求时间
  • version(可选) :记录接口版本
  • traceId(可选):记录链路追踪id
json 复制代码
{
    "code":4000,
    "message":"提交格式错误"
    "data":null,
    "errorDetails":[
         {
             "field":"username",
             "errorMsg":"名称必须在6-20个字符内"
         }
    ]
}

message字段提供一个整体的错误概述,errorDetails则提供具体的错误细节,两者结合可以让前端更全面地了解错误情况

封装常量的案例

请求状态码的枚举值

java 复制代码
@Getter
public enum ResultEnum {

    SUCCESS("请求成功", 2000),
    ERROR("服务器内部错误", 5000),
    PARAM_ERROR("参数错误", 4001),
    ;

    private String msg;
    private Integer code;

    ResultEnum(String msg, Integer code) {
        this.msg = msg;
        this.code = code;
    }
}

统一出参的格式封装

java 复制代码
@Data
public class ResultData<T> {

    /**
     * 默认生成的序列号
     */
    private static final long serialVersionUID = 1L;
    private Integer code = 2000;//默认成功
    private String msg = "";
    private T data;

    public ResultData(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public ResultData(Integer code, String msg, T data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }

    public static <T> ResultData<T> error(Integer code, String msg) {
        return new ResultData<T>(code, msg);
    }

    public static <T> ResultData<T> error(String msg) {
        return new ResultData<T>(ResultEnum.ERROR.getCode(), msg);
    }

    public static <T> ResultData<T> success(String msg) {
        return new ResultData<T>(ResultEnum.SUCCESS.getCode(), msg);
    }

    public static <T> ResultData<T> success(String msg, T data) {
        return new ResultData<T>(ResultEnum.SUCCESS.getCode(), msg, data);
    }


}

全局异常处理

@RestControllerAdvice是 Spring 框架提供的用于全局异常处理的注解 ,通过该注解可以是实现集中在一个类中处理异常的问题,避免重复处理异常代码 , 在具体的方法上使用@ExceptionHandler针对性的处理不同异常导致的问题

使用方式

java 复制代码
@RestControllerAdvice 
public class GlobalExceptionHandlerAdvice {
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResultData<Object> resolveMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        FieldError fieldError = e.getBindingResult().getFieldError();
        log.error("【参数校验异常】错误提示:{}", fieldError != null ? fieldError.getDefaultMessage() : "");
        return ResultData.error(ResultEnum.PARAM_ERROR.getCode(), e.getBindingResult().getFieldError().getDefaultMessage());
    }
}

例如对于之前使用参数校验时@Valid@Validated进行参数验证失败的情况,抛出的MethodArgumentNotValidException异常,就会统一到上述这个方法上处理,bindingResult中能获取到错误的字段和错误的信息,将其打印出来和结果值返回给前端

常见的需补充的错误异常

还有一些其他异常,可以选择性的对其进行一一的处理封装成ResultData返回给前端,最后兜底使用Exception处理异常保证最后输出还是为统一格式。

  • SQLExceptionDataAccessException : 与数据库交互过程中产生的异常
  • HttpRequestMethodNotSupportedException : 请求方式不支持的异常
  • MethodArgumentNotValidException、 ConstraintViolationException : 使用@Valid@Validated时校验参数时可能会遇到的异常
  • HttpMessageNotReadableExceptionHttpMessageNotWritableException : 请求体的数据读写导致的问题
  • IllegalArgumentException : 参数格式错误的异常
  • CustomeException(自定义异常):自定义实现标识特定异常错误
  • Exception : 最后兜底顶级的异常,保证异常处理最后会走到的地方

ExceptionHandler 处理异常优先级

处理的优先级,必然是有精确匹配到的异常处理方法先走,如果没有精确匹配的异常处理,会根据子类->父类的顺序来处理 。

如有如下三种异常,ExceptionA,ExceptionB,ExceptionC ,分别是ExceptionA 是 ExceptionB的父类,ExceptionB是ExceptionC的父类 ,

less 复制代码
@ExceptionHandler(ExceptionA.class)
@ResponseBody
public ResultData<Object> ExceptionASolve(ExceptionA e) {
}

@ExceptionHandler(ExceptionB.class)
@ResponseBody
public ResultData<Object> exceptionBSolve(ExceptionB e) {
}
@RequestMapping("/hello")
public String hello() {
    throw new ExceptionC("sss");
}

此时对应请求抛出ExceptionC,全局处理异常方法exceptionBSolve会去对应处理它

注意: 当然还有另外一种情况,你在方法体里定义对同一个异常处理下有多个方法,会导致最后执行到哪个方法是属于不确定性的,因为会取决于方法的定义顺序,类的加载顺序等因素。

总结

和前端沟通设计好统一的返回模板,便于前端进行交互,一般场景下,可以直接使用简单的通用模板来返回。针对额外复杂的场景可以适当的增加参数做区分判断。还有就是对异常进行统一的集中处理,封装成模板返回给前端。

相关推荐
六毛的毛2 分钟前
Springboot开发常见注解一览
java·spring boot·后端
AntBlack9 分钟前
拖了五个月 ,不当韭菜体验版算是正式发布了
前端·后端·python
315356691312 分钟前
一个简单的脚本,让pdf开启夜间模式
前端·后端
程序漫游人16 分钟前
centos8.5安装jdk21详细安装教程
java·linux
小扎仙森18 分钟前
关于服务器宝塔转移wordperss子比主题问题
运维·服务器
小小小糖果人21 分钟前
Linux云计算基础篇(5)
linux·运维·服务器
uzong37 分钟前
curl案例讲解
后端
超级码.里奥.农1 小时前
零基础 “入坑” Java--- 七、数组(二)
java·开发语言
KENYCHEN奉孝1 小时前
Rust征服字节跳动:高并发服务器实战
服务器·开发语言·rust
hqxstudying1 小时前
Java创建型模式---单例模式
java·数据结构·设计模式·代码规范