springboot 主键重复导致数据重复_Springboot实现防重复提交和防重复点击(附源码)...
背景#
同一条数据被用户点击了多次,导致数据冗余,需要防止弱网络等环境下的重复点击
目标#
通过在指定的接口处添加注解,实现根据指定的接口参数来防重复点击
说明#
这里的重复点击是指在指定的时间段内多次点击按钮
技术方案#
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实现防重复提交和防重复点击(附源码)...相关推荐
- 基于Vue+SpringBoot智慧校园疫情防控系统(PC端、手机端)--附源码
介绍 智慧校园疫情防控系统--PC 手机端 多端并行 项目源码下载:https://download.csdn.net/download/DeepLearning_/87340321 软件架构 手机端 ...
- 萌新Java开发实战记录:大数据开发之”IP热力图、地点热门TopN(文章底部附源码)
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 目录 一. 课程设计背景概述 1. <IP经纬热力图>概述 2. <电商分析系统>概述 二.需求分析 1.&l ...
- 上传图片到linux返回url,Springboot 将前端传递的图片上传至Linux服务器并返回图片的url(附源码)...
问题由来: 用户个人信息需要添加头像功能 当前端程序是微信小程序时,前端将直接将图片 url 传送至服务端 但是当前端是 Web 页面时,前端传递的参数是一张图片,服务端需要将图片保存至 Linux ...
- springcloud 图片和数据一起提交_SpringCloud网上商城系统(附源码及教程)
简介 设计精良的网上商城系统,包括前端.后端.数据库.负载均衡.数据库缓存等,使用SpringCloud框架,基于Java开发.该项目可部署到服务器上,不断完善中! 预览 功能说明 用户微服务 用 ...
- mysql添加数据不阻塞_主键表插入数据不提交,外键表插入数据被阻塞
有客户和我说:他在含主外键的表中实验发现,在主表数据未提交,然后在外键表插入该数据数据时,出现外键表hang住现象.我开始以为是不同的会话,根据oracle数据库的一致性原则,应该新会话在外键表中不能 ...
- (附源码)springboot 西安市小学生护眼平台开发 080855
springboot西安市中小学生护眼平台开发 摘要 俗话说:眼睛是心灵的窗户!可在这个科学技术日新月异发展的大千世界里,戴眼镜的人却随处可见.特别是我国在校学生3.2亿,平均近视率超过60%,其中小 ...
- flink sql 知其所以然(二)| 自定义 redis 数据维表(附源码)
感谢您的关注 + 点赞 + 再看,对博主的肯定,会督促博主持续的输出更多的优质实战内容!!! 1.序篇-本文结构 背景篇-为啥需要 redis 维表 目标篇-做 redis 维表的预期效果是什么 ...
- Netty实战:Springboot+Netty+protobuf开发高性能服务器 (附源码下载)
Springboot-cli 开发脚手架系列 Netty系列:Springboot使用Netty集成protobuf开发高性能服务器 文章目录 Springboot-cli 开发脚手架系列 简介 1. ...
- 手把手教你做一个数据图表生成器(附源码)...
我的需求:手动配置X轴.Y轴.图表标题等参数自动通过Pyecharts模块生成可视化的html数据图表,并将浏览器图表展示到UI界面上. [阅读全文] 制作出图表后的效果展示如下: 另外,生成后的图表 ...
- 利用SpringBoot和Vue实现前后端分离(附源码)
利用SpringBoot和Vue实现前后端分离(附源码) 引言: 本文主要分享了SpringBoot和Vue整合实现前后端分离,实现了简单的增删查改:包括:项目的搭建.后端的实现.前台的实现:(附源码 ...
最新文章
- layui 表格内容写temple函数_layui表格-template模板的三种用法
- 从Eclipse转移到IntelliJ IDEA一点心得
- c语言表达式用法,C语言开发之运算符、表达式用法
- 基础才是重中之重~再说面向接口的编程
- Spring Boot 集成 Druid 监控数据源
- java开发中实际遇到的问题总结_内行总结:装修中最容易遇到的16个问题及其解决方式!...
- 信息学奥赛一本通(1091:求阶乘的和)
- Servlet四种访问属性--知识点
- smale学习之数学表达式(day1)
- Cisco路由器配置静态路由
- python读取excel并绘制折线图
- 使用PicGo和阿里云OSS建立云图床
- 台式计算机无线网经常掉线,台式机无线上网经常断网怎么办呢
- 手把手实现AI诗歌生成(AI写诗)
- Mysq连接l数据库有效文章
- http://blog.csdn.net/LANGXINLEN/article/details/50421988
- curl调用新版jenkins crumb报错 No valid crumb was included in the request 解决方法
- Ai智能对话页面html,js人工智能对话框 - osc_q50is30g的个人空间 - OSCHINA - 中文开源技术交流社区...
- IT奶爸带娃记之幼儿园选择困难症
- Java的Closeable接口close()方法的意义
热门文章
- Unity调用安卓Android的Toast
- 【Linux】使用xshell登陆时密码框为灰色,无法输入密码
- php 中continue break exit return 的区别
- [Java基础]Scanner的使用(秋招在线笔试高频使用)版本:Java™ Platform Standard Ed. 8...
- Swift - 委托(delegate)的介绍,及使用样例
- 【网络文件共享】02、NFS服务基础
- htc在ubuntu上找不到devieces,提示权限不够的解决方法
- 原生态纯JavaScript 100大技巧大收集
- 面试官:引入RabbitMQ后,你如何保证全链路数据100%不丢失?
- 一个项目在启动前都应该明确哪些事