引言
最近发现了使用标准的HTTP
状态码出现了无法准确表达业务的问题。
登录状态,同样是401
未授权,需要表达用户名或密码错误、验证码错误等多种场景,历史的处理方式无法满足场景。
考虑参照各大厂商规范,制定新的接口响应规范。
接口设计
状态码 VS 自定义Code
以国内腾讯、阿里、京东、微博为首的API
响应规范如下:
在《阿里巴巴开发手册(泰山版)》中已经给出了通用的业务code
规范。
{
"code": 0,
"message": "OK",
"data": {}
}
所有接口的状态码都是200
,通过code
区分业务。在微信小程序里尤甚,wx.request
的响应,401
、500
也算success
,只有无响应(数据包丢了)才走error
。
最初的实现
最初设计尝试使用自定义code
的形式,需要从原响应架构迁移,为了降低迁移成本,设计方案如下(为了让图片看起来更清晰,换成了白色主题):
期望结果,在原API
不变的情况下:
@GetMapping
public List getAll() {
}
返回结果从:
[{
"id": 1,
"name": "Hello Kitty"
}, {
"id": 2,
"name": "史努比"
}]
变更为:
{
"code": 0,
"message": "OK",
"data": [{
"id": 1,
"name": "Hello Kitty"
}, {
"id": 2,
"name": "史努比"
}]
}
其他code
不为0
的情况,统一在异常处理中进行变更。
经过一系列的DEBUG
尝试:
框架调用API
方法处理HTTP
请求,返回结果需要通过实现HandlerMethodReturnValueHandler
接口的相关类中的handleReturnValue
方法进行处理。Spring Boot
中有诸多响应值处理器,框架根据当前返回的是页面、普通对象还是响应式对象来调用相关的处理器。
核心思想就是自定义处理器,将返回值包装为code
格式的类型。
在WebMvcConfigurer
接口中已经定义过相关添加返回值处理器的方法addReturnValueHandlers
:
void addReturnValueHandlers(List handlers) {
}
经测试无效,新加的handler
添加到List
中后,Spring
其余的handler
会添加到自定义handler
的前面,因Spring
选择处理器是根据最先匹配算法进行选择,所以选择不到自定义的处理器。
private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
boolean isAsyncValue = isAsyncReturnValue(value, returnType);
for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
continue;
}
if (handler.supportsReturnType(returnType)) {
return handler;
}
}
return null;
}
Spring Boot
默认存在15
个返回值处理器,而处理普通对象json
响应的处理器为RequestResponseBodyMethodProcessor
,另一种实现方案就是替换掉默认的RequestResponseBodyMethodProcessor
。
核心思想是使用装饰器与RequestMappingHandlerAdapter
进行实现,具体代码请参考https://www.tuicool.com/artic...
注:个人觉得代码写得不规范,注意适当参考,使用ListIterator
与ApplicationListener
实现会更优雅。
新规范
在综合参考了Google
、Microsoft
、Twitter
的接口设计后,决定还是放弃国内的API
形式,跟随Google
的设计规范,仍然沿用状态码的形式。(有人说Microsoft
的规范更好,但是我没太看懂)
Google API
设计规范:https://cloud.google.com/apis...
请求成功(状态码为200
时)的设计不变:
[{
"id": 1,
"name": "Hello Kitty"
}, {
"id": 2,
"name": "史努比"
}]
失败时添加state
状态与message
错误详细信息:
{
"state": "INVALID_ARGUMENT",
"message": "密码不能为空"
}
HTTP 状态码 | 状态 | 错误详细信息 |
---|---|---|
400 | INVALID_ARGUMENT |
密码不能为空 |
401 | CODE_INVALID |
验证码无效 |
401 | CREDENTIALS_INVALID |
用户名或密码错误 |
控制器负责抛出携带详细错误信息的异常,异常处理器根据异常类型确定返回的状态码与业务状态。
@ExceptionHandler(InvalidArgumentException.class)
public HttpErrorResponse invalidArgumentExceptionHandler(InvalidArgumentException exception, HttpServletResponse response) {
return this.error(HttpErrorType.INVALID_ARGUMENT, exception, response);
}
private HttpErrorResponse error(HttpErrorType type, RuntimeException exception, HttpServletResponse response) {
response.setStatus(type.getCode());
return new HttpErrorResponse(type.getValue(), exception.getMessage());
}
总结
除非特殊要求,正常情况下,优先推荐选择标准HTTP
状态码形式设计API
,使用state
区分业务状态。