Springboot 3封装统一返回结果+全局异常处理


前言

统一结果封装可以带来以下几个好处:

  • 提高一致性:所有API接口返回的数据格式一致,方便前端处理和展示
  • 易于调试:统一的响应格式便于日志记录和错误追踪
  • 增强安全性:可以在统一封装过程中对响应数据进行过滤和处理,避免敏感信息泄露
  • 便于扩展:统一的响应格式有助于在项目扩展时保持代码的可维护性
{
    "code": "code value",
    "status": true or false,
    "message": "message value",
    "data": {}
}

这种返回格式主要包含四部分:

  1. code:状态码,由后端统一定义各种返回结果的状态码
  2. status:状态信息,用于标识请求是否成功
  3. message:返回信息,向客户端传递处理结果的描述
  4. data:实际数据,包含业务处理的具体结果

我们返回的信息至少包括codemessagedata三部分,其中code是我们后端和前端约定好的状态码,message为返回信息,data为返回的实际数据,没有返回数据则为null。除了这三部分外,你还可以定义一些其他字段,比如状态信息status,请求时间timestamp


一、统一结果集处理器

1. 自定义常用结果枚举类

  • 在utils包下新建enums包,其中新建HttpCodeEnum文件,在这里我们需要定义一个状态码的枚举类,不同的状态码对应不同的描述信息,如下:
/**
 * <p>
 * 常用结果枚举类
 * </p>
 *
 * @author Patrick
 * @since 2024-07-02
 */
@Getter
public enum HttpCodeEnum {

    //==================== 登录相关枚举 ======================
    /**
     * 登陆超时
     */
    RC100(100, "登陆超时!"),

    /**
     * 用户未登录
     */
    RC101(101, "用户未登录,请先进行登录!"),

    /**
     * 账户被禁用,请联系管理员解决
     */
    RC102(102, "账户被禁用,请联系管理员解决!"),

    /**
     * 用户信息加载失败
     */
    RC103(103, "用户信息加载失败!"),

    /**
     * 用户身份信息获取失败
     */
    RC104(104, "用户身份信息获取失败!"),

    /**
     * 用户名不能为空
     */
    RC105(105, "用户名不能为空!"),

    /**
     * 密码不能为空
     */
    RC106(106, "密码不能为空!"),

    /**
     * 用户名或密码错误
     */
    RC107(107, "用户名或密码错误!"),

    /**
     * 用户登录成功
     */
    RC108(108, "用户登录成功!"),

    /**
     * 用户注销成功
     */
    RC109(109, "用户注销成功!"),

    //==================== 注册相关枚举 ======================

    /**
     * 验证码错误
     */
    RC300(300, "验证码错误!"),

    /**
     * 验证码过期
     */
    RC301(301, "验证码已过期!"),

    /**
     * 用户名已存在
     */
    RC302(302, "用户名已存在!"),

    //======================= 其他枚举 ==============================

    /**
     * 参数格式不合法
     */
    RC400(400, "参数格式不合法,请检查后重试!"),
    
    /**
     * 没有权限
     */
    RC403(403, "您没有操作权限!"),

    /**
     * 页面不存在
     */
    RC404(404, "未找到您请求的资源!"),

    /**
     * 请求方式错误
     */
    RC405(405, "请求方式错误,请检查后重试!"),

    /**
     * 操作成功
     */
    SUCCESS(200, "操作成功!"),

    /**
     * 操作失败
     */
    ERROR(500, "操作失败!"),

    /**
     * 未知异常
     */
    RC600(600, "服务器繁忙或服务器错误,请稍后再试!");

    // 响应状态码
    private final Integer code;

    // 响应返回信息
    private final String message;

    HttpCodeEnum(Integer code, String message) {
        this.code = code;
        this.message = message;
    }

}

该枚举类为我们和前端约定好的返回状态码和描述信息,可根据自己的需求修改状态码和返回信息

2. 自定义统一返回格式

  • 在utils包下新建JsonResult文件,在这里我们定义一个统一返回结果的处理类:
/**
 * <p>
 * 统一结果集处理类
 * </p>
 *
 * @author Patrick
 * @since 2024-07-02
 */
@Data
public class JsonResult {

    @Schema(description = "状态码")
    private Integer code;

    @Schema(description = "状态信息")
    private Boolean status;

    @Schema(description = "返回信息")
    private String message;

    @Schema(description = "数据")
    private Map<String, Object> data = new HashMap<>();

    /**
     * 请求成功
     *
     * @return success
     */
    public static JsonResult success() {
        JsonResult result = new JsonResult();
        result.setStatus(true);
        result.setCode(HttpCodeEnum.SUCCESS.getCode());
        result.setMessage(HttpCodeEnum.SUCCESS.getMessage());
        return result;
    }

    /**
     * 请求失败
     *
     * @return error
     */
    public static JsonResult error() {
        JsonResult result = new JsonResult();
        result.setStatus(false);
        result.setCode(HttpCodeEnum.ERROR.getCode());
        result.setMessage(HttpCodeEnum.ERROR.getMessage());
        result.data(null);
        return result;
    }

    /**
     * 设置 状态码 和 信息 (一)
     *
     * @param code 状态码
     * @param message 信息
     * @return JsonResult
     */
    public JsonResult codeAndMessage(Integer code, String message) {
        this.setCode(code);
        this.setMessage(message);
        return this;
    }

    /**
     * 设置 状态码 和 信息 (二)
     *
     * @param httpCodeEnum 枚举信息
     * @return JsonResult
     */
    public JsonResult codeAndMessage(HttpCodeEnum httpCodeEnum) {
        this.setCode(httpCodeEnum.getCode());
        this.setMessage(httpCodeEnum.getMessage());
        return this;
    }

    /**
     * 设置 数据 (一)
     *
     * @param key key
     * @param value value  ==> Object
     * @return JsonResult
     */
    public JsonResult data(String key, Object value) {
        this.data.put(key, value);
        return this;
    }

    /**
     * 设置 数据 (二)
     *
     * @param data Map 集合
     * @return JsonResult
     */
    public JsonResult data(Map<String, Object> data) {
        this.setData(data);
        return this;
    }

}

@Data注解为Lombok工具类库中的注解,提供类的get、set、equals、hashCode、canEqual、toString方法,使用时需配置Lombok,如不配置请手动生成相关方法。

  • 定义了统一返回类后,controller层返回数据时统一使用JsonResult.success()方法封装
@RestController
@Tag(name = "测试")
@RequestMapping("/test")
public class UserController {

    @GetMapping("test1")
    public JsonResult data() {
        HashMap<String, String> map = new HashMap<>();
        map.put("username", "admin");
        map.put("password", "123456");
        return JsonResult.success().data("list", map);
    }

}
  • 例如在以上代码中,我们的需求是查询测试用户信息,我们调用这个test1接口就返回了以下的结果:
{
    "code": 200,
    "status": true,
    "message": "操作成功!",
    "data": {
        "list": {
            "password": "123456",
            "username": "admin"
        }
    }
}

二、自定义异常的基本步骤

1. 解析异常的使用

1. 异常分类

  • java异常体系结构图
运行时异常: RuntimeException及其直接间接子类
编译时异常: 非RuntimeException及其直接间接子类
Exception异常
Throwable
Error

2. 为什么处理异常

  • 处理异常,可以让程序在发生异常时不中断,提高代码的健壮性、容错性

3. 什么时候用异常

  1. 系统自动抛出异常
  2. 程序员手动抛出异常

4. 如何处理异常(两种方法)

  1. try - catch 直接解决异常
  2. throws 向上抛异常

2. 自定义业务异常处理

  • 很多时候我们在处理业务逻辑的时候需要抛出一些异常,例如查询某个用户信息,如果用户根本不存在,则需要抛出异常

手动抛出异常的步骤:

  1. 自定义异常类,extends继承Excepion(编译时异常)或者RunTimeException(运行时异常)
  2. 自定义构造方法,无参、带参都可,传入异常信息
  3. 上抛throws异常
/**
 * 业务异常处理类
 */
@Getter
public class BusinessException extends RuntimeException {

    /**
     * 状态码
     */
    private final Integer code;

    /**
     * 报错信息
     */
    private final String message;

    /**
     * 全参构造方法
     *
     * @param code    状态码
     * @param message 报错信息
     */
    public BusinessException(Integer code, String message) {
        this.code = code;
        this.message = message;
    }

    /**
     * 构造方法
     *
     * @param message 报错信息
     */
    public BusinessException(String message) {
        this(HttpCodeEnum.ERROR.getCode(), message);
    }

    /**
     * 构造方法
     * @param httpCodeEnum http枚举类
     */
    public BusinessException(HttpCodeEnum httpCodeEnum) {
        this(httpCodeEnum.getCode(), httpCodeEnum.getMessage());
    }

}

3. 自定义全局异常处理

  • 创建GlobalException类,添加@RestControllerAdvice注解
  • 创建一个方法,方法上添加@ExceptionHandler注解,指定异常类型,分别处理不同的异常

@RestControllerAdvice:是一个 Spring Boot 中用于全局异常处理的注解。它结合了@ControllerAdvice 和@ResponseBody 两个注解的功能,可以用于处理所有控制器中抛出的未处理异常,并返回自定义的响应数据
@ExceptionHandler:是异常处理器,两个结合表示当出现异常的时候执行某个通知
Exception.class:是 Java 中的一个类对象,它表示了 java.lang.Exception 类。Exception 是所有异常类的父类,它包含了一些通用的方法和属性,例如异常信息、异常堆栈跟踪等

/**
 * 全局异常信息处理类
 */
@Slf4j
@RestControllerAdvice
public class GlobalException {

    /**
     * 处理业务异常
     *
     * @param e BusinessException
     * @return businessException
     */
    @ExceptionHandler(value = BusinessException.class)
    public JsonResult businessException(BusinessException e) {
        log.error("业务异常 => code: {}, 原因是: {}", e.getCode(), e.getMessage());
        return JsonResult.error().codeAndMessage(e.getCode(), e.getMessage());
    }

    /**
     * 处理未知(600)异常
     *
     * @param e otherException
     * @return globalException
     */
    @ExceptionHandler(value = Exception.class)
    public JsonResult globalException(Exception e) {
        log.error("未知(600)异常 => 原因是: {}", e.getMessage());
        return JsonResult.error().codeAndMessage(HttpCodeEnum.RC600);
    }

}

需要注意的是一个异常只会被捕获一次,比如空指针异常,只会被第二个方法捕获,处理之后不会再被最后一个方法捕获。当上面两个方法都没有捕获到指定异常时,最后一个方法指定了@ExceptionHandler(value = Exception.class)就可以捕获到所有的异常,相当于if elseif else语句

分别测试自定义异常、空指针异常以及其他异常:

  1. 自定义异常
  • 接口:
@RestController
@RequestMapping("/test")
public class TestController {

    /**
     * 测试业务异常
     */
    @PostMapping("/test")
    public void test() {
        int i = 0;
        throw new BusinessException(HttpCodeEnum.RC403);
    }

}
  • 返回json:
{
    "code": 403,
    "status": false,
    "message": "您没有操作权限!",
    "data": null
}
  • 控制台日志:

在这里插入图片描述

  1. 空指针异常:
  • 接口:
    /**
     * 测试空指针异常
     */
    @PostMapping("/test1")
    public void test1(int id, String name) {
        boolean equals = name.equals("id");
        System.out.println("id: " + id + ", name: " + name);
    }
  • 返回json:
{
    "code": 400,
    "status": false,
    "message": "参数格式不合法,请检查后重试!",
    "data": null
}
  • 控制台日志:

在这里插入图片描述

  1. 其他异常:
  • 接口:
    /**
     * 测试其他异常
     */
    @PostMapping("/test2")
    public void test2() {
        int i = 1 / 0;
    }
  • 返回json:
{
    "code": 600,
    "status": false,
    "message": "服务器繁忙或服务器错误,请稍后再试!",
    "data": null
}
  • 控制台日志:

在这里插入图片描述

4. 处理404与405异常

1. 处理404异常

首先,我们在application.yml配置文件增加以下配置项:

spring:
  #  关闭默认的静态资源路径映射
  web:
    resources:
      add-mappings: false
  • 现在当我们请求一个不存在的接口是,控制台会报noHandlerFound警告,然后被全局异常处理捕获到,并统一返回
  • 控制台日志:

在这里插入图片描述

  • 返回json:
{
    "code": 600,
    "status": false,
    "message": "服务器繁忙或服务器错误,请稍后再试!",
    "data": null
}

  • 当发生404错误时,我们可以在全局异常处理中单独对NoHandlerFoundException异常进行处理
    /**
     * 处理页面不存在(404)异常
     *
     * @param e HttpServletRequest
     * @return noHandlerFoundException
     */
    @ExceptionHandler(value = NoHandlerFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public JsonResult noHandlerFoundException(HttpServletRequest e) {
        log.error("请求地址错误(404)异常 => 请求方式: {}, 请求地址: {}", e.getMethod(), e.getServletPath());
        return JsonResult.error().codeAndMessage(HttpCodeEnum.RC404);
    }

在上面中,我们使用@ExceptionHandler(value = NoHandlerFoundException.class)单独捕获处理404异常,统一返回格式中code也设置为404。你也可以使用@ResponseStatus(HttpStatus.NOT_FOUND)注解指定http状态码为404

  • 现在当我们再次发生404异常时,返回json如下:
{
    "code": 404,
    "status": false,
    "message": "未找到您请求的资源!",
    "data": null
}
  • 控制台日志:

在这里插入图片描述

2. 处理405异常

  • 405错误对应的异常为HttpRequestMethodNotSupportedException
    /**
     * 处理请求方式错误(405)异常
     *
     * @param e HttpServletRequest
     * @return httpRequestMethodNotSupportedException
     */
    @ExceptionHandler(value = HttpRequestMethodNotSupportedException.class)
    public JsonResult httpRequestMethodNotSupportedException(HttpServletRequest e) {
        log.error("请求方式错误(405)异常 => 请求方式: {}, 请求地址: {}", e.getMethod(), e.getServletPath());
        return JsonResult.error().codeAndMessage(HttpCodeEnum.RC405);
    }

  • 返回json:
{
    "code": 405,
    "status": false,
    "message": "请求方式错误,请检查后重试!",
    "data": null
}

  • 控制台日志:

在这里插入图片描述


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值