spring aop 、Redis实现拦截重复操作
一、问题:项目中有一些重复操作的情况,比如:
1.从场景有用户快速点击提交按钮,或者postMan测试时快速点击
2.从业务上来说,用户注册、用户下单等
3.黑客攻击
二、解决办法
1、使用springAop、Redis
2、代码
/** * 2020/7/22 9:59 AM * * @author shoo * @describe 校验重复操作 (aop实现) */ @Aspect @Component public class ParaLogAspect { private static final Logger logger = LoggerFactory.getLogger(ParaLogAspect.class); @Autowired private RedisTemplate<String, Object> redisTemplate; //定义一个切点,要切的类、方法 @Pointcut("execution(* com.meritdata.cloud.middleplatform.dataservice.account.integral.controller.*.*(..))" + "||execution(* com.meritdata.cloud.middleplatform.dataservice.account.platformBase.controller.*.*(..))" + "||execution(* com.meritdata.cloud.middleplatform.dataservice.account.storeConsume.controller.*.*(..))" + "||execution(* com.meritdata.cloud.middleplatform.dataservice.account.vipcard.controller.*.*(..))" + "||execution(* com.meritdata.cloud.middleplatform.dataservice.shell.controller.*.*(..))") public void authRepeat(){ } @Around("authRepeat()") public Object authRepeat(ProceedingJoinPoint joinPoint) throws Throwable{ ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); StringBuffer body = new StringBuffer(); Object[] arguments = joinPoint.getArgs(); //获取方法的参数 //注意这里只取了第一个参数,如果想兼容多个参数的方法请自行处理 if(arguments.length!=0){ try { Map<String, Object> params = params = (Map<String, Object>)arguments[0]; for (String key:params.keySet() ) { body.append(key).append("=").append(params.get(key)).append("&"); } }catch (Exception ex){ logger.info("=====方法接收参数[{}]",arguments[0].toString()); } } // key:请求者IP+请求URL+参数 String key = request.getRemoteAddr() + ";" + request.getRequestURL().toString() + "?" + body.toString(); logger.info("====key[{}]",key.substring(0,key.length()-1)); Object obj = null; Object[] args = joinPoint.getArgs(); //重复提交校验 if(!authRepeat(key)){ logger.info("重复提交,key[{}]",key); return MapResult.build("请勿频繁操作",false); } //不是重复提交则继续主进程 try { obj = joinPoint.proceed(args); } catch (Throwable e) { logger.error("重复操作校验环绕通知出错", e); } return obj; } //重复提交校验 // Redis的increment方法:把key的值加上指定数值,如果key不存在则默认创建,该操作是单线程的 private boolean authRepeat(String key){ long repeat = redisTemplate.opsForValue().increment(key,1); logger.info("repeat:[{}]",repeat); if(repeat>1){ return false; } redisTemplate.expire(key,1, TimeUnit.SECONDS); return true; } }
3.说明
a、首先用springAop切入需要校验的类或者方法,这里用的是环绕通知(around),如果一秒内操作次数超过一次则返回错误提示请勿频繁操作
b、校验规则是 请求者IP+请求URL+方法参数
c、Redis的increment是单线程的原子操作
三、测试
用locust启动十个用户同时访问用户注册接口,数据库中只注册成功了一个数据,剩余的都提示请勿频繁操作
播种和收获通常不在一个季节,而中间的过程叫做坚持~
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构