springBoot防重复提交

一、重复提交原因

由于客户端抖动,人为快速点击,造成服务器重复处理


二、后端防重复提交

1、基于token
访问请求到达服务器,服务器端生成token,分别保存在客户端和服务器。提交请求到达服务器,服务器端校验客户端带来的token与此时保存在服务器的token是否一致,如果一致,就继续操作,删除服务器的token。如果不一致,就不能继续操作,即这个请求是重复请求。

这种方案,每次提交要发送两次请求。对前端不是特别友好。

2、基于缓存
在这里插入图片描述

下面用缓存这种方案,技术实现:springBoot + 自定义注解 + 拦截器 + Redis缓存(分布式环境用分布式缓存Redisson);

注解RepeatSubmit

@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RepeatSubmit {
   
   /**
    * 防重复操作限时标记数值(存储redis限时标记数值)
    */
   String value() default "value" ;
   
   /**
    * 防重复操作过期时间(借助redis实现限时控制)
    */
   long expireSeconds() default 10;
}
@Slf4j
@Component
@Aspect
public class NoRepeatSubmitAspect {

    @Autowired
    private RedisTemplate redisTemplate;
    /**
     * 定义切点
     */
    @Pointcut("@annotation(com.ruihua.tech.master.common.ann.RepeatSubmit)")
    public void preventDuplication() {}

    @Around("preventDuplication()")
    public Object around(ProceedingJoinPoint joinPoint) throws Exception {

        /**
         * 获取请求信息
         */
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder
                .getRequestAttributes();

        HttpServletRequest request = attributes.getRequest();

        // 获取执行方法
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();

        //获取防重复提交注解
        RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class);

        // 获取token以及方法标记,生成redisKey和redisValue
        String token = request.getHeader(IdempotentConstant.TOKEN);

        String url = request.getRequestURI();

        /**
         *  通过前缀 + url + token + 函数参数签名 来生成redis上的 key
         *  没有token,换成UserId
         */
        String redisKey = IdempotentConstant.PREVENT_DUPLICATION_PREFIX
                .concat(url)
                //.concat(token)
                .concat(getMethodSign(method, joinPoint.getArgs()));
        log.info("redisKey ====== {}",redisKey);
        // 这个值只是为了标记,不重要
        String redisValue = redisKey.concat(annotation.value()).concat("submit duplication");

        if (!redisTemplate.hasKey(redisKey)) {
            // 设置防重复操作限时标记(前置通知)
            redisTemplate.opsForValue()
                    .set(redisKey, redisValue, annotation.expireSeconds(), TimeUnit.SECONDS);
            try {
                //正常执行方法并返回
                //ProceedingJoinPoint类型参数可以决定是否执行目标方法,
                // 且环绕通知必须要有返回值,返回值即为目标方法的返回值
                return joinPoint.proceed();
            } catch (Throwable throwable) {
                //确保方法执行异常实时释放限时标记(异常后置通知)
                redisTemplate.delete(redisKey);
                throw new RuntimeException(throwable);
            }
        } else {
            // 重复提交了抛出异常,如果是在项目中,根据具体情况处理。
            throw new BizException("请勿重复提交");
        }


    }

    /**
     * 生成方法标记:采用数字签名算法SHA1对方法签名字符串加签
     *
     * @param method
     * @param args
     * @return
     */
    private String getMethodSign(Method method, Object... args) {
        StringBuilder sb = new StringBuilder(method.toString());
        for (Object arg : args) {
            sb.append(toString(arg));
        }
        return DigestUtil.sha1Hex(sb.toString());
    }

    private String toString(Object arg) {
        if (Objects.isNull(arg)) {
            return "null";
        }
        if (arg instanceof Number) {
            return arg.toString();
        }
        return JSONUtil.toJsonStr(arg);
    }

}

常量类

public interface IdempotentConstant {
    String TOKEN = "Authorization";

    String PREVENT_DUPLICATION_PREFIX = "PREVENT_DUPLICATION_PREFIX:";
}

多次点击,结果如下:
在这里插入图片描述
基于Java注解+AOP切面快速实现防重复提交,可胜任非高并发场景下实施应用。高并发场景下存在线程安全问题;可以使用redisson分布式锁解决;
在这里插入图片描述

  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值