# 为什么要用 RESTful >[info]RESTful 给我的最大感觉就是规范、易懂和优雅,一个结构清晰、易于理解的 API 完全可以省去许多无意义的沟通和文档。并且 RESTful 现在越来越流行, >[info]在开始介绍 RESTful API 之前,先讲一下 RESTful 架构。 ## RESTful 架构 >[info]REST,即Representational State Transfer 的缩写。意为 " 表现层状态转化 " 。 >[info]要理解RESTful架构,最好的方法就是去理解 Representational State Transfer 这个词组到底是什么意思,它的每一个词代表了什么涵义。如果把这个名称搞懂了,也就不难体会 REST 是一种什么样的设计。 ## 资源 (Resources) >[info]REST的名称"表现层状态转化"中,省略了主语。"表现层"其实指的是"资源"(Resources)的"表现层"。 >[info]所谓"资源",就是网络上的一个实体,或者说是网络上的一个具体信息。它可以是一段文本、一张图片、一首歌曲、一种服务,总之就是一个具体的实在。你可以用一个 URI(统一资源定位符)指向它,每种资源对应一个特定的 URI 。要获取这个资源,访问它的 URI 就可以,因此 URI 就成了每一个资源的地址或独一无二的识别符。所谓"上网",就是与互联网上一系列的"资源"互动,调用它的 URI 。 ## 表现层(Representation) >[info]"资源"是一种信息实体,它可以有多种外在表现形式。我们把"资源"具体呈现出来的形式,叫做它的"表现层"(Representation)。 比如,文本可以用 txt 格式表现,也可以用 HTML 格式、 XML 格式、JSON 格式表现,甚至可以采用二进制格式;图片可以用 JPG 格式表现,也可以用 PNG 格式表现。 URI 只代表资源的实体,不代表它的形式。严格地说,有些网址最后的" .html "后缀名是不必要的,因为这个后缀名表示格式,属于"表现层"范畴,而 URI 应该只代表"资源"的位置。它的具体表现形式,应该在 HTTP 请求的头信息中用 Accept 和 Content-Type 字段指定,这两个字段才是对"表现层"的描述。 >[info]状态转化(State Transfer) 访问一个网站,就代表了客户端和服务器的一个互动过程。在这个过程中,势必涉及到数据和状态的变化。 互联网通信协议 HTTP 协议,是一个无状态协议。这意味着,所有的状态都保存在服务器端。因此,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生"状态转化"(State Transfer)。而这种转化是建立在表现层之上的,所以就是"表现层状态转化"。 客户端用到的手段,只能是HTTP协议。具体来说,就是HTTP协议里面,四个表示操作方式的动词:GET 、 POST 、 PUT 、 DELETE 。 它们分别对应四种基本操作: GET 用来获取资源, POST 用来新建资源,PUT 用来更新资源,DELETE 用来删除资源。 ## 综述 >[info]总结一下什么是RESTful架构: 每一个URI代表一种资源; 客户端和服务器之间,传递这种资源的某种表现层; 客户端通过四个HTTP动词,对服务器端资源进行操作,实现"表现层状态转化"。 RESTful API 的设计 介绍完 RESTful 的含义,再说说 RESTful API 的设计。 ## 协议 >[info]如果能全站 HTTPS 当然是最好的,不能的话也请尽量将登录、注册等涉及密码的接口使用 HTTPS。 ## 域名 >[info]应该尽量将API部署在专用域名之下。如: https://api.example.com 如果确定API很简单,不会有进一步扩展,可以考虑放在主域名下。 https://example.org/api/ ## 版本号 >[info]API 的版本号和客户端 APP 的版本号是毫无关系的,不要让 APP 将它们用于提交应用市场的版本号传递到服务器,而是提供类似于v1、v2之类的 API 版本号。 版本号拼接在 URL 中。如: api.example.com/v1/users 或是放在 Header 中: api.example.com/users version=v1 ## 请求 >[info]一般来说 API 的外在形式无非就是增删改查(当然具体的业务逻辑肯定要复杂得多),而查询又分为详情和列表两种,在 RESTful 中这就相当于通用的模板。例如针对文章(Article)设计 API,那么最基础的 URL 就是这几种: ~~~ GET /articles: 文章列表 GET /articles/id:文章详情 POST /articles/: 创建文章 PUT /articles/id:修改文章 DELETE /articles/id:删除文章 ~~~ ## Token 和 Sign >[info]API 需要设计成无状态,所以客户端在每次请求时都需要提供有效的 Token 和 Sign,在我看来它们的用途分别是: >[info]Token 用于证明请求所属的用户,一般都是服务端在登录后随机生成一段字符串(UUID)和登录用户进行绑定,再将其返回给客户端。Token 的状态保持一般有两种方式实现:一种是在用户每次操作都会延长或重置 TOKEN 的生存时间(类似于缓存的机制),另一种是 Token 的生存时间固定不变,但是同时返回一个刷新用的 Token,当 Token 过期时可以将其刷新而不是重新登录。 Sign 用于证明该次请求合理,所以一般客户端会把请求参数拼接后并加密作为 Sign 传给服务端,这样即使被抓包了,对方只修改参数而无法生成对应的 Sign 也会被服务端识破。当然也可以将时间戳、请求地址和 Token 也混入 Sign,这样 Sign 也拥有了所属人、时效性和目的地。 ## 业务参数 >[info]在 RESTful 的标准中,PUT 和 PATCH 都可以用于修改操作,它们的区别是 PUT 需要提交整个对象,而 PATCH 只需要提交修改的信息。但是在我看来实际应用中不需要这么麻烦,所以我一律使用 PUT,并且只提交修改的信息。 >[info]另一个问题是在 POST 创建对象时,究竟该用表单提交更好些还是用 JSON 提交更好些。其实两者都可以,在我看来它们唯一的区别是 JSON 可以比较方便的表示更为复杂的结构(有嵌套对象)。另外无论使用哪种,请保持统一,不要两者混用。 >[info]还有一个建议是最好将过滤、分页和排序的相关信息全权交给客户端,包括过滤条件、页数或是游标、每页的数量、排序方式、升降序等,这样可以使 API 更加灵活。但是对于过滤条件、排序方式等,不需要支持所有方式,只需要支持目前用得上的和以后可能会用上的方式即可,并通过字符串枚举解析,这样可见性要更好些。例如: >[info]搜索,客户端只提供关键词,具体搜索的字段,和搜索方式(前缀、全文、精确)由服务端决定: >[info]/users/?query=ScienJus 过滤,只需要对已有的情况进行支持: >[info]/users/?gender=1 分页: >[info]/users/?page=2&per_page=20 响应 尽量使用 HTTP 状态码,常用的有: ~~~ 200:请求成功 201:创建、修改成功 204:删除成功 400:参数错误 401:未登录 403:禁止访问 404:未找到 500:系统错误 ~~~ >[info]但是有些时候仅仅使用 HTTP 状态码没有办法明确的表达错误信息,所以也可以在里面再包一层自定义的返回码,例如: ~~~ 成功时: { "code": 100, "msg": "成功", "data": {} } { "code": -1000, "msg": "用户名或密码错误" } ~~~ >[info]data是真正需要返回的数据,并且只会在请求成功时才存在,msg只用在开发环境,并且只为了开发人员识别。客户端逻辑只允许识别code,并且不允许直接将msg的内容展示给用户。如果这个错误很复杂,无法使用一段话描述清楚,也可以在添加一个doc字段,包含指向该错误的文档的链接。 >[info]返回数据 JSON 比 XML 可视化更好,也更加节约流量,所以尽量不要使用 XML。 >[info]创建和修改操作成功后,需要返回该资源的全部信息。 >[info]返回数据不要和客户端界面强耦合,不要在设计 API 时就考虑少查询一张关联表或是少查询 / 返回几个字段能带来多大的性能提升。并且一定要以资源为单位,即使客户端一个页面需要展示多个资源,也不要在一个接口中全部返回,而是让客户端分别请求多个接口。 >[info]最好将返回数据进行加密和压缩,尤其是压缩在移动应用中还是比较重要的。