API 文档编写建议、范例
如无特殊说明,本文默认前后端使用 REST 风格的 HTTP 协议通讯。GraphQL 不在讨论范围。
本文侧重于 app 专有的 API,部分内容(如 HATEOAS 部分)不适用于公用服务,如政府公开数据。
你可以使用 GitLab 或 GitHub 自带的 wiki 撰写文档,我写了一个示例,你可以在此基础上创建你的文档。
本文的格式说明是基于 markdown 格式的。
Markdown 编辑推荐使用 VSCode,插件推荐 markdownlint
OpenAPI 也是不错的选择,标准的东西,迁移方便,很多工具支持,推荐使用。
也可以尝试使用其他第三方工具。一些我用过的:
REST 是一种架构风格,包含的内容很多。资源路径命名,HTTP 的请求方法、状态码的选用,无状态,除了这些经常被讨论的内容,围绕 HTTP 头有大量文章可做,比如常常被忽视的缓存(笔者参与的几十个项目有做服务端 cache 控制的屈指可数)。
符合规范的最大好处是减少开发,越规范,能利用的现有工具越多,需要的二次开发越少。其次是相对更安全,自己想出来的方案很难比不过时的标准考虑全面。但在实战中,不那么 RESTful 很多时候对项目的进行影响其实也不大。
下面是一些具体分析。
路径命名、请求方法、状态码的选用,基本上讲 REST 规范的文本都会讨论这仨。设计得好好处很明显——看着就清晰:
看 URL 就知道要什么
看 HTTP method 就知道干什么
看 HTTP status code 就知道结果如何
除了清晰外对服务器扩容有点好处。
在列出接口列表并说明之前,应当对诸如鉴权、错误处理、如何分页、服务器地址等通用行为进行说明。
多数情况我们会使用 JSON 格式,注意此时 HTTP 响应(response)的 Content-Type 应为 application/json
。
客户端首先应根据 HTTP 响应的状态码判定请求是否成功,后台应返回符合 HTTP 规范的状态码。当错误发生时,后台的响应中应包括业务上的错误码,毕竟 HTTP 有限的状态码无法完全覆盖业务上的需求。
后台返回的 HTTP 状态码建议细化但不必须,只要能区分大类即可:成功的请求应该返回 2xx,因为客户端的原因出错返回 4xx,因后台服务的原因呢出错返回 5xx。3xx 用于重定向,但 API 一般用不到。
当 4xx 错误时,后台应返回错误结构。这个错误结构中需要有一个业务上的错误码,用来指示客户端如何处理错误。5xx 错误建议也返回。
举例
登录接口,账号未注册、密码错误和其他一般错误应该返回不同的错误码,因为客户端可能会根据这些情况作做不同的处理:未注册时弹窗提示注册;密码错误时高亮密码区域并提示;一般错误仅报出来即可。
那么文档可以写成
错误码 * 1101 账号不存在 * 1102 账号与密码不匹配
错误码的自定义范围建议 1000-9999,不用负数。大于 1000 避免与 HTTP 状态码重复,但如果响应的 HTTP 状态码足以准确反应是何种错误且无需扩展,错误码应和 HTTP 状态码保持一致。
多数项目是有用户系统的,需要使用 token 或类似的等价体进行权限管理。建议:
不要引入 refresh token、expires in 的概念
OAuth 中有 refresh token、expires in、grant 等概念,第三方 API 用合适,但不适合应用的 API 使用。
遇到这种需要刷 token 的,绝大部分情况开发者会选择应用启动的时候强制刷新一下。因为 app 里请求是一般是并行的,如不在启动时强刷,而是等过期了需要时再刷,在刷的时候已经发出去的请求怎么办,刷新的过程中需要进新的请求怎么办?业务层不可能正确处理这些情况的,只能靠接口框架统一处理。而除了专门针对 OAuth 的,一般的通用框架是不会带这种处理的。
而且就算启动时强刷也不完美,请求时网络不好超时怎么办,一直让用户等超时结束体验很差,要等几秒钟再进 app,还没完成的请求取不取消?就算取消了也可能后台已经刷 token 了,只是响应未到达;要不取消,有可能其他请求用旧的 token 正发着呢,刷新完成了,这些请求可能回收到 token 失效请登出的错误,这时怎么办?
综上,几乎就没有 app 端开发者能严格意义上的正确实现这个机制。但第三方服务就不同了,因为使用场景单一、可控,可能串行队列就能满足需求,即使出错,影响也有限。
token 和基本用户信息一起返回
这点一般会涉及到注册、登录接口,有的后台会只返回 token,app 需要再拉一个接口才能获取到用户信息。因为多数情况 app 需要用户的 ID 结合 token 才能算登录成功(建用户的数据库需要用户 ID),如果能一块返回的话会更快、网络成功率更高。
综上,如果后台不是特殊架构,一起返回比较困难的话,还是一起返回吧。
开一个特殊的登录接口,App 每次启动调用登录,在这个接口刷新 token 的有效期,并返回用户基本的信息
token 的有效期一般还是需要的,app 启动的时候刷也比较稳妥;用户信息正常也需要至少获取一次的,另获也行,能一个接口完成最好。
好处:
如果返回的结构不是非常大的话。
这样做便于 model 更新后直接刷新 UI,无需重新拉取 moedel 数据。直接用发给服务器的值修改本地 model 可不可以呢?考虑一些情景:
还是用服务器返回的新 model 更统一、更简单。
若支持 Prefer return 头更佳。
如果使用的是 wiki 的话,一般支持多个 page 并会有自动生成的 page 目录,单个 page 里推荐手写一个目录,列出这个 page 里的接口。
## 目录
* [登录注册](#SectionLogin)
* [发送登录注册短信](#SMSSend)
## <a name="SectionLogin"></a>登录注册
### <a name="SMSSend"></a>发送登录注册短信
一般需要包括:
Hypermedia as the Engine of Application State,虽然是 REST 规范的一部分,但我认为是低效、过时的,不适合 API 用,维护好文档才是最现实、有效的。
HATEOAS 字面意思是通过在返回的响应中附加信息(hypermedia)控制客户端的可达的状态(application state)。在实践中有两个方向:
对于一个应用来说,HATEOAS 化的接口提供的部分潜在好处是:
首先第一点,服务器下发请求配置文件就能做到的事,在接口里现去一个个发现效率低下。不只是响应变大了,原本能多个接口并行请求,变成需要一层层逐级展开。
第二点,直接返回一个自定义的可操作列表,比整这些简单快捷多了。
第三点,标准化方便的主要是爬虫……另外再看看标准方案感人的效果……
在 HTTP 头添加带属性的链接,这玩意基本就是给搜索引擎爬虫和浏览器用的,看注册的 Relation Types 就知道。最有用的场景是帮助浏览器插件自动加载下一页,其他方面对用户体验帮助不大。
标准里也说了 Relation Type 可以自定义,但 app 的接口用这个?明显不合适——功能弱,解析费劲。
这俩都是返回结构标准化的尝试。除非你的接口就是让人抓的,看例子你觉得有必要用么?
一些非标准的实现供参考:
想用随意。
This work is licensed under a Creative Commons Attribution 4.0 International License