相信大家或多或少都听过restful这个词,所谓的restful接口指的是一种基于 REST(Representational State Transfer,表述性状态转移) 架构风格设计的网络服务接口,主要用于实现系统间的高效、可扩展的通信。用人话来说restful接口需要满足:
1、使用HTTP协议指向一个URL资源
2、接口内容使用统一的规范,如XML或者JSON
3、不同的请求方式对应不同的行为
1、以资源为中心
想象所有的资源都是长在树上的一颗果实,每次请求都是从树根出发,经过某个树干,最终找到果实所在的位置。restful接口全部指向一个URL资源,一般采用以下标准:
http(s)://域名:端口[/版本]/资源1[/子资源2/.../子资源n][/路径变量]
举例说明:
查找v1版本的api接口中ID为10的MT报文:
反例:
select_id表示一个行为,并不是访问一个资源的路径,同时在接口处暴露了代码逻辑,大大增加了被攻击的风险
2、统一接口
一般我们统一使用XML或者JSON格式的报文,数据只包含基础的信息,不要有展示层相关的信息,接口需要在公司内制定统一宣讲形成规范。
举例说明:
请求采用json格式报文,其中header中需要填写服务编号,商户号等内容,body中填写实际请求的内容,auth用于填写权限校验信息:
{
header:{
serviceId : XXX,
merchNo : YYY
},
body: {
id : 123
},
auth: {
check: d8dmf^8
}
}
返回报文供养采用json格式,其中code表示返回码,msg表示返回信息,body表示实际返回的内容,实际返回内容中不要用HTML标签等表示层的内容
{
code : 200,
msg : success,
body : {}
}
3、不同请求对应不同的行为
GET: 请求对应的资源
GET test.com/v1/blog/art…
获取v1版本中编号为10的博客文章
POST: 新增对应资源
POST test.com/v1/blog/art…
{id:8, msg:{...文章内容...}}
新增ID为8的博客文章
PUT: 更新对应资源
PUT test.com/v1/blog/art…
{id:8, msg:{...文章内容...}}
更新ID为8的博客文章
Delete: 删除对应资源
DELETE test.com/v1/blog/art…
删除ID为8的博客文章
标准的RESTFUL接口通过不同的请求方式定义了不同的行为,但是日常工作中,有些公司并不是完全按照标准的RESTFUL格式进行接口的定义。比如,统一采用POST请求,在POST请求体中定义请求的行为。无论接口的形式是什么,只要能够方便见名知意,通过接口名大概了解这个接口的作用,形成统一的规范,这就是一个合格的接口,这也是RESTFUL风格接口的重要意义之一。
实际开发中的注意事项
1、注意接口语义一定要保持统一的规范
在我们定义接口时,一定要遵循固定的规范,如返回码字段,0统一表示成功,1XX表示参数异常等。部分公司有专门的返回码和返回信息专题,其中定义了每一位返回码的意义,一定要按照公司统一规范要求团队严格按照语义进行编码。
2、保持接口的幂等性
所谓的幂等性,就是说相同的接口无论是只请求1次,还是重复请求多次,最后的结果需要保持一致。 举例说明:
POST test.com:443/api/inward
{payee:10001, amount: 100 }
我们定义一个汇入的接口,表示10001这个账号收到了100块的转入申请,收到请求后需要给这个账户余额增加100块。正常逻辑下这个接口没有什么问题,假设某一天由于网络出现了波动,请求方发出的请求没有收到返回,因此请求方重试再次请求了该接口,接口的接收方实际收到了两次增加100快的请求,预期账户余额增加100,实际增加了200。
解决方案有很多,但大体上都是通过限制每个状态流转的原子性。比如通过数据库乐观锁机制进行解决:
POST test.com:443/api/inward
{payee:10001, amount: 100, version: 100 }
在请求中添加一个版本号字段,这样在数据库中执行更新操作时,每次对余额进行增加的同时,版本号递增,类似以下代码:
update account set amount = amount + 100, version=version+1 where accountId = 10001 and version = 100
这样相同的请求,由于版本号已经更新,不会造成重复的余额增加,保证了幂等性。
3、接口需要采用无状态的设计
假设客户第一次请求,接口访问的ServerA,提示客户需要登陆。客户登陆后,ServerA保存了客户登陆的信息。第二次客户执行一个查询请求,接口发送到ServerB,由于ServerB中没有客户登陆的信息,又需要让客户再重新登陆一次。
也就是说,我们的接口相当于有一个统一的入口,不会因为具体哪台服务器处理而产生不同的结果,结果这个问题一般有两种方案。
1、客户端保存状态,在接口中附带状态信息
2、服务端能够共享地访问状态
第一种方案典型的例子为JWT方案,客户登陆后,服务端生成一个TOKEN,下次请求客户端将TOKEN附在接口请求中,这样无论是后端哪台服务器收到接口请求,都可以根据TOKEN判断客户是否已经登陆。 第二种方案典型的方式为将Session统一保存在分布式缓存中(如:Redis),这样每台服务器收到请求,都可以从缓存中获取该客户是否已经登陆。
总结
restful接口以资源为导向,可以让我们更方便地梳理业务脉络。各位在实际开发中有遇到关于RESTFUL接口方面的经验和教训,欢迎在评论区交流分享。