1、前言

作为开发人员,一定会和API打交道,大家平时也一定设计和开发自己的API。以前,API是作为前端和后端通讯的桥梁,随着微服务的兴起,API更是起到了各个子系统的粘合剂的作用。
优秀的 API 之于代码,就如良好语言对于每个人。优秀的 API设计不但利于使用者理解,开发时也会事半功倍,后期维护更是方便快捷。
API的设计千差万别,很难有处处适用的准则规范,所以在讨论原则和最佳实践时,无论这些原则和最佳实践是什么,一定有适应的场景和不适应的场景。因此我们在此讨论的是,尽可能的适用于我们的约定,而非强制规则!

2、域名

域名应该尽量将API部署在专用域名之下:

  1. https://api.myun.info

如果在前后端不分离的情况下,可以考虑放在主域名下:

  1. https://h5.myun.info/api/

3、版本号

API 接口应该尽量兼容之前的版本。但是,在实际业务开发场景中,可能随着业务需求的不断迭代,现有的 API 接口无法支持旧版本的适配,此时如果强制升级服务端的 API 接口将导致客户端旧有功能出现故障。
为了解决这个版本不兼容问题,在设计API的一种实用的做法是使用版本号。一般情况下,我们会在 url 中保留版本号,并同时兼容多个版本。

  1. https://api.myun.info/v1/users/get-users-list
  2. https://api.myun.info/v2/users/get-users-list

现在,我们可以在不改变版本 v1 的查询用户列表的 API 接口的情况下,新增版本 v2 的查询用户列表的 API 接口以满足新的业务需求,此时,客户端的产品的新功能将请求新的服务端的 API 接口地址。
虽然服务端会同时兼容多个版本,但是同时维护太多版本对于服务端而言是个不小的负担,因为服务端要维护多套代码。这种情况下,常见的做法不是维护所有的兼容版本,而是只维护最新的几个兼容版本,例如维护最新的三个兼容版本。
在一段时间后,当绝大多数用户升级到较新的版本后,废弃一些使用量较少的服务端的老版本API 接口版本,并要求使用产品的非常旧的版本的用户强制升级。
需要注意的是,“不改变版本 v1 的查询用户列表的 API接口”主要指的是对于客户端的调用者而言它看起来是没有改变。而实际上,如果业务变化太大,服务端的开发人员需要对旧版本的 API 接口使用重定向将请求重定向到新的API接口上。

4、以资源为中心的URL设计

以资源为中心设计URL,而非以模块来命名URL。
虽然我们的API并非严格意义的RestFul API,但依旧应该以资源为中心,毕竟外部通过API想获取的是资源,而并不关心你内部的模块结构。

  • URL是资源的集合,应该使用复数,并且只包含名词(某些情况下需要形容词):

    1. https://api.myun.info/v1/users
    2. https://api.myun.info/v1/tags
    3. https://api.myun.info/v1/overdue-goods
  • 方法是对资源的具体操作

我们的请求统一使用的是Post方法,并未采用RestFul模式,因此URL最后部分是具体的方法名,应以是一个动 作:

  1. https://api.myun.info/v1/users/get-user-list
  2. https://api.myun.info/v1/tags/add-tag
  • 大小写

URL是大小敏感的,建议全部使用小写,用”-“作为间隔符!之所以不采用驼峰命名,是因为在命名比较长或者使
用缩写时,不利于使用者理解,也容易导致书写错误。

5、参数

考虑到很多后端直接映射数据库,在此不对参数命名做强制要求,但至少要做到语义明确。

  • 请求参数:

请求端在输入请求参数时,请遵循所写既所用的原则,不要传入一些无用的和废弃的参数,从而导致误解。
接受端在接收到请求参数时,需对参数的合法性做一定的校验,对于非必传参数,需合理的设置默认值,从而避
免一些因参数不正确导致的异常问题。

  • 响应参数:

返回的响应参数,请遵循所求既所得的原则,不要返回一些无用参数,从而导致增加传输负担和降低解析效率。

我们以前设置API时,总是寄希望于制造一个银弹,一个API在各个子系统和各个端中横行天下。这种方式虽然某种程度上减少了一点开发量,但是牺牲了很大的性能,也降低了后期的可维护性和扩展性。 合理的方式应该是,一个业务的接口I在不同的应用场景中应该独立分开成多个API。

6、合适的状态码

HTTP应答中,需要带一个很重要的字段:status code。它说明了请求的大致处理情况,是否正常完成、是否需要进一步处理、出现了什么错误,这对于客户端非常重要。状态码都是三位的整数,大概分成了几个区间:

  • 2XX:请求正常处理并返回
  • 3XX:重定向,请求的资源位置发生变化
  • 4XX:客户端发送的请求有错误
  • 5XX:服务器端错误

在 HTTP API 设计中,经常用到的状态码以及它们的意义如下表:

状态码 LABEL 解释
200 OK 请求成功接收并处理,一般响应中都会有 body
201 Created 请求已完成,并导致了一个或者多个资源被创建,最常用在 POST 创建资源的时候
202 Accepted 请求已经接收并开始处理,但是处理还没有完成。一般用在异步处理的情况,响应 body 中应该告诉客户端去哪里查看任务的状态
204 No Content 请求已经处理完成,但是没有信息要返回,经常用在 PUT 更新资源的时候(客户端提供资源的所有属性,因此不需要服务端返回)。如果有重要的 metadata,可以放到头部返回
301 Moved Permanently 请求的资源已经永久性地移动到另外一个地方,后续所有的请求都应该直接访问新地址。服务端会把新地址写在 Location
头部字段,方便客户端使用。允许客户端把 POST 请求修改为 GET。
304 Not Modified 请求的资源和之前的版本一样,没有发生改变。用来缓存资源,和条件性请求(conditional request)一起出现
307 Temporary Redirect 目标资源暂时性地移动到新的地址,客户端需要去新地址进行操作,但是不能修改请求的方法。
308 Permanent Redirect 和 301 类似,除了客户端不能修改原请求的方法
400 Bad Request 客户端发送的请求有错误(请求语法错误,body 数据格式有误,body 缺少必须的字段等),导致服务端无法处理
401 Unauthorized 请求的资源需要认证,客户端没有提供认证信息或者认证信息不正确
403 Forbidden 服务器端接收到并理解客户端的请求,但是客户端的权限不足。比如,普通用户想操作只有管理员才有权限的资源。
404 Not Found 客户端要访问的资源不存在,链接失效或者客户端伪造 URL 的时候回遇到这个情况
405 Method Not Allowed 服务端接收到了请求,而且要访问的资源也存在,但是不支持对应的方法。服务端必须返回 Allow
头部,告诉客户端哪些方法是允许的
415 Unsupported Media Type 服务端不支持客户端请求的资源格式,一般是因为客户端在 Content-Type
或者 Content-Encoding
中申明了希望的返回格式,但是服务端没有实现。比如,客户端希望收到 xml
返回,但是服务端支持 Json
429 Too Many Requests 客户端在规定的时间里发送了太多请求,在进行限流的时候会用到
500 Internal Server Error 服务器内部错误,导致无法完成请求的内容
502 Bad GateWay 服务器作为网关或代理,从上游服务器收到无效响应。
503 Service Unavailable 服务器因为负载过高或者维护,暂时无法提供服务。服务器端应该返回 Retry-After
504 Gateway timeout 服务器作为网关或代理,但是没有及时从上游服务器收到请求
505 HTTP Version Not Supported 不支持的 HTTP 版本

7、错误处理

除了使用http自带的status code,来处理请求的系统级错误外,我们同样需要在响应参数中加入code和message,来处理一些业务级别的错误。

目前我们常用IsSuccess只是简单的bool类型,无法明确具体的业务错误

8、安全性

8.1、Https

所有的访问API行为,都需要用 TLS 通过安全连接来访问。没有必要搞清或解释什么情况需要 TLS 什么情况不需要 TLS,直接强制任何访问都要通过 TLS。
理想状态下,通过拒绝所有非 TLS 请求,不响应 http 或80端口的请求以避免任何不安全的数据交换。如果现实情况中无法这样做,可以返回403 Forbidden响应。

8.2、签名

针对所有请求,请求端需根据请求参数生成签名,接收端对签名进行验证,具体方式有两种:
1、对所有请求参数做一次json stringfiy,然后对string做md5加密
2、对说有请求参数做acs排序,然后对生成的字符串做md5加密
两种方式可分别参考我们的模板工程:easy-front-express-apieasy-front-open-api-server

8.3、限流

限流一般针对对外的open api,内部api因为效率问题不推荐使用。
限流主要是对访问的次数加以控制,如果不控制很可能会造成 API 被滥用,甚至被DDos。根据使用者不同的身份对其进行限流,可以防止这些情况,减少服务器的压力。
具体可以参考我们的模板工程:easy-front-open-api-server

8.4、其他

另外基于安全的措施还有AppKey和TimeStamp

  • AppKey:只允许合法的第三方请求API
  • TimeStamp:防止请求回放

9、如何编写API文档

API 最终是给人使用的,即使我们遵循了上面提到的所有规范,设计的API 非常优雅,如果没有优秀的文档,用户还是不知道怎么使用我们的API。因此最后还有非常重要的一步:为API 编写优秀的文档。

一般API文档需包含以下几个公共部分:

  • 版本号:描述文档每次修改针对的API版本号,并清楚描述修改内容、修改人、修改时间
  • 协议说明:描述API整体的请求方式、使用的协议、数据的格式
  • 请求域名:描述生产、测试、验收等环境的域名地址
  • 公共请求参数和响应参数:描述所有请求和响应都会有的参数,比如timestamp、sign
  • 返回码code:描述code各个值所代表的具体含义
  • 签名和验签:描述签名和验签的具体规则方法

一般每个API需要描述清楚以下几个部分:

  • 场景:描述使用API的业务场景
  • 发起方:描述谁请求这个API
  • 接受方:描述谁处理这个API
  • 请求方式:Post还是Get、PUT
  • URL:具体的请求路径
  • 请求参数:包含参数名、参数类型、是否必须、参数说明
  • 响应参数:包含参数名、参数类型、是否必须、参数说明
  • 示例:具体的请求和响应的数据示例