设为首页 加入收藏

TOP

切面实现下单请求防重提交功能(自定义注释@repeatSubmit)(一)
2023-08-26 21:11:22 】 浏览:44
Tags:单请求 @repeatSubmit
该切面功能适用场景
  • 下单请求多次提交,导致生成多个相同的订单
解决方案
  • 前端解决:限制点击下单按钮为1次后失效。不足:用户体验下降,能绕过前端

  • 后端解决:防重提交切面解决,自定义注释实现该功能(如下)

    • 步骤:
      • 自定义注释类RepeatSubmit
      • 创建切面并有该注释绑定,在切面类实现防重提交功能:
        • 方式一:引入redission进行加锁5秒,原理redis的setAbsent
        • 方式二:将token存入redis中,下单成功删除token,下单前需要调用获取token接口才能成功下单(类似于加锁,和方式一原理相同)
  • RepeatSubmit

/**
 * 自定义防重提交
 */
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RepeatSubmit {
    /**
     * 防重提交类型。  方法、令牌
     */
    enum Type {PARAM, TOKEN}

    /**
     * 默认防重提交,是方法参数
     * @return
     */
    Type limitType() default Type.PARAM;

    /**
     * 加锁过期时间,默认5秒
     * @return
     */
    long lockTime() default 5;

}

  • 自定义切面类
/**
 * 定义一个切面类
 */
@Aspect
@Component
@Slf4j
public class RepeatSubmitAspect {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    /**
     * 定义 @Pointcut注解表达式,
     * 方式一:@annotation:当执行的方法上拥有指定的注解时生效(我们采用这)
     */
    @Pointcut("@annotation(repeatSubmit)")
    public void pointCutNoRepeatSubmit(RepeatSubmit repeatSubmit) {

    }

    /**
     * 环绕通知, 围绕着方法执行
     *
     * @param joinPoint
     * @param repeatSubmit
     * @return
     * @throws Throwable
     * @Around 可以用来在调用一个具体方法前和调用后来完成一些具体的任务。
     */
    @Around("pointCutNoRepeatSubmit(repeatSubmit)")
    public Object around(ProceedingJoinPoint joinPoint, RepeatSubmit repeatSubmit) throws Throwable {


        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();

        Long accountNo = LoginInterceptor.threadLocal.get().getAccountNo();

        // 记录成功或者失败
        Boolean res = false;


        // 防重提交类型
        String type = repeatSubmit.limitType().name();
        if (type.equalsIgnoreCase(RepeatSubmit.Type.PARAM.name())) {
            //方式一,参数形式防重提交

            long lockTime = repeatSubmit.lockTime();

            String ipAddr = CommonUtil.getIpAddr(request);

            MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();

            Method method = methodSignature.getMethod();
            String className = method.getDeclaringClass().getName();
            String key = "order-server:repeat_submit"+CommonUtil.MD5(String.format("%s-%s-%s-%s", ipAddr, className, method, accountNo));

            // 加锁
            //res = redisTemplate.opsForValue().setIfAbsent(key,"1",lockTime, TimeUnit.SECONDS);
            RLock lock = redissonClient.getLock(key);

            // 尝试加锁,最多等待2秒,上锁以后5秒自动解锁 [lockTime默认为5s, 可以自定义]
            res = lock.tryLock(2, lockTime, TimeUnit.SECONDS);

        } else if (type.equalsIgnoreCase(RepeatSubmit.Type.TOKEN.name())) {
            //方式二,令牌形式防重提交
            String requestToken = request.getHeader("request-token");
            if (StringUtils.isBlank(requestToken)) {
                throw new BizException(BizCodeEnum.ORDER_CONFIRM_TOKEN_EQUAL_FAIL);
            }
            String key = String.format(RedisKey.SUBMIT_ORDER_TOKEN_KEY, accountNo, requestToken);
            /**
             * 提交表单的token key
             * key是 order:submit:accountNo:token,然后直接删除成功则完成
             */
            res = redisTemplate.delete(key);

        }
        if (!res) {
            log.error("订单请求重复提交");
            return null;
        }

        log.info("环绕通知执行前");

        Object obj = joinPoint.proceed();

        log.info("环绕通知执行后");
        return obj;
    }
}

  • RedissionConfiguration配置类(用于加锁)
@Configuration
public class RedissionConfiguration {

    @Value("${spring.redis.host}")
    private String redisHost;

    @Value("${spring.redis.port}")
    private String redisPort;


首页 上一页 1 2 下一页 尾页 1/2/2
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇9、Spring之代理模式 下一篇SpringBoot3之Web编程

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目