确保接口安全:六大方案有效解决幂等性问题
文章目录
- 六大方案解决接口幂等问题
- 什么是接口幂等?
- 天然幂等
- 不做幂等会怎么样?
- 解决方案
- 1)insert前先select
- 2)使用唯一索引
- 3)去重表加悲观锁
- 4)加乐观锁之版本号机制
- 5)使用 Redisson 分布式锁
- 6)Token 机制
六大方案解决接口幂等问题
什么是接口幂等?
幂等(idempotency)本身是一个数学概念,常见与抽象代数中,代表一个函数或操作的结果不受其输入或者执行次数的影响,例如,f(n) = 1^n,无论 n 为多少,f(n)的值永远为 1 。
在软件开发领域,幂等对请求执行结果的一个描述,这个描述就是无论执行多少次相同的请求,产生的效果和返回的结果和发出单个请求是一样的。
举个例子🌰:
- 有时我们在填写某些
form表单
时,保存按钮不小心快速点了两次,表中竟然产生了两条重复的数据,只是id不一样。 - 我们在项目中为了解决
接口超时
问题,通常会引入了重试机制
。第一次请求接口超时了,请求方没能及时获取返回结果(此时有可能已经成功了),为了避免返回错误的结果(这种情况不可能直接返回失败吧?),于是会对该请求重试几次,这样也会产生重复的数据。
天然幂等
那有没有有些情况是天然支持幂等的呢?当然有!比如说我要更新一个某记录的状态 status = 1
具体的 sql 为 update table set status = 1 where id = 1
这种情况,无论我执行多少次这条 sql 他的效果是一样的,这就是天然支持幂等的。
不做幂等会怎么样?
比如说用户在付款的时候,同时点击多次付款按钮,后端处理了多次扣款请求,结果导致用户的账户扣了多次钱。妥妥 p0 事故呀!
到这你又会说,前端做个置灰按钮不就行了吗,第一次付款完毕后,那用户或者恶意攻击你服务器的人直接用脚本搞你不走前端,你是防止不了的。
那接下来,我将介绍六大解决接口幂等的方案,速速点赞上车!!!
解决方案
1)insert前先select
通常情况下,在保存数据的接口中,我们为了防止产生重复数据,一般会在insert
前,先根据name
或code
字段select
一下数据。如果该数据已存在,则执行update
操作,如果不存在,才执行 insert
操作。
该方案可能是我们平时在防止产生重复数据时,使用最多的方案。但是该方案不适用于并发场景,在并发场景中,要配合其他方案一起使用,否则同样会产生重复数据。
2)使用唯一索引
通过在表中加上唯一索引,保证数据的唯一性。如果有重复的数据插入,会抛出DuplicateKeyException
异常,程序可以捕获异常并处理。不过,这种方法只适用于插入数据的场景。
create table t_order(id int unsigned PRIMARY KEY AUTO_INCREMENT COMMENT "主键",code varchar(200) not null COMMENT "流水号",user_id int unsigned COMMENT "用户id",amount decimal(10,2) unsigned not null COMMENT "总金额",UNIQUE unq_code(code)
) COMMENT="订单表";
不要依靠唯一索引来保证接口幂等,但建议使用唯一索引作为兜底,避免产生脏数据。
伪代码如下:
public void idempotent(OrderDO orderDO){try {// 执行核心业务...orderMapper.insert(orderDO);}catch(DuplicateKeyException e) {// 有重复的数据插入}
}
3)去重表加悲观锁
去重表本质上也是一种唯一索引方案。去重表是一张专门用于记录请求信息的表,其中某个字段需要建立唯一索引,用于标识请求的唯一性当客户端发出请求时,服务端会将这次请求的一些信息(如订单号、交易流水号等)插入到去重表中,如果插入成功,说明这是第一次请求,可以执行后续的业务逻辑;如果插入失败,说明这是重复请求,可以直接返回或者忽略。
CREATE TABLE deduplication_table (id int unsigned PRIMARY KEY AUTO_INCREMENT COMMENT "主键",processed_code varchar(200) not null COMMENT "已处理的订单流水号",-- 省略其他字段UNIQUE unq_processed_code(processed_code)
) COMMENT="去重表";
使用for update
加锁每次查询到都是最新的数据
select * from deduplication_table where processed_code = 'xxx' for update
伪代码如下:
public boolean idempotent(OrderDO orderDO){// 执行核心业务之前DoMain domain = deduplicationMapper.selectForUpdate(orderDO.getProcessedCode);if(doamin != null) {// 订单已经支付}
}
4)加乐观锁之版本号机制
既然悲观锁有性能问题,为了提升接口性能,我们可以使用乐观锁。需要在表中增加一个timestamp
或者version
字段,这里以version
字段为例。
在更新数据之前先查询一下数据:
select id,amount,version from user id=123;
如果数据存在,假设查到的version
等于1
,再使用id
和version
字段作为查询条件更新数据:
update user set amount = amount + 100, version = version + 1 where id = 123 and version = 1;
更新数据的同时version+1
,然后判断本次update
操作的影响行数,如果大于0,则说明本次更新成功,如果等于0,则说明本次更新没有让数据变更。
由于第一次请求version
等于1
是可以成功的,操作成功后version
变成2
了。这时如果并发的请求过来,再执行相同的sql:
update user set amount = amount + 100,version = version + 1 where id = 123 and version = 1;
该update
操作不会真正更新数据,最终sql的执行结果影响行数是0
,因为version
已经变成2
了,where
中的version=1
肯定无法满足条件。但为了保证接口幂等性,接口可以直接返回成功,因为version
值已经修改了,那么前面必定已经成功过一次,后面都是重复的请求。
具体流程如下:
具体步骤:
- 先根据id查询用户信息,包含version字段
- 根据id和version字段值作为where条件的参数,更新用户信息,同时version+1
- 判断操作影响行数,如果影响1行,则说明是一次请求,可以做其他数据操作。
- 如果影响0行,说明是重复请求,则直接返回成功。
5)使用 Redisson 分布式锁
基于 MySQL 也可以实现分布式锁,但一般我们不会采用这种方式。
通常情况下,我们一般会选择基于 Redis 或者 ZooKeeper 实现分布式锁,Redis 用的要更多一点。
// 唯一标识
String uniqueId = "orderId123";
// 1. 根据唯一标识生成分布式锁对象
RLock lock = redisson.getLock("lock:" + uniqueId);try {// 2. 尝试获取锁(Watch Dog 自动续期机制) if (lock.tryLock()) {// 3. 如果成功获取到锁,说明请求还没有被处理,执行业务逻辑} else {// 请求已经被处理,直接返回}
} finally {// 4. 释放锁lock.unlock();
}
6)Token 机制
Token 机制的核心思想是为每一次操作生成一个唯一性的凭证 token。这个 token 需要由服务端生成的,因为服务端可以对 token 进行签名和加密,防止篡改和泄露。如果由客户端生成 token,可能会存在安全隐患,比如客户端伪造或重复 token,导致服务端无法识别和校验。
这样的话,就需要两次请求才能完成一次业务操作:
-
请求获取服务器端 token,token 需要设置有效时间(可以设置短一点),服务端将该 token 保存起来。
-
执行真正的请求,将上一步获取到的 token 放到 header 或者作为请求参数。服务端验证 token 的有效性,如果有效(一般是通过删除 token 的方式来验证,删除成功则有效),执行业务逻辑,并删除 token,防止重复提交;如果无效,拒绝请求,返回提示信息。
// 获取token
public String getToken(Long busId, Long userId){String UUID = UUID.randomUUID().toString();stringRedisTemplate.opsForValue().set(busId+userId, UUID, 20, TimeUnit.SECONDS)return UUID;
}
// 发起业务请求携带token
public void doSomeBusiness(Parameter parameter) {Long busId = parameter.getBusId;Long userId = UserContext.getUserId();// 判断token是否存在Boolean deleted = stringRedisTemplate.delete(busId+userId);if(deleted) {// 删除成功,代表重复请求不进行操作,直接返回return;}// doSomeBusiness...
}
具体步骤:
- 用户访问页面时,浏览器自动发起获取 token 请求。
- 服务端生成 token,保存到 redis 中,然后返回给浏览器。
- 用户通过浏览器发起请求时,携带该 token。
- 从 redis 中尝试删除 token 如果删除失败,说明是第一次请求,做则后续的数据操作。
- 如果删除成功,说明是重复请求,不做任何操作。
相关文章:

确保接口安全:六大方案有效解决幂等性问题
文章目录 六大方案解决接口幂等问题什么是接口幂等?天然幂等不做幂等会怎么样? 解决方案1)insert前先select2)使用唯一索引3)去重表加悲观锁4)加乐观锁之版本号机制5)使用 Redisson 分布式锁6&a…...
代码随想录算法训练营第二十九天| 93. 复原 IP 地址,78. 子集, 90. 子集 II
93. 复原 IP 地址,78. 子集, 90. 子集 II 93. 复原 IP 地址78. 子集90. 子集 II 93. 复原 IP 地址 有效 IP 地址 正好由四个整数(每个整数位于 0 0 0 到 255 255 255之间组成,且不能含有前导 0 0 0),整…...

【WebGis开发 - Cesium】三维可视化项目教程---初始化场景
系列文章目录 【WebGis开发 - Cesium】三维可视化项目教程—图层管理基础【WebGis开发 - Cesium】三维可视化项目教程—视点管理 目录 系列文章目录引言一、Cesium引入项目1.1 下载资源1.2 项目引入Cesium 二、初始化地球2.1 创建基础文件2.1.1 创建Cesium工具方法文件2.1.2 创…...

点云中ICP算法的详解
ICP(Iterative Closest Point)算法是一种用于刚性点云配准的经典算法。其核心思想是通过迭代地寻找两个点云之间的最近点对,并计算最优的刚性变换(包括旋转和平移),使得源点云在目标点云的坐标系下对齐。IC…...

抽象类Abstart Class
抽象类其实就是一种不完全的设计图 必须用abstract修饰 模板方法:建议使用final修饰,不能被重写。...

Redis:通用命令 数据类型
Redis:通用命令 & 数据类型 通用命令SETGETKEYSEXISTSDELEXPIRETTLTYPEFLUSHALL 数据类型 Redis的客户端提供了很多命令用于操控Redis,在Redis中,key的类型都是字符串,而value有多种类型,每种类型都有自己的操作命…...
【Python高级编程】探索Python库:创建引人入胜的交互界面
1.制作交互界面常用到的库 在 Python 中,有多个库可以用于创建交互界面(GUI)。 以下是一些常用的 Python GUI 库: Tkinter: Python 的标准 GUI 库,通常随 Python 一起安装。简单易用,适合快速开发小型应用…...

OpenCV Canny()函数
OpenCV Canny()函数被用来检测图像物体的边缘。其算法原理如下: 高斯滤波:使用高斯滤波器平滑图像以减少噪声。高斯滤波器是一种线性滤波器,可以消除图像中的高频噪声,同时保留边缘信息。计算梯度强度和方向:使用Sobe…...

Java基础(3)
基本数据类型 Java 中的几种基本数据类型了解么? Java 中有 8 种基本数据类型,分别为: 6 种数字类型: 4 种整数型:byte、short、int、long2 种浮点型:float、double1 种字符类型:char1 种布尔…...

【C语言】VS调试技巧
文章目录 什么是bug什么是调试(debug)debug和releaseVS调试快捷键监视和内存观察编程常见错误归类 什么是bug bug本意是“昆虫”或“虫子”,现在一般是指在电脑系统或程序中,隐藏着的一些未被发现的缺陷或问题,简称程…...

【华为HCIP实战课程七】OSPF邻居关系排错MTU问题,网络工程师
一、MTU MUT默认1500,最大传输单元,一致性检测 [R3-GigabitEthernet0/0/1]mtu 1503//更改R3的MTU为1503 查看R3和SW1之间的OSPF邻居关系正常: 默认华为设备没有开启MTU一致性检测! [R3-GigabitEthernet0/0/1]ospf mtu-enable //手动开启MTU检测 [SW1-Vlanif30]ospf mtu…...
速盾:休闲类游戏如何选择高防cdn?
休闲类游戏的流行度日益增长,越来越多的玩家在业余时间里选择放松自己,享受游戏带来的乐趣。然而,在休闲类游戏中,网络延迟和游戏载入速度的问题常常会影响到玩家的游戏体验。为了解决这些问题,选择一个高防CDN&#x…...

电脑插上U盘不显示怎么回事?怎么解决?
平时使用电脑的时候经常会使用U盘来传输数据或是备份文件,有时候会遇到一个令头疼的问题,比如,将U盘插入电脑的USB口后,设备却显示不出来。电脑上插入U盘后却不显示会影响我们的正常工作。接下来,我们一起分析一下故障…...

Python 如何使用 SQLAlchemy 进行复杂查询
Python 如何使用 SQLAlchemy 进行复杂查询 一、引言 SQLAlchemy 是 Python 生态系统中非常流行的数据库处理库,它提供了一种高效、简洁的方式与数据库进行交互。SQLAlchemy 是一个功能强大的数据库工具,支持结构化查询语言(SQL)…...
nginx主配置文件
Nginx的主配置文件nginx.conf,一般定义了Nginx的基本设置和全局配置。下面是对这个配置文件的详细解释: 文件结构 #user nobody; worker_processes 1;#error_log logs/error.log; #error_log logs/error.log notice; #error_log logs/error.log …...

使用数据库:
数据库: 1.为何需要数据库? 存储数据方法 第一种:用大脑记住数据, 第二种:写纸上, 第三种:写在计算机的内存中, 第四种:写出磁盘文件 2.数据库能做什么࿱…...

python list, tuple dict,set的区别 以及**kwargs 的基本用法
在python中, list, tuple, dict, set有什么区别, 主要应用在什么样的场景? 定义: list:链表,有序的项目, 通过索引进行查找,使用方括号”[]”; tuple:元组,元组将多样的对象集合到一起,不能修改,通过索引进行查找, 使用括号”()”; dict:字典,字典是一组键(key)和值(value…...

实用生活英语口语学习成人零基础入门柯桥专业外语培训
“秋裤”的英语表达 首先,秋裤肯定不是autumn pants,chill cool就更离谱了! 最地道的美语说法一定会用到“thermal”这个单词: ▼ “thermal”的意思是“热的、保温的”,由此延伸出“秋裤、保暖内衣”的表达ÿ…...
FLINK SQL数据类型
Flink SQL支持非常完善的数据类型,以满足不同的数据处理需求。以下是对Flink SQL数据类型的详细归纳: 一、原子数据类型 字符串类型 CHAR、CHAR(n):定长字符串,n代表字符的定长,取值范围为[1, 2147483647]。如果不指…...

汇编语言教程:打造你的第一款汇编语言小游戏 汇编语言教程攻略
目录 游戏详细简介 完整代码示例(不少于70行) 如何自学汇编语言游戏开发攻略及功能 游戏详细简介 游戏名称:“太空探险” 游戏简介:这是一款基于x86汇编语言开发的简单2D游戏。在游戏中,玩家扮演一名宇航员&#…...

为什么需要建设工程项目管理?工程项目管理有哪些亮点功能?
在建筑行业,项目管理的重要性不言而喻。随着工程规模的扩大、技术复杂度的提升,传统的管理模式已经难以满足现代工程的需求。过去,许多企业依赖手工记录、口头沟通和分散的信息管理,导致效率低下、成本失控、风险频发。例如&#…...
条件运算符
C中的三目运算符(也称条件运算符,英文:ternary operator)是一种简洁的条件选择语句,语法如下: 条件表达式 ? 表达式1 : 表达式2• 如果“条件表达式”为true,则整个表达式的结果为“表达式1”…...

MODBUS TCP转CANopen 技术赋能高效协同作业
在现代工业自动化领域,MODBUS TCP和CANopen两种通讯协议因其稳定性和高效性被广泛应用于各种设备和系统中。而随着科技的不断进步,这两种通讯协议也正在被逐步融合,形成了一种新型的通讯方式——开疆智能MODBUS TCP转CANopen网关KJ-TCPC-CANP…...
Nginx server_name 配置说明
Nginx 是一个高性能的反向代理和负载均衡服务器,其核心配置之一是 server 块中的 server_name 指令。server_name 决定了 Nginx 如何根据客户端请求的 Host 头匹配对应的虚拟主机(Virtual Host)。 1. 简介 Nginx 使用 server_name 指令来确定…...
GitHub 趋势日报 (2025年06月08日)
📊 由 TrendForge 系统生成 | 🌐 https://trendforge.devlive.org/ 🌐 本日报中的项目描述已自动翻译为中文 📈 今日获星趋势图 今日获星趋势图 884 cognee 566 dify 414 HumanSystemOptimization 414 omni-tools 321 note-gen …...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
docker 部署发现spring.profiles.active 问题
报错: org.springframework.boot.context.config.InvalidConfigDataPropertyException: Property spring.profiles.active imported from location class path resource [application-test.yml] is invalid in a profile specific resource [origin: class path re…...
LRU 缓存机制详解与实现(Java版) + 力扣解决
📌 LRU 缓存机制详解与实现(Java版) 一、📖 问题背景 在日常开发中,我们经常会使用 缓存(Cache) 来提升性能。但由于内存有限,缓存不可能无限增长,于是需要策略决定&am…...
Vue 模板语句的数据来源
🧩 Vue 模板语句的数据来源:全方位解析 Vue 模板(<template> 部分)中的表达式、指令绑定(如 v-bind, v-on)和插值({{ }})都在一个特定的作用域内求值。这个作用域由当前 组件…...

保姆级【快数学会Android端“动画“】+ 实现补间动画和逐帧动画!!!
目录 补间动画 1.创建资源文件夹 2.设置文件夹类型 3.创建.xml文件 4.样式设计 5.动画设置 6.动画的实现 内容拓展 7.在原基础上继续添加.xml文件 8.xml代码编写 (1)rotate_anim (2)scale_anim (3)translate_anim 9.MainActivity.java代码汇总 10.效果展示 逐帧…...