Restful API 规范

很早的时候接触Restful的时候,觉得其标准很不错; 但是在实际项目中发现, 大部分程序员甚至资深的工程师都没有类似的意识。

接触久了发现可能是受国内大厂影响。 很多大厂的开放API的接口也完全没有考虑Restful的标准。 甚至很多接口的URL定义为一个,通过POST的请求体里面的参数来区分不同的接口。

但是针对新的开发项目来讲,一套标准的规范可以有效的增加可读性,提供协同效率。

这里主要讲述URL 规范, 因为请求方法比较简单, 实际项目中保持如下使用即可:

  1. GET - 获取资源
  2. POST - 新增资源
  3. PUT - 对资源的修改,(有些类似流程的操作,也相当于是资源的修改,建议采用PUT)
  4. DELTE - 删除资源

资源路径: URL

一个好的URL具有很强可读性的,具有自描述性, 加上 动词 Http Method 可以很清晰的表达出接口的含义。

例如:GET /orders/1001/items/1 代表获取 订单1001的子订单1的信息。 我们拆解分析如下:

  • . orders - 名词的复数,代表一个集合和一个池子,也可以理解为DDD中的领域
    • . 1001 - 一个集合后面的{ID} 或者说标识 表示这个集合中的1001这个这个对象
      • . items - 第二个名词复数,代表了一个子集合,这个 集合率属于 orders:1001
        • . 1 - 标识items这个集合下标识为1的这个对象

这里的标识一般代表能从一个集合找到一个唯一对象的主键。 同理推断:POST /orders 则代表向order集合新建对象。

/版本号/资源路径
/v1/users/{userId}  //URL 可以定位到具体的用户
/v1/users?[&keyword=xxx][&enable=1][&offset=0][&limit=20]  // URL 可以定位到一个用户集合

版本号

命名版本号可以解决版本不兼容问题,在设计 RESTful API 的一种实用的做法是使用版本号。一般情况下,我们会在 url 中保留旧版本号,并同时兼容多个版本

GET /v1/users/{userId} // 版本 v1 的获取id为{userId}的用户
GET /v2/users/{user_id} // 版本 v2 的获取id为{userId}的用户

资源路径

URI 严格意义不能包含动词,只能是名词(命名名词的时候,要使用小写、数字及下划线来区分多个单词)。

资源的路径应该从根到子依次如下:

/{resources}/{resource_id}/{sub_resources}/{sub_resource_id}/{sub_resource_property}

标准示例

GET   /users # 查询用户信息列表
GET   /users/1001 # 查看某个用户信息
POST  /users # 新建用户信息
PUT   /users/1001 # 更新用户信息(全部字段)
PATCH /users/1001 # 更新用户信息(部分字段), 一般不用, 用PUT即可
DELETE /users/1001 # 删除用户1001

// 子目录示例
POST  /users/{user_id}/roles // 添加用户的角色

请求方法的延伸

当一个资源变化难以使用标准的 RESTful API 来命名, 或者说容易混淆; 比如”密码重置“ 、 ”修改密码“。这种场景可以考虑用 PATCH /users/1001 但是URL可读性就有所丢失。这个时候我一般会在URL末尾用动词来延伸从而保证URL的可读性。

一般我会采取下面两种方式:

  1. 通过添加 ’/actions/{action}'; PUT /users/1001/actions/reset-pwd 代表重置用户1001的密码
  2. 通过添加 '_{actioin}'; PUT /users/1001/_reset-pwd 代表重置用户1001的密码

两种方式采取一种保持一致。


查询参数

RESTful API 接口应该提供参数,过滤返回结果。

GET /{version}/{resources}/{resource_id}?offset=0&limit=20

响应体

针对响应体,标准Restful是通过HttpStatus来标识返回结果的成功和失败,如果是成功返回,直接返回实际业务体。 但是国内大部分项目都会在响应体外面包一层,通过自定义的方式来标识成功和失败。 比如以下格式:

{
    "code": 0,
    "msg": "success",
    "data": {
        "name": "Joe"
    }
}

如果是直接返回data, 就需要结合状态码来操作。 如果是data外面包一层, 无论成功失败都返回status 200, 具体成功失败需要根据响应体的code字段来判断。

重点强调几点

  • 尽量返回修改后的内容,比如新增和修改接口,返回修改后新的内容
  • 点赞、收藏等操作也同样需要返回修改后的内容,方便客户端局部更新,避免刷新页面

状态码

使用适合的状态码很重要,而不应该全部都返回状态码 200

状态码,可根据以下标准按照项目扩展自身状态码:

  • 200~299段 表示操作成功:

  • 200 操作成功,正常返回

  • 201 操作成功,已经正在处理该请求

  • 300~399段 表示参数方面的异常

  • 300 参数类型错误

  • 301 参数格式错误

  • 302 参数超出正常取值范围

  • 303 token过期

  • 304 token无效

  • 400~499段 表示请求地址方面的异常:

  • 400 找不到地址

  • 500~599段 表示内部代码异常:

  • 500 服务器代码异常

关于对标准的反思

下面是来自百度百科对标准的定义:

标准是对重复性事物和概念所做的统一规定,它以科学技术和实践经验的结合成果为基础,经有关方面协商一致,由主管机构批准,以特定形式发布作为共同遵守的准则和依据。

如果大部分人都没有按标准来, 那他还是个标准吗? 曾经在一个大几千万的项目中, 第三方对接我团队的接口时候很诧异的问我,为什么有PUT请求。 后面还私下和业主方表达了我们接口的不专业。 我们在对接携程的接口的时候,发现接口就一个URL,POST请求。 大部分知名的国内平台最多就两个请求方法: POST 和 GET, 而且URL上无法定位到资源。

反思过后,只能说一致性 > 标准, 大家都一致了, 非标准的也就标准了。

标准Restful的好与坏

万物都有两面, 如果对其缺点要求很高可以选择性遵循。

优点

  1. API接口可读性, 请求方法 + URI 可以很明确的知道接口是做什么的
  2. 减少不必要的DTO;有些标识在URI上已经有了,就没有必要写DTO了。 (很多项目中,每个接口一个请求DTO,看着头大。)

缺点

缺点就是不方便监控。

非标准的URL干同一类的事情URI是固定的, 比如:删除用户 POST /deleteuser 具体哪个用户在请求体, 这样就很方便统计监控这个API,因为URL没变。

而标准的Restful的URL会是这样的:DELETE /users/1001, 删除不同用户的地址的URL是不同的, 监控统计会有些麻烦。

不方便监控, 不代表不能监控,取决于监控平台对URI的支持力度。

最后更新于 3rd Nov 2022