前言
越来越多的Web应用程序使用JSON作为API的一种数据交换格式进行交互。本文档的目标是使HTTP JSON API的设计风格保持一致,容易被理解和维护。一个优秀的API,应该是在其生命周期内能够持续提供稳定、易用、受信任的服务,并且在API的生命周期结束时能让其平滑的消亡。
注:RESTful API是目前比较成熟的一套Web应用程序的API设计理论,本文不对RESTful API过多介绍。在实际快速增长和多变的业务应用中,采用RESTful API需要更高的成本和对后端开发人员有更高的要求,我们更多采用这种轻量化的HTTP JSON API的设计。
约定
在本文档中,使用的关键字会以中文 中括号包含的关键字英文表示:必须[MUST]。关键字”MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL”按照RFC 2119中的描述进行解释。
JSON数据类型
JSON(JavaScript Object Notation)是一种轻量级,基于文本,语言无关的数据交换格式。其包括了4种基本数据类型和2种结构数据类型,共6种数据类型。
基本数据类型
- String 表示一个字符串。
- Number 可以表示整数和浮点数。
- Boolean 可以表示真假,值为true或false。
- Null 通常用于表示空对象。
“true”和true,这两个数据代表的是不同的数据类型。非字符串类型数据输出时一定不要[MUST NOT]为两端加上双引号,否则可能产生不希望的后果(如if中判断”false”的结果是true)。其他容易产生错误的例子如:0和”0″等。
结构数据类型
- Object(对象)是无序的集合,以键值对的方式保持数据。一个Object中包含零到多个name/value的数据,数据间以逗号(,)分隔。name为String类型,value可以是任意类型的数据。Object的最后一个元素之后一定不要[MUST NOT]加上分隔符的逗号,否则可能导致解析出错。
- Array(数组)为多个值的有序集合,数组元素间以逗号(,)分隔。
协议
使用HTTP或HTTPS协议。
URL规范
URL代表所提供的API的唯一性和永久性,在此之前我们应该[SHOULD]设计合理的URL:
必须[MUST]全部使用小写字母拼写URL
代码语言:javascript复制// good
http://www.example.com/api/v1/users?orderby=name
// bad
http://www.example.com/API/V1/users?orderBy=name
必须[MUST]使用破折号 –
代码语言:javascript复制// good
http://www.example.com/api/v1/user-info
// bad
http://www.example.com/api/v1/user_info
破坏性行为(create,delete,update)必须[MUST]使用POST方法
代码语言:javascript复制// good
POST http://www.example.com/api/v1/user/delete
// bad
GET http://www.example.com/api/v1/user/detete?id=123
建议[RECOMMENDED]使用容易理解的英文单词
代码语言:javascript复制// good
POST http://www.example.com/api/v1/user/list
// bad
GET http://www.example.com/api/v1/user/operate
HTTP响应头
status
http响应的status必须(MUST)为200。通常JSON数据被用于通过XMLHttpRequest对象访问,通过javascript进行处理。返回错误的状态码可能导致错误不被响应,数据不被处理。
参考:List_of_HTTP_status_codes
Content-Type
Content-Type字段定义了响应体的类型。一般情况下,浏览器会根据该类型对内容进行正确的处理。对于传输JSON数据的响应,Content-Type推荐(RECOMMENDED)设置为”text/javascript”或”text/plain”。 避免(MUST NOT)将Context-Type设置为text/html,否则可能导致安全问题。
Content-Type中可以指定字符集。通常 需要(SHOULD)明确指定一个字符集。如果是通过XMLHTTPRequest请求的数据,并且字符编码为UTF-8时,可以不指定字符集。
Content-Type 示例
代码语言:javascript复制text/javascript;charset=UTF-8
HTTP响应体
返回的数据包含在HTTP响应体中。数据必须[MUST]是一个JSON Object。该Object可能[SHOULD]包含3个字段:code,msg,data。
代码语言:javascript复制{
code: 200,
msg: 'success',
data: {
xxx: '123'
}
}
code
code 字段被设计为业务自定义的状态码
, 必须(MUST)是一个不小于0的JSON Number整数,表示请求的状态。这个字段 不可以(SHOULD NOT)被省略。
是否要在API里面自定义业务状态码,非常具有争议,因为Http请求本身已经有了完备的状态码,再定义一套状态码直观上感受多此一举,但在实际开发中,可能由于用户未登录、登录过期而有不同的返回结果和处理方式,所以必须[MUST]存在code字段。
状态码的定义也最好有一套规范,如按照用户相关、授权相关、各种业务,做简单的分类:
代码语言:javascript复制// 授权相关
1001: 无权限访问
1002: access_token过期
1003: unique_token无效
...
// 用户相关
2001: 未登录
2002: 用户信息错误
2003: 用户不存在
// 业务1
3001: 业务1XXX
3002: 业务1XXX
// ...
msg
msg字段通常[SHOULD]是一个JSON String或JSON Object,表示除了请求状态外服务端想要对本次请求做出的说明,使客户端能够获取更多信息进行后续处理。这个字段是可选的[OPTIONAL] 。下面的两个例子中,msg字段的信息都可以用于客户端程序的后续处理,但是粒度和处理方式会有不同。
简单说明的msg:
代码语言:javascript复制{
"code": 1,
"msg": "参数错误"
}
具有更多信息的msg:
代码语言:javascript复制{
"code": 1,
"msg": {
"text": "参数错误",
"parameters": {
"ticket": "ticket参数无效"
}
}
}
data
data字段可以是任意JSON类型,表示请求返回的数据主体。这个字段是可选的[OPTIONAL]。数据主体data包含了在请求成功时有意义的数据。
一个查询姓名请求的返回数据:
代码语言:javascript复制{
"code": 200,
"data": "John"
}
一个查询用户信息请求的返回数据:
代码语言:javascript复制{
"code": 200,
"data": {
"username": "John",
"age": "31",
"gender": "male"
}
}
一个查询用户列表信息请求的返回数据:
代码语言:javascript复制{
"code": 200,
"data": [
{
"username": "John",
"age": "31",
"gender": "male"
},
{
"username": "Lily",
"age": "28",
"gender": "female"
}
]
}
数据场景
本章为常见数据场景定义了通用的标准数据格式,用于数据传输和使用。
变通数据格式必须[MUST]是一个JSON Object,其中必须[MUST]包含e-type属性和data属性。e-type属性标识数据类型,便于对数据进行解析;data属性包含变通后的数据。变通数据可以[MAY]包含其他的属性,标识数据的其他扩展信息。
变通数据格式的e-type属性定义了table值。e-type属性可以使用者扩展其他属性值,扩展的属性值必须[MUST]以“项目缩写-名称”命名,如“fc-list”,自主解析。
日期类型
日期类型不属于JSON数据类型。对于日期类型,我们必须[MUST]使用JSON String来表示。为了让日期能够更容易的被显示和被解析,对于日期我们应当[SHOULD)]使用更适合Internet的格式,遵循RFC 3339。
日期展示格式
用来将日期展示给前端或者前端回传给后端的格式:
代码语言:javascript复制// 一般日期格式
2018-12-6 11:21:08
// 时间戳格式(十位秒级)
1544066565
// 示例
{
code: 0,
msg: 'success',
data: '2018-12-6 11:21:08'
}
记录项
记录项代表二维表中的一行,通常用于表示某个具体事务抽象的属性。标准记录项数据必须[MUST]为一个JSON Object,记录项的主键命名必须[MUST]为“id”。
标准记录项
代码语言:javascript复制{
code: 0,
msg: 'success',
data: {
"id": 1,
"name": "John",
"sex": "male",
"age": 31
}
}
变通记录项
代码语言:javascript复制{
code: 0,
msg: 'success',
data: [
{
label: '记录ID',
value: 1,
type: 'number'
},
{
label: '用户名称',
value: 'John',
type: 'string'
},
{
label: '用户性别',
value: 'male',
},
{
lable: '用户年龄',
value: 31
}
]
}
二维表
二维表类型表识为table,是关系模型的主要数据结构。二维表结构具有变通数据格式。标准二维表数据必须[MUST]以一维JSON Array形式表示,JSON Array中每一项是一个JSON Object,代表一条记录。JSON Object的每个成员代表一个字段。每条记录的主键命名必须[MUST]为”id”。
在标准二维表中,字段名在每条记录中都被传输,会造成额外的数据量传输。这个问题会随着记录数的增大会更加突出。为了减少传输数据量,变通格式使用二维JSON Array传输数据,扩展fields属性用于字段说明。fields字段为JSON Array。
标准二维表
代码语言:javascript复制{
code: 0,
msg: 'success',
data: [
{
"id": 1,
"name": "John",
"sex": "male",
"age": 31
},
{
"id": 2,
"name": "Lily",
"sex": "female",
"age": 28
}
]
}
变通二维表
代码语言:javascript复制{
code: 0,
msg: 'success',
data: {
"e-type": "table",
"fields": ["id", "name", "sex", "age"],
"data": [
[1, "John", "male", 31],
[2, "Lily", "female", 28]
]
}
}
键值对
在一个JSON Object中表示键/值对:
- 键的属性名必须[MUST]为name, 杜绝[MUST NOT]使用key或k
- 值的属性名必须[MUST]为value, 杜绝[MUST NOT]使用v。
- 可以[MAY]为其扩展属性名label,一般同name值相同。
简单键/值
代码语言:javascript复制{
code: 0,
msg: 'success',
data: {
"name": "John",
"value": 1,
"lable": "John" // 可选
}
}
有序集合
键/值有序集合表示对事务或逻辑类型的抽象与分类。常见的应用场景有单选复选框集合,下拉菜单等。
标准的键/值有序集合是一个JSON Array,集合中的每一项是一个JSON Object。项 必须[MUST] 包含name和value属性。 可以[MAY] 通过其他的属性修饰每一项的特殊信息,如selected。
代码语言:javascript复制{
code: 0,
msg: 'success',
data: [
{
"name": "不满意",
"value": 0,
"selected": true
},
{
"name": "满意",
"value": 1
},
{
"name": "非常满意",
"value": 2,
"selected": true
}
]
}
数据页
数据页是列表数据常用的数据方式,可能通过查询或翻页获得数据。数据页是二维表数据的包装,包含列表数据本身更多的信息。
数据页必须[MUST]是一个JSON Object,其中必须[MUST]包含的属性为data。data是一个二维表。数据页可以包括一些可选[OPTIONAL]的属性,表示当前数据页的信息。下表列举了数据页的可选属性。
参数/属性
- pageNumber{Number} – 当前页码,计数必须[MUST]为不小于1的整数,从1开始。通常简写为:pn
- pageSize{Number} – 每页显示条数, 必须[MUST]大于0。通常简写为:ps
- total{Number} – 列表总记录数, 必须[MUST]为不小于0的整数。表示当前条件下所有记录的数目,非本页的记录数。
- keyword{String} – 列表所属的搜索关键字。
- orderBy{String} – 列表排序规则。多个排序规则之间以逗号分割(,);正序或倒序以asc或desc表示,与字段名之间以一个空格间隔。例:”id desc,name asc”
- condition{Object} – 列表所属的搜索条件集合。属性中可以包含或不包含keyword字段,如果不包含,建议(RECOMMMANDED)在解析的时候附加搜索关键字keyword条件。
- startTime{Datetime} – 开始时间,用来搜索带有创建时间的列表数据,一般跟endTime成对出现
- endTime{Datetime} – 结束时间,同上
数据页示例
代码语言:javascript复制{
code: 0,
msg: 'success',
data: {
"pn": 1,
"ps": 10,
"total": 100,
"keyword": "John",
"orderBy": "id desc, name asc",
"condition": {},
"startTime": "2010-11-11 11:11:11",
"endTime": "2018-11-11 11:11:11",
"data": [
{
"id": 1,
"name": "John",
"sex": "male",
"age": 31
},
{
"id": 2,
"name": "Lily",
"sex": "female",
"age": 28
}
]
}
}
树结构
树形数据用于表示层叠的数据结构。树型数据必须[MUST]是一个JSON Object,代表树型数据的根节点。下面是标准定义的可选节点列表,不在列表中的属性可以[SHOULD]自行扩展。
节点属性
- id {Number|String} – 节点的唯一标识。
- text {String}- 名称或用于显示的字符串。
- children {Array} – 子节点列表。
数据示例
代码语言:javascript复制// good
{
code: 0,
msg: 'success',
data: {
"id": 1,
"text": "中国",
"children": [
{
"id": 10,
"text": "北京",
"children": [
{
"id": 100,
"text": "东城区"
},
{
"id": 101,
"text": "西城区"
},
{
"id": 102,
"text": "海淀区"
}
]
},
{
"id": 31,
"text": "海南",
"children": [
{
"id": 600,
"text": "海口"
},
{
"id": 601,
"text": "三亚"
},
{
"id": 602,
"text": "五指山"
}
]
}
]
}
}
//bad
{
code: 0,
msg: 'success',
data: [
{
"id": 1,
"text": "中国",
"parentId": 0,
},
{
"id": 2,
"text": "北京",
"parentId": 1,
},
{
"id": 3,
"text": "东城区",
"parentId": 2,
},
{
"id": 4,
"text": "西城区",
"parentId": 2,
}
]
}
参考
- E-JSON数据传输标准
- API返回结果设计经验与总结
- 如何设计一个优秀的API
- RESTful API 设计指南