SpringBoot 统一接口封装
半塘 2024/1/21 SpringBoot 框架
# 1、RESTful API
- Restful参考:什么是 RESTful API? (opens new window)
- 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
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
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 BusinessResponseStatus {
SUCCESS("200", "success"),
FAIL("500", "服务器异常"),
HTTP_STATUS_200("200", "success"),
HTTP_STATUS_400("400", "请求异常"),
HTTP_STATUS_401("401", "no authentication"),
HTTP_STATUS_403("403", "no authorities"),
HTTP_STATUS_500("500", "server error"),
HTTP_STATUS_10001("10001", "系统繁忙"),
HTTP_STATUS_10002("10002", "重复请求,请稍后再试试");
/**
* 响应码
*/
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
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 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(BusinessResponseStatus.SUCCESS.getDescription())
.status(BusinessResponseStatus.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(BusinessResponseStatus.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
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
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
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
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
2
3
4
5
6
7
8
9
10
11
12
13
测试响应结果