当前位置: 首页 > article >正文

java 接口防抖

防抖:防止重复提交

在Web系统中,表单提交是一个非常常见的功能,如果不加控制,容易因为用户的误操作或网络延迟导致同一请求被发送多次,进而生成重复的数据记录。要针对用户的误操作,前端通常会实现按钮的loading状态,阻止用户进行多次点击。而对于网络波动造成的请求重发问题,仅靠前端是不行的。为此,后端也应实施相应的防抖逻辑,确保在网络波动的情况下不会接收并处理同一请求多次。

限制 用户在请求返回之前再次进行请求(前台按钮多次点击)

思路:在第一次请求时对该用户加锁,如果锁存在就不执行方法

1. 定一个注解

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME) 
public @interface PreventShaking {

    /**
     * redis锁前缀 (用于区分方法)
     */
    String prefix() default "";

    /**
     *  用来辨别是否是一次重复的请求,支持SpEL,可以从方法的入参中获取
     */
    String key();

    /**
     * redis锁过期时间  默认3秒
     */
    int expire() default 3;

    /**
     * 提示信息
     */
    String message() default "请求过于频繁,请稍后再试";

}

2. AOP 切面

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
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.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.Arrays;

@Component
@Aspect
@Slf4j
public class PreventShakingAspect {

    private static final String INTERFACE_SHAKING = "interface:shaking:";

    @Resource
    private RedisTemplate redisTemplate;


    @Pointcut("@annotation(PreventShaking)")
    public void pointCut() {
        log.info("接口防抖");
    }


    @Around("pointCut()")
    public Object interceptor(ProceedingJoinPoint joinPoint) {
        // 获取执行的方法
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();

        PreventShaking shaking = method.getAnnotation(PreventShaking.class);

        // 创建 SpEL 上下文
        StandardEvaluationContext context = new StandardEvaluationContext();
        // 设置方法参数作为变量
        Object[] args = joinPoint.getArgs();
        String[] parameterNames = ((MethodSignature) signature).getParameterNames();
        for (int i = 0; i < args.length; i++) {
            // 将参数放入上下文
            context.setVariable(parameterNames[i], args[i]);
        }
        // 解析 EL 表达式
        ExpressionParser parser = new SpelExpressionParser();
        Expression expression = parser.parseExpression(shaking.key());
        String key = StringUtils.hasText(shaking.prefix()) ? shaking.prefix() : method.getName();

        key = INTERFACE_SHAKING +  key + ":" + expression.getValue(context);

        // 如果key存在缓存中 返回0;不存在就添加并访问1
        String script = "if redis.call('exists', KEYS[1]) == 1 then\n" +
                "    \n" +
                "    return 0\n" +
                "else\n" +
                "    redis.call('set', KEYS[1], \"1\", \"ex\", ARGV[1])\n" +
                "    \n" +
                "    return 1\n" +
                "end";

        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);

        Object execute = redisTemplate.execute(redisScript, Arrays.asList(key), shaking.expire());
        if (Long.valueOf(String.valueOf(execute)) <= 0){
            log.warn("您的操作太快了,请稍后重试");
            throw new RuntimeException(shaking.message());
        }

        try {
            return joinPoint.proceed();
        } catch (Throwable e) {
            log.error("系统异常", e);
            throw new RuntimeException("系统异常");
        }finally {
            // 方法执行结束,释放锁
            redisTemplate.delete(key);
        }

    }
    
}

参考:

SpringBoot 接口防抖(防重复提交)的一些实现方案_requestlock-CSDN博客

一个注解,优雅的实现接口幂等性!-CSDN博客

java接口防抖的优雅处理_java 接口防抖-CSDN博客


http://www.kler.cn/a/416904.html

相关文章:

  • Java 响应式编程 Lambda、Funcation、Stream 知识要点总结
  • C++ 字符串中数字识别
  • 【第三讲】Spring Boot 3.4.0 新特性详解:增强的配置属性支持
  • webrtc视频会议学习(三)
  • [2024年3月10日]第15届蓝桥杯青少组stema选拔赛C++中高级(第二子卷、编程题(2))
  • 品优购PC端静态页面(列表页、详情页、登录页)
  • SpringBoot 新冠密接者跟踪系统:校园疫情防控的智能守护者
  • 使用 pycharm 新建使用 conda 虚拟 python 环境的工程
  • 【JAVA】反射和注解
  • 设计模式----迭代器模式
  • 项目学习:仿b站的视频网站项目06 -视频分类01
  • 文档加密怎么做才安全?
  • flutter in_app_purchase google支付 PG-GEMF-01错误
  • java面向对象知识点: 封装,构造,重载
  • 安装软件显示乱码天正2014安装报错修复
  • SeggisV1.0 遥感影像分割软件【源代码】讲解
  • QT按下两次按钮,保存这期间内变换的QtextEdit控件内的数据
  • 「Mac畅玩鸿蒙与硬件37」UI互动应用篇14 - 随机颜色变化器
  • PDF文件怎么加密?如何给pdf文档加密码保护?(2025全新科普)
  • css使盒子在屏幕的地点固定
  • EasyDSS视频推拉流技术的应用与安防摄像机视频采集参数
  • 数据结构题库9
  • 实时数据开发 | Flink反压机制原因、影响及解决方案
  • 【PyTorch】(基础三)---- 图像读取和展示
  • 关于音频 DSP 的接口种类以及其应用场景介绍
  • 【Spring篇】SpringMVC的常见数据绑定