SpringBoot 统一接口封装

1/21/2024 SpringBoot 框架

# 1、RESTful API

  • REST 是 Representational State Transfer 的缩写,如果一个架构符合 REST 原则,就称它为 RESTful 架构。
  • RESTful 架构可以充分的利用 HTTP 协议的各种功能,是 HTTP 协议的最佳实践。
  • RESTful API 是一种软件架构风格、设计风格,可以让软件更加清晰,更简洁,更有层次,可维护性更好。
  • RESTful API 请求格式案例

  • GET/zoos列出所有动物园
  • POST/zoos新建一个动物园
  • GET/zoos/:id获取某个指定动物园的信息
  • PUT/zoos/:id更新某个指定动物园的全部信息
  • PATCH/zoos/:id更新某个指定动物园的部分信息
  • DELETE/zoos/:id删除某个动物园
  • GET/zoos/:id/animals列出某个指定动物园的所有动物
  • DELETE/zoos/:id/animals/:id删除某个指定动物园的指定动物
  • RESTful API 服务器响应包含哪些内容?

  • 状态行:常见状态码:200、201、400、404。可以参考:HTTP 状态码 | 菜鸟教程 (opens new window)
  • 信息正文:响应数据,可以以 XML 或 JSON 格式。
  • :响应也包含有关响应的头或元数据。提供有关响应的更多背景内容,包含服务器、编码、日期和内容类型等信息。

响应案例

{
  "timestamp": 1627485822000,
  "status": 200,
  "message": "success",
  "data": {
    "userId": 1,
    "userName": "张三"
  }
}
1
2
3
4
5
6
7
8
9

# 2、统一接口封装好处

我们看一下没有封装和封装之后的结果对比:

// 未封装结果
{
  "userId": 1,
  "userName": "张三"
}

// 封装后结果
// 正常成功返回结果
{
  "timestamp": 1627485822000,
  "status": 200,
  "message": "success",
  "data": {
    "userId": 1,
    "userName": "张三"
  }
}

// 异常返回结果 
{
  "timestamp": 100,
  "status": 503,
  "message": "服务器异常",
  "data": null
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

结论

  • 现在大多数项目都采用前后端分离的开发模式,封装后这种模式有利于前端开发者进行开发和封装,同时,当出现问题时,可以提供统一的响应编码和信息。
  • 符合Restful API规范,更加清晰,更简洁,更有层次,可维护性更好。

# 3、实现案例

# 3.1、封装状态码

如果有其他状态码,可以继续添加。

@Getter
@AllArgsConstructor
public enum ResponseStatus {

    SUCCESS("200", "success"),
    FAIL("500", "failed"),
    HTTP_STATUS_200("200", "success"),
    HTTP_STATUS_400("400", "request error"),
    HTTP_STATUS_401("401", "no authentication"),
    HTTP_STATUS_403("403", "no authorities"),
    HTTP_STATUS_500("500", "server error");

    /**
     * 响应码
     */
    private final String responseCode;

    /**
     * 相应描述
     */
    private final String description;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 3.2、返回内容封装

@Data
@Builder
public class ResponseResult<T> {

    /**
     * 响应时间
     */
    private long timestamp;

    /**
     * 响应码
     */
    private String status;

    /**
     * 结果信息
     */
    private String message;

    /**
     * 返回数据
     */
    private T data;

    /**
     * 成功并返回空
     * @param <T>
     * @return
     */
    public static <T> ResponseResult<T> success() {
        return success(null);
    }

    /**
     * 成功并返回数据data
     * @param data
     * @param <T>
     * @return
     */
    public static <T> ResponseResult<T> success(T data) {
        return ResponseResult.<T>builder().data(data)
                .message(ResponseStatus.SUCCESS.getDescription())
                .status(ResponseStatus.SUCCESS.getResponseCode())
                .timestamp(System.currentTimeMillis())
                .build();
    }

    /**
     * 失败并返回信息
     * @param message
     * @param <T>
     * @return
     */
    public static <T extends Serializable> ResponseResult<T> fail(String message) {
        return fail(null, message);
    }

    /**
     * 失败并返回数据与信息
     * @param data
     * @param message
     * @param <T>
     * @return
     */
    public static <T> ResponseResult<T> fail(T data, String message) {
        return ResponseResult.<T>builder().data(data)
                .message(message)
                .status(ResponseStatus.FAIL.getResponseCode())
                .timestamp(System.currentTimeMillis())
                .build();
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73

# 3.3、测试返回调用

@RestController
@RequestMapping("/student")
public class StudentController {

    @Autowired
    private StudentServiceImpl studentService;

    @RequestMapping("/list2")
    public ResponseResult<List<StudentPO>> userList2(){
        return ResponseResult.success(studentService.list());
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13

测试响应结果

# 3.4、注解方式返回调用

我们每次返回结果时,都要用ResponseResult来封装结果,很麻烦,我们可以使用注解方式来简化这个操作。

  • 创建@ResponseResultApi注解,我们希望在类或者方法上都生效
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResponseResultApi {
}
1
2
3
4
5
  • 创建RespinseResultAdvice继承ResponseBodyAdvice拦截处理结果

ResponseBodyAdvice是SpringMVC提供的一个拦截处理返回结果的接口,我们可以通过实现该接口来拦截处理返回结果。

@ControllerAdvice // 声明为全局响应处理
public class RespinseResultAdvice implements ResponseBodyAdvice<Object> {

    /**
     * 返回true表示对响应进行处理
     * @param methodParameter
     * @param converterType
     * @return
     */
    @Override
    public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> converterType) {
        // 是否包含@ResponseResult注解
        boolean hasMethodAnnotation = methodParameter.hasMethodAnnotation(ResponseResultApi.class);
        // 方法不包含@ResponseResult注解再判断方法所属类是否包含@ResponseResult注解
        if (!hasMethodAnnotation) {
            ResponseResultApi anno = methodParameter.getMethod() == null ? null : methodParameter.getMethod().getDeclaringClass().getAnnotation(ResponseResultApi.class);
            if (anno != null) {
                hasMethodAnnotation = true;
            }
        }

        return hasMethodAnnotation;
    }

    /**
     * 响应处理
     * @param body
     * @param methodParameter
     * @param selectedContentType
     * @param selectedConverterType
     * @param request
     * @param response
     * @return
     */
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        // 已经封装了就直接返回
        if(body instanceof ResponseResult){
            return body;
        }
        if (body instanceof String) {
            // 解决返回值为字符串时,不能正常包装
            return JSON.toJSONString(ResponseResult.success(body));
        }
        return ResponseResult.success(body);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
  • 测试注解返回调用
@RestController
@RequestMapping("/student")
public class StudentController {

    @Autowired
    private StudentServiceImpl studentService;

    @ResponseResultApi
    @RequestMapping("/list3")
    public List<StudentPO> userList3(){
        return studentService.list();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

测试响应结果