• 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!
  • 相关阅读:
    ios 视频旋转---分解ZFPlayer
    IOS lame库 pcm转mp3 分析(方案二)
    IOS lame库 pcm转mp3 分析(方案一)
    ios 动态库合成包(真机&模拟器)脚本
    lame 制作ios静态库
    React Native scrollview 循环播放
    React Native Image多种加载图片方式
    汉字转拼音(包含多音字)
    React Native Alert、ActionSheet
    React Native Picker (城市选择器)
  • 原文地址:https://www.cnblogs.com/shog808/p/14078787.html
Copyright © 2020-2023  润新知