架构设计 | 接口幂等性原则,防重复提交Token管理
本文源码:GitHub·点这里 || GitEE·点这里
一、幂等性概念
1、幂等简介
编程中一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。就是说,一次和多次请求某一个资源会产生同样的作用影响。
2、HTTP请求
遵循Http协议的请求,越来越强调Rest请求风格,可以更好的规范和理解接口的设计。
GET:用于获取资源,不应有副作用,所以是幂等的;
POST:用于创建资源,重复提交POST请求可能产生两个不同的资源,有副作用不满足幂等性;
PUT:用于更新操作,重复提交PUT请求只会对其URL中指定的资源有副作用,满足幂等性;
DELETE:用于删除资源,有副作用,但它应该满足幂等性;
HEAD:和GET本质是一样的,但HEAD不含有呈现数据,仅是HTTP头信息,没有副作用,满足幂等性;
OPTIONS:用于获取当前URL所支持的请求方法,满足幂等性;
二、场景业务分析
1、订单支付
实际开发中,经常会面对订单支付问题,基本流程如下:
- 客户端发起订单支付请求 ;
- 支付前系统本地相关业务处理 ;
- 请求第三方支付服务执行扣款;
- 第三方支付返回处理结果;
- 本地服务基于支付结果响应客户端;
该业务流程中要处理相当复杂的问题,比如事务,分布式事务,接口延迟超时,客户端重复提交等等,这里只基于幂等接口角度来看该流程,其他问题后续再聊。
2、幂等接口
当上述流程的支付请求有明确结果的时候:失败或成功,这样业务流程都好处理,但是例如支付场景如果请求超时,如何判断服务的结果状态:客户端请求超时,本地服务超时,请求支付超时,支付回调超时,客户端响应超时等等。
这就需要设计流程化的状态管理。
3、基础操作案例
模拟管理上述流程,设计幂等接口:
表结构设计
CREATE TABLE `dp_order_state` (
`order_id` BIGINT (20) NOT NULL AUTO_INCREMENT COMMENT '订单id',
`token_id` VARCHAR (50) DEFAULT NULL COMMENT '防重复提交',
`state` INT (1) DEFAULT '1' COMMENT '1创建订单,2本地业务,3支付业务',
PRIMARY KEY (`order_id`)
) ENGINE = INNODB DEFAULT CHARSET = utf8 COMMENT = '订单状态表';
CREATE TABLE `dp_state_record` (
`id` INT (11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`order_id` BIGINT (20) NOT NULL COMMENT '订单id',
`state_dec` VARCHAR (50) DEFAULT NULL COMMENT '状态描述',
PRIMARY KEY (`id`)
) ENGINE = INNODB DEFAULT CHARSET = utf8 COMMENT = '状态记录表';
模拟业务流程
将订单创建,本地业务,支付业务,分开分段管理提交。分阶段测试异常熔断的业务。
@Service
public class OrderServiceImpl implements OrderService {
@Resource
private OrderStateMapper orderStateMapper ;
@Resource
private StateRecordMapper stateRecordMapper ;
@Override
public OrderState queryOrder(OrderState orderState) {
Map<String,Object> paramMap = new HashMap<>() ;
paramMap.put("order_id",orderState.getOrderId());
List<OrderState> orderStateList = orderStateMapper.selectByMap(paramMap);
if (orderStateList != null && orderStateList.size()>0){
return orderStateList.get(0) ;
}
return null ;
}
@Override
public boolean createOrder(OrderState orderState) {
int saveRes = orderStateMapper.insert(orderState);
if (saveRes > 0){
saveStateRecord(orderState.getOrderId(),"订单创建成功");
}
return saveRes > 0 ;
}
@Override
public boolean localBiz(OrderState orderState) {
orderState.setState(2);
int updateRes = orderStateMapper.updateState(orderState) ;
if (updateRes > 0){
saveStateRecord(orderState.getOrderId(),"本地业务成功");
}
return updateRes > 0;
}
@Override
public boolean paymentBiz(OrderState orderState) {
orderState.setState(3);
int updateRes = orderStateMapper.updateState(orderState) ;
if (updateRes > 0){
saveStateRecord(orderState.getOrderId(),"支付业务成功");
}
return updateRes > 0;
}
private void saveStateRecord (Long orderId,String stateDec){
StateRecord stateRecord = new StateRecord() ;
stateRecord.setOrderId(orderId);
stateRecord.setStateDec(stateDec);
stateRecordMapper.insert(stateRecord) ;
}
}
测试接口
根据订单状态,分段补偿执行未完成的业务,如果该订单已经完成,多次提交不影响最终结果。
@Api(value = "OrderController")
@RestController
public class OrderController {
@Resource
private OrderService orderService ;
@PostMapping("/submitOrder")
public String submitOrder (OrderState orderState){
OrderState orderState01 = orderService.queryOrder(orderState) ;
if (orderState01 == null){
// 正常业务流程
orderService.createOrder(orderState) ;
orderService.localBiz(orderState) ;
orderService.paymentBiz(orderState) ;
} else {
switch (orderState01.getState()){
case 1:
// 订单创建成功:后推执行本地和支付业务
orderService.localBiz(orderState01) ;
orderService.paymentBiz(orderState01) ;
break ;
case 2:
// 订单本地业务成功:后推执行支付业务
orderService.paymentBiz(orderState01) ;
break ;
default:
break ;
}
}
return "success" ;
}
}
絮叨一句
:实际开发中,该流程是不会由页面多次提交完成,订单是不能重复提交的,下面会演示如何控制,这里业务是执行后推到完成,也可能业务向前清理,把整个流程置为失败,这里涉及关键状态判断,要选取一个状态作为成功或失败的标识,判断后续操作流程。在分布式系统中这种复杂流程最难处理的是分布式事务,最终一致性问题,后续再聊。
三、接口重复提交
1、表单重复提交
在实际情况中,接口如果处理时间过长,用户可能会点击多次提交按钮,导致数据重复。
常见的一个解决方案:在表单提交中隐藏一个token_id参数,一起提交到接口服务中,数据库存储订单和关联的tokenId,如果多次提交,直接返回页面提示信息即可。
2、演示案例
订单关联Token查询
@Service
public class OrderServiceImpl implements OrderService {
@Override
public Boolean queryToken(OrderState orderState) {
Map<String,Object> paramMap = new HashMap<>() ;
paramMap.put("order_id",orderState.getOrderId());
paramMap.put("token_id",orderState.getTokenId());
List<OrderState> orderStateList = orderStateMapper.selectByMap(paramMap);
return orderStateList.size() > 0 ;
}
}
测试接口
@RestController
public class OrderController {
@Resource
private OrderService orderService ;
@PostMapping("/repeatSub")
public String repeatSub (OrderState orderState){
boolean flag = orderService.queryToken(orderState) ;
if (flag){
return "请勿重复提交订单" ;
}
return "success" ;
}
}
四、源代码地址
GitHub·地址
https://github.com/cicadasmile/data-manage-parent
GitEE·地址
https://gitee.com/cicadasmile/data-manage-parent
推荐阅读:数据和架构管理
架构设计 | 接口幂等性原则,防重复提交Token管理的更多相关文章
- (亿级流量)分布式防重复提交token设计
大型互联网项目中,很多流量都达到亿级.同一时间很多的人在使用,而每个用户提交表单的时候都可能会出现重复点击的情况,此时如果不做好控制,那么系统将会产生很多的数据重复的问题.怎样去设计一个高可用的防重复 ...
- resubmit 渐进式防重复提交框架简介
resubmit resubmit 是一款为 java 设计的渐进式防止重复提交框架. 推荐阅读: 面试官:你们的项目中是怎么做防止重复提交的? resubmit 渐进式防重复提交框架简介 创作目的 ...
- (九)Struts2 防重复提交
所有的学习我们必须先搭建好Struts2的环境(1.导入对应的jar包,2.web.xml,3.struts.xml) 第一节:重复提交示例演示 struts.xml <?xml version ...
- AJAX防重复提交的办法总结
最近的维护公司的一个代理商平台的时候,客服人员一直反映说的统计信息的时候有重复数据,平台一直都很正常,这个功能是最近新进的一个实习生同事写的功能,然后就排查问题人所在,发现新的这个模块的AJAX提交数 ...
- 浅谈C#在网络波动时防重复提交
前几天,公司数据库出现了两条相同的数据,而且时间相同(毫秒也相同).排查原因,发现是网络波动造成了重复提交. 由于网络波动而重复提交的例子也比较多: 网络上,防重复提交的方法也很多,使用redis锁, ...
- SpringMVC后台token防重复提交解决方案
本文介绍如何使用token来防止前端重复提交的问题. 目录 1.思路 2.拦截器源码实现 3.注解源码 4.拦截器的配置 5.使用指南 6.结语 思路 1.添加拦截器,拦截需要防重复提交的请求 2.通 ...
- Spring MVC表单防重复提交
利用Spring MVC的过滤器及token传递验证来实现表单防重复提交. 创建注解 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RU ...
- struts2学习(15)struts2防重复提交
一.重复提交的例子: 模拟一种情况,存在延时啊,系统比较繁忙啊啥的. 模拟延迟5s钟,用户点了一次提交,又点了一次提交,例子中模拟这种情况: 这样会造成重复提交: com.cy.action.St ...
- JavaWeb -- Struts2,对比, 简单表单提交,校验,防重复提交, 文件上传
Struts2核心流程图 1. Struts2 和 Struts1 对比 struts1:基于Servlet(ActionServlet),actionForm众多(类的爆炸),action单例(数据 ...
随机推荐
- cocos2dx新建项目
首先你得下载好cococs2dx,还有python2.x版本,还有vs2017 然后cmd在你Cocos2dx的路径下输入 python setup.py 然后你就回车回车回车 然后重新打开cmd 这 ...
- Docker简单操作(二)
1.docker容器简单操作 docker search 镜像名 #搜索镜像.如docker search nginx docker pull alpine #拉取镜像.alpine是比较小的镜像 d ...
- vs code 打开文件时,取消文件目录的自动定位跟踪
文件-->首选项-->设置-->在搜索栏中搜索:explorer.autoReveal; 去掉勾选即可.
- mybatis源码学习(一):Mapper的绑定
在mybatis中,我们可以像下面这样通过声明对应的接口来绑定XML中的mapper,这样可以让我们尽早的发现XML的错误. 定义XML: <?xml version="1.0&quo ...
- Java之JVM(初学者)
学习Java的第一次总结 1.Java程序的编译和执行 通过上图,我们轻易得出java执行过程:由javac编译为字节码文件,通过JVM转换为底层操作系统可识别的命令操作. 注意:①Java跨平台的始 ...
- C语言编程入门题目--No.13
题目:打印出所有的"水仙花数",所谓"水仙花数"是指一个三位数,其各位数字立方和等于该数 本身.例如:153是一个"水仙花数",因为153= ...
- 2019年广东工业大学腾讯杯新生程序设计竞赛(同步赛)E-缺席的神官
链接:https://ac.nowcoder.com/acm/contest/3036/E 来源:牛客网 题目描述 面前的巨汉,让我想起了多年前的那次,但这个巨汉身上散布着让人畏惧害怕的黑雾.即使看不 ...
- socket编程之并发回射服务器3
在socket编程之并发回射服务器一文中,服务器采用多进程的方式实现并发,本文采用多线程的方式实现并发. 多线程相关API: // Compile and link with -pthread int ...
- python字符串分段组合(更新)
描述 获得输入的一个字符串s,以字符减号(-)分割s,将其中首尾两段用加号(+)组合后输出. ...
- ASP.NET Core的配置信息
ASP.NET Core的配置信息 Key-Value键值对 内存里.JSON.XML.INI等文件 配置信息与配置系统是解耦的 可以依赖注入 ASP.NET Core的配置信息来源 appsetti ...