• aop+注解防止表单重复提交


    表单重复提交,我认为和幂等还是有一点区别的。

    这里要防止的就是表单重复提交,比如前端没做loading而你手速快点了多次,网络重试等。

    某些操作不做防护其实还是有问题的,比如项目里面批量生成兑换码,一批生成10000个,手一抖生成了2万个。

    思路:

      URI+userId+参数 ,做一下md5哈希,作为key,放到redis打标记,value值可以随意,并设置有效期,比如3秒。

    统一在aop做处理,自定义注解支持不同接口自定义延迟时间,因为每个业务要求不同。

    代码:

    注解

    import java.lang.annotation.*;
    
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface RepeatSubmit {
    
        /**
         * 延迟多少秒后可以重复提交
         * @return
         */
        long delaySecond() default 1;
    }

    Aspect切面

    package com.xdf.ucan.web.aop;
    
    
    import cn.hutool.crypto.digest.MD5;
    import com.alibaba.fastjson.JSONArray;
    import com.xdf.ucan.service.response.Result;
    import com.xdf.ucan.service.utils.RedisKeyUtil;
    import com.xdf.ucan.service.utils.RequestWebUtil;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.lang3.ArrayUtils;
    import org.apache.commons.lang3.StringUtils;
    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.aspectj.lang.reflect.MethodSignature;
    import org.redisson.api.RLock;
    import org.redisson.api.RedissonClient;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.core.annotation.AnnotationUtils;
    import org.springframework.data.redis.core.StringRedisTemplate;
    import org.springframework.stereotype.Component;
    
    import javax.servlet.http.HttpServletRequest;
    import java.lang.reflect.Method;
    import java.util.Arrays;
    import java.util.List;
    import java.util.Objects;
    import java.util.concurrent.TimeUnit;
    
    /**
     * @author lihaoyang
     * @date 2022/2/18
     */
    @Aspect
    @Component
    @Slf4j
    public class RepeatSubmitAspect {
    
    
        @Autowired
        private HttpServletRequest request;
    
        @Autowired
        private StringRedisTemplate stringRedisTemplate;
    
    
        @Pointcut("execution(public * com.xdf.ucan.web.controller..*.*(..))")
        public void controllerPoint() {
        }
    
        @Around("controllerPoint()")
        public Object doControllerPointAround(ProceedingJoinPoint joinPoint) throws Throwable {
            Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
            //获取注解
            RepeatSubmit repeatSubmit = AnnotationUtils.findAnnotation(method, RepeatSubmit.class);
            if (Objects.isNull(repeatSubmit)) {
                return joinPoint.proceed();
            }
            //延迟秒数
            long delaySecond = repeatSubmit.delaySecond();
            //请求参数
            Object[] args = joinPoint.getArgs();
            String accountId = RequestWebUtil.getAccountId();
            String lockKeyParam = request.getRequestURI() + "/" + accountId;
            if (ArrayUtils.isNotEmpty(args)) {
                List<Object> argsList = Arrays.asList(args);
                JSONArray jsonArray = new JSONArray(argsList);
                lockKeyParam += jsonArray.toJSONString();
            }
            String lockKey = RedisKeyUtil.getRepeatSubmitKey(MD5.create().digestHex(lockKeyParam));
            Boolean isLock = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, accountId, delaySecond, TimeUnit.SECONDS);
            if (!isLock) {
                //请稍后执行
                log.info("重复提交,lockKey:{} ", lockKey);
                return Result.error("重复提交,请"+delaySecond+"秒后重试");
            }
            return joinPoint.proceed();
        }
    }

    使用

    /**
         * 生成商品兑换码
         *
         * @param generateReq
         * @return
         */
        @RepeatSubmit(delaySecond = 5)
        @PostMapping("generate")
        public Result generate(@Valid @RequestBody ExchangeCodeGenerateReq generateReq) {
            generateReq.setAccountId(RequestWebUtil.getAccountId());
            log.info("生成兑换码,参数:{}", JSONUtil.toJsonStr(generateReq));
            String batchId = "";
            try {
                batchId = exchangeCodeService.batchGenerate(generateReq);
            } catch (Exception e) {
                return Result.error("生成失败,请重试");
            }
            return Result.OK(batchId);
        }

    Redis打的标记

    SECONDS
  • 相关阅读:
    Linux笔记 三剑客之sed
    Linux笔记 用户组管理
    Linux实验之软件管理
    Linux实验之系统安装
    gd_t , bd_t 结构分析
    编辑linux内核与bosybox 时,make menuconfig 出现错误
    4412裸板开发 (1点灯)
    tiny4412SDK 1312B 启动ubuntuDsektop
    tiny4412 启动方式
    linux minitools+minicom 安装及使用
  • 原文地址:https://www.cnblogs.com/lihaoyang/p/15910621.html
Copyright © 2020-2023  润新知