zoukankan      html  css  js  c++  java
  • 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启动十个用户同时访问用户注册接口,数据库中只注册成功了一个数据,剩余的都提示请勿频繁操作

    stay hungry stay foolish!
  • 相关阅读:
    优化SQL查询:如何写出高性能SQL语句
    提高SQL执行效率的16种方法
    Spring Ioc DI 原理
    java内存泄漏
    转:js闭包
    LeetCode Best Time to Buy and Sell Stock III
    LeetCode Best Time to Buy and Sell Stock with Cooldown
    LeetCode Length of Longest Fibonacci Subsequence
    LeetCode Divisor Game
    LeetCode Sum of Even Numbers After Queries
  • 原文地址:https://www.cnblogs.com/shog808/p/14078787.html
Copyright © 2011-2022 走看看