Spring Boot 接口防重复提交解决方案

文章目录

    • 前言
    • 使用Token机制
      • 实现步骤
        • 1.生成Token
        • 2.传递Token
        • 3.验证Token
    • 使用Redis
      • 实现步骤
        • 1.引入Redis依赖
        • 2.生成Token
        • 3.传递Token
        • 4.验证Token
    • 使用Spring AOP
      • 实现步骤
        • 1.定义注解
        • 2.创建切面
        • 3.使用注解
    • 总结

前言

在Web开发中,防止用户重复提交表单是一个常见的需求。用户可能会因为网络延迟、误操作等原因多次点击提交按钮,导致后台接收到多个相同的请求。这不仅会浪费服务器资源,还可能导致数据不一致等问题。本文将介绍几种在Spring Boot中实现接口防重复提交的方法。
在这里插入图片描述

使用Token机制

Token机制是一种常见的防重复提交方法。具体步骤如下:
生成Token:用户每次请求表单页面时,服务器生成一个唯一的Token,并将其存储在Session中。
传递Token:将Token嵌入到表单中,随表单一起提交。
验证Token:服务器接收到请求后,首先验证Token是否有效,如果有效则继续处理请求,并从Session中移除该Token;如果无效,则返回错误信息。

实现步骤

1.生成Token

在Controller中生成Token并存储在Session中:

/**
 * form
 * @param session
 * @author senfel
 * @date 2024/11/12 11:29
 * @return org.springframework.web.servlet.ModelAndView
 */
@GetMapping("/form")
public ModelAndView showForm(HttpSession session) {
    ModelAndView form = new ModelAndView("form");
    String token = UUID.randomUUID().toString();
    session.setAttribute("token", token);
    form.addObject("token", token);
    return form;
}
2.传递Token

在表单中添加隐藏字段来传递Token:

<form action="/base/submit" method="post">
    <input type="hidden" name="token" th:value="${token}">
    <!-- 其他表单字段 -->
    <button type="submit">Submit</button>
</form>
3.验证Token

在Controller中验证Token:

/**
 * handleForm
 * @param token
 * @param session
 * @author senfel
 * @date 2024/11/12 11:34
 * @return java.lang.String
 */
@PostMapping("/submit")
public String handleForm(@RequestParam String token, HttpSession session) {
    String sessionToken = (String) session.getAttribute("token");
    if (sessionToken == null || !sessionToken.equals(token)) {
        throw new RuntimeException("Duplicate submit detected");
    }
    // 移除Token
    session.removeAttribute("token");
    // 处理表单数据
    return "success";
}

使用Redis

Redis是一个高性能的键值存储系统,可以用来存储和验证Token。具体步骤如下:
生成Token:用户每次请求表单页面时,服务器生成一个唯一的Token,并将其存储在Redis中。
传递Token:将Token嵌入到表单中,随表单一起提交。
验证Token:服务器接收到请求后,首先验证Token是否存在于Redis中,如果存在则继续处理请求,并从Redis中删除该Token;如果不存在,则返回错误信息。

实现步骤

1.引入Redis依赖

在 pom.xml 中添加Redis依赖:

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2.生成Token

在Controller中生成Token并存储在Redis中:

@Autowired
private StringRedisTemplate redisTemplate;

/**
 * formByRedis
 * @author senfel
 * @date 2024/11/12 11:50
 * @return org.springframework.web.servlet.ModelAndView
 */
@GetMapping("/formByRedis")
public ModelAndView showFormByRedis() {
    ModelAndView form = new ModelAndView("form");
    String token = UUID.randomUUID().toString();
    // 设置过期时间
    redisTemplate.opsForValue().set(token, token, 5, TimeUnit.MINUTES);
    form.addObject("token", token);
    return form;
}
3.传递Token

在表单中添加隐藏字段来传递Token:

<form action="/base/submitByRedis" method="post">
    <input type="hidden" name="token" th:value="${token}">
    <!-- 其他表单字段 -->
    <button type="submit">Submit</button>
</form>
4.验证Token

在Controller中验证Token:

/**
 * submitByRedis
 * @param token
 * @author senfel
 * @date 2024/11/12 11:50
 * @return java.lang.String
 */
@PostMapping("/submitByRedis")
public String handleFormByRedis(@RequestParam String token) {
    String redisToken = redisTemplate.opsForValue().get(token);
    if (redisToken == null) {
        throw new RuntimeException("Duplicate submit detected");
    }
    // 删除Token
    redisTemplate.delete(token);
    // 处理表单数据
    return "success";
}

使用Spring AOP

Spring AOP(Aspect-Oriented Programming)可以用来实现切面编程,从而在多个方法中复用防重复提交的逻辑。

实现步骤

1.定义注解

创建一个自定义注解 @PreventDuplicateSubmit:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * PreventDuplicateSubmit
 * @author senfel
 * @date 2024/11/12 11:56
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PreventDuplicateSubmit {
    /**重复请求时间*/
    int expireSeconds() default 10;
}
2.创建切面

创建一个切面类 DuplicateSubmitAspect:

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

/**
 * DuplicateSubmitAspect
 * @author senfel
 * @version 1.0
 * @date 2024/11/12 11:57
 */
@Slf4j
@Aspect
@Component
public class DuplicateSubmitAspect {

    protected static final Logger logger = LoggerFactory.getLogger(DuplicateSubmitAspect.class);
    @Autowired
    private StringRedisTemplate redisTemplate;

    /**
     * around
     * @param joinPoint
     * @author senfel
     * @date 2024/11/12 15:45
     * @return java.lang.Object
     */
    @Around("@annotation(com.example.ccedemo.aop.PreventDuplicateSubmit)")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        StringBuilder key = new StringBuilder();
        //获取class
        String simpleName = joinPoint.getTarget().getClass().getSimpleName();
        key.append(simpleName);
        // 获取请求方法
        MethodSignature signature=(MethodSignature)joinPoint.getSignature();
        Method method = signature.getMethod();
        String methodName = method.getName();
        key.append(":").append(methodName);
        //获取请求参数
        Object[] args=joinPoint.getArgs();
        for (Object arg : args) {
            key.append(":").append(arg.toString());
        }
        //TODO 获取客户端IP

        // 获取注解信息
        PreventDuplicateSubmit annotation = method.getAnnotation(PreventDuplicateSubmit.class);
        // 判断是否已经请求过
        if(redisTemplate.hasKey(key.toString())){
            throw new RuntimeException("请勿重复提交");
        }
        //标记请求已经处理过
        redisTemplate.opsForValue().set(key.toString(),"1",annotation.expireSeconds(), TimeUnit.SECONDS);
        return joinPoint.proceed();
    }
}
3.使用注解

在Controller方法上使用 @PreventDuplicateSubmit 注解:

/**
 * handleFormByAnnotation
 * @param param
 * @author senfel
 * @date 2024/11/12 11:59
 * @return java.lang.String
 */
@PostMapping("/submitByAnnotation")
@PreventDuplicateSubmit
public String handleFormByAnnotation(@RequestParam String param) {
    // 处理表单数据
    return "success";
}

总结

本文介绍了三种在Spring Boot中实现接口防重复提交的方法:使用Token机制、使用Redis和使用Spring AOP。每种方法都有其适用场景和优缺点,可以根据实际需求选择合适的方法。通过这些方法,可以有效防止用户重复提交表单,提高系统的稳定性和用户体验。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/915242.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【毫米波雷达(九)】前雷达软件开发遇到的问题汇总及解决方法

前雷达软件开发遇到的问题汇总及解决方法 一、CAN/CANFD通信1、雷达CAN未能正常发出数据2、雷达在车上接收不到车身信息3、程序下载失败4、DV试验发送数据偶发断连5、发送感知信息丢帧或者丢报文6、上电发出第一帧的报文时间长7、ZCANPRO有错误帧二、协议转换&#xff08;以太网…

图像处理实验四(Adaptive Filter)

一、Adaptive Filter简介 自适应滤波器&#xff08;Adaptive Filter&#xff09;是一种能够根据输入信号的统计特性自动调整自身参数以达到最佳滤波效果的滤波器。它广泛应用于信号处理领域&#xff0c;如信道均衡、系统识别、声学回波抵消、生物医学、雷达、波束形成等模块。 …

计算机网络(8)数据链路层之子层

上一篇已经讲到数据链路层可以分为两个子层&#xff0c;这次将重点讲解子层的作用和ppp协议 数据链路层的子层 数据链路层通常被分为两个子层&#xff1a; 逻辑链路控制子层&#xff08;LLC&#xff0c;Logical Link Control&#xff09;&#xff1a; LLC子层负责在数据链路…

论文5—《基于改进YOLOv5s的轻量化金银花识别方法》文献阅读分析报告

论文报告&#xff1a;基于改进YOLOv5s的轻量化金银花识别方法 论文报告文档 基于改进YOLOv5s的轻量化金银花识别方法 论文报告文档摘要国内外研究现状国内研究现状国外研究现状 研究目的研究问题使用的研究方法试验研究结果文献结论创新点和对现有研究的贡献1. 目标检测技术2. …

雷池waf安装并部署防护站点

雷池waf安装并部署防护站点 最低配置要求 操作系统&#xff1a;Linux 指令架构&#xff1a;x86_64 软件依赖&#xff1a;Docker 20.10.14 版本以上 软件依赖&#xff1a;Docker Compose 2.0.0 版本以上 最小化环境&#xff1a;1 核 CPU / 1 GB 内存 / 5 GB 磁盘 写在前面 本文…

2024第四次随堂测验参考答案

从第四次开始答案会以c语言提供&#xff0c;自行了解&#xff0c;学习 6-1 报数 报数游戏是这样的&#xff1a;有n个人围成一圈&#xff0c;按顺序从1到n编好号。从第一个人开始报数&#xff0c;报到m&#xff08;<n&#xff09;的人退出圈子&#xff1b;下一个人从1开始报…

开源 - Ideal库 - 常用枚举扩展方法(二)

书接上回&#xff0c;今天继续和大家享一些关于枚举操作相关的常用扩展方法。 今天主要分享通过枚举值转换成枚举、枚举名称以及枚举描述相关实现。 我们首先修改一下上一篇定义用来测试的正常枚举&#xff0c;新增一个枚举项&#xff0c;代码如下&#xff1a; //正常枚举 in…

如何平滑切换Containerd数据目录

如何平滑切换Containerd数据目录 大家好&#xff0c;我是秋意零。 这是工作中遇到的一个问题。搭建的服务平台&#xff0c;在使用的过程中频繁出现镜像本地拉取不到问题&#xff08;在项目群聊中老是被人出来&#x1f605;&#xff09;原因是由于/目录空间不足导致&#xff0…

(附项目源码)Java开发语言,监督管家APP的设计与实现 58,计算机毕设程序开发+文案(LW+PPT)

摘要 随着互联网的快速发展和智能手机的普及&#xff0c;越来越多的用户选择通过移动应用程序进行事项设定、提醒通知和事项打卡。监督管家APP作为一个专注于事项设定、提醒通知、事项打卡的监督管理平台&#xff0c;具有广泛的应用前景和商业价值。本研究旨在构建一个功能丰富…

ffmpeg+D3D实现的MFC音视频播放器,支持录像、截图、音视频播放、码流信息显示等功能

一、简介 本播放器是在vs2019下开发&#xff0c;通过ffmpeg实现拉流解码功能&#xff0c;通过D3D实现视频的渲染功能。截图功能采用libjpeg实现&#xff0c;可以截取jpg图片&#xff0c;图片的默认保存路径是在C:\MYRecPath中。录像功能采用封装好的类Mp4Record实现&#xff0c…

LLM在Transformer上的改动

LLM在Transformer上的改动 1.multi-head共享1.1BERT的逻辑1.2multi-head共享 2.attention的前后网络2.1传统Transformer&#xff1a;2.2GPTJ结构&#xff1a; 3.归一化层的位置&#xff08;LayerNorm&#xff09;4.归一化层函数的选择4.1LayerNorm4.2RMSNorm 3.激活函数4.LLama…

git命令及原理

git: 目录则被称之为“树” 文件被称作 Blob 对象. git help <command>: 获取 git 命令的帮助信息 git init: 创建一个新的 git 仓库&#xff0c;其数据会存放在一个名为 .git 的目录下 git status: 显示当前的仓库状态 git add <filename>: 添加文件到暂存区 git …

scala 迭代更新

在Scala中&#xff0c;迭代器&#xff08;Iterator&#xff09;是一种用于遍历集合&#xff08;如数组、列表、集合等&#xff09;的元素而不暴露其底层表示的对象。迭代器提供了一种统一的方法来访问集合中的元素&#xff0c;而无需关心集合的具体实现。 在Scala中&#xff0c…

快速掌握——python类 封装[私有属性方法]、继承【python进阶】(内附代码)

1.类的定义 与 实例化对象 在python中使用class关键字创建一个类。 举例子 class Stu(object):id 1001name 张三def __init__(self):passdef fun1(self):pass# 实例化对象 s1 Stu() s2 Stu() print(s1.name) print(s2.name) 第一个方法 __init__是一种特殊的方法&#x…

51c自动驾驶~合集10

我自己的原文哦~ https://blog.51cto.com/whaosoft/11638131 #端到端任务 说起端到端&#xff0c;每个从业者可能都觉得会是下一代自动驾驶量产方案绕不开的点&#xff01;特斯拉率先吹响了方案更新的号角&#xff0c;无论是完全端到端&#xff0c;还是专注于planner的模型&a…

BFS 算法专题(三):BFS 解决边权为 1 的最短路问题

目录 1. 迷宫中离入口最近的出口 1.1 算法原理 1.2 算法代码 2. 最小基因变化 ★★★ 2.1 算法原理 2.2 算法代码 3. 单词接龙 3.1 算法原理 3.2 算法代码 4. 为高尔夫比赛砍树 (hard) 4.1 算法原理 4.2 算法代码 1. 迷宫中离入口最近的出口 . - 力扣&#xff08;…

Flink_DataStreamAPI_执行环境

DataStreamAPI_执行环境 1创建执行环境1.1getExecutionEnvironment1.2createLocalEnvironment1.3createRemoteEnvironment 2执行模式&#xff08;Execution Mode&#xff09;3触发程序执行 Flink程序可以在各种上下文环境中运行&#xff1a;我们可以在本地JVM中执行程序&#x…

46.第二阶段x86游戏实战2-拆解自动打怪流程

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 本次游戏没法给 内容参考于&#xff1a;微尘网络安全 本人写的内容纯属胡编乱造&#xff0c;全都是合成造假&#xff0c;仅仅只是为了娱乐&#xff0c;请不要…

解决C盘空间不足的三种方案

方案一&#xff1a;网上盛传的C盘磁盘碎片整理&#x1f9e9;&#xff08;原理&#xff1a;将分散的文件片段整理到相邻的磁盘区域&#xff0c;减少文件的碎片化程度&#xff09;(效果不明显) 方案二&#xff1a;把其他盘的空间给C盘 &#x1f4bd;&#xff08;效果显著&#xf…

同一套SDK 兼容第二块板卡

尽可能分开写,避免兼容性变差