前后端接口规范

前言

面对项目的各种需求,使用口头约定与联调校对的前后端联调对接方式,前后端已经很难快速应对各种需求与需求变更,协作效率相对低下。为此,决定制定对接规范来提升前后端的协作效率,提高项目开发速度。

目标

  1. 尽可能的缩小沟通成本,用最少的交流来确定大部分的工作
  2. 花最少的时间写文档,尽可能的让协作人员看懂所有文档内容
  3. 熟练后不用看文档,也能知道各种接口大概的逻辑与对应的操作
  4. 提高代码可读性

前后端协作流程规范

前后端团队一定时间的合作,一般可以大致规划出对于双方开发效率较优的协作流程. 将这个流程再进行优化便可落实为规范流程。

一个典型的前后端协作流程如下:

图片

  1. 需求分析。参与者一般有前后端、测试、以及产品. 由产品主持,对需求进行宣贯,接受开发和测试的反馈,确保大家对需求有一致的认知
  2. 系统设计。讨论应用的开发设计和功能模块,沟通技术点、难点、以及分工问题
  3. 设计接口文档。可以由前后端一起设计;或者由后端设计、前端确认是否符合要求
  4. 并行开发。前后端并行开发,在这个阶段,前端可以先实现静态页面; 或者根据接口文档对接口进行Mock, 来模拟对接后端接口
  5. 在联调之前,要求后端做好接口测试
  6. 测试环境联调。前端将接口请求代理到后端服务,进行环境联调

确定接口规范

首先应该确定的是接口规范。其实使用哪种接口标准是其次,重要的是统一,且要满足前后端的开发效率要求。对于越是庞大且复杂的项目,越是不建议后端去定义自己的接口标准,而应该去选择一些通用的、有标准定义接口形式, 例如:

  • RESTful: RESTful是目前使用最为广泛的API设计规范, 基于HTTP本身的机制来实现。该规范也是使用最广泛的API形式,所以社区上有很多工具来对RESTful接口进行文档化、测试和模拟。介绍链接
  • JSONRPC 这是一种非常简单、容易理解的接口规范。简单则不容易产生分歧,新手也可以很快接受。介绍链接
  • GraphQL 更为先进、更有前景的API规范。但是你要说服后端配合你使用这种标准可能很有难度。介绍链接

我们当前使用的接口规范已经确定,即 RESTful API 设计规范。

接口规范内容

由前端和后端一起协定接口规范的内容, 确定接口的地址(URL), 输入(request)和输出(response)格式, 必要的时候详细注释每一个字段的含义和数据类型,以下为规范具体内容:

接口地址和请求方式

接口地址即接口的 URL, 定义时使用相对路径(即不用带上域名信息), 建议分模块来定义, 大方向遵循 RESTful 风格。

关于接口地址,例如:

  • GET “/api/product/list” 表示获取产品列表信息
  • POST “/api/product/add” 表示添加产品
  • POST “/api/product/delete” 表示删除产品
  • POST “/api/product/edit” 表示编辑产品,即更新操作
  • GET “/api/refund/reasons” 表示获取退货原因
  • GET “/api/refund/orderlist” 表水获取退货订单列表,注意这里都是采用小写

如上,接口地址定义时使用相对路径(即不用带上域名信息), 采用分模块来定义,模块名后接上接口操作(主要是post方式的增删改)或者获取的是什么(主要是get方式的获取内容)。

项目后期可能会存在很多的模块,包括主模块和子模块的嵌套,还有资源需要多级分类,这样比较容易写出多层级的URL 接口,例如获取某个产品的某个sku 信息:

GET “/api/product/12/sku/2”

这种 URL 不利于扩展,语义也不够明确,更好的做法是除了第一级,其他级别都用查询字符串表达:

GET “/api/product/12?sku=2”

关于请求方式:

  • 获取信息尽量采用GET方式请求
  • 有涉及到增删改传递参数的尽量采用POST方式请求
  • PUT,PATCH 和 DELETE 三个请求方式项目中暂时没有采用,这里暂时不做约定

请求方式在当前项目中还是存在随后端人员随意设定情况,后续建议根据约定规范请求方式:用GET获取,POST新增,PUT(更新)和PATCH(部分更新),DELETE删除。

关于 api 版本控制:

  • 应该将API的版本号放入URL,采用多版本并存,增量发布的方式
  • 另一种做法是,将版本号放在HTTP头信息中
  • 项目当前还未使用 api 版本控制

接口参数

向接口传递参数时, 如果是少量参数可以作为 URL query string 追加到接口的 URL 中, 或者作为 Content-Type: application/x-www-form-urlencoded 放在请求体(body)中(即表单提交的方式),这是我们当前接口采用的提交方式。

对于复杂的接口参数(例如嵌套了多层的数据结构), 其实个人比较推荐在 HTTP 请求体(body)中包含一个 JSON 字符串作为接口的参数, 并设置 Content-Type: application/json; charset=utf-8 的接口提交方式。由于我们项目已经是采用上面的接口参数提交方式,这里便不做更改了。

接口内容约定(重点关注)

  • 接口必须返回统一的数据结构,参考接口返回的数据结构。

  • 接口查询不到数据时, 即空数据的情况下返回给前端怎样的数据:

    • 必须返回非 null 的对应数据类型初始值, 例如对象类型的返回空对象({}), 数组类型的返回空数组([]), 其他原始数据类型(String/Number/Boolean…)也使用对应的默认值。即必须明确数据类型,很多后端写的接口都是string和number不分的,如果没有规范,前端就需要针对这个属性字段做特殊处理,这也可能是潜在的bug。
    • 这样可以减少前端很多琐碎的非空判断, 直接使用接口中的数据,不会产生是否存在字段,是否有数值的隐藏bug,并且也可以减轻测试的工作量。
    • 例如:某个字段本身返回数字类型的数字,但是没有数据的时候却返回了一个空字符串,前端就会显示空字符串而不会显示0。
    • 再例如:获取 data 对象下的 name :data.name,如果 data 为 null, 可想而知会前端会报错 Uncaught TypeError: Cannot read property “name ” of null。
  • 返回数据中图片 URL 是完整的还是部分的:

    • https://www.maxthoven.com/upload/aaa.png 这就是完整的, 前端直接使用这个 URL。
    • /upload/bbb.png 这就是部分的, 一般省略域名部分, 前端需要自己拼接后才能使用 ‘https://www.maxthoven.com' + ‘/path/to/img.png’。
    • 由于开发经常分各种环境,图片的 URL 建议返回完整的 URL,除非图片分环境存储在不同的服务器资源目录下。
    • 如果存在资源服务器则最好采用资源服务器来存储图片。
  • 返回数据中页面跳转的 URL 是给完整的还是部分的:

    • 内部页面返回部分的, 或者只给相关参数的, 由前端自己拼接, 例如:只给出商品ID, 让前端自己拼接商品详情页的 URL。
    • 外部页面返回完整的, 例如:广告授权要跳转去亚马逊。
  • 返回数据中日期的格式, 是使用时间戳还是格式化好的文字:

    • 对于需要前端再次处理的日期值(例如根据日期计算倒计时), 可以使用时间戳(简单暴力), 例如: 1458885313711, 或者参考 Date.prototype.toJSON 提供 ISO 标准格式(需要考虑时区时)。
    • 对于纯展示用的日期值, 推荐返回为格式化好的文字或数字, 例如: 2019-10-29 08:39:11。
    • 避免每个端(例如H5/APP/小程序)都需要对时间做统一的格式化实现, 一旦需要调整, 需要各个端都调整一遍。
  • 对于需要特殊处理的数字,例如:金额千分位显示,若前端需要计算处理则后端返 Number 类型的数字,否则返回 String 类型的字符串。

  • 对于大数字(例如 Java 的 long 类型), 返回给前端时需要设置为字符串类型, 否则 JavaScript 会发生溢出, 造成得到的数值错误:

    • 例如: 返回 JSON 数据 {“id”: 362909601374617692} 前端拿到的值却是: 362909601374617660。
  • 分页参数和分页信息:

    • 统一分页的数据格式,即统一分页请求的参数和分页结果的数据结构。
    • 分页返回的数据结构详情见接口返回的数据结构。
  • 调用接口失败的常用错误码, 例如未授权时调用需要授权的接口返回 “status”: 401。

  • 接口需要登录时需要如何处理, 特别是同时涉及到 Web 端/微信端/App 端, 需要前端针对运行环境判断如何跳转到特定登录页面。

接口返回的数据结构

接口返回的响应体类型推荐为 Content-Type: application/json; charset=utf-8, 返回的数据包含在 HTTP 响应体中, 是一个 JSON Object. 该 Object 包含 3 个字段 data, status, tip:

接口调用成功时返回:

1
2
3
4
5
6
7
8
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
"data": {}, // 分返回的是对象还是列表,如果有数据时返回的数据是数组,则默认为([])
"status": 200,
"tip": “给用户的提示信息” // 前端需要提示才显示,例如授权成功
}

关于data, 如果接口只是表示删除,授权等成功,不需要显示数据,则返回([])或者({})都行。

接口调用失败时返回:

1
2
3
4
5
6
7
8
9
10
11
HTTP/1.1 404 error
Content-Type: application/json; charset=utf-8

{
"status": 401,
“tip”: “Unauthorized”,
"error": {
“message”:”未授权”
“code”: “10401”
}
}

字段说明:

图片

列表返回的数据结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
"data": [
{
region: 1
sid: 11
},
{
region: 2
sid: 12
},…
],
"status": 200,
"tip": “给用户的提示信息” // 前端需要提示才显示,例如授权成功
}

若带有分页,则返回的数据结构再嵌套一层分页相关信息(根据具体项目约定返回):

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
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
"data": {
current_page: 1
data: [
{
place_date: "2019-10-22 08:48:02",
interval: 2,
},…
]
first_page_url: "http://maxthoven-test.com/api/refund/orderlist?page=1"
from: 1
last_page: 14
last_page_url: "http://maxthoven-test.com/api/refund/orderlist?page=14"
next_page_url: "http://maxthoven-test.com/api/refund/orderlist?page=2"
path: "http://maxthoven-test.com/api/refund/orderlist"
per_page: 15
prev_page_url: null
to: 15
total: 210
},
"status": 200,
"tip": “给用户的提示信息” // 前端需要提示才显示,例如授权成功
}

接口文档规范

我们当前采用的是 YApi - 一款开源的可视化接口管理平台。将代码部署在本地服务器,配置 host 后即可使用。Github地址

后端完成接口,并填充后即可看到相关接口信息:

图片

如上图,前端需要的接口信息基本都可以展示出来,但是建议后端对可能产生歧义的字段可以进行解释备注,不然前端不懂还是要去咨询后端,字段尽量采用可理解的英文名称或英文缩写。

接口错误码规范

采用前后端分离开发模式的项目越来越多, 前端负责调用后端的接口来展现界面, 如果有界面显示异常, 需要有快速方便的手段来排查线上错误和定位出职责范围。

综合了经验总结和行业实践, 最简单有效的手段是制定出一套统一的错误码规范, 协助多方人员来排查出接口的错误。

例如:

  • 用户发现错误, 可以截错误码的图, 就能够提供有效的信息帮助开发人员排查错误。
  • 测试人员发现错误, 可以通过错误码, 快速定位是前端的问题还是后端接口的问题。
  • 因此我们确定提示信息规范为: 当后端接口调用出错时, 接口提供一个用户可以理解的错误提示, 前端展示给用户错误提示和错误码, 给予用户反馈。

对于错误码的规范, 参考行业实践, 大致有两种方案:

  1. 做显性的类型区分, 快速定位错误的类别, 例如通过字母划分类型: A101, B131
  2. 固定位数, 设定区间(例如手机号码, 身份证号码)来划分不同的错误类型。

具体可采用如下方法:

  • 错误码固定长度, 以区间来划分错误类型(例如 HTTP 的状态码)。
  • 例如: 10404 表示 HTTP 请求 404 错误, 20000 表示 API 调用失败, 30000 代表业务错误, 31000 表示业务A错误, 32000 表示业务B错误。
  • 错误码可不固定长度, 以首字母来划分错误类型, 可扩展性更好, 但实际运作还是需要划分区间。
  • 例如: H404 表示 HTTP 请求 404 错误, A100 表示 API 调用失败, B100 表示业务A错误, B200 表示业务B错误。

关于错误分类的原则, 我们可以根据发送请求的最终状态来划分:

  • 发送失败(即请求根本就没有发送出去)
  • 发送成功
  • HTTP 异常状态(例如 404/500…),接口调用失败
  • HTTP 正常状态(例如 200),接口调用成功

关于接口协作

由于接口规范是在开发前制定的,而接口的实现是后续进行的工作, 并且涉及到多人协作, 因此在开发过程中可能出现接口规范与实现不同步, 亦可能某些接口需要进行相对特殊处理,最终造成实际的接口不符合规范的定义, 接口规范就会慢慢失去存在的意义。

为了尽量避免这种问题, 后端在实现接口的过程中应该确保与接口规范保持一致, 一旦出现脱离规范的内容, 必须在与前端协商后修改该规范, 尽可能保持规范与沟通同步。

参考链接:

if 我是前端团队 Leader,怎么制定前端协作规范?

前后端接口规范