• 防重复操作的一种实现


    分享一种防重复操作的实现方式,基于注解和redis锁的实现方式。

    重复请求枚举:
    /**
     * 重复请求枚举
     * TOKEN 根据token强制重复
     * FINAL 指定key
     * SP_EL 根据入参
     * @author zhangtaotao
     * @since 2020/11/16 16:50
     **/
    public enum NonRepeatEnum {
        TOKEN,
        FINAL,
        SP_EL;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    注解的定义:
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface NonRepeat {
    
        /**
         * 锁定时间 seconds
         *
         * @return
         */
        int lockTime() default 120;
    
    
        /**
         * lockKey策略
         *
         * @see NonRepeatEnum
         */
        NonRepeatEnum lockKeyPolicy() default NonRepeatEnum.TOKEN;
    
        /**
         * 前缀 psi:common:repeat:{className}:{methodName}:
         * 默认根据 token
         * 支持 spEL 取值非string类型会序列化为json,JSON.toJsonString
         *
         * @return
         */
        String lockKey() default "";
    
        /**
         * 是否在执行结束后释放锁
         *
         * @return
         */
        boolean releaseAfterReturn() default true;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    重复请求控制切面逻辑:
    /**
     * 重复请求限制
     *
     * @author zhangtaotao
     * @since 2020/11/16 17:24
     **/
    @Slf4j
    @Aspect
    @Component
    public class NonRepeatAspect {
    
        private GlobalLockAppService globalLockAppService;
    
        private final ExpressionParser parser = new SpelExpressionParser();
    
        @Around(value = "@annotation(nonRepeat)")
        public Object before(ProceedingJoinPoint pjp, NonRepeat nonRepeat) throws Throwable {
            UserLocal.USER.saveLocal(Locale.CHINESE);
            Object[] args = pjp.getArgs();
            String lockKey = buildLockKey(args, nonRepeat);
            String className = pjp.getTarget().getClass().getName();
            String methodName = pjp.getSignature().getName();
            String realKey =
                    String.join(BaseConstants.COLON, RedisConstants.REDIS_REPEAT_LOCK, className, methodName, lockKey);
            GlobalLock globalLock = GlobalLock.of(realKey, Duration.ofSeconds(nonRepeat.lockTime()));
            if (!globalLockAppService.getLock(globalLock)) {
                throw new BizsException(ResultEnum.NON_REPEAT_ERROR);
            }
            Object result;
            try {
                result = pjp.proceed();
            } finally {
                if (nonRepeat.releaseAfterReturn()) {
                    globalLockAppService.unlock(globalLock);
                }
            }
            return result;
        }
    
        private String buildLockKey(Object[] args, NonRepeat nonRepeat) {
            String lockKey = null;
            NonRepeatEnum lockKeyPolicy = nonRepeat.lockKeyPolicy();
            Objects.requireNonNull(lockKeyPolicy, "can not find lock key policy");
            switch (lockKeyPolicy) {
                case FINAL:
                    lockKey = nonRepeat.lockKey();
                    break;
                case SP_EL:
                    lockKey = buildKeyForExpression(args, nonRepeat);
                    break;
                case TOKEN:
                    lockKey = UserLocal.USER.getToken();
                    break;
                default:
                    break;
            }
            return lockKey;
        }
    
        private String buildKeyForExpression(Object[] args, NonRepeat nonRepeat) {
            Objects.requireNonNull(args, "args can not be null");
            String lockKey;
            if (StringUtils.isEmpty(nonRepeat.lockKey())) {
                throw new BizsException(ResultEnum.VALIDATE_PARAM_ERROR);
            }
            Expression exp = parser.parseExpression(nonRepeat.lockKey());
            StandardEvaluationContext ctx = new StandardEvaluationContext();
            if (args.length == 1) {
                ctx.setRootObject(args[0]);
            } else {
                ctx.setRootObject(args);
            }
            Object value = exp.getValue(ctx);
            if (value instanceof String) {
                lockKey = String.valueOf(value);
            } else {
                lockKey = JSON.toJSONString(value);
            }
            return lockKey;
        }
    
        @Autowired
        public void setGlobalLockAppService(GlobalLockAppService globalLockAppService) {
            this.globalLockAppService = globalLockAppService;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    应用

    getLockKey()方法定义在入参DoApproveFlowVO中,用于定义锁的唯一内容

    	@NonRepeat(lockKeyPolicy = NonRepeatEnum.SP_EL, lockKey = "getLockKey()")
        @ApiOperation(value = "审批操作接口")
        @PostMapping(value = "/doApprove")
        public ResponseResult<Object> doApprove(@RequestBody @Validated DoApproveFlowVO doApproveFlowVO) {
        	//审批操作
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    至于redis锁的实现可以参考另外一篇文章:redis锁的一种实现

  • 相关阅读:
    rabbitmq+springboot实现幂等性操作
    Mac连接linux的办法(自带终端和iterm2)
    七、全屏粒子特效
    全志A33使用主线U-Boot方法
    搭建一个windows的DevOps环境记录
    Mac系统国内通过nvm快速安装node
    【微信小程序】页面tabBar切换、下拉刷新
    SpringBoot整合Shiro、Mybatis、Thymeleaf
    思科 Packet Tracer实验(一)
    为什么学完了 C#觉得自己什么都干不了?
  • 原文地址:https://blog.csdn.net/weixin_41501644/article/details/128001937