Web后端规范
不久将来,整个行业存在的唯一目的就是消费平台上的数据。你的API越容易使用,那么就 会有越多的人去用它。立足现在,展望未来,restful风格的api是我们必然的趋势。
定义
- 资源: 一个对象的实例
- 集合:一群同种对象
- HTTP:跨网络的通信协议
- 客户端:可以创建HTTP请求的客户端应用程序
- 第三方开发者:这个开发者不属于你的项目但是有想使用你的数据
- 服务器:一个HTTP服务器或者应用程序,客户端可以跨网络访问它
- 端点:这个API在服务器上的URL用于表达一个资源或者一个集合
- 幂等:无边际效应,多次操作得到相同的结果
- URL段:在URL里面已斜杠分隔的内容
URL
URI 表示资源,资源一般对应服务器端领域模型中的实体类。
URL规范
- 不用大写字符
- 只用下划线
_
连接 - 名词表示资源的集合,使用复数
资源集合,单个资源
资源集合
/zooms/ # 所有动物园
/zooms/1/employees/ # id为1的动物园的所有职工
单个资源
/zooms/1/ # id为1的动物园
避免层级过深的url
/
在url中表达层级,用于按实体关联关系进行对象导航,一般根据id导航。
过深的导航容易导致url膨胀,不易维护,如 GET /zooms/1/areas/3/animals/4, 尽量使用查询参数代替路径中的实体导航,如GET /animals/?zoom=1&area=3。
Request
动词
五个非常重要的HTTP动词必须知道
- GET (选择):从服务器上获取一个具体的资源或者一个资源列表,具备幂等性
- POST (创建): 在服务器上创建一个新的资源, 不具备幂等性
- PUT (更新):以整体的方式更新服务器上的一个资源, 具备幂等性
- PATCH (部分更新):只更新服务器上一个资源的部分属性, 具备幂等性
- DELETE (删除):删除服务器上的一个资源, 具备幂等性
原则上,只允许客户端或者第三方调用者使用这五个HTTP动词进行数据交互,并且在URL段 里面不出现任何其他的动词。有写操作的动词性很强, 不太容易用一个名词来表述,可以 逻辑上换一个方式来处理,比如作废一张订单,可以采用两种方式来实现。一是修改订单的 状态和相关字段(PATCH方法,这种的业务逻辑性不强,不清晰,不推荐),另一种是把作废 的订单看作一种逻辑资源,只需要在这种资源集合中使用POST方法创建一个资源即可。
另外一般情况下,GET请求要考虑缓存(客户端,服务器都要考虑),减少服务器检索数据的 压力。
版本化
API是服务器与客户端之间的一个公共契约。对服务器上的API做了一个更改,并且这些更改 无法向后兼容,那么就打破了这个契约。这时候既要确保应用程序逐步的演变,又要让客户 端继续可用。那么必须在引入新版本API的同时保持旧版本API仍然可用。所以接口必须版本 化
- 如果只是简单的增加一个新的特性到API上,如资源上的一个新属性或者增加一个新的端 点,不需要增加API的版本。因为这些并不会造成向后兼容性的问题,只需要修改文档即可。
- 申明不支持一个特性并不意味着关闭或者破坏它。而是告诉客户端旧版本的API将在某个 特定的时间被删除,并且建议他们使用新版本的API。
- 在URL中包含版本信息
GET /v1/zooms/ GET /v2/zooms/1/animals/
UserAgent
所有请求的Header信息中必须按照格式填写User-Agent信息,格式:
type|os|os_version|product|version|...
说明:
-
type
: 客户端类型, [MOBILE, DESKTOP, BROWSER] -
os
: 操作系统, [Android, iOS, Chrome, IE ...] -
os_version
: 操作系统版本号, 包含完整信息,比如:Windows 7 Ultimate Service Pack 1 64bit -
product
: 产品, [KLICEN_APP, ZEUS(宙斯系统),ZEUS_APP(宙斯系统App), KLICEN_EP(凯励程企业版) ,INSTALL(安装工具), VUS(欣悦途), EWS(预警系统)] -
version
: 客户端版本号
手机端必须增加 手机品牌|手机型号|屏幕分辨率
没有的用空表示;必须包含前面五个字段。例如:
"User-Agent": "MOBILE|Android|7.0|KLICEN_APP|2.2.5|Dalvik/2.1.0 (Linux; U; Android 7.0; FRD-AL00 Build/HUAWEIFRD-AL00)|1080*1794|863549034263964"
Response
内容类型
接口只提供两种类型的返回内容
- 全部接口默认支持json
- 部分接口支持excel表格/csv,数据导出作为一种返回格式,导出权限可配置
/animals/?zoom=1&format=excel # 导出id为1的动物园的所有动物数据
### 预期返回的内容 - GET /collection/?page=1&page_size=15: 返回一系列资源对象,分页
- GET /collection/resource/: 返回单独的资源对象
- POST /collection/: 返回新创建的资源对象
- PUT /collection/resource/: 返回完整的资源对象
- PATCH /collection/resource/: 返回完整的资源对象
- DELETE /collection/resource/: 返回一个空文档
使用ISO8601的国际化时间格式
接收和返回时间数据时只使用UTC格式
"create_at": "2012-01-01T12:00:00Z"
枚举型
枚举类型的字段返回其名值对:
{
"key": 存储值,
"verbose": 显示值
}
提供标准的时间戳
提供默认的资源创建时间,更新时间 created_at and updated_at, 例如:
{
...
"created_at": "2012-01-01T12:00:00Z",
"updated_at": "2012-01-01T13:00:00Z",
...
}
这些时间戳可能不适用于某些资源,这种情况下可以忽略省去。
包装
response 的 body 统一做如下包装。示例:
{
"code": 0,
"msg": "请求成功"(用户友好),
"data": {
"id": 1,
"name": "张三"
}
}
使用djangorestframework的情况下,可以在配置文件中配置REST_FRAMEWORK
中的DEFAULT_RENDERER_CLASSES
为utils.renders.CustomJsonRender
, 它的具体实现如下:
# -*- coding: utf-8 -*-
from rest_framework.renderers import JSONRenderer
class CustomJsonRender(JSONRenderer):
""" 自定义返回数据 Json格式
{
"code": 0,
"msg": "success",
"data": { ... }
}
"""
def render(self, data, accepted_media_type=None, renderer_context=None):
if renderer_context:
response = renderer_context['response']
code = 0 if int(response.status_code / 100) == 2 else response.status_code
msg = 'success'
if isinstance(data, dict):
msg = data.pop('msg', msg)
code = data.pop('code', code)
data = data.pop('data', data)
if code != 0 and data:
msg = data.pop('detail', 'failed')
response.status_code = 200
res = {
'code': code,
'msg': msg,
'data': data,
}
return super().render(res, accepted_media_type, renderer_context)
else:
return super().render(data, accepted_media_type, renderer_context)
可能的错误信息的code
可根据实际情况扩展, 所以要让客户端能获取到这些code
。
code码
- 0 OK - [GET]: 客户端向服务器请求获取数据成功
- 201 CREATED - [POST/PUT/PATCH]: 客户端向服务器请求改变数据成功
- 204 NO CONTENT - [DELETE]: 客户端要求服务器删除一个资源,服务器删除成功
- 400 BAD REQUEST - [POST/PUT/PATCH]: 客户端向服务器提供了不正确的数据,服务器什么也不做
- 401 UNAUTHORIZED - [*]: 用户未认证
- 403 FORBIDDEN - [*]: 已认证但权限不够
- 404 NOT FOUND - [*]: 客户端引用了一个不存在的资源或集合
- 500 INTERNAL SERVER ERROR - [*]: 服务器发生内部错误,客户端无法得知结果,请求可能已经处理成功
嵌套外键关系
序列化的外键关系建立在一个有嵌套关系的对象之上, 例如:
{
"name": "成都动物园",
"manager": {
"id": 1
},
...
}
而不是这样, 例如:
{
"name": "成都动物园",
"owner_id": 1,
...
}
这种方式把相关联的资源信息内联在一起,在返回更多信息时不用改变响应资源的结构, 例如:
{
"name": "成都动物园",
"owner": {
"id": 1,
"name": "张三",
"email": "[email protected]"
},
...
}
文档
- 避免使用文档自动化生成器,即便用了,你也要保证自己审阅过并让它具有更好的版式。
- 不要截断示例中请求与响应的内容,展示完整的东西。并在文档中使用高亮语法。
- API的验证授权,包含获取及使用验证tokens
- API稳定性及版本控制, 包含如何选择所需要的版本
- 正确的响应和错误的响应都应该文档化,并说明在什么情况下会产生这些的错误
如果时间允许,甚至可以创建一个控制台来让开发者可以立即体验一下API的功能。
API分析
API分析就是持续跟踪那些正为人使用的API的版本和端点信息,这样做的好处是:
- 决定什么时候不再支持某个版本
- 找到那些使用最广泛的API,用其作为指导业务方向或者优化效率的重要的依据
- 更多...
认证与安全
认证
采用OAuth2.0,OAuth2.0提供了一个非常好的方法去做这件事。在每一个请求里, 你可以明确知道哪个客户端创建了请求,哪个用户提交了请求,并且提供了一种标准的访问 过期机制或允许用户从客户端注销,所有这些都不需要第三方的客户端知道用户的登陆认证 信息。
安全
这个暂时不是当前的重点,这里只列出几个方向,后期再完善
- 采用https
- 访问频次的控制
- api访问记录的跟踪分析