架构设计 | 接口幂等性原则,防重复提交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 {@Resourceprivate OrderStateMapper orderStateMapper ;@Resourceprivate StateRecordMapper stateRecordMapper ;@Overridepublic 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 ;}@Overridepublic boolean createOrder(OrderState orderState) {int saveRes = orderStateMapper.insert(orderState);if (saveRes > 0){saveStateRecord(orderState.getOrderId(),"订单创建成功");}return saveRes > 0 ;}@Overridepublic boolean localBiz(OrderState orderState) {orderState.setState(2);int updateRes = orderStateMapper.updateState(orderState) ;if (updateRes > 0){saveStateRecord(orderState.getOrderId(),"本地业务成功");}return updateRes > 0;}@Overridepublic 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 {@Resourceprivate 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 {@Overridepublic 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 {@Resourceprivate 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管理相关推荐
- 处理接口超时_架构设计 | 接口幂等性原则,防重复提交Token管理
一.幂等性概念 1.幂等简介 编程中一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同.就是说,一次和多次请求某一个资源会产生同样的作用影响. 2.HTTP请求 遵循Http协议的请 ...
- 如何处理接口幂等性问题(重复提交)
接口幂等:多次请求,结果一致. 同样的请求参数,多次去访问同一个接口,得到的结果是一致的.且服务端(针对于数据入库或数据修改)只处理一次.通俗点讲就是:防止重复提交. 以下演示相关案例 案例1: 数据 ...
- AOP+自定义注解token令牌和参数防重复提交实战
目录 一.哪些因素会引起重复提交? 二.重复提交会带来哪些问题? 三.订单的防重复提交你能想到几种方案? 四.自定义注解方式 4.1Java核心知识-自定义注解(先了解下什么是自定义注解) 4.1.1 ...
- 关于表单防重复提交一些东东
前阵子弄了些表单防重复提交的东西,想整理整理,免得下次要用时再四处去找,其实这里的东西还是挺简单的. 原理: 在Session中保存一个表单的唯一编号,将该编号放在一个隐藏域中,同其他数据一同提交.在 ...
- 软件系统架构设计的六大原则
软件系统架构设计的六大原则 1.单一职责原则(SRP) 2.开放封闭原则(OCP) 3.里氏替换原则(LSP) 4.最少知识原则(LKP) 5.接口隔离原则(ISP) 6.依赖倒置原则(DIP) 1. ...
- Java实现防重复提交
欢迎访问我的个人博客:www.ifueen.com 防重复提交的重要性? 在业务开发中,为什么我们要去想办法解决重复提交这一问题发生?网上的概念很多:导致表单重复提交,造成数据重复,增加服务器负载,严 ...
- 工作中的亮点事情-防重复提交
防重复提交(aop): 1.自定义注解,在需要防重的接口添加注解,默认时间200毫秒 2.url+sessionId +关键字 为主键设置一个缓存,存在则直接返回错误信息 定义redis锁 切面.切点 ...
- springboot 主键重复导致数据重复_Springboot实现防重复提交和防重复点击(附源码)...
背景# 同一条数据被用户点击了多次,导致数据冗余,需要防止弱网络等环境下的重复点击 目标# 通过在指定的接口处添加注解,实现根据指定的接口参数来防重复点击 说明# 这里的重复点击是指在指定的时间段内多 ...
- redis+aop防重复提交
文章目录 1.防重复提交注解 2.redis分布式锁 3.防止重复提交Aop 之前有记录一篇用redis+拦截器防重复提交的内容: redis+拦截器防重复提交 1.防重复提交注解 @Target(E ...
最新文章
- LeetCode简单题之唯一元素的和
- 微信小程序搜索功能!附:小程序前端+PHP后端
- Python之字符串格式化(format)
- 进程间通信(1) dll 实现进程的内存共享
- 用python处理excel 数据分析_Python应用实现处理excel数据过程解析
- python控制流代码怎么用_Python学习笔记控制流的元素
- 人如果没有愿望。。。。。。
- 跨域的另一种解决方案——CORS(Cross-Origin Resource Sharing)跨域资源共享
- 如何将文件快速拷入自己的谷歌云盘
- HTML颜色代码大全
- 看视频用这个太爽了!自动实时翻译英语视频
- Frontiers of Physics中科院二区期刊,两个月内接收,无需版面费,影响因子不断上涨
- 高级前端面试100问(必会)
- 生物医学工程实用在线工具
- win7安装php失败,win7打印机驱动安装失败怎么办
- C#窗体设计中InitializeComponent的用法
- 威斯康星大学-机器学习导论2020
- Java实验01 Java编程基础(猜数字游戏、随机点名器)
- matlab使用出现矩阵为奇异值、接近奇异值或缩放错误。结果可能不准确。RCOND = NaN。
- POI(excel)中ROW应用实践应用总结