Java Spring Boot 规范统一响应体结构

发布时间 2023-12-14 17:03:36作者: 进击的davis

在进行 web开发 中,如果我们的返回数据不统一,会是啥样呢,比如像下面这种:

@RestController
public class DemoController {
    @GetMapping("/haha")
    public Object haha() {
        return "";
    }

    @GetMapping("/heihei")
    public Object heihei() {
        Map<String, Object> data = new HashMap<>();
        data.put("one", 1);
        return data;
    }

}

如果返回数据格式不统一,对前后端的数据交互不太友好。另外为了代码更加规范、好维护,我们通常都采取统一的响应体结构。

通常来说不可缺少的三要素:

  • code,响应码,标识不同响应
  • message,响应信息,作为描述性的
  • data,如果有传输的数据,将数据放在data中

有的人会说了,code响应码不是有 httpStatusCode 吗,为何还要指定自己的 code 的呢?其实,这里主要是为了更好地区分不同的响应数据,比如同样的 200 状态码,只要后台正常运行,你能知道到底啥问题吗,但是如果我们限定 0表示成功,非0表示失败,甚至更加具体的如传输文件过大,字段不合规,数据库连接超时等,同样 状态码都是 200 就很难知道到底是什么问题。

为了更加统一,我们通过 枚举 的方式限定 成功、失败 的具体 code和msg,然后在 统一的响应体的规定结构,再写几个不同参数的响应方法,基本就够用了,如果有新的错误等,我们也可以在 枚举 中再添加,大神请勿喷。

接下来我们按照 枚举和响应体类及示例的顺序,演示规范处理。

为了减少代码的书写,我们引入 lombok,依赖如下:

<!-- Lombok 工具 -->
<!-- @Data && slf4j -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

code与msg的枚举

package com.example.springbootuniformrespandexceptionhandle.params;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public enum RespCodeEnums {
    SUCCESS(0, "Success"),
    FAIL(1, "Operation failed."),

    FILE_OVER_SIZE(1000, "File over size."),
    FILE_FORMAT_WRONG(1001, "File format is wrong."),
    PARAM_ERROR(1003, "Request param is wrong."),
    JSON_PARSE_ERROR(1004, "Json parse error."),
    UNAUTHENTICATION_ERROR(1100, "Not authenticate."),
    UNAUTHORIZED_ERROR(1101, "Not Authorized."),
    REQUEST_METHOD_ERROR(1201, "Request method wrong."),
    API_NOT_EXIST(1301, "Api not exist."),
    TIMESTAMP_ERROR(1401, "Timestamp wrong."),
    DATA_NOT_EXIST(1501, "Data not exist."),

    REDIS_CONN_ERROR(2001, "Redis Conn wrong."),
    REDIS_OPER_ERROR(2002, "Redis operate wrong."),
    MYSQL_CONN_ERROR(2101, "MySQL Conn wrong."),
    MYSQL_QUERY_TIMEOUT(2102, "MySQL Query timeout."),
    MYSQL_DATA_NOT_EXIST(2103, "MySQl Cannot find data."),
    ELASTICSEARCH_CONN_ERROR(2201, "Redis Conn wrong."),
    ELASTICSEARCH_QUERY_TIMEOUT(2202, "Elasticsearch query timeout.");

    private int code;

    private String msg;
}

上面的 枚举 只是很小的一部分,实际的项目开发具体分析,这部分具体内容请结合自己项目编码。

封装统一的响应体

package com.example.springbootuniformrespandexceptionhandle.params;

import lombok.Data;

@Data
public class RespInfo<T> {
    private int code;

    private String msg;

    private T data;

    /**
     * 全参方法
     * @param code
     * @param msg
     * @param data
     * @param <T>
     * @return
     */
    private static <T> RespInfo<T> response(int code, String msg, T data) {
        RespInfo<T> respInfo = new RespInfo<>();
        respInfo.setCode(code);
        respInfo.setMsg(msg);
        respInfo.setData(data);

        return respInfo;
    }

    /**
     * 缺失data方法
     * @param code
     * @param msg
     * @param <T>
     * @return
     */
    private static <T> RespInfo<T> response(int code, String msg) {
        RespInfo<T> respInfo = new RespInfo<>();
        respInfo.setCode(code);
        respInfo.setMsg(msg);

        return respInfo;
    }

    public static <T> RespInfo<T> success() {
        return response(RespCodeEnums.SUCCESS.getCode(), RespCodeEnums.SUCCESS.getMsg());
    }

    public static <T> RespInfo<T> success(T data) {
        return response(RespCodeEnums.SUCCESS.getCode(), RespCodeEnums.SUCCESS.getMsg(), data);
    }

    public static <T> RespInfo<T> fail() {
        return response(RespCodeEnums.FAIL.getCode(), RespCodeEnums.FAIL.getMsg());
    }

    public static <T> RespInfo<T> fail(int code, String msg) {
        return response(code, msg);
    }

    public static <T> RespInfo<T> fail(int code, String msg, T data) {
        return response(code, msg, data);
    }
}

我们主要限定三个字段,code、msg、data,其中 data 具体的数据结构我们不太清楚,所以这里引入泛型,在实际的项目开发中,这个也很常见。

另外,我们实现了 成功返回的默认、带数据的成功返回,失败返回默认。待具体描述的失败返回等,这些看下代码就一目了然。

示例

这里我们实现一个简单的用户信息管理的控制器类:

package com.example.springbootuniformrespandexceptionhandle.controller;

import com.example.springbootuniformrespandexceptionhandle.model.User;
import com.example.springbootuniformrespandexceptionhandle.params.RespCodeEnums;
import com.example.springbootuniformrespandexceptionhandle.params.RespInfo;
import org.springframework.web.bind.annotation.*;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@RestController
@RequestMapping("/user")
public class UserController {
    // 存在于运行内存中
    private static Map<String, Object> users = new ConcurrentHashMap<>();

    @PostMapping("/add")
    public RespInfo addUser(@RequestBody User user) {
        users.put(user.getName(), user);

        return RespInfo.success();
    }

    @PostMapping("/info")
    public RespInfo getInfo(@RequestBody User user) {
        if (!users.containsKey(user.getName())) {
            return RespInfo.fail(RespCodeEnums.DATA_NOT_EXIST.getCode(), RespCodeEnums.DATA_NOT_EXIST.getMsg());
        }

        return RespInfo.success(users.get(user.getName()));
    }

    @GetMapping("/allUsers")
    public RespInfo getAllInfo() {
        return RespInfo.success(users);
    }

}

可以添加用户,查询某个用户信息,查询所有用户信息,相关的用户 bean如下:

package com.example.springbootuniformrespandexceptionhandle.model;

import lombok.Data;

@Data
public class User {
    private String name;

    private int age;

    private String phone;
}

以下是测试情况:

未查询到数据

b05ae2dd246b64f4bac16e085863bb9.png

查询到数据

dec7c8b75fd4eb339bc95f79f72631b.png

获取所有数据

9986e9683fd69cbb006393c02fa6517.png