REST API规范

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=1Query参数JSON响应体
查询资源关联的资源GET/users/{userId}/rolesPath参数JSON响应体
下载资源导入excel模版GET/users/excel/templateapplication/octet-stream
通过excel导出资源GET/users/excelQuery参数application/octet-stream
新增资源POST/usersJSON请求体JSON响应体
新增资源关联的资源POST/users/{userId}/rolesPath参数
JSON请求体
JSON响应体
通过excel导入资源POST/users/excelmultipart/form-dataJSON响应体
修改资源PUT/usersJSON请求体JSON响应体
修改资源关联的资源PUT/users/{userId}/rolesPath参数
JSON请求体
JSON响应体
修改资源的一部分PATCH/usersJSON请求体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层校验?

  1. 输入内容的校验也是业务规则的一部分
    Service层为核心业务的实现层,而基础的输入内容的校验(非空、长度等)也是业务规则的一部分,理应由Service层负责。
  2. 替换Controller层会导致校验规则的缺失
    Controller层相较于Service层更偏向于技术接口层,仅负责Http通信协议的处理,并将业务处理委托给Service层。考虑到架构的可持续演进,Controller层作为接口层也可能会被替换为其他的通信协议,如gRPC等,若将输入内容校验放到Controller层,则替换其他通信协议时会导致校验规则的缺失。
  3. 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数组类型参数的实验

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

罗小爬EX

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值