本文源码: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

推荐阅读:数据和架构管理

序号 标题
A01 数据源管理:主从库动态路由,AOP模式读写分离
A02 数据源管理:基于JDBC模式,适配和管理动态数据源
A03 数据源管理:动态权限校验,表结构和数据迁移流程
A04 数据源管理:关系型分库分表,列式库分布式计算
A05 数据源管理:PostGreSQL环境整合,JSON类型应用
A06 数据源管理:基于DataX组件,同步数据和源码分析
A07 数据源管理:OLAP查询引擎,ClickHouse集群化管理
C01 架构基础:单服务.集群.分布式,基本区别和联系
C02 架构设计:分布式业务系统中,全局ID生成策略
C03 架构设计:分布式系统调度,Zookeeper集群化管理

架构设计 | 接口幂等性原则,防重复提交Token管理的更多相关文章

  1. (亿级流量)分布式防重复提交token设计

    大型互联网项目中,很多流量都达到亿级.同一时间很多的人在使用,而每个用户提交表单的时候都可能会出现重复点击的情况,此时如果不做好控制,那么系统将会产生很多的数据重复的问题.怎样去设计一个高可用的防重复 ...

  2. resubmit 渐进式防重复提交框架简介

    resubmit resubmit 是一款为 java 设计的渐进式防止重复提交框架. 推荐阅读: 面试官:你们的项目中是怎么做防止重复提交的? resubmit 渐进式防重复提交框架简介 创作目的 ...

  3. (九)Struts2 防重复提交

    所有的学习我们必须先搭建好Struts2的环境(1.导入对应的jar包,2.web.xml,3.struts.xml) 第一节:重复提交示例演示 struts.xml <?xml version ...

  4. AJAX防重复提交的办法总结

    最近的维护公司的一个代理商平台的时候,客服人员一直反映说的统计信息的时候有重复数据,平台一直都很正常,这个功能是最近新进的一个实习生同事写的功能,然后就排查问题人所在,发现新的这个模块的AJAX提交数 ...

  5. 浅谈C#在网络波动时防重复提交

    前几天,公司数据库出现了两条相同的数据,而且时间相同(毫秒也相同).排查原因,发现是网络波动造成了重复提交. 由于网络波动而重复提交的例子也比较多: 网络上,防重复提交的方法也很多,使用redis锁, ...

  6. SpringMVC后台token防重复提交解决方案

    本文介绍如何使用token来防止前端重复提交的问题. 目录 1.思路 2.拦截器源码实现 3.注解源码 4.拦截器的配置 5.使用指南 6.结语 思路 1.添加拦截器,拦截需要防重复提交的请求 2.通 ...

  7. Spring MVC表单防重复提交

    利用Spring MVC的过滤器及token传递验证来实现表单防重复提交. 创建注解 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RU ...

  8. struts2学习(15)struts2防重复提交

    一.重复提交的例子: 模拟一种情况,存在延时啊,系统比较繁忙啊啥的. 模拟延迟5s钟,用户点了一次提交,又点了一次提交,例子中模拟这种情况: 这样会造成重复提交:   com.cy.action.St ...

  9. JavaWeb -- Struts2,对比, 简单表单提交,校验,防重复提交, 文件上传

    Struts2核心流程图 1. Struts2 和 Struts1 对比 struts1:基于Servlet(ActionServlet),actionForm众多(类的爆炸),action单例(数据 ...

随机推荐

  1. 好程序员分享Web前端面试题汇总JS篇之跨域问题

    为什么80%的码农都做不了架构师?>>>   好程序员分享Web前端面试题汇总JS篇之跨域问题,接着上一篇文章我们继续来探讨web前端面试必备面试题. 跨域解决方案 1. 通过jso ...

  2. WLAN 无线网络 03 - RF 基础

    射频(Radio frequency),又称无线电频率.无线射频.高周波,常被用来当成无线电的同义词,为在3 kHz至300 GHz这个范围内的震荡频率,这个频率相当于无线电波的频率,以及携带着无线电 ...

  3. TEC-004-php文件下载任意文件读取漏洞修复

    修改download?u参数值,将/public/files/14842030529.txt,替换为../../../../../../../../../../etc/passwd    functi ...

  4. 关于IE8上传文件的一些问题

    问题1: IE8下上传完文件后,对后台返回的JSON格式的数据,浏览器提示了下载该文件. 原因是因为IE8还不支持'application/json"类型的响应. 解决方法将后台返回的JSO ...

  5. Linux 开发之线程条件锁那些事

    2019独角兽企业重金招聘Python工程师标准>>> 条件锁即在一定条件下触发,那什么时候适合用条件锁呢,那当然是你在等待一个符合的条件下触发.一个常用的例子就是在线程中无限循环执 ...

  6. pycharm(破解教程)

    1.下载破解补丁 下载补丁文件 jetbrains-agent.jar  2.双击 pycharm-professional-2019.3.exe 安装 pycharm 如果你是刚下载的pycharm ...

  7. 配置Ansible加速

    下载安装包 wget https://files.pythonhosted.org/packages/source/m/mitogen/mitogen-0.2.7.tar.gz tar axf mit ...

  8. Programmatically add an application to Windows Firewall

    Programmatically add an application to Windows Firewall 回答1   Not sure if this is the best way, but ...

  9. Kubernetes中 Pod 是怎样被驱逐的?

    前言 在 Kubernetes 中,Pod 使用的资源最重要的是 CPU.内存和磁盘 IO,这些资源可以被分为可压缩资源(CPU)和不可压缩资源(内存,磁盘 IO).可压缩资源不可能导致 Pod 被驱 ...

  10. u-boot spl 学习总结

    什么是SPL? SPL(secondary program loader)是一个十分小的bin文件,它是用来引导主u-boot文件.对于一些SRAM很小的SOC,无法一次性加载ROM中的bootloa ...