美文网首页
Aop+Redis防止接口重复提交

Aop+Redis防止接口重复提交

作者: 一只浩子 | 来源:发表于2022-05-13 17:00 被阅读0次
一、为什么要防止接口重复提交?

对于有些敏感操作接口,比如提交数据接口、付款接口,如果用户操作不当,多次点击提交按钮,接口就会被多次请求,最后可能生成重复数据,导致系统异常,影响用户使用。

二、后端解决方案:
  1. 自定义注解@AvoidDuplicateSubmit 标记所有Controller中提交的请求
  2. 通过AOP对所有标记了@AvoidDuplicateSubmit 的方法进行拦截
  3. 切面类实现拦截思路:
    3.1 同一ip地址的用户在xx秒内同一方法和参数只能提交成功一次;
    3.2 生成本次提交的唯一key, lockKey = ip_ hashCode
    前缀 = ip ,后缀 = 用户ID+获取类名+方法名+参数的hashCode;
    3.3 利用redisTemplate.opsForValue().setIfAbsent(key, value, time, TimeUnit.SECONDS); 这个方法的特性来保证唯一执行一次方法。
    解释setIfAbsent:缓存放入并设置过期时间,如果不存在就添加,返回true,如果存在,不会做任何操作,返回false;
三、代码如下:
  1. maven
<!-- asp  -->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
    </dependency>
  1. 创建自定义注解AvoidDuplicateSubmit
/**
 * 防止重复提交注解
 * @DateTime: 2022/5/7 下午2:17
 * @Author: zenghao
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AvoidDuplicateSubmit {

    /**
     * @return long
     * @Description 指定时间内不可重复提交,单位秒
     **/
    long timeout() default 2;

}

  1. 创建切面类AvoidDuplicateSubmitAspect
/**
 * 防止重复提交注解切面
 * @DateTime: 2022/5/7 下午2:16
 * @Author: zenghao
 */
@Component
@Aspect
public class AvoidDuplicateSubmitAspect {

    private static final Logger log = LoggerFactory.getLogger(AvoidDuplicateSubmitAspect.class);

    @Autowired
    private RedisUtil redisUtil;

    /**
     * 定义切入点
     */
    @Pointcut("@annotation(com.yuanben.scms.base.annotation.duplicate.AvoidDuplicateSubmit)")
    public void noRepeat() {}


    @Around("noRepeat()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {

        // 获取request对象
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
        Assert.notNull(request, "request not null");
        // 获取用户信息
        GlobalSession globalSession = ControllerUtil.getGlobalSession(request);
        String userId = globalSession.getUserId();
        // 获取ip
        String ip = ControllerUtil.getClientIp(request);

        // 获取方法签名
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        // 获取类名、方法名、参数
        String className = method.getDeclaringClass().getName();
        String methodName = method.getName();
        Map<String, String[]> parameMap = request.getParameterMap();
        StringBuilder parameStr = new StringBuilder("");
        // js 会传这个参数variableParameter(时间戳)
        for (Map.Entry<String, String[]> entry : parameMap.entrySet()) {
            if (!"variableParameter".equals(entry.getKey())) {
                parameStr.append(Arrays.toString(entry.getValue()));
            }
        }
        // 拼接key
        String lockKey_last = String.format("%s#%s#%s#%s", userId, className, methodName, parameStr);
        int hashCode = Math.abs(lockKey_last.hashCode());
        // 拼接redisKey,如:127.0.0.1_1898984393
        String lockKey = StaticValue.COMMON_DUPLICATE_SUBMIT + ":" + String.format("%s_%d", ip, hashCode);
        log.info("lockKey = " + lockKey + "   lockKey_last =" + lockKey_last);
        // 获取注解的过期时间
        AvoidDuplicateSubmit avoidDuplicateSubmit = method.getAnnotation(AvoidDuplicateSubmit.class);
        long timeout = avoidDuplicateSubmit.timeout();
        if (timeout <= 0) {
            timeout = 2;
        }
        // 从redis获取数据
        Object redisValue = redisUtil.get(lockKey);
        // 判断是否存在
        if (!Objects.isNull(redisValue)) {
            throw new JsonException("请勿重复提交");
        }
        // 第一次提交,插入redis
        boolean resultBoolean = redisUtil.setIfAbsent(lockKey, Tools.getUUID(), timeout);
        // 如果失败,说明存在
        log.info("resultBoolean =" + String.valueOf(resultBoolean));
        if (!resultBoolean) {
            throw new JsonException("请勿重复提交");
        }
        // 继续执行方法
        return joinPoint.proceed();
    }
}
  1. 使用注解
 /**
    * 订单确定导入,数据提交
    * @Params: [times, orderType, remark, redisKey]
    * @DateTime: 2021/7/30 下午6:34
    * @Author: zenghao
    */
    @AvoidDuplicateSubmit(timeout = 5)
    @ResponseBody
    @RequestMapping(value = "/submit")
    public AjaxResponse<Object> submit(String times, String rcvAddress, String orderType, String remark, String redisKey) {
  1. 名词理解
    5.1 @Around 环绕通知(Around advice) :包围一个连接点的通知,类似Web中Servlet规范中的Filter的doFilter方法。可以在方法的调用前后完成自定义的行为,也可以选择不执行。这时aop的最重要的,最常用的注解。用这个注解的方法入参传的是ProceedingJionPoint pjp,可以决定当前线程能否进入核心方法中——通过调用pjp.proceed();
    5.2 setIfAbsent底层实现:
    Redis setnx 命令,SET if Not exists,即在键值对不存在的时候才能设值成功。
    作用:将key的值设置成value,当且仅当key不存在,若给定的key已经存在,则setnx不需要任何动作

  2. 参考文章

1. 利用自定义注解+aop+redis防止重复提交
2. spring aop的@Before,@Around,@After,@AfterReturn,@AfterThrowing的理解
3. Spring AOP
4. Redis 分布式锁
5. 记录一次分布式锁的学习

相关文章

  • Aop+Redis防止接口重复提交

    一、为什么要防止接口重复提交? 对于有些敏感操作接口,比如提交数据接口、付款接口,如果用户操作不当,多次点击提交按...

  • AOP防止接口重复提交

    实现原理 通过自定义注解标记哪些接口需要防范重复提交问题,并定义保持时间; 在Aspect中定义切点,织入所有被自...

  • 防止重复提交

  • 防止重复提交

    简介 在现在的web开发中我们经常使用ajax从后端获取数据,提交数据。对于有些游戏爱好者或者手速甚快的同学来说,...

  • AOP实现防止接口重复提交

    背景:之前自己写了一个简单的书写文章小项目,测试的时候发现多次点击添加文章按钮系统会出现重复的文章,所以就想着用a...

  • JAVA AOP实现防止接口重复提交

    实现逻辑1.自定义防重复提交的注解和切面 2.在需要验证的接口上增加注解(一般是创建、修改的接口) 3.以每次调用...

  • 防止表单重复提交

    嘿,大家好,今天我来介绍几种简单的防止表单重复提交的方法: 防止表单重复提交 方法一:前端方式 当点击提交或者保存...

  • 防止表单重复提交

    第一种(JavaScript): <%@ page language="java" import="java.ut...

  • 防止表单重复提交

    防止表单重复提交: 方法1:页面限制按钮 方法2:如图

  • 防止表单重复提交

    随机产生一个字符串(token) ,保存到session中,在向服务端发送请求时会携带token,本地token与...

网友评论

      本文标题:Aop+Redis防止接口重复提交

      本文链接:https://www.haomeiwen.com/subject/uhghurtx.html