HTTP JSON API设计规范

前言

越来越多的 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

// good
http://www.example.com/api/v1/users?orderby=name

// bad
http://www.example.com/API/V1/users?orderBy=name

_ 必须 [MUST]_ 使用破折号 –

// good
http://www.example.com/api/v1/user-info

// bad
http://www.example.com/api/v1/user_info

破坏性行为 (create,delete,update)_ 必须 [MUST]_ 使用 POST 方法

// good
POST http://www.example.com/api/v1/user/delete

// bad
GET http://www.example.com/api/v1/user/detete?id=123
// 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 示例

text/javascript;charset=UTF-8

HTTP 响应体

返回的数据包含在 HTTP 响应体中。数据 _ 必须 [MUST]_ 是一个 JSON Object。该 Object_ 可能 [SHOULD]_ 包含 3 个字段:code,msg,data。

{
    code: 200,
    msg: 'success',
    data: {
        xxx: '123'
    }
}

code

code 字段被设计为 业务自定义的状态码, _ 必须 (MUST)_ 是一个不小于 0 的 JSON Number 整数,表示请求的状态。这个字段 _ 不可以 (SHOULD NOT)_ 被省略。

是否要在 API 里面自定义业务状态码,非常具有争议,因为 Http 请求本身已经有了完备的状态码,再定义一套状态码直观上感受多此一举,但在实际开发中,可能由于用户未登录、登录过期而有不同的返回结果和处理方式,所以 _ 必须 [MUST]_ 存在 code 字段。

状态码的定义也最好有一套规范,如按照用户相关、授权相关、各种业务,做简单的分类:

// 授权相关
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:
{
    "code": 1,
    "msg": "参数错误"
}
具有更多信息的 msg:
{
    "code": 1,
    "msg": {
        "text": "参数错误",
        "parameters": {
            "ticket": "ticket参数无效"
        }
    }
}

data

data 字段可以是任意 JSON 类型,表示请求返回的数据主体。这个字段是 _ 可选的 [OPTIONAL]_。数据主体 data 包含了在请求成功时有意义的数据。

一个查询姓名请求的返回数据:
{
    "code": 200,
    "data": "John"
}
一个查询用户信息请求的返回数据:
{
    "code": 200,
    "data": {
        "username": "John",
        "age": "31",
        "gender": "male"
    }
}
一个查询用户列表信息请求的返回数据:
{
    "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

日期展示格式

用来将日期展示给前端或者前端回传给后端的格式:

// 一般日期格式
2018-12-6 11:21:08

// 时间戳格式(十位秒级)
1544066565


// 示例
{
  code: 0,
  msg: 'success',
  data: '2018-12-6 11:21:08'
}

记录项

记录项代表二维表中的一行,通常用于表示某个具体事务抽象的属性。标准记录项数据 _ 必须 [MUST]_ 为一个 JSON Object,记录项的主键命名 _ 必须 [MUST]_ 为“id”。

标准记录项
{
  code: 0,
  msg: 'success',
  data: {
    "id": 1,
    "name": "John",
    "sex": "male",
    "age": 31
  }
}
变通记录项
{
  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。

标准二维表
{
  code: 0,
  msg: 'success',
  data: [
    {
        "id": 1,
        "name": "John",
        "sex": "male",
        "age": 31
    },
    {
        "id": 2,
        "name": "Lily",
        "sex": "female",
        "age": 28
    }
  ]
}
变通二维表
{
  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 值相同。
简单键/值
{
  code: 0,
  msg: 'success',
  data: {
    "name": "John",
    "value": 1,
    "lable": "John" // 可选
  }
}
有序集合

键/值有序集合表示对事务或逻辑类型的抽象与分类。常见的应用场景有单选复选框集合,下拉菜单等。

标准的键/值有序集合是一个 JSON Array,集合中的每一项是一个 JSON Object。项 必须 [MUST] 包含 name 和 value 属性。 可以 [MAY] 通过其他的属性修饰每一项的特殊信息,如 selected。

{
  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} – 结束时间,同上
数据页示例
{
  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} – 子节点列表。
数据示例
// 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,
    }
  ]
}

参考

Licensed under CC BY-NC-SA 4.0
使用 Hugo 构建
主题 StackJimmy 设计