Skip to content
On this page

API 响应规范

本文以问答形式介绍。

HTTP 状态码还是code表示状态?

不用争,不用犟,2 个流派都可以。code流派的做法是,HTTP 状态码一律200,然后code只要不是成功的那个值(比如2000什么的),就默认表示出错状态。所以code流派其实也能用。

响应错误包括几类?

运行时级别错误框架级别错误业务级别错误
原因后端程序员造成的错误前端程序员造成的错误不符合用户预期
举例最著名的是空指针异常前端少传参数、数据类型错误等登录过期、删除失败等
HTTP 状态码500 等,由 Spring Boot 捕获并返回同左某些错误也可被 Spring Boot 捕获,其他错误由程序员自己决定

前端是否应考虑运行时级别错误?

不应考虑。

可能你会说,你见过一些网站抛出过运行时错误。没错,我连 Nginx 报错都见过,我也见过整屏的 Java 报错,但这些网站都是渣滓网站,如果比烂而不是比优,那么本文也没有讨论意义。

我们应当考虑如何避免一切运行时错误,而不是考虑前端如何优雅展示运行时错误。

前端是否应考虑框架级别错误?

Spring Boot 是框架,抛出的是框架级别错误,也就是说大部分属于前后端 Bug 和故障,既然是 Bug 就要早早解决,而不是总想着抛给前端让前端去提示。在解决了这些 Bug 之后,Spring Boot 依然可能返回的非200状态码应当只有401

后端是否应细分 HTTP 状态码?

需要细分比如201403404502吗?除非你的项目是一个纯资源型网站,那么你可以去细分,否则不用。99.99%的项目都不用去细分。

你只需要使用200401500这 3 个状态码就足够,你以为的那些七七八八的状态,都可以用这 3 个状态码概括。

前端业务如何区分200/401/500

  • 200:在用户预期内的响应,一律用200状态码。包括空列表也是200状态码。

  • 500:在用户预期外的响应,一律用500状态码。包括业务逻辑导致的预期外,比如无权限访问、资源不存在、创建失败、修改失败、删除失败等情况。

  • 401:用户登录态过期的情况,使用401状态码。

业务是否应细分错误code

比如微信小程序云开发的ErrCode 列表有通用的几十个 ErrCode,每个 API 还有自己的几十个 ErrCode,那么我们小公司小团队是否也应该这样做?

这就引出一个细节:面向开发者的 API 和面向用户的 API 是两码事。

微信小程序云开发的 API 是面向开发者的,这意味着 API 不是本公司在维护,没办法联调,只能单向调试,为方便调试,ErrCode 详尽为宜。但是面向用户的 API 意味着前后端都是本公司在维护,可以联调,所以并不需要如此详尽的 ErrCode。

那么是不是code可以统一成500呢?也不是。事实上:

  • 微信小程序云开发的通用 ErrCode,相当于 Spring Boot 的框架级别报错

  • 微信小程序云开发的单个 API 的 ErrCode,相当于 Spring Boot 的业务级别报错

所以说,我们的项目的通用 ErrCode 直接依赖 Spring Boot 返给前端,而后端程序员只需要抛出业务级别错误就行了,同时,还是那句话,没必要搞那么多code

总结 HTTP 状态码和code的使用

本节的讨论是基于上文的精简状态码的前提下讨论。加粗字体的是两个流派的区别。

利用 HTTP 状态码流派

状态码含义code是否有意义
200成功无意义
500可能是业务级别报错,全部设为500有意义,应细分
500可能是 Spring Boot 的框架级别报错无意义
其他值Spring Boot 的框架级别报错无意义

不用 HTTP 状态码流派

状态码含义code是否有意义
200可能是成功有意义,可设为2000
200可能是业务级别报错有意义,可设为除了2000以外的值
500可能是 Spring Boot 的框架级别报错无意义
其他值Spring Boot 的框架级别报错无意义

公司如何内定成功和失败code

也有很多流派,哪个流派都行,数字而已,不重要。举两个例子:

表示成功表示失败
例一0-1-2等负值
例二200005000150002等大于 50000 的数值

axios 拦截器怎么写?

利用 HTTP 状态码流派

伪代码如下:

js
service.interceptors.response.use(
  (response) => {
    const code = response.status;
    if (code === 200) {
      // 下面这个 data 不是响应体里的data,而是整个响应体
      return response.data;
    } else if (code === 401) {
      removeToken();
      router.replace({ name: 'Login' });
    } else {
      return Promise.reject(response.data.code);
    }
  },
  () => {
    Message.alert('网络不给力');
  }
);

不用 HTTP 状态码流派

伪代码如下:

js
service.interceptors.response.use(
  (response) => {
    const code = response.data.code;
    if (/* code 匹配上成功 code */) {
      return response.data;
    }
    // 你的框架可能是用状态码返回`401`,也可能是用`code`返回,根据情况改
    else if (response.status === 401) {
      removeToken();
      router.replace({ name: 'Login' });
    } else {
      return Promise.reject(code);
    }
  },
  () => {
    Message.alert('网络不给力');
    return Promise.reject('Network Error');
  }
);

注意事项

  1. 上方代码实际上没有考虑框架级别报错(只考虑了401错误),因为生产环境不应当出现框架级别报错,如果出现,要么是 Bug,要么是服务器故障,没必要针对 Bug 写业务逻辑。

  2. 拦截器的失败分支,即弹窗又返回了Message.alert('网络不给力')return Promise.reject(),用意是统一弹出“网络不给力”,但有些页面区块需要显示重试按钮,因此需要获取网络不给力的反馈,所以既弹窗又return。这时.then()的第二个回调既可能收到错误code,也可能收到Network Error,根据这个可以判断是业务错误还是网络错误。

  3. 拦截器的失败分支也包含网络连接超时的情况,为什么不提示“网络连接超时”呢?因为用户并不懂这是什么意思,也没有指导下一步的作用,所以统称“网络不给力”就够了。

前端应该无脑弹出 msg 字段吗?

显然,不应该。后端返回的msg应当是简洁又无歧义的文字,甚至可以是英文,即便是中文,前端程序员也不能将前端弹出的内容交给后端程序员去控制,因为后端只负责 API 功能,前端负责 API 呈现。

为何某些一站式框架要这么干?

某些前端一站式框架在 axios 响应拦截器无脑弹出错误消息,这怎么解释?

  1. 那些框架是后台管理框架,业务逻辑很“单纯”,弹出之后没有后续逻辑,所以无脑弹出也有一定的道理。

  2. 它弹出错误消息之后,继续把错误内容抛给了.then()的第 2 个回调,如果真的需要后续业务逻辑,可以继续写逻辑。

  3. 那些框架是给全栈开发者使用的,开发者后端前端一肩挑,当然可以仔细思索msg的内容,写在后端也无妨。

所以后端管理系统如果不是足够复杂的话,可以前端无脑弹msg内容。

普适的话前端怎么处理msg

msg内容仅供参考,前端弹出的文字还得是前端程序员自己写,然后在.then()的第 2 个回调里弹出。

js
this.getList(this.params).then(
  (response) => {
    // 处理成功响应
  },
  (code) => {
    switch (code) {
      case -1:
        // ...
        break;
      // ...
      case 'Network Error':
        // ...
        break;
    }
  }
);

TIP

对于后台管理系统,业务失败后通常没有后续逻辑,即便无网络或连接超时也已经在拦截器做了提示,因此可以直接省掉失败分支。

杨亮的前端解决方案