RESTfull定义
摘抄自百度百科词条RESTful。
REST(英文:Representational State Transfer,简称REST)描述了一个架构样式的网络系统,比如 web 应用程序。它首次出现在 2000 年 Roy Fielding 的博士论文中,Roy Fielding是 HTTP 规范的主要编写者之一。在目前主流的三种Web服务交互方案中,REST相比于SOAP(Simple Object Access protocol,简单对象访问协议)以及XML-RPC更加简单明了,无论是对URL的处理还是对Payload的编码,REST都倾向于用更加简单轻量的方法设计和实现。值得注意的是REST并没有一个明确的标准,而更像是一种设计的风格。
REST 指的是一组架构约束条件和原则。满足这些约束条件和原则的应用程序或设计就是 RESTful。
RESTFUL特点包括:
1、每一个URI代表1种资源;
2、客户端使用GET、POST、PUT、DELETE4个表示操作方式的动词对服务端资源进行操作:GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源;
3、通过操作资源的表现形式来操作资源;
4、资源的表现形式是XML或者HTML;
5、客户端与服务端之间的交互在请求之间是无状态的,从客户端到服务端的每个请求都必须包含理解请求所必需的信息。
RESTful标准用法实践
这里以一个物流系统为例定义一组接口,这里需要完成的功能如下
- 维护物流运单。用户需要对物流运单进行增删改查操作。每个运单上都有单号,承运商,运输方式等信息。
- 维护发票信息。用户需要管理发票,每个发票都有重量,金额和相关物流运单号。
- 统计不同运输方式、承运商在给定时间的费用信息。
通过需求可以清晰的看到两个资源,物流运单和发票,分别定义为delivery和invoice,根据REST风格的特点,定义如下接口
-
创建物流运单
REST建议使用POST创建资源
创建物流运单接口 URL:[POST]/delivery Request: { "doNum": "12345678", "carrier": "shunfeng", "mot": "kongyun", "weight": 234 } Response: { "doNum": "12345678", "carrier": "shunfeng", "mot": "kongyun", "weight": 234 }
错误码:
-
200: 正常创建
- 409:单号冲突了
- 422:参数不正确。仅靠错误码那难以给出更为准确的提示,类似:运输方式kongyun错误,有效运输方式为[Air,Sea,Truck]。
-
查询物流运单
REST建议使用GET来获取资源。
查询所有: URL:[GET]/delivery Response: [ {运单信息}, {运单信息} ] 根据ID查询 URL:[GET]/delivery/{delivery-id} Response: {运单信息} 多条件查询 URL:[GET]/delivery/?mot={运输方式}&carrier={承运商}... Response: [ {运单信息}, {运单信息} ]
错误码:
- 200: 正常查询
- 404: 查询的资源不存在
- 422: 查询参数不正确
在实际应用中,这个查询被挑战的最多
-
参数太多难以传递
GET传递参数的常规方式就是URL参数,但URL长度是受限的,难以完成类似一次查询1万单号这种需求。
-
复杂参数难以传递
GET传递参数的常规方式就是URL参数,但是URL上的参数只能传递结构简单的数据,难以处理对象数组参数,类似[{“carrier”=“c1”, “mot”=“Sea”}, {“carrier”=“c2”, “mot”=“Air”}]这种复杂参数需要特殊处理。对于把文件当做过滤条件的就更处理不了了。
-
工作量多
后台在处理复杂查询请求时,一般都会有一个表示请求条件的结构,通过URL参数构造这个结构需要额外工作量,前台在发送请求时,将所有参数拼接到URL中也需要工作量,还面临着各种编码问题。
-
修改物流运单
REST建议使用PUT来获取资源。
修改物流运单接口 URL:[PUT]/delivery/{delivery-id} Request: { "doNum": "12345678", "carrier": "shunfeng", "mot": "kongyun", "weight": 234 } Response: { "doNum": "12345678", "carrier": "shunfeng", "mot": "kongyun", "weight": 234 }
错误码:
- 200: 正常修改
- 422: 参数错误。和创建一样无法报告更为准确的错误。
在实际应用中,这个修改操作还面临着批量修改和部分修改的挑战
-
批量修改
用户希望修改满足条件的所有运单的某些属性为固定值。这个需求很常见,但是REST没说怎么办,当前基本上是开发人员在自由发挥。
-
部分修改
复杂的业务数据经常涉及到部分修改功能。比如每抵达一站,都会有一个称重员对货物称重,记录称重结果,这个人不关系目的地一类的信息,他只是在这个运单上增加一个称重记录
-
删除物流运单
REST建议使用DELETE来获取资源。
删除物流运单接口 URL:[DELETE]/delivery/{delivery-id} Response: { "doNum": "12345678", "carrier": "shunfeng", "mot": "kongyun", "weight": 234 }
实际操作还有批量删除的场景
-
批量上传物流运单
REST没建议
-
批量下载物流运单
REST没建议
-
发票管理接口。参照物流运输单来一套就好了。
-
统计接口
统计信息是发票和运单的综合数据,我们称他为分摊后发票,是每个发票在单个运单上的金额信息,记为delivery-invoice。在后台可以有一个组件在自动更新。虽然这个接口是一个查询接口,但是查询的内容不是原始数据,是统计数据,不知道应该叫什么URL了。
REST enhance
技术和规范都是服务于业务的,在业务需求的推动下,所有的技术和规范必须与时俱进。针对上面提到的问题,做做了一些调整。
基本规则
-
不使用HTTP Code返回错误信息
HTTP Code肩负着传输层的功能,让它兼职业务逻辑,其实很为难它。所有的业务返回信息,都放在Response中更为合理。所以定义了一个通用的返回结构
{ "error": [], // 使用字符串返回所有严重的影响业务继续执行的错信息 "warning": [], // 使用字符串返回所有警告类业务信息 "message": ... // 根据业务需要返回需要的信息 }
-
扩展URI指定的资源
URI仍然用于定位资源,现在资源的范围扩展到原有资源的属性
/delivery // 表示运单资源 /delivery/123 // 表示某一个具体的运单资源 /delivery/123/weight-check // 运单123相关的重量检测记录 /delivery/123/weight-check/1 // 运单123上id为1的重量检测记录 /delivery/mot // 这个不是资源,不能这么写
-
通过Head扩展不同的Command
在原来的4个Method无法表达的情况下,通过Head中增加REST_Command来扩展功能
REST_Command=BATCH_CREATE // 表示批量创建请求 REST_Command=SEARCH // 表示当前操作是一个查询请求 REST_Command=SEARCH_SUMMARY // 表示查询摘要信息,根据业务需要返回比较少的内容,类似只返回运单基本信息,没有重量检测信息 REST_Command=PART_UPDATE // 部分修改。此时修改操作仅仅修改运单数据,不影响重量检测列表 REST_Command=BATCH_UPDATE // 批量修改。 REST_Command=BATCH_DELETE // 批量删除 REST_Command=GENERATE // 对于没有持久化,而是实时计算出来的结果,使用这个命令
-
Request为业务参数
业务参数必须和业务相关才是业务参数,类似用户登录信息,用于标记查询、修改一类的命名,都不是业务信息。
Demo
-
创建物流运单
-
创建运单
URL:[POST]/delivery Request: { "doNum": "12345678", "carrier": "shunfeng", "mot": "kongyun", "weight": 234 } Response: { "error": [], "warning": [], "message": { "doNum": "12345678", "carrier": "shunfeng", "mot": "kongyun", "weight": 234 } }
-
创建运单子对象
URL:[POST]/delivery/{delivery-id}/weight-check Request: { "id": 1, "time": "2020-02-02 02:02:02", "weight": 234 } Response: { "error": [], "warning": [], "message": { "id": 1, "time": "2020-02-02 02:02:02", "weight": 234 } }
子对象是必须已存与运单的信息,如果没有了运单,这个信息也没有了。运单的重量检测记录,属于子对象,但是分摊到运单的发票不是,没有运单,发票还在呀。
-
-
批量创建
创建物流运单接口 URL:[POST]/delivery Head: REST_Command=BATCH_CREATE Request: { // 这个request可以根据需求做一些定制 "doNum": ["12345678", "1234569"], "carrier": "shunfeng", "mot": "kongyun", "weight": 234 } Response: { "error": [], "warning": [], "message": [{ "doNum": "12345678", "carrier": "shunfeng", "mot": "kongyun", "weight": 234 }, ...] }
-
查询物流运单
-
查询所有
URL:[GET]/delivery Head: REST_Command=SEARCH;SEARCH_SUMMARY; // 不同的命令返回数据不同 Response: { "error": [], "warning": [], "message": [{运单信息}, {运单信息}] }
-
根据ID查询单个
URL:[GET]/delivery/{delivery-id} Head: REST_Command=SEARCH;SEARCH_SUMMARY; // 不同的命令返回数据不同 Response: { "error": [], "warning": [], "message": { "doNum": "12345678", "carrier": "shunfeng", "mot": "kongyun", "weight": 234, "weightCheck": [{ "id": 1, "time": "2020-02-02 02:02:02", "weight": 234 } ] } }
-
复杂条件查询
URL:[POST]/delivery Head: REST_Command=SEARCH;SEARCH_SUMMARY; // 不同的命令返回数据不同 Request: { "mot": ["haiyun", "kongyun"], "carrier": ["承运商1", "承运商2"] } Response: { "error": [], "warning": [], "message": [{运单信息}, {运单信息}] }
-
查询子对象列表
URL:[GET]/delivery/{delivery-id}/weight-check/{check-id} Response: { "error": [], "warning": [], "message": [{重量检测信息}, {重量检测信息}] }
-
-
修改物流运单
-
全量修改运单
URL:[PUT]/delivery/{delivery-id} Request: { "doNum": "12345678", "carrier": "shunfeng", "mot": "kongyun", "weight": 234 } Response: { "error": [], "warning": [], "message": {运单信息} }
-
部分修改运单
URL:[PUT]/delivery/{delivery-id} Head: REST_Command=PART_UPDATE Request: { "doNum": "12345678", "carrier": "shunfeng", "mot": "kongyun", "weight": 234 } Response: { "error": [], "warning": [], "message": {运单信息} }
-
修改运单子对象
URL:[PUT]/delivery/{delivery-id}/weight-check/1 Request: { "id": 1, "time": "2020-02-02 02:02:02", "weight": 234 } Response: { "error": [], "warning": [], "message": { "id": 1, "time": "2020-02-02 02:02:02", "weight": 234 } }
-
批量修改运单
URL:[PUT]/delivery/ Head: REST_Command=BATCH_UPDATE Request: { // 业务自定义请求内容 "doNum": ["12345678", "12345679"] "carrier": "shunfeng", "mot": "kongyun", "weight": 234 } Response: { "error": [], "warning": [], "message": [{运单信息}] }
-
-
删除物流运单
删除操作在删除对象不存在时直接返回,因为期望结果和当前结果一致的。
-
删除运单
URL:[DELETE]/delivery/{delivery-id} Response: { "error": [], "warning": [], "message": [{运单信息}] }
-
删除运单子对象
URL:[DELETE]/delivery/{delivery-id}/weight-check/{check-id} Response: { "error": [], "warning": [], "message": [{重量检测信息}] }
-
批量删除
URL:[POST]/delivery Head: REST_Command=BATCH_DELETE Request: { "mot": ["haiyun", "kongyun"], "carrier": ["承运商1", "承运商2"] } Response: { "error": [], "warning": [], "message": [{运单信息}, {运单信息}] }
-
-
批量上传物流运单
批量上传可以按照批量创建的接口做。更新REST_Command=BATCH_UPLOAD。
-
批量下载物流运单
批量下载可以按照批量查询的接口做。更新REST_Command=DOWNLOAD。
-
统计接口
统计信息是发票和运单的综合数据,我们称他为分摊后发票,是每个发票在单个运单上的金额信息,记为delivery-invoice。在后台可以有一个组件在自动更新。虽然这个接口是一个查询接口,但是查询的内容不是原始数据,是统计数据,所以命名为delivery-invoice-report。
URL:[POST]/delivery-invoice-report Head: REST_Command=GENERATE Request: { // 自定义查询条件 "mot": ["haiyun", "kongyun"], "carrier": ["承运商1", "承运商2"] } Response: { "error": [], "warning": [], "message": {统计结果} }
总结
- URI固定表示资源,子对象也算资源,实时计算的内容使用虚拟资源
- HTTP Code是传输层的内容,不再参与业务逻辑
- 使用公共的Response结构来返回各种错误信息
- 扩展Head,增加REST_Command完成各种多样功能