Web API规范设计指引

前言

本文本身不是规范,而是指引做规范的。

关于RESTful

应认真考虑要不要使用RESTful规范,不要盲目跟风。它的缺点在小公司里特别明显:

  • 高度抽象,需要一定的设计能力。初级程序员很容易破坏整体设计,这不可能都被Review到。接口使用者也未必能做好反馈
  • 需要对HTTP协议有一定的理解
  • 一般越好的设计就有越多的约束,也就可能有越高复杂度,因此交接工作的学习成本高
  • 产品迭代很快时,接口可能变动很频繁导致版本升级也很快,各种为了好设计而花费的时间都失去意义
  • 不针对业务,复杂场景需要多次请求

如果确定使用,可以参考Github上这两个最多star的规范

  • OpenAPI-Specification
  • http-api-design

文档系统

无论是独立的wiki还是整合在网关系统中,文档系统都应该支持全局模糊搜索。

文档有3种,全局设计规范、API参考手册、文档系统本身的使用指南。

其中,参考手册的每个API说明应包括这些信息:

  • URL
  • method
  • 传参方式(URL query、HTTP header、body)
  • 请求与响应的参数表
    • 参数名
    • 类型(string、array、object、int、float、bool)
    • 是否必填,必填时的默认值
    • 说明:中文名称、功能意义、取值范围。至少要能看出对应需求文档或UI稿的哪个东西
  • 可能的错误码与错误信息
  • 示例

全局规范

URL格式

大小写和连接符的规范应该全局统一:snake_case或者camelCase。一般snake_case会更像普通的web URL。

如果使用RESTful,URL格式可以是

https://api.example.com/{service_name}/{version}/{api_name}?{filters}

https://www.example.com/api/{service_name}/{version}/{api_name}?{filters}

注:Web URL和API URL的规范是不同的。

header

如果不用RESTful,最好也不要把参数放到header里,尽量在HTTP协议框架内实现业务。在此前提下,如果存在(所有接口都需要的)公共参数,可以放在URL query里;或者最方便地使所有接口的method都是POST然后放body里。

JSON格式的Content-Typeapplication/json。如果进行了加密和BASE64转码,正确的Content-Type应该是text/plain

如有必要,可进一步指定编码:application/json;charset=utf-8

业务参数

(以下讨论的是放在body里的JSON。)

各个key的大小写和连接符的规范应该全局统一:snake_case或者camelCase。

必填参数应该约定默认值。如不指定,可认为各类型的默认值是00.0{}[]""false,决不能是nullundefined

非必填参数在无值时有3种风格,应该选定一种全局统一:

  • 不存在这个key
  • value是null
  • value是undefined

非必填参数不允许是这个参数类型的默认值(00.0{}[]""false

值是数字就用数字类型,不要用字符串。

布尔类型用JSON的语义true/false来表示,不要用1/0

值为枚举时,尽量用字符串表示而不是用数字。牺牲一点点性能但可以大大增强代码可读性。这能大幅降幅维护成本,减少出错。

// 直接把枚举value写成字符串更便于开发维护
type: "duck",
type: "chicken"
// “用数字表示然后在文档中详细说明”可读性差,通常不会有人把文档复制成代码注释
type: 1,  // duck
type: 2   // chicken

尽量不要为前端做格式化。例如时间,应返回“1970年1月1日0点至今的秒数”或者“按ISO8601进行格式化的UTC(世界标准时间)时间”,而不是直接返回“2018年11月11日 23:22:33”。让大前端自己做格式化能更好应对UI变化以及兼容特殊要求。比如客户端从中文切换成英文,界面上的“5月6日”需要变成“May 6th”;这种场景下如果是后端传的“5月6日”,那无论是再次请求英文还是客户端自行解释时间后做转换都是糟糕的设计。

保证向后兼容的前提下及时删除废弃的参数或接口。可以先对参数或接口标记Deprecated,在前端发布后或客户端强制升级后删除。

同一意义的字段名,在不同接口返回的命名统一。不要这边叫“page_count”,那边叫“page_size”或"page_amount"。

响应体

较常见的JSON结构是这样的:

{
  "status": 0,
  "message": "",
  "data": {}
}
  • status:0表示正常/成功,非0代表错误码。
  • message:表示错误信息。
  • data:业务数据。所有的业务信息都应该放到data对象上。data一定是对象!

其中,错误码和错误信息也可以设计一份全局统一的对照表。需要注意的是,这里的status都表示业务情况,跟HTTP的status不要混用。 各级网关都可能以HTTP status表示错误,故它无法明确表示是业务API的问题。简单的例子是,业务API鉴权失败,HTTP也应该返回200 OK而不是返回401。因为接口是正常的,是数据逻辑不正确。

如果不用考虑多语言,message错误信息可以是面向用户的中文语句,由前端/客户端直接toast告知用户。

分页设计

请求参数应包含:

  1. 当前页码。接口文档应注释是从0还是从1开始计数。
  2. 每页条数
  3. (可选)排序

响应参数应包含:

  1. (可选)当前页码
  2. (可选)每页条数
  3. (可选)排序
  4. 数据数组
  5. 总页数或总条数,或是否还有下一页。总之不要让使用者再请求一次才知道没有更多数据了。

组合请求

为了减少请求数,后端可提供组合请求接口,并且可组合任意接口。以下设计仅是举例:

假如有3个接口(示例的响应体经简化仅保留data):

  1. /a:请求{"a": "a"}会响应{"data": {"d": "d"}}
  2. /b:请求{"b": "b"}会响应{"data": {"e": "e"}}
  3. /c:请求{"c": "c"}会响应{"data": {"f": "f"}}

增加一个接口/combo可以一次性获取这3个接口的数据:

// 请求
{
  "api": {
    "/a": {"a": "a"},
    "/b": {"b": "b"},
    "/c": {"c": "c"}
  }
}
// 响应
{
  "data": {
    "/a": {"data": {"d": "d"}},
    "/b": {"data": {"e": "e"}},
    "/c": {"data": {"f": "f"}}
  }
}

防攻击

  • 加密机制。可对body做加密,使用AESDES3DESRSADSAECC等算法。一般会对密文做BASE64转码再在网络上传输。
  • 校验机制,防篡改。对body做签名,可使用MD5SHA1HMAC等算法。签名只能放在URL query或HTTP header中。
  • 防重放机制。可使用timestampnonce等机制,在一定时间内重复即认为是重放。或者timestamp距今超过一定时间的,认为是非法请求。
  • 鉴权。也就是常用的token。

最好在测试环境能通过参数控制上述机制的开关,方便调试、测试。

你可能感兴趣的:(后端)