目录
REST
+ JSON
已成为Http接口的事实标准,本文给出我平时推荐的REST API规范及相关注意事项。
一、REST相关规范
1.1 Http Method
之前见过技术团队为了前后端交互方便,所有的请求都使用POST方法,支持queryParam和Json Body两种参数形式。如此虽然简化了组件的开发难度,但却丢失了REST的语义。我推荐你使用一切可用的 HTTP 方法,因为这就是它们的目的。你可以把所有的 CRUD 操作映射到 POST、GET、UPDATE 和 DELETE。我只想让你别用 GET 来创建或更新数据。请不要这样做!
关于Http Method的推荐使用方式如下:
GET
读取(查询)
数据,该方法不会改变后端数据,它只读取数据。GET 请求是安全的,可多次重复执行的
,并且不会对系统存储的数据产生影响。
POST
保存(新增)
数据,意味着你将在数据库创建
一条新记录,新增的内容(参数)支持多种格式(Content-Type
):multipart/form-data、 x-www-form-urlencoded 、raw(application/json,text/plain等),推荐使用 application/json
的形式,使用JSON数据可以支持更多复杂的请求(例如支持批量列表、对象嵌套等)。POST 操作是不安全的
,因为它会在服务器端改变一些东西,并且向同一个POST端点(例如新增数据接口)发出同样的2个请求会导致创建2个不同的资源。
PUT
更新
数据,根据数据ID进行全量的更新(对资源对象的所有属性进行更新)
,PUT 也是不安全的操作,因为它在服务器端做了改动,但PUT操作应该是幂等(idempotent)的,即多次重复修改后资源数据都是一样的结果。
DELETE
删除
数据,这个操作肯定不安全的,会导致后端数据的删除,不过删除接口也可以做成幂等的,支持重复删除,若删除不存在的记录也意味着删除成功。
PATCH
更改数据的一部分
,相较于PUT对资源对象的所有属性都进行更新,PATCH仅对资源对象的部分属性进行更新。PATCH既不是安全的,也不是幂等的(例如更新订单状态,状态的更改是有先后顺序的,在已支付状态修改为发货状态,多次重复修改为发货状态时会提示已不是已支付状态)。
既然我们已经了解了基础知识,那么接下来就是实际生活中所发生的事情。大部分人都是用 GET、POST,还有一些人使用 PUT 和 DELETE。我几乎没有见过有谁在用 PATCH。我推荐你使用一切可用的 HTTP 方法,因为这就是它们的目的。你可以把所有的 CRUD 操作映射到 POST、GET、UPDATE 和 DELETE。我只想让你别用 GET 来创建或更新数据。请不要这样做!
关于Http Method的使用场景总结如下表:
HTTP动词 | 操作含义 | 幂等性 | 安全性 |
---|---|---|---|
GET | 从服务器取出资源 (一项或多项) | 是 | 是 |
POST | 在服务器新建一个资源 | 否 | 否 |
PUT | 在服务器更新资源 (客户端提需供改变后的完整资源) | 是 | 否 |
PATCH | 在服务器更新资源 (客户端仅提供改变的属性) | 不确定 | 否 |
DELETE | 从服务器删除资源 | 是 | 否 |
1.2 REST接口格式
REST接口URL定义推荐参照以下规则:
URL规则 | 示例 |
---|---|
不要用动词,而是用名词 | /users /goods |
用复数替代单数 | 使用/users,而不推荐/user |
尽量使用单个单词而不是多个单词, 若必须使用多单词推荐snake_case | /users/{userId}/roles /users/{userId}/ roles_permissions |
下面给出REST接口各场景下method、url、参数及响应结果的参考规范:
注: 以下接口定义采用 用户管理users、角色管理roles 为例。
场景 | HTTP动词 | URL格式 | 参数形式 | 结果形式 | 幂等性 |
---|---|---|---|---|---|
查询一个资源 | GET | /users/{userId} | Path参数 | JSON响应体 | 是 |
根据不同条件查询资源列表 | GET | /users?name=luo&sex=1 | Query参数 | JSON响应体 | 是 |
查询资源关联的资源 | GET | /users/{userId}/roles | Path参数 | JSON响应体 | 是 |
下载资源导入excel模版 | GET | /users/excel/template | 无 | application/octet-stream | 是 |
通过excel导出资源 | GET | /users/excel | Query参数 | application/octet-stream | 是 |
新增资源 | POST | /users | JSON请求体 | JSON响应体 | 否 |
新增资源关联的资源 | POST | /users/{userId}/roles | Path参数 JSON请求体 | JSON响应体 | 否 |
通过excel导入资源 | POST | /users/excel | multipart/form-data | JSON响应体 | 否 |
修改资源 | PUT | /users | JSON请求体 | JSON响应体 | 是 |
修改资源关联的资源 | PUT | /users/{userId}/roles | Path参数 JSON请求体 | JSON响应体 | 是 |
修改资源的一部分 | PATCH | /users | JSON请求体 | JSON响应体 | 不确定 |
删除一个资源 | DELETE | /users/{userId} | Path参数 | JSON响应体 | 是 |
删除多个资源 | DELETE | /users/{userIds} | Path参数 (多个ID使用英文逗号分隔,例如/users/1,2,3) | JSON响应体 | 是 |
删除资源关联的资源 | DELETE | /users/{userId}/roles/{roleId} | Path参数 | JSON响应体 | 是 |
注:
若GET请求查询参数过长(极端情况),
可考虑使用POST + JSON Body,
可以理解为每次查询的结果是一个新创建的临时结果集。
1.3 Http Status
- 信息型响应代码(以 1xx 开头)
- 成功响应代码(以 2xx 开头)
- 重定向响应代码(从 3xx 开始)
- 客户端错误响应代码(从 4xx 开始)
- 服务器错误响应代码(以 5xx 开头)
1.4 版本控制
推荐在REST接口的URL添加版本号前缀,格式如下:
# V1版本
/api/v1/...
/api/v1/users/...
/api/v1/roles/...
# V2版本
/api/v2/...
/api/v2/users
/api/v2/rolse
切记在升级应用版本时,同步升级接口URL中对应的版本号,
例如应用使用语义化版本号:MAJOR.MINOR.PATCH
格式时,
URL中的版本号即对应MAJOR
版本,所以在升级MAJOR
版本号时需同步升级接口版本号。
在URL中添加版本号,不仅可以方便不同版本的接口管理,也可以帮助开发人员在纷杂的线上环境中通过浏览器控制台、接口定义等一眼识别出目标系统的版本,快速定位问题。
二、SpringMVC相关规范
2.1 参数校验
1)集成Java Validation完成基础校验
通过集成Java Validation相关注解@Validated、@Valid、@NotBlank等,完成输入参数的基础校验。
更多内容请访问: JSR 380: Bean Validation
通过使用Java Validation尽可能让错误前置,避免必填项、数字、日期格式等低级错误穿透到业务逻辑或持久层。
若存在复杂的业务校验,如编码是否唯一,数据关联验证等,则在Service层通过手动编码完成。
注:
关于SpringBoot Validation的更多介绍可参见:
https://luoex.blog.csdn.net/article/details/120074866
2)集成校验的层次
若存在Service层,则推荐将校验逻辑统一放置到Service层。
为什么不在Controller层校验,而是在Service层校验?
- 输入内容的校验也是业务规则的一部分
Service层为核心业务的实现层,而基础的输入内容的校验(非空、长度等)也是业务规则的一部分,理应由Service层负责。- 替换Controller层会导致校验规则的缺失
Controller层相较于Service层更偏向于技术接口层,仅负责Http通信协议的处理,并将业务处理委托给Service层。考虑到架构的可持续演进,Controller层作为接口层也可能会被替换为其他的通信协议,如gRPC等,若将输入内容校验放到Controller层,则替换其他通信协议时会导致校验规则的缺失。- Service层间的彼此调用也需要保证输入内容的合法性
若将校验放到Controller层,则Service层的不同Service实现间相互调用将无法触发校验,可能会导致业务异常,而将校验逻辑放到Service层则可以保证不同Service实现间相互调用将正常触发校验,显然将校验逻辑放到Service层更合理。
3)关于校验提示信息
若使用Java Validation内建注解来完成基础参数的校验,校验注解中的message无需设置(保持为空即可),由校验框架自行生成(支持国际化)。原则上该部分校验信息不是给用户看的,是给开发人员联调时排查问题的,所以保持默认提示信息即可。强烈推荐在前端(如浏览器端、移动端)页面完成基础的参数校验,避免不合规的数据传到后端,而后端接口中的参数校验只作为最后的闸门,避免异常业务数据进入系统中。
对于Service中的自定义校验逻辑,若校验不通过,需给出相应的响应码及提示信息(可通过自定义业务异常实现,如BizException)。
2.2 响应结果
1)SystemResponse
推荐封装统一的SystemResponse :
class SystemResponse<T> {
/**
* 返回状态码
*/
public String code;
/**
* 返回消息信息
*/
private String msg;
/**
* 返回业务数据
*/
private T data;
//getter/setter...
}
其中code
表示响应码,由业务实现方自行定义,
msg
表示响应提示信息,在发生业务异常时用来给出相应的错误提示,
data
则表示具体的响应数据,例如调用查询商品接口时返回的商品详情信息、商品分页列表等。
2.3 接口全局异常处理
API接口层(Controller层)需捕获并处理一切业务异常,并通过日志框架记录相关异常,然后通过SystemResponse统一封装响应信息,接口层的异常信息不可直接暴露到响应结果中,接口统一返回Http Status 200,例如在SpringMVC中可采用@ControllerAdvice实现。
2.4 参数校验提示信息支持国际化
关于Java Validation相关注解中的message信息,如无特殊需求默认保持空即可,
若需设置自定义提示信息,可支持直接设置错误提示信息【不推荐】:
@NotBlank(message ="姓名不能为空")
private String name;
又或者【推荐】通过{…}指定国际化信息的key,支持国际化提示信息(需结合Spring Message国际化):
@NotBlank(message ="{BIZ.USER.NAME.NOT_BLANK}")
private String name;
关于国际化KEY的命名规范,推荐格式如下:
格式:
{APP}.{MODULE}.{PROP}.{RULE}
示例:
BIZ.GOODS.NAME.NOT_NULL=商品名称不能为空
RBAC.USER.USER_ACCOUNT.LENGTH=用户账号长度不能超过256
2.5 响应码规范
对于Service中的自定义校验逻辑,若校验不通过,可通过抛出BizException结束相关业务处理,在抛出异常时需同步给出相应的响应码,该异常会被全局异常处理器捕捉并处理,后续该响应码会作为SystemResponse的code返回给前端:
//抛出业务异常
throw new BizException(yourRespCode, yourRespMsg)
亦可通过工具类AssertUtils生成BizException并给出相应的响应码:
//判断条件是否成立,若不成立则抛出业务异常
AssertUtils.assertTrue(yourAssertCondition, yourRespCode)
//判断条件是否不成立,若成立则抛出业务异常
AssertUtils.assertFalse(yourAssertCondition, yourRespCode)
//判断对象是否为空,若非空则抛出业务异常
AssertUtils.assertNull(yourObj, yourRespCode)
//判断对象是否不为空,若为空则抛出业务异常
AssertUtils.assertNotNull(yourObj, yourRespCode)
//其他:assertBlank, assertNotBlank, assertEmpty, assertNotEmpty
其中响应码的命名规范,推荐格式如下:
格式:
{APP}.{MODULE}.{NUM_CODE}
其中NUM_CODE
建议使用如下前缀:
输入异常: 100…
内部业务处理异常: 200…
调用第三方服务异常: 300…
具体示例:
RBAC.COMMON.100101
RBAC.COMMON.100201
RBAC.USER.200101
RBAC.USER.200201
可为响应码定义相应的国际化提示信息,在全局异常处理逻辑中会提取BizException中的响应码所对应的国际化提示信息,并将该提示信息设置到SystemReponse中的msg属性,用于给出更详细的业务提示信息。结合前面给出的响应码格式定义,给出如下参考国际化信息定义:
# 输入异常
RBAC.DEPT.100101=组织编码已存在
RBAC.DEPT.100102=组织编码不存在
RBAC.DEPT.100201=组织父编码不存在
# 内部业务处理异常
RBAC.DEPT.200101=组织下存在已绑定的用户,请先解除绑定后再删除
RBAC.DEPT.200201=组织下存在子组织,请先删除子组织后再删除
# 调用第三方服务异常
RBAC.DEPT.300101=同步组织信息异常
参考:
REST 十诫
RESTful GET,如果存在大量参数,是否有必要变通一下?
@RequestParam和@PathVariable传递数组,json数组类型参数的实验