6

引言

最近发现了使用标准的HTTP状态码出现了无法准确表达业务的问题。

登录状态,同样是401未授权,需要表达用户名或密码错误、验证码错误等多种场景,历史的处理方式无法满足场景。

考虑参照各大厂商规范,制定新的接口响应规范。

接口设计

状态码 VS 自定义Code

以国内腾讯、阿里、京东、微博为首的API响应规范如下:

在《阿里巴巴开发手册(泰山版)》中已经给出了通用的业务code规范。

{
  "code": 0,
  "message": "OK",
  "data": {}
}

所有接口的状态码都是200,通过code区分业务。在微信小程序里尤甚,wx.request的响应,401500也算success,只有无响应(数据包丢了)才走error

最初的实现

最初设计尝试使用自定义code的形式,需要从原响应架构迁移,为了降低迁移成本,设计方案如下(为了让图片看起来更清晰,换成了白色主题):

期望结果,在原API不变的情况下:

@GetMapping
public List<Student> 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尝试:

image.png

image.png

image.png

框架调用API方法处理HTTP请求,返回结果需要通过实现HandlerMethodReturnValueHandler接口的相关类中的handleReturnValue方法进行处理。Spring Boot中有诸多响应值处理器,框架根据当前返回的是页面、普通对象还是响应式对象来调用相关的处理器。

核心思想就是自定义处理器,将返回值包装为code格式的类型。

WebMvcConfigurer接口中已经定义过相关添加返回值处理器的方法addReturnValueHandlers

void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> 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

image.png

核心思想是使用装饰器与RequestMappingHandlerAdapter进行实现,具体代码请参考https://www.tuicool.com/artic...

注:个人觉得代码写得不规范,注意适当参考,使用ListIteratorApplicationListener实现会更优雅。

新规范

在综合参考了GoogleMicrosoftTwitter的接口设计后,决定还是放弃国内的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区分业务状态。


张喜硕
2.1k 声望423 粉丝

浅梦辄止,书墨未浓。