违RESTful规范设计
3.1 文档系统
无论是独立的wiki还是整合在网关系统中,文档系统都应该支持全局模糊搜索。文档有3种,全局设计规范、API参考手册、文档系统本身的使用指南。其中,参考手册的每个API说明应包括这些信息:
- URL
- method
- 传参方式(URL query、HTTP header、body)
- 请求与响应的参数表
- 参数名
- 类型(string、array、object、int、float、bool)
- 是否必填,必填时的默认值
- 说明:中文名称、功能意义、取值范围。至少要能看出对应需求文档或UI稿的哪个东西
- 可能的错误码与错误信息
- 示例
3.2 全局规范
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的规范是不同的。 - 简单查询尽量用GET,好处是可以直接带查询参数copy api路径。
- 复杂查询和更新用POST,用的最多。
- 不用PUT和DELETE,原因是增加复杂度,并没有带来什么好处。看看BAT的很多openapi,也是写着restful,实际没有严格遵守,都是get和post,这是也很多人不知道put和delete的原因。
//根据订单id获取订单 GET api/order/queryOrderById?id=value1¶m2=value2 //根据订单id List获取订单 POST api/order/queryOrderByIdList //根据条件查询订单,带分页参数 POST api/order/queryOrderByCondition //更新订单收款状态 POST api/order/updateOrderCollectionStatus //批量更新订单收款状态 POST api/order/updateOrderCollectionStatusInBatch //批量更新订单收款状态 POST api/order/updateOrderCollectionStatusInBatch //批量删除订单,带操作来源 POST api/order/deleteOrderInBatch
header
如果不用RESTful,最好也不要把参数放到header里,尽量在HTTP协议框架内实现业务。在此前提下,如果存在(所有接口都需要的)公共参数,可以放在URL query里;或者最方便地使所有接口的method都是POST然后放body里。
JSON格式的Content-Type
是application/json
,如果进行了加密和BASE64转码,则应该是text/plain
。如有必要,可进一步指定编码:application/json;charset=utf-8
。
业务参数 以下讨论的是放在body里的JSON
-
各个key的大小写和连接符的规范应该全局统一:snake_case或者camelCase。
-
必填参数应该约定默认值。如不指定,可认为各类型的默认值是
0、0.0、{}、[]、""、false
,决不能是null或undefined
。 -
非必填参数在无值时有3种风格,应该选定一种全局统一:
- 不存在这个key
- value是null
- value是undefined
-
非必填参数不允许是这个参数类型的默认值(
0、0.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告知用户。
分页设计
- 请求参数应包含:
- 当前页码。接口文档应注释是从0还是从1开始计数。
- 每页条数
- (可选)排序
- 响应参数应包含:
- (可选)当前页码
- (可选)每页条数
- (可选)排序
- 数据数组
- 总页数或总条数,或是否还有下一页。总之不要让使用者再请求一次才知道没有更多数据了。
组合请求
为了减少请求数,后端可提供组合请求接口,并且可组合任意接口。假如有3个接口(示例的响应体经简化仅保留data):
/a:请求{"a": "a"}会响应{"data": {"d": "d"}} /b:请求{"b": "b"}会响应{"data": {"e": "e"}} /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做加密,使用AES、DES、3DES、RSA、DSA、ECC等算法。一般会对密文做BASE64转码再在网络上传输。
- 校验机制,防篡改。对body做签名,可使用MD5、SHA1、HMAC等算法。签名只能放在URL query或HTTP header中。
- 防重放机制。可使用timestamp、nonce等机制,在一定时间内重复即认为是重放。或者timestamp距今超过一定时间的,认为是非法请求。
四、合理性验证
- URI是否短小且容易输人
- URI是否能让人一眼看懂
- URI是否只有小写字母组成
- URI是否容易修改
- URI是否反映了服务器端的架构
- URI规则是否统一
- 有没有使用合适的HTTP方法
- URI里用到的单词所表示的意思是否和大部分API相同
- URI里用到的名词是否采用了复数形式
- URI里有没有空格符及需要编码的字符
- URI里的单词和单词之间有没有使用连接符
- 分页的设计是否恰当
- 登录有没有使用 Oauth2.0
- 响应数据格式有没有使用JSON作为默认格式
- 是否支持通过查询参数来指定数据格式
- 是否支持不必要的 JSONP
- 响应数据的内容能不能从客户端指定
- 响应数据中是否存在不必要的封装
- 响应数据的结构有没有尽量做到扁平化
- 响应数据有没有用对象来描述,而不是用数组
- 响应数据的名称所选用的单词的意思是否和大部分API相同
- 响应数据的名称有没有用尽可能少的单词来描述
- 响应数据的名称由多个单词连接而成时,连接方法在整个AP1里是否一致
- 响应数据的名称有没有使用奇怪的缩写形式
- 响应数据的名称的单复数形式是否和数据内容相一致
- 出错时响应数据中是否包含有助于客户端剖析原因的信息
- 出错时有没有返回HTML数据
- 有没有返回合适的状态码
- 服务器端在维护时有没有返回503状态码
- 有没有返回合适的媒体类型
- 必要时能不能支持CORS
- 有没有返回 Cache- Contro1、ETag、Last- Modified、Vary等首部以便客户端采用合适的缓存策略
- 不想缓存的数据有没有添加 Cache- Contro1:no- cache首部信息
- 有没有对API进行版本管理
- API版本的命名有没有遵循语义化版本控制规范
- 有没有在URI里嵌入主版本编号,并且能够让人一目了然
- 有没有考虑API终止提供时的相关事项
- 有没有在文档里明确注明API的最低提供期限
- 有没有使用 HTTPS来提供API
- 有没有认真执行JSON转义
- 能不能识别x- Requested-With首部,让浏览器无法通过 SCRIPT元素读取JSON数据
- 通过浏览器访问的API有没有使用 XSRF token
- API在接收参数时有没有仔细检查非法的参数(负数等)
- 有没有做到即使请求重复发送,数据也不会多次更新
- 有没有在响应消息里添加各种增强安全性的首部
- 有没有实施访问限速
- 对预想的用例来说限速的次数有没有设置得过少
五、参考资料
- 微软官方文档:Web API 设计
- Github上star最多的规范: OpenAPI-Specification、http-api-design