本文源码: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. SpringBoot Mybatis-Plus 整合 dynamic-datasource-spring-boot-starter 对数据库进行读写分离

    准备工作 对 MySql 进行主从搭建 引入 dynamic-datasource-spring-boot-starter 坐标 引入 druid-spring-boot-starter 坐标 对应框 ...

  2. Linux网络服务第二章DHCP原理与配置

    1.笔记 服务端端口:67 客户端端口:68 dhcliemt -r:释放IP地址 dhcliemt -d:重新获取IP地址 :.,$ s/190.168.200 / 192.168.100 /g 从 ...

  3. js 实现文字滚动功能,可更改配置参数 带完整版解析代码。

    前言:         本人纯小白一个,有很多地方理解的没有各位大牛那么透彻,如有错误,请各位大牛指出斧正!小弟感激不尽.         本篇文章为您分析一下原生JS写文字滚动效果 需求分析: 需要 ...

  4. Eureka重点原理解析

    前言 带着问题学习,事半功倍.本文将对如下几个问题进行总结说明: 1.EurekaServer端服务注册的流程和设计模式 2.Eureka服务续约的bug 3.EurekaClient的启动流程 4. ...

  5. 父级元素绑定定mouseout和mouseover,移过子元素是都会触发

    2019独角兽企业重金招聘Python工程师标准>>> mouseover与mouseenter 不论鼠标指针穿过被选元素或其子元素,都会触发 mouseover 事件. 只有在鼠标 ...

  6. Android环境配置----在Ubuntu上安装JDK

    jdk download address: jdk-6u22-linux-i586.bin http://www.oracle.com/technetwor ... ownloads/index.ht ...

  7. winform练习-通过遍历Control容器中的对象统一委托事件-楼盘选择器

    1.窗体布局如下,一个label标签内容如下,一个btnSave按钮,用于保存,其他九个按钮用于选择楼盘. 2. 按钮存于Control容器中,编写方法遍历容器中的button,通过条件过滤掉不是bu ...

  8. VS Code 全部快捷键一览表(巨TM全)

    常用 General 按 Press 功能 Function Ctrl + Shift + P,F1 显示命令面板 Show Command Palette Ctrl + P 快速打开 Quick O ...

  9. Codeforces Round #530 (Div. 1) 1098A Sum in the tree

    A. Sum in the tree Mitya has a rooted tree with nn vertices indexed from 11 to nn, where the root ha ...

  10. Nacos作为配置中心时,多个服务共用一个dataId的配置

    写在前面 本文是对我之前一篇文章<Spring Cloud+nacos+Feign,实现注册中心及配置中心>的补充.此文章中简单写了如何将Nacos作为配置中心.在使用配置中心时,我们会遇 ...