背景#

同一条数据被用户点击了多次,导致数据冗余,需要防止弱网络等环境下的重复点击

目标#

通过在指定的接口处添加注解,实现根据指定的接口参数来防重复点击

说明#

这里的重复点击是指在指定的时间段内多次点击按钮

技术方案#

springboot + redis锁 + 注解

使用 feign client 进行请求测试

最终的使用实例#

1、根据接口收到 PathVariable 参数判断唯一

Copy/**     *  根据请求参数里的 PathVariable 里获取的变量进行接口级别防重复点击     *     * @param testId 测试id     * @param requestVo 请求参数     * @return     * @author daleyzou     */    @PostMapping("/test/{testId}")    @NoRepeatSubmit(location = "thisIsTestLocation", seconds = 6)    public RsVo thisIsTestLocation(@PathVariable Integer testId, @RequestBody RequestVo requestVo) throws Throwable {        // 睡眠 5 秒,模拟业务逻辑        Thread.sleep(5);        return RsVo.success("test is return success");    }

2、根据接口收到的 RequestBody 中指定变量名的值判断为一

Copy/**     *  根据请求参数里的 RequestBody 里获取指定名称的变量param5的值进行接口级别防重复点击     *     * @param testId 测试id     * @param requestVo 请求参数     * @return     * @author daleyzou     */    @PostMapping("/test/{testId}")    @NoRepeatSubmit(location = "thisIsTestBody", seconds = 6, argIndex = 1, name = "param5")    public RsVo thisIsTestBody(@PathVariable Integer testId, @RequestBody RequestVo requestVo) throws Throwable {        // 睡眠 5 秒,模拟业务逻辑        Thread.sleep(5);        return RsVo.success("test is return success");    }

ps: jedis 2.9 和 springboot有各种兼容问题,无奈只有降低springboot的版本了

运行结果#

Copy收到响应:{"succeeded":true,"code":500,"msg":"操作过于频繁,请稍后重试","data":null}收到响应:{"succeeded":true,"code":500,"msg":"操作过于频繁,请稍后重试","data":null}收到响应:{"succeeded":true,"code":500,"msg":"操作过于频繁,请稍后重试","data":null}收到响应:{"succeeded":true,"code":200,"msg":"success","data":"test is return success"}

测试用例#

Copypackage com.dalelyzou.preventrepeatsubmit.controller;import com.dalelyzou.preventrepeatsubmit.PreventrepeatsubmitApplicationTests;import com.dalelyzou.preventrepeatsubmit.service.AsyncFeginService;import com.dalelyzou.preventrepeatsubmit.vo.RequestVo;import org.junit.jupiter.api.Test;import org.springframework.beans.factory.annotation.Autowired;import java.io.IOException;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;/** * TestControllerTest * @description 防重复点击测试类 * @author daleyzou * @date 2020年09月28日 17:13 * @version 1.3.1 */class TestControllerTest extends PreventrepeatsubmitApplicationTests {    @Autowired    AsyncFeginService asyncFeginService;    @Test    public void thisIsTestLocation() throws IOException {        RequestVo requestVo = new RequestVo();        requestVo.setParam5("random");        ExecutorService executorService = Executors.newFixedThreadPool(4);        for (int i = 0; i <= 3; i++) {            executorService.execute(() -> {                String kl = asyncFeginService.thisIsTestLocation(requestVo);                System.err.println("收到响应:" + kl);            });        }        System.in.read();    }    @Test    public void thisIsTestBody() throws IOException {        RequestVo requestVo = new RequestVo();        requestVo.setParam5("special");        ExecutorService executorService = Executors.newFixedThreadPool(4);        for (int i = 0; i <= 3; i++) {            executorService.execute(() -> {                String kl = asyncFeginService.thisIsTestBody(requestVo);                System.err.println("收到响应:" + kl);            });        }        System.in.read();    }}

定义一个注解#

Copypackage com.dalelyzou.preventrepeatsubmit.aspect;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/** * NoRepeatSubmit * @description 重复点击的切面 * @author daleyzou * @date 2020年09月23日 14:35 * @version 1.4.8 */@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface NoRepeatSubmit {    /**     * 锁过期的时间     * */    int seconds() default 5;    /**     * 锁的位置     * */    String location() default "NoRepeatSubmit";    /**     * 要扫描的参数位置     * */    int argIndex() default 0;    /**     * 参数名称     * */    String name() default "";}

根据指定的注解定义一个切面,根据参数中的指定值来判断请求是否重复#

Copypackage com.dalelyzou.preventrepeatsubmit.aspect;import com.dalelyzou.preventrepeatsubmit.constant.RedisKey;import com.dalelyzou.preventrepeatsubmit.service.LockService;import com.dalelyzou.preventrepeatsubmit.vo.RsVo;import com.google.common.collect.Maps;import com.google.gson.Gson;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Pointcut;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Component;import org.springframework.util.StringUtils;import java.lang.reflect.Field;import java.util.Map;@Aspect@Componentpublic class NoRepeatSubmitAspect {    private static final Logger logger = LoggerFactory.getLogger(NoRepeatSubmitAspect.class);    private static Gson gson = new Gson();    private static final String SUFFIX = "SUFFIX";    @Autowired    LockService lockService;    /**     * 横切点     */    @Pointcut("@annotation(noRepeatSubmit)")    public void repeatPoint(NoRepeatSubmit noRepeatSubmit) {    }    /**     *  接收请求,并记录数据     */    @Around(value = "repeatPoint(noRepeatSubmit)")    public Object doBefore(ProceedingJoinPoint joinPoint, NoRepeatSubmit noRepeatSubmit) {        String key = RedisKey.NO_REPEAT_LOCK_PREFIX + noRepeatSubmit.location();        Object[] args = joinPoint.getArgs();        String name = noRepeatSubmit.name();        int argIndex = noRepeatSubmit.argIndex();        String suffix;        if (StringUtils.isEmpty(name)) {            suffix = String.valueOf(args[argIndex]);        } else {            Map keyAndValue = getKeyAndValue(args[argIndex]);            Object valueObj = keyAndValue.get(name);            if (valueObj == null) {                suffix = SUFFIX;            } else {                suffix = String.valueOf(valueObj);            }        }        key = key + ":" + suffix;        logger.info("==================================================");        for (Object arg : args) {            logger.info(gson.toJson(arg));        }        logger.info("==================================================");        int seconds = noRepeatSubmit.seconds();        logger.info("lock key : " + key);        if (!lockService.isLock(key, seconds)) {            return RsVo.fail("操作过于频繁,请稍后重试");        }        try {            Object proceed = joinPoint.proceed();            return proceed;        } catch (Throwable throwable) {            logger.error("运行业务代码出错", throwable);            throw new RuntimeException(throwable.getMessage());        } finally {            lockService.unLock(key);        }    }    public static Map getKeyAndValue(Object obj) {        Map map = Maps.newHashMap();        // 得到类对象        Class userCla = (Class) obj.getClass();        /* 得到类中的所有属性集合 */        Field[] fs = userCla.getDeclaredFields();        for (int i = 0; i < fs.length; i++) {            Field f = fs[i];            // 设置些属性是可以访问的            f.setAccessible(true);            Object val = new Object();            try {                val = f.get(obj);                // 得到此属性的值                // 设置键值                map.put(f.getName(), val);            } catch (IllegalArgumentException e) {                logger.error("getKeyAndValue IllegalArgumentException", e);            } catch (IllegalAccessException e) {                logger.error("getKeyAndValue IllegalAccessException", e);            }        }        logger.info("扫描结果:" + gson.toJson(map));        return map;    }}

完整地址:https://github.com/daleyzou/PreventRepeatSubmit

原文链接:https://www.cnblogs.com/daleyzou/p/noSubmitRepeat.html

如果觉得本文对你有帮助,可以转发关注支持一下

springboot 主键重复导致数据重复_Springboot实现防重复提交和防重复点击(附源码)...相关推荐

  1. 基于Vue+SpringBoot智慧校园疫情防控系统(PC端、手机端)--附源码

    介绍 智慧校园疫情防控系统--PC 手机端 多端并行 项目源码下载:https://download.csdn.net/download/DeepLearning_/87340321 软件架构 手机端 ...

  2. 萌新Java开发实战记录:大数据开发之”IP热力图、地点热门TopN(文章底部附源码)

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 目录 一. 课程设计背景概述 1. <IP经纬热力图>概述 2. <电商分析系统>概述 二.需求分析 1.&l ...

  3. 上传图片到linux返回url,Springboot 将前端传递的图片上传至Linux服务器并返回图片的url(附源码)...

    问题由来: 用户个人信息需要添加头像功能 当前端程序是微信小程序时,前端将直接将图片 url 传送至服务端 但是当前端是 Web 页面时,前端传递的参数是一张图片,服务端需要将图片保存至 Linux ...

  4. springcloud 图片和数据一起提交_SpringCloud网上商城系统(附源码及教程)

    简介 设计精良的网上商城系统,包括前端.后端.数据库.负载均衡.数据库缓存等,使用SpringCloud框架,基于Java开发.该项目可部署到服务器上,不断完善中! 预览 ​ 功能说明 用户微服务 用 ...

  5. mysql添加数据不阻塞_主键表插入数据不提交,外键表插入数据被阻塞

    有客户和我说:他在含主外键的表中实验发现,在主表数据未提交,然后在外键表插入该数据数据时,出现外键表hang住现象.我开始以为是不同的会话,根据oracle数据库的一致性原则,应该新会话在外键表中不能 ...

  6. (附源码)springboot 西安市小学生护眼平台开发 080855

    springboot西安市中小学生护眼平台开发 摘要 俗话说:眼睛是心灵的窗户!可在这个科学技术日新月异发展的大千世界里,戴眼镜的人却随处可见.特别是我国在校学生3.2亿,平均近视率超过60%,其中小 ...

  7. flink sql 知其所以然(二)| 自定义 redis 数据维表(附源码)

    感谢您的关注  +  点赞 + 再看,对博主的肯定,会督促博主持续的输出更多的优质实战内容!!! 1.序篇-本文结构 背景篇-为啥需要 redis 维表 目标篇-做 redis 维表的预期效果是什么 ...

  8. Netty实战:Springboot+Netty+protobuf开发高性能服务器 (附源码下载)

    Springboot-cli 开发脚手架系列 Netty系列:Springboot使用Netty集成protobuf开发高性能服务器 文章目录 Springboot-cli 开发脚手架系列 简介 1. ...

  9. 手把手教你做一个数据图表生成器(附源码)...

    我的需求:手动配置X轴.Y轴.图表标题等参数自动通过Pyecharts模块生成可视化的html数据图表,并将浏览器图表展示到UI界面上. [阅读全文] 制作出图表后的效果展示如下: 另外,生成后的图表 ...

  10. 利用SpringBoot和Vue实现前后端分离(附源码)

    利用SpringBoot和Vue实现前后端分离(附源码) 引言: 本文主要分享了SpringBoot和Vue整合实现前后端分离,实现了简单的增删查改:包括:项目的搭建.后端的实现.前台的实现:(附源码 ...

最新文章

  1. layui 表格内容写temple函数_layui表格-template模板的三种用法
  2. 从Eclipse转移到IntelliJ IDEA一点心得
  3. c语言表达式用法,C语言开发之运算符、表达式用法
  4. 基础才是重中之重~再说面向接口的编程
  5. Spring Boot 集成 Druid 监控数据源
  6. java开发中实际遇到的问题总结_内行总结:装修中最容易遇到的16个问题及其解决方式!...
  7. 信息学奥赛一本通(1091:求阶乘的和)
  8. Servlet四种访问属性--知识点
  9. smale学习之数学表达式(day1)
  10. Cisco路由器配置静态路由
  11. python读取excel并绘制折线图
  12. 使用PicGo和阿里云OSS建立云图床
  13. 台式计算机无线网经常掉线,台式机无线上网经常断网怎么办呢
  14. 手把手实现AI诗歌生成(AI写诗)
  15. Mysq连接l数据库有效文章
  16. http://blog.csdn.net/LANGXINLEN/article/details/50421988
  17. curl调用新版jenkins crumb报错 No valid crumb was included in the request 解决方法
  18. Ai智能对话页面html,js人工智能对话框 - osc_q50is30g的个人空间 - OSCHINA - 中文开源技术交流社区...
  19. IT奶爸带娃记之幼儿园选择困难症
  20. Java的Closeable接口close()方法的意义

热门文章

  1. Unity调用安卓Android的Toast
  2. 【Linux】使用xshell登陆时密码框为灰色,无法输入密码
  3. php 中continue break exit return 的区别
  4. [Java基础]Scanner的使用(秋招在线笔试高频使用)版本:Java™ Platform Standard Ed. 8...
  5. Swift - 委托(delegate)的介绍,及使用样例
  6. 【网络文件共享】02、NFS服务基础
  7. htc在ubuntu上找不到devieces,提示权限不够的解决方法
  8. 原生态纯JavaScript 100大技巧大收集
  9. 面试官:引入RabbitMQ后,你如何保证全链路数据100%不丢失?
  10. 一个项目在启动前都应该明确哪些事