1 目的
本文的主要目的是为了定义系统RESTful接口的相关规范,为研发人员设计业务接口时提供参考和指引。
2 关于REST
REST(Representational State Transfer,译为表现层状态转换), 是Roy Thomas Fielding在他2000年的博士论文中提出来的一种万维网软件架构风格,目的是便于不同软件/程序在网络(例如互联网)中互相传递信息。REST本身不是架构,而是一种架构风格。了解以下基本概念,有助于我们更好的理解REST
- 资源(Resource): 资源通过URI来指定,每个资源都有对应的URI。资源可以是一个商品、一个订单、一个用户账号等,比如一个订单号为123456的订单,/orders/123456
- 表现层(Representation): 即资源具体的呈现形式及内容,比如我们可以通过xml、json等来展示订单的信息(通常我们使用的是JSON)
- 状态转换(State Transfer): 通常我们需要对业务系统的资源进行操作,这个过程称之为状态转换。在REST中主要通过HTTP协议中的Method(GET、POST、PUT、DELETE等)来对资源进行操作
简单点讲,REST提供了一种接口交互的设计风格,通过URI定位资源,并且使用HTTP Method描述对资源的操作。
3 接口规范
3.1 协议
API与用户的通信协议,应该总是使用HTTPS协议,特别是需要对外网用户提供服务的系统。
3.2 版本
应该将API的版本好放到URL中,相比于把版本号放在HTTP头信息中,这种方法更加直观。比如我们可以这么设计
https://api.govthai.cn/v1/
3.3 端点(Endpoint)
端点(Endpoint)表示API的具体网址。在RESTful架构中,每个网址代表一种资源(Resource),所以网址中不能有动词,只能有名词,而且所用的名词往往与数据表明相对应。一般来说,数据库中的表都是同种记录的集合,所以API中的名词也应该使用复数形式。以商城为例,商城中有商品、订单、会员、收货地址等信息,我们会将端点设计成下面这样。
https://api.govthai.cn/v1/products # 商品列表
https://api.govthai.cn/v1/orders # 订单列表
https://api.govthai.cn/v1/members # 会员列表
https://api.govthai.cn/v1/addresses # 收货地址列表
3.4 HTTP动词(Http Method)
对于资源的具体操作类型,由HTTP动词表示。常用的HTTP动词有下面五个,为了更好的对比以下列出对应的数据库操作
HTTP动词 | 对应SQL命令 | 描述 |
GET | SELECT | 从服务器获取资源(一项或多项) |
POST | INSERT | 在服务器新建一个资源 |
PUT | UPDATE | 在服务器更新资源(客户端提供改变后的完整资源,即请求Body中应当包含资源的所有字段) |
PATCH | UPDATE | 在服务器更新资源(客户端提供需要改变的属性,即请求Body中只包含需要修改的字段) |
DELETE | DELETE | 从服务器删除资源 |
下面是一些例子
GET /v1/orders # 列出所有订单
POST /v1/orders # 创建一个新的订单
GET /v1/orders/123456 # 查询订单号为123456的订单信息
PUT /v1/addresses/ID # 更新某个指定(ID)地址的信息,请求Body提供地址的全部信息
PATCH /v1/addresses/ID # 更新某个指定(ID)地址的信息,请求Body提供地址需要修改的部分信息
DELETE /v1/addresses/ID # 删除某个指定(ID)地址的信息
3.4.1 不符合CURD的操作
很多刚接触到REST的同学都碰到类似的问题,HTTP动词可以从原生语意上解决CURD这种类型的操作,但是不符合CURD的操作又应当如何操作呢?下面给出一些解决方法:
- 以子资源对待。例如:GitHub上,对一个gists加星操作:PUT /gists/:id/star 并且取消星操作:DELETE /gists/:id/star.
- 有时候操作确实难以某个具体资源对应上时,比如search。那么一个这么设计,GET /_search, 用下划线开头代表特殊的操作。
3.4.2 客户端不兼容的情况
3.5 过滤信息(Filtering)
通常我们通过资源端点查数据时需要按需对结果进行过滤,这种过滤可以由端点的api提供参数,过滤返回结果。下面是一些常见的参数
GET /v1/orders?consigneePhone=13800138000 # 查询收货手机号码为13800138000的订单列表
GET /v1/orders?page=0&size=20 #分页查询订单列表,page为页码,size为每页的记录数
3.6 信息排序
有时候我们获取资源的列表信息之后,希望对列表进行排序,这种情况我们可以通过在api添加sorts参数支持实现排序功能,比如:
GET /v1/orders?sorts=creationDate+ # 按订单创建时间的正序获取订单列表
GET /v1/orders?sorts=creationDate # 按订单创建时间的正序获取订单列表,缺省使用正序
GET /v1/orders?sorts=creationDate- # 按订单创建时间的倒序获取订单列表
其中,creationDate具体的字段名称,“+”指的是正序相当于SQL中ASC(默认的排序方法), “-”指的是倒序相当于SQL中的DESC。如果需要同时使用多个字段进行排序,多个排序的设置可以通过英文的逗号隔开,比如我们可以这么设计api
GET /v1/orders?sorts=creationDate-,originCode # 按订单创建时间的倒序,渠道编号正序的组合排序方式获取订单列表
3.7 状态码(Status Codes)
服务器向用户返回的状态码和提示信息,常见的有以下一些
状态码 | 对应HTTP动词 | 描述 |
200 | GET/PUT/PATCH/DELETE | OK, 服务器成功返回用户的请求数据,该操作是幂等的 |
201 | POST | Created, 用户创建数据成功 |
400 | POST/PUT/PATCH | Invalid Request, 用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的 |
401 | * | Unauthorized, 未授权比如用户未登录、无效的Token等 |
403 | * | Forbidden, 表示用户得到授权(与401错误相对),但是访问是被禁止的。比如用户已登录,但是没有权限访问某个资源。 |
404 | * | Not Found, 用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。 |
500 | * | INTERNAL SERVER ERROR, 服务器发生错误,用户将无法判断发出的请求是否成功。 |
3.8 错误处理
如果接口的返回的状态码为4xx时,应当向用户返回错误信息(404可以不写错误信息) , 我们规定错误信息必须包含错误编码和错误信息两个字段,参考以下返回结果。
{
"code":"CRM0010001",
"message":"XXXX错误"
}
3.9 返回结果
3.9.1 返回结果的格式
返回结果(请求响应内容)的格式我们以JSON为主,不建议采用xml的方式
3.9.2 返回结果的内容
返回结果为返回的业务对象的序列化结果,比如我们请求一个收货地址。
{
"id":"1",
"consignee":"测试138",
"consigneePhone1":"",
"consigneePhone2":"138****8000",
"provinceCode":"110000",
"provinceName":"北京市",
"cityCode":"110101",
"cityName":"东城区",
"regionCode":"110101",
"regionName":"",
"customArea":"这泼猴个民工明明你",
"deliveryAddress":"北京市东城区这泼猴个民工明明你",
"isDefault":false,
"idCardNumber":""
}
3.9.3 分页的返回结果
为更好的规范返回结果,我们对分页数据结构定义了以下规范字段。
字段名 | 字段类型 | 字段描述 |
content | Array | 分页的列表内容 |
total | int | 总共的数据量 |
size | int | 每个分页的记录数 |
number | int | 当前的页码 |
以下给出请求的分页的一个示例
{
"content":[
{
"id":28722,
"createdBy":"liubihui",
"createdDate":1550546286000,
"lastModifiedBy":"yexueying",
"lastModifiedDate":1550628050000,
"recordVersion":3,
"dtype":1,
"name":"女性-东阿阿胶跟价一药网",
"platforms":"all",
"bgnDate":1545205380000,
"endDate":1551369599000,
"remark":"女性-东阿阿胶跟价一药网",
"status":6,
"lastAuditedBy":"zhongweiheng",
"auditLevel":1,
"lastAuditedDate":1550546589000
},
{
"id":28599,
"createdBy":"weihuili",
"createdDate":1548746295000,
"lastModifiedBy":"weihuili",
"lastModifiedDate":1550625855000,
"recordVersion":9,
"dtype":1,
"name":"日常-负毛利-12月",
"platforms":"all",
"bgnDate":1543593600000,
"endDate":1551369599000,
"remark":"延期11月活动中负毛利产品",
"status":6,
"lastAuditedBy":"zhongweiheng",
"auditLevel":1,
"lastAuditedDate":1548750721000
}
],
"total":2,
"size":10,
"number":0
}
3.10.0 身份认证
由于RESTful架构中,接口的设计都是无状态的,所以不能通过传统的Session来判断用户身份,我们可以通过HTTP的Authorization头来进行接口的认证。常用的认证方式有以下两种。
3.10.1 OAuth2认证
OAuth2认证是我们推荐使用的方式,特别是对于客户端。OAuth2认证需要先从认证中心通过用户名密码、手机号码验证码等方式获取Token,然后在请求接口时加上Authorization HTTP头,以便服务端进行鉴权。
Authorization: Bearer $TOKEN
3.10.2 Basic认证
这种方式比较不安全,不推荐。但是有时候我们为了简化接入的复杂度,会使用到这种方法。该方法主要是将用户名密码Base64之后,再通过Authorization头传递给服务端进行鉴权。
Authorization: Basic $TOKEN
Basic Token的算法的伪代码如下:
base64(username + ":" + password)
4 参考示例
twitter的API: https://dev.twitter.com/rest/public
paypal的API: https://developer.paypal.com/docs/api/overview/
kubernetes的API: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.13/