springboot实现后端防重复提交(AOP+redis分布式锁)单机情况下

news2024/11/18 9:25:52

文章目录

  • 0、依赖
  • 1、自定义接口
  • 2、实现redis分布式锁
  • 3、自定义AOP
  • 4、测试

为什么要实现这个功能呢,可能用户在提交一份数据后,可能因为网络的原因、处理数据的速度慢等原因导致页面没有及时将用户刚提交数据的后台处理结果展示给用户,这时用户可能会进行如下操作:

  1. 点击提交按钮两次,导致重复提交表单。
  2. 使用浏览器后退按钮重复之前的操作,导致重复提交表单
    数据库中会存在大量重复的信息。

怎么实现呢:进入正题👇

0、依赖

		<dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.6.8</version>
        </dependency>
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-core</artifactId>
            <version>9.0.21</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-redis</artifactId>
            <version>2.6.3</version>
        </dependency>

1、自定义接口

/**
 * 自定义注解防止表单重复提交
 * @author Yuan Haozhe
 */
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RepeatSubmit {
}

2、实现redis分布式锁

@Component
public class RedisLock {
	@Autowired
    private StringRedisTemplate redisTemplate;
	
	 /**
     * 该加锁方法仅针对单实例 Redis 可实现分布式加锁
     * 对于 Redis 集群则无法使用
     *
     * 支持重复,线程安全
     *
     * @param lockKey   加锁键
     * @param clientId  加锁客户端唯一标识(采用UUID)
     * @param seconds   锁过期时间
     * @return
     */
    public boolean tryLock(String lockKey, String clientId, long seconds) {
        //
        return redisTemplate.opsForValue().setIfAbsent(lockKey, clientId, seconds, TimeUnit.SECONDS);
    }


}

对参数进行介绍:
第一个为key,我们使用key来当锁,因为key是唯一的。我们传的是lockKey
第二个为value,通过给value赋值为clientId,我们就知道这把锁是哪个请求加的了,在解锁的时候就可以有依据。clientId可以使用UUID.randomUUID().toString()方法生成。
第三个为time,代表key的过期时间
第四个为time的单位
setIfAbsent 相当于NX,设置过期时间相当于EX

也可用Jedis实现:

  1. 添加依赖:
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.0</version>
</dependency>
  1. 加锁代码
	private static final String LOCK_SUCCESS = "OK";
    private static final String SET_IF_NOT_EXIST = "NX";
    private static final String SET_WITH_EXPIRE_TIME = "PX";
    private static final String RELEASE_LOCK_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

    @Autowired
    private Jedis jedis;
    
	public boolean tryLock(String lockKey, String clientId, long seconds) {
        String result = jedis.set(lockKey, clientId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, seconds);
        if (LOCK_SUCCESS.equals(result)) {
            return true;
        }
        return false;
    }
    /**
     * 与 tryLock 相对应,用作释放锁
     *
     * @param lockKey
     * @param clientId
     * @return
     */
    public boolean releaseLock(String lockKey, String clientId) {
    	//这里使用Lua脚本的方式,尽量保证原子性。
       return jedis.eval(RELEASE_LOCK_SCRIPT,Collections.singletonList(lockKey),Collections.singletonList(clientId)).equals(1L);
        
    }

对参数进行介绍:
第一个为key,我们使用key来当锁,因为key是唯一的。我们传的是lockKey
第二个为value,通过给value赋值为clientId,我们就知道这把锁是哪个请求加的了,在解锁的时候就可以有依据。clientId可以使用UUID.randomUUID().toString()方法生成。
第三个为nxxx,这个参数我们填的是NX,意思是SET IF NOT EXIST,即当key不存在时,我们进行set操作;若key已经存在,则不做任何操作;
第四个为expx,这个参数我们传的是PX,意思是我们要给这个key加一个过期的设置,具体时间由第五个参数决定。
第五个为time,与第四个参数相呼应,代表key的过期时间。

可以看到,我们的加锁就一行代码,保证了原子性,总的来说,执行上面的set()方法就只会导致两种结果:1. 当前没有锁(key不存在),那么就进行加锁操作,并对锁设置个有效期,同时value表示加锁的客户端。2. 已有锁存在,不做任何操作。

3、自定义AOP

@Component
@Aspect
@Slf4j
public class NoRepeatSubmitAspect {
    @Pointcut("@annotation(com.xxl.sso.base.annotation.RepeatSubmit)")
    public void repeatSubmit(){}

    @Autowired
    private HttpServletRequest request;

    @Autowired
    private RedisLock redisLock;


    @Around("repeatSubmit()")
    public ReturnT around(ProceedingJoinPoint joinPoint) {
        log.info("校验重复提交");
        //用户的身份标识,这里用cookie只是方便做测试
        String userId = CookieUtil.getValue(request, Conf.SSO_SESSIONID).split("_")[0];
        String key = getKey(userId, request.getServletPath());
        String clientId = getClientId();
        boolean isSuccess = redisLock.tryLock(key, clientId, 3);
        // 如果缓存中有这个url视为重复提交
        if (isSuccess) {
            Object result = null;
            try {
                result = joinPoint.proceed();

            } catch (Throwable e) {
                log.error(e.getMessage());
            }
            //finall{
            //redisLock.releaseLock(key, clientId)
            //}
            return ReturnT.SUCCESS;
        } else {
            log.error("重复提交");
            return ReturnT.FAIL;
        }
    }

    private String getKey(String token, String path) {
        return token + path;
    }

    private String getClientId() {
        return UUID.randomUUID().toString();
    }
}

4、测试

 //测试重复提交
    @GetMapping("/test-get")
    //添加RepeatSubmit注解
    @RepeatSubmit
    public ReturnT repeatTest(){
        return ReturnT.SUCCESS;
    }

结果:
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LayoutTransformer: Layout Generation and Completion with Self-attention

LayoutTransformer: Layout Generation and Completion with Self-attention (Paper reading) Kamal Gupta, University of Maryland, US, Cited:41, Code, Paper 1. 前言 我们解决了在各种领域中&#xff08;如图像、移动应用、文档和3D对象&#xff09;进行场景布局生成的…

后台管理系统模板 - zhadminvue

没事的时候写了一个后台管理系统模板&#xff1a;zhadminvue&#xff0c;欢迎大家提issue和PR&#xff0c;以及想要添加一些有意思的功能模块&#xff0c;在没事的时候我也会加进去~ github:https://github.com/iotzzh/zh-admin-vue gitee: https://gitee.com/iotzzh/zh-admin.…

算法模板(4):动态规划(1)

动态规划 闫氏 d p dp dp 分析法&#xff1a; 集合&#xff1a;怎么划分。通常以集合的划分来定义数组。 f ( i , j ) f(i, j) f(i,j)&#xff1a;选前 i i i 个物品. 体积不超过 j j j。全部初始化为 0 0 0&#xff1b; j j j 非负时状态才合法。体积恰好是 j j j。 f…

Springboot集成magic-api

目录 1、前言 2、springboot集成magic-api 2.1、添加maven依赖 2.2、application.yml配置 2.3、编写测试接口 2.4、启动程序&#xff0c;访问接口 2.5、magic-api脚本 3、magic-api其他语法 4、注意事项 1、前言 今天项目中遇到一个问题&#xff0c;springboot后端项目…

《机器学习算法竞赛实战》-chapter3数据探索

《机器学习算法竞赛实战》学习笔记&#xff0c;记录一下自己的学习过程&#xff0c;详细的内容请大家购买作者的书籍查阅。 数据探索 数据探索是竞赛的核心模块之一&#xff0c;贯穿竞赛始终&#xff0c;也是很多竞赛胜利的关键。 在竞赛中如何确保自己准备好竞赛使用的算法模…

cmake 添加一个库

目录 项目格式 cmake基本语法 添加库 链接库 添加库的头文件 cmake打印字符串 库的cmake文件 cmake生辰库 mian函数中使用 让库成为可选的 cmake基本语法 设置option变量 cmake设置条件链接库 链接库 添加头文件 修改cmake配置文件 修改引用的源码 项目格式 …

MM32F3273G8P火龙果开发板MindSDK开发教程12 -获取msa311加速器的敲击事件

MM32F3273G8P火龙果开发板MindSDK开发教程12 -获取msa311加速器的敲击事件 1、功能描述 msa311可以识别单击、双击事件&#xff0c;类似手机上的点击返回&#xff0c;双击截屏功能。 单击&#xff0c;双击都能产生中断事件。 中断事件产生后&#xff0c;从对应的状态寄存器读…

算法模板(4):动态规划(3) 做题积累(1)

动态规划 1. 背包 1. 1024. 装箱问题 题意&#xff1a;有一个箱子容量为 V&#xff0c;同时有 n 个物品&#xff0c;每个物品有一个体积&#xff08;正整数&#xff09;。要求 n 个物品中&#xff0c;任取若干个装入箱内&#xff0c;使箱子的剩余空间为最小。别学那么死板。…

2023夏-PAT甲级题解

目录 总结&#xff1a; A-1 Trap Input Specification: Output Specification: Sample Input: Sample Output: 题意&#xff1a; 思路&#xff1a; AC代码&#xff1a; A-2 Queue Using Two Stacks Input Specification: Output Specification: Sample Input: Sa…

SpringBoot 项目部署笔记

1. 直接通过 jar 包部署 本地直接 build package 成 jar 包&#xff0c;上传至服务器 ps -ef|grep XXX #查找项目进程sudo kill -9 19727 #杀掉项目进程nohup sudo java -jar *.jar >> app.log & #后台运行 jar &代表让该命令在后台执行 3. 通过 Jenkins …

pytorch笔记:conv2d

来自B站视频&#xff0c;API查阅&#xff0c;TORCH.NN nn.conv2d 中一般 kernel_size 是小奇数&#xff0c;padding 设置为 k − 1 2 \frac{k-1}{2} 2k−1​&#xff08;实际上padding的是 k − 1 k-1 k−1&#xff0c;因为参数的意义是左右各padding&#xff09;&#xff0c;

探索Xiotman:物联网软件架构的创新之路

文章目录 探索Xiotman&#xff1a;物联网软件架构的创新之路什么是物联网什么是XiotmanXiotman的特点Xiotman的架构Xiotman的使用安装env工具获取源代码使用其他教程 Xiotman的开源地址 总结 探索Xiotman&#xff1a;物联网软件架构的创新之路 什么是物联网 &#x1f680;&am…

高燃盛会全程回顾|鸿雁加速推进数字转型之路

6月10日&#xff0c;以“双翅齐振雁南飞”为主题的鸿雁电器数字化营销启动大会暨中山古镇鸿雁全屋智能体验中心开业庆典&#xff0c;在中山古镇华艺广场圆满落幕。 古镇镇长阮志力、华艺集团董事长区锦标、华艺广场总经理丁瑜、古镇灯饰传媒董事长曹利晖以及杭州鸿雁电器有限公…

spring boot + xxl-job 分布式任务调度

一、介绍 1、任务调度 1.1、什么是任务调度 我们可以先思考一下下面业务场景的解决方案&#xff1a; 某电商系统需要在每天上午10点&#xff0c;下午3点&#xff0c;晚上8点发放一批优惠券。某财务系统需要在每天上午10点前结算前一天的账单数据&#xff0c;统计汇总。某电…

那年我头脑发热,选择了自动化,后来我掉入计算机的世界无法自拔

首先&#xff0c;小雅兰是22届高考考生&#xff0c;而且当时填报志愿也没有填报到计算机相关的专业去&#xff0c;小雅兰是自动化专业的学生&#xff0c;是由于一次偶然的机会&#xff0c;了解到了这个行业&#xff0c;对于写代码所带来的成就感&#xff0c;总之&#xff0c;我…

Java013——常见进制以及转换

一、常见进制 十进制 数字组成&#xff1a;0-9这十个数字组成&#xff0c;不能以0开头 进位规则&#xff1a;满10进1 int num1 10;//对应的十进制为10二进制 数字组成&#xff1a;0-1这两个数字组成&#xff0c;以0b或0B开头 进位规则&#xff1a;满2进1 int num2 0b1010;…

华为存储IA篇仿真器搭建

设备清单 编号 设备名 数量 备注 01 Windows系统主机 1台 为VMware提供安装位置 02 VMware软件 1份 提供存储仿真器的部署环境 03 仿真器文件 1份 用于部署estor虚拟机 【注意】&#xff1a;暂无注意事项 一、下载安装文件并配置虚拟机设备清单 1.1…

TypeScript 自定义装饰器

&#xff08;预测未来最好的方法就是把它创造出来——尼葛洛庞帝&#xff09; 装饰器 装饰器一种更现代的代码模式&#xff0c;通过使用的形式注入在属性&#xff0c;寄存器&#xff0c;方法&#xff0c;方法参数和类中&#xff0c;比如在Angular&#xff0c;Nestjs和midway等…

大数据Doris(三十九):Spark Load 注意事项

文章目录 Spark Load 注意事项 Spark Load 注意事项 1、现在Spark load 还不支持 Doris 表字段是String类型的导入,如果你的表字段有String类型的请改成varchar类型,不然会导入失败,提示 type:ETL_QUALITY_UNSATISFIED; msg:quality not good enough to cancel 2、使用Spa…

无敌!我用【C语言】手搓出了一个体系完整的【员工管理系统】还能玩游戏听音乐?(超详细,附完整源码)

博主简介&#xff1a;Hello大家好呀&#xff0c;我是陈童学&#xff0c;一个与你一样正在慢慢前行的人。 博主主页&#xff1a;陈童学哦 所属专栏&#xff1a;C语言程序设计实验项目 如果本文对你有所帮助的话&#xff0c;还希望可以点赞&#x1f44d;收藏&#x1f4c2;支持一下…