多种方式防止表单重复提交

1.前端方案:

通过js将按钮绑定一个方法,点击后3s内将按钮设置成不可用,或者隐藏。

缺点:绕过前端,例如通过postman发请求。

2.hashmap版:

请求携带一个参数,将请求携带的参数(可以是用户id)存到内存的hashmap(用syc修饰)里,value存放请求时间。

当下次发送请求时,先判断map里有没有参数参数作为key。当请求时先判断map里面有没有这个key,有并且时间超过3分钟就覆盖key,没有超过三分钟就返回请求频繁。没有请求成功。

缺点:随着时间推移map越来越大,oom。

3.commons-collections 轮子

Apache 为我们提供了一个 commons-collections 的框架,里面有一个非常好用的数据结构 LRUMap 可以保存指定数量的固定的数据,并且它会按照 LRU 算法,帮你清除最不常用的数据。

java 复制代码
<dependency>
	<groupId>org.apache.commons</groupId>
	<artifactId>commons-collections4</artifactId>
	<version>4.4</version>
</dependency>
java 复制代码
// 最大容量 100 个,根据 LRU 算法淘汰数据的 Map 集合
    private LRUMap<String, Integer> reqCache = new LRUMap<>(100);
    @RequestMapping("/add")
    public String addUser1(String id) {
        // 非空判断(忽略)...
        synchronized (this.getClass()) {
            // 重复请求判断
            if (reqCache.containsKey(id)) {
                // 重复请求
                System.out.println("请勿重复提交!!!" + id);
                return "执行失败";
            }
            // 存储请求 ID
            reqCache.put(id, 1);
        }
        // 业务代码...
        System.out.println("添加用户ID:" + id);
        return "执行成功!";
    }

思考:当采用分布式部署,多台服务器上部署项目,用户请求时由于负载均衡、不知道请求到那台服务器上了,上面的方法就不好用了。

4.分布式锁(用在多台服务器时,上面的方法用在单台服务器)

分布式锁就是对于一个对象只能有一个线程修改(对对象修改的方法只能有一个线程调用)。

基于数据库实现、redis实现、zookeeper实现

(1)数据库实现

存方法名、线程信息(设计可重入性)、失效时间(设置定时任务清除,防止死锁)

当执行某个方法时,将方法名和线程插入,插入前先查询是不是存在,存在就返回错误。不存在就插入。

(2)redis实现

  • 将方法名和参数拼接作为key,uuid作为value,并设置有效时间(防止死锁)。利用setnx(如果不存在就存进入,如果存在就什么也不做)
  • 先判断redis中有没有key,如果有就是重复请求,不做处理。
  • 如果redis中没有key,就不是重复请求,执行业务。执行业务后,使用lua脚本释放锁。使用lua脚本,因为lua脚本在redis中以原子形式操作,要么全做,要么全不做。避免检查锁和删除锁引发的并发错误。

redis分布式锁方式,点击查看代码

(3)ZooKeeper实现分布式锁

ZooKeeper是一个为分布式应用提供一致性服务的开源组件,它内部是一个分层的文件系统目录树结构,规定同一个目录下只能有一个唯一文件名。基于ZooKeeper实现分布式锁的步骤如下:

创建一个目录mylock;

线程A想获取锁就在mylock目录下创建临时顺序节点;

获取mylock目录下所有的子节点,然后获取比自己小的兄弟节点,如果不存在,则说明当前线程顺序号最小,获得锁;

线程B获取所有节点,判断自己不是最小节点,设置监听比自己次小的节点;

线程A处理完,删除自己的节点,线程B监听到变更事件,判断自己是不是最小的节点,如果是则获得锁。


这里推荐一个Apache的开源库Curator,它是一个ZooKeeper客户端,Curator提供的InterProcessMutex是分布式锁的实现,acquire方法用于获取锁,release方法用于释放锁。

优点:具备高可用、可重入、阻塞锁特性,可解决失效死锁问题。

缺点:因为需要频繁的创建和删除节点,性能上不如Redis方式。

相关推荐
Escalating_xu4 小时前
【Linux 网络编程】五种 I/O 模型与非阻塞 I/O:从“等数据”到正确处理 EAGAIN
java·linux·网络
程序猿乐锅5 小时前
万字详解Single-flight(单飞模式)
java·开发语言·分布式·后端·学习·single-flight
Java后端的Ai之路5 小时前
【AIAgent开发】-Agent工具封装成CLI(纯干货!!!)
java·javascript·人工智能·agent·cli
AntsData6 小时前
用 12K+ 条亚马逊评论信号,定位机械键盘下一版该改什么
java·大数据·计算机外设
code2cat6 小时前
【随笔】连续批处理:长短请求怎样在迭代之间补位
java·人工智能·后端·大语言模型·vllm
最强小杰6 小时前
DeepSeek V4 Flash + Codex CLI 接入教程:原生 Responses API 适配,从配置到跑通收藏这篇就够了(2026)
java·前端·python·ai
Flynn Liang6 小时前
Spring AI 2.0 深度解析:ChatClient、Advisor 链与 MCP——Java 工程师的 AI 落地最优解
java·人工智能·spring·大模型·架构设计·spring ai
灿飞6 小时前
Kotlin与Java虚拟机六:基础与类型系统:一切皆对象,但对象有代价
java·kotlin
计算机毕设定制辅导-无忧学长6 小时前
《基于微信小程序健康习惯激励系统设计与实现》
java·spring boot·mysql·微信小程序·uniapp·健康习惯激励系统
雪芽蓝域zzs7 小时前
第 1 章:IDEA 创建 SpringBoot3.5.16 Maven 项目
java·maven·intellij-idea