Restful API 开发规范

推荐简单业务场景 / 开放接口使用 Restful 风格,业务复杂场景 / 内部接口可借鉴 Restful 的优点,不一定完全遵守(抽象所有资源),以更容易理解业务。

统一响应体格式

返回值统一为json格式,最外层只包含 codemsgdata 三个字段,接口返回包含数据内容时,仅使用 data 字段。

{
	"code":"错误码,成功为0",
	"msg":"返回的(错误)描述信息,不能有堆栈信息",
	"data":{数据}
}

响应举例

空响应-成功:
{
	"code":"0",
	"msg":"",
	"data":null
}
空响应-出错:
{
	"code":"0xaabc100f",
	"msg":"username can't be null",
	"data":null
}
单值响应:
{
	"code":"0",
	"msg":"",
	"data":{
	    "username": "cnlym",
	    "nickName": "产码机器"
	}
}
多值响应(分页)
{
	"code":"0",
    "msg":"",
	"data":{
		"total":11, //总条数
		"pageNo": 1,  //当前页
		"pageSize":20, //当前分页记录数
		"list": [{  //分页内容
			"userId": 1,
			"name": "admin"
			},{
			"userId": 2,
			"name": "leader"
		}]
	}
}
多值响应(不分页)
{
	"code":"0",
    "msg":"",
	"data":{
	  "total":11,
	  "list": [{  //分页内容
      			"userId": 1,
      			"name": "admin"
      			},{
      			"userId": 2,
      			"name": "leader"
      		}]
	}
}

统一 api 文档约束

为了便于对接,组件 doc 目录下须包含以下文件

  • api.json

    • 统一使用 swagger json 文档

      • 需要指定 info-title, description,version,contact

      • basePath 对应 context 值,格式为:/<appId>

  • api.md

    • api文档,包含如下,不描述则采用本文的推荐设计:

      • 简介

      • 认证

      • 鉴权规则

      • 安全传输与加密算法

      • 返回值规则

      • 空响应

      • 单值响应

      • 多值响应(分页)

      • 多值响应(不分页)

  • ext.md

    • swagger 返回值无法描述泛型等,额外标注说明

  • guidance.md

    • 编程指引

  • appendix.md

    • 附录,错误码、数据字典、翻译项、复杂参数解释

  • <appId>-api-sdk.jar

    • 接口sdk

Restful 请求方法含义

类型

说明

GET

从服务器取出资源

POST

在服务器新建一个资源,修改一个资源或者删除一个资源

PUT

在服务器更新资源(客户端提供改变后的完整资源)

DELETE

从服务器删除资源

HEAD

获取资源的元数据

PATCH

在服务器更新资源(客户端提供改变的属性)

OPTIONS

获取信息,关于资源的哪些属性是客户端可以改变的

常用 HTTP 状态码

HTTP状态码 说明

200 OK

服务器成功返回用户请求的数据。处理成功时默认该值。

201 Created

修改数据成功(返回之前已经入库)

202 Accepted

表示一个请求已经进入后台排队(异步任务/如批量异步删除)

204 No Content

表示请求成功但无返回值,可用于修改,删除

301 Moved Permanently

资源的URI已被永久更新

302 Found

资源URI重定向,与301类似(临时)

304 Not Modified

资源未更改,客户的缓存资源是最新的, 请客户端使用缓存(缓存),ES-ik插件的动态更新用到了这个

400 Invalid Request

用户发出的请求有错误,是无效请求,如字段映射不正确,参数不正确都可以用这个

401 Unauthorized

需要先进行认证(登录)

403 Forbidden

服务器拒绝执行,一般是由于权限不够,也可能ip进了黑名单等

404 Not Found

用户发出的请求是针对不存在的记录

405 Method Not Allowed

不允许执行目标方法,如只允许POGT却使用GET,响应中应该带有 Allow 头,内容为对该资源有效的 HTTP 方法

406 Not Acceptable

用户请求的格式不可得,如服务器返回 abc.txt,浏览器期望返回数据的MIME类型没有 txt

410 Gone

用户请求的资源被永久删除,且不会再得到

422 Unprocesable entity

参数格式正确,但语义错误(创建资源时,校验未通过),浏览器将它与400处理方式相同,因此也可以使用400

429 Too Many Request

请求过多,触发限流时,可用于反爬虫,418(触发彩蛋)也可用于反爬虫

500 Internal Error

服务器发生错误,用户将无法判断发出的请求是否成功

503 Service Unavailable

服务不可用状态,多半是因为服务器问题,例如CPU占用率大,等等

丢失更新问题

HTTP 201 可以包含一个ETag响应头字段,指示刚刚创建的请求变量的实体标签的当前值。ETag头字段可以在以后的条件请求中使用,以防止“丢失更新”问题。

当多人编辑资源而不了解彼此的更改时,会发生丢失的更新问题。在这种情况下,最后一个更新资源的人“获胜”,之前的更新将丢失。ETag可以与If-Match标头结合使用,让服务器决定是否应该更新资源。如果ETag不匹配,则服务器通过412 (Precondition Failed)响应通知客户端。

数据库中添加dataVersion,每次修改+1,可以使用该字段来避免并发更新问题。

请求头约束

命名规则

单词首字母大写,单词与单词间使用中划线(-)分割。如:User-Agent

请求头保留字段

参数名 类型 必选 说明

User-Agent

string

终端类型

Token

string

接口认证使用:身份认证信息,采用 base64 编码。

X-Sid

string

×

秘钥协商使用:安全会话 ID。

X-Dk

string

×

秘钥协商使用:使用协商密钥加密的 数据密钥

User-Id

string

×

用户 ID

Trace-Id

string

用于标识一笔业务的唯一序号,UUID 格式

Span-Id

string

用于标识某一阶段调用序号,32位字母或数字

Biz-Id

string

×

业务标识

X-B3-TraceId

hex string

UUID 格式

X-B3-SpanId

hex string

64 位值,采用小写 16 进制字符显示

X-B3-ParentSpanId

hex string

64 位值,采用小写 16 进制字符显示

X-B3-Sampled

boolean

×

0 – 不采样, 1 – 采样

X-B3-Flags

string

×

1 – debug,要记录本次所有调用链信息

请求体约束

除文件和表单数据外,POSTPUT 等带请求体的方法,请求体统一为 JSON 格式,

请求参数要求

参数须提供 取值范围格式限制枚举限制

时间字段和格式

  • 当查询条件需要使用起止时间时,参数名统一采用 beginTimeendTime

  • 采用 ISO8601 格式,使用带 Time-zone 的标准时间格式,如:2020-7-13T00:00:00.000+08:00;

字符串和编码
  • 字符串的所有操作统一使用 UTF-8 编码。

  • 长度限制如下

场景 推荐长度

ID、标识、名称、别名

64, 128

描述、备注、文件名(全路径)

512, 1024

详情

2048

文本、文件

不限制

常用字段约束

统一常用的参数名称

分页查询参数

demo: 每页20条记录,查询第一页,先按照 username 正序排序,再按照 phoneNo 逆序排序

{
	"pageNo":"1",
	"pageSize":"20",
	"conditions": [
        {
	        "property": "username",
	        "like": "cn"
	    },{
            "property": "level",
            "equal": "vip"
        }
	],
	"orderBy":[
	    {
	        "property": "username",
	        "direction": "asc"
	    },{
            "property": "phoneNo",
            "direction": "desc"
        }
     ]
}
用途 参数名 举例

查询页码

pageNo

1

分页大小

pageSize

20

排序参考

sortBy

["name"]

排序规则

order

"asc"

分页查询参数名称

参数字段顺序

  • 标识性字段(id、name)

  • 必填字段(sex)

  • 常用字段(phone)

  • 描述性字段(note)

  • 通用型字段(updateTime)

统一错误码格式

版本号设计

软件中任何事物都有变的可能性,api 接口也是这样的,因此需要在 api 的请求路径中添加版本号。如 /api/xxService/v2/**

安全传输

参考 密钥协商

编码注意事项

  • 定义统一返回值类 BaseResponse,且该类只能在接口层使用

  • 所有 Controller 层接口函数统一返回 BaseResponse,或使用全局返回值自动包装

  • 入参,返回值尽量不要有 Map

  • Controller 层进行 DTO 转换,不允许将 DTO 传递倒业务层

  • Controller 不要出现 Request,Response 这类对象

  • 一般不需要在这里打印日志,异常处理,使用统一的日志打印

  • 注意防枚举(id有顺序)、防重放

Ajax VS Restful RPC api 接口

  • 虽然两者都是 HTTP协议 JSON 格式

  • Restful 是无状态的,属于 RPC 的一种形式,职责为服务间通信。校验主要为 token 认证。

  • Ajax 是前后端局部刷新增加用户体验的。校验包含各种网络攻击。

Restful with HTTP status VS 统一返回 200,响应种自定义码值

Shoulder 种采用方案二,自定义返回值状态码,响应种 200 表示接口正常返回,4xx 表示客户端错误,前端开发先排差,5xx 表示服务端异常,后端开发先排差。

方案一:只以HTTP状态码来表示状态
  • 200时候返回内容就是数据,最多有个分页

分页也可以像 github 那样 放到header,响应内容更整洁

  • 只有在抛出Exception异常(即使业务逻辑上有问题,也抛出APIException异常)才返回像下面这样的响应:

HTTP/1.1 405 Method Not Allowed
Content-Type: application/json

{"status_code": 405, "message": "Method 'DELETE' not allowed."}
方案二:所有接口都返回200

响应体里包含:自定义码值、信息、数据

{ "status_code: 1000 "message": "xxxxx" "result": { "id": 1 …​ } }

两种方案优缺点比较
  • 方案一

    • 优点

      • 作为服务端更倾向于方案一,因为大多框架和各家提供的API都是这么干的,其实也很简单(先看状态码,然后直接根据状态码决定后续动作)

      • 使用本身通讯协议作为语义,更符合该种协议的约定

      • 有利于中间层对请求进行缓存

      • 客户端进行相应封装后,代码都好维护,结构整洁

    • 缺点

      • 可能有奇葩运营商对某些HTTP状态码进行一些自定义行为(https可规避),如非 200 可能有广告

      • 可能有业务异常较多,是 HTTP status 无法描述的,或不足以描述的,如登录失败 401 可能是账号不存在(引导注册),也可能是密码错误(清空密码框重新输入)

      • 可移植性较差,当不是 HTTP 通信时,需要推翻所有客户端逻辑

      • 某些技术欠佳、不负责任的客户端开发会认为,只要不是200一定是后端问题,认为接口不稳定

      • 需要所有服务端开发者都能完全理解并实现 RESTful API,且客户端开发人员也能理解

      • 非 200 响应,可能会被浏览器拦截处理

  • 方案二

    • 优点

      • 一些客户端开发希望是方案二,因为这样会让客户端的处理逻辑变简单(200以外全去捕获异常,200时再看status_code做不同处理)

      • API响应码值语义分层明确,HTTP status 是为协议层的使用的,API 中可以自定义语义状态,互不影响

      • 早些年,普遍都是这样做,符合习惯,兼容旧系统

      • 一些客户端/前端/APP开发不懂 HTTP 协议,不知道状态码是什么,这样做更直白

      • 一些客户端框架,响应不是200则抛异常,要么显示捕获、要么就得深入了解框架的设计,且该框架支持扩展

    • 缺点

      • 即使自定义了码值,仍然复用了 HTTP status 的 200

      • 服务器端不一定真能保证总是返回200的状态码,一些框架中原始就抛出 401 / 403 / 404 / 500 等异常,需要服务端开发改造来规避