防抖也即防重复提交,那么如何确定两次接口就是重复的呢?首先,我们需要给这两次接口的调用加一个时间间隔,大于这个时间间隔的一定不是重复提交;其次,两次请求提交的参数比对,不一定要全部参数,选择标识性强的参数即可(生产环境还可以加上用户ID);最后,如果想做的更好一点,还可以加一个请求地址的对比。

        分布式部署下接口防抖有有很多方法,如:使用共享缓存,使用分布式锁,在web开发中一般新增后者

        思路如下:

1. 自定义注解@RequestLock,用于标记需要防抖的接口方法,记录锁的一些信息如:锁过期时间、时间单位等。

2. 自定义@RequestKeyParam注解用于指定生成唯一键的参数(生成锁名的依据)。

3. 封装RequestKeyGenerator 类定义锁的生成逻辑,返回生成的锁名。

4. 实现一个切入点为添加了@RequestLock注解的方法的环绕通知,通知的内容是:生成锁名key, 获取目标对象的的@RequestLock携带的锁过期作为锁名key存入Redis的TTL。

5. 在需要增强的方法上添加@RequestLock,用于加锁的参数上添加@RequestKeyParam。

实现过程:

@RequestLock

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 RequestLock {String prefix() default "";long expire() default 5; // 锁过期时间,单位:秒String timeUnit() default "SECONDS";String delimiter() default "&";
}

@RequestKeyParam

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** @description 加上这个注解可以将参数设置为key*/
@Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface RequestKeyParam {}

RequestKeyGenerator 类( 由于 @RequestKeyParam 可以放在方法的参数上,也可以放在对象的属性上,所以这里需要进行两次判断,一次是获取方法上的注解,一次是获取对象里面属性上的注解。)

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;public class RequestKeyGenerator {/*** 获取LockKey** @param joinPoint 切入点* @return*/public static String getLockKey(ProceedingJoinPoint joinPoint) {//获取连接点的方法签名对象MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature();//Method对象Method method = methodSignature.getMethod();//获取Method对象上的注解对象RequestLock requestLock = method.getAnnotation(RequestLock.class);//获取方法参数final Object[] args = joinPoint.getArgs();//获取Method对象上所有的注解final Parameter[] parameters = method.getParameters();StringBuilder sb = new StringBuilder();for (int i = 0; i < parameters.length; i++) {final RequestKeyParam keyParam = parameters[i].getAnnotation(RequestKeyParam.class);//如果属性不是RequestKeyParam注解,则不处理if (keyParam == null) {continue;}//如果属性是RequestKeyParam注解,则拼接 连接符 "& + RequestKeyParam"sb.append(requestLock.delimiter()).append(args[i]);}//如果方法上没有加RequestKeyParam注解if (StringUtils.isEmpty(sb.toString())) {//获取方法上的多个注解(为什么是两层数组:因为第二层数组是只有一个元素的数组)final Annotation[][] parameterAnnotations = method.getParameterAnnotations();//循环注解for (int i = 0; i < parameterAnnotations.length; i++) {final Object object = args[i];//获取注解类中所有的属性字段final Field[] fields = object.getClass().getDeclaredFields();for (Field field : fields) {//判断字段上是否有RequestKeyParam注解final RequestKeyParam annotation = field.getAnnotation(RequestKeyParam.class);//如果没有,跳过if (annotation == null) {continue;}//如果有,设置Accessible为true(为true时可以使用反射访问私有变量,否则不能访问私有变量)field.setAccessible(true);//如果属性是RequestKeyParam注解,则拼接 连接符" & + RequestKeyParam"sb.append(requestLock.delimiter()).append(ReflectionUtils.getField(field, object));}}}//返回指定前缀的keyreturn requestLock.prefix() + sb;}
}

Redis

import java.lang.reflect.Method;
import com.summo.demo.exception.biz.BizException;
import com.summo.demo.model.response.ResponseCodeEnum;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.util.StringUtils;/*** @description 缓存实现*/
@Aspect
@Configuration
@Order(2)
public class RedisRequestLockAspect {private final StringRedisTemplate stringRedisTemplate;@Autowiredpublic RedisRequestLockAspect(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}@Around("execution(public * * (..)) && @annotation(com.summo.demo.config.requestlock.RequestLock)")public Object interceptor(ProceedingJoinPoint joinPoint) {MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature();Method method = methodSignature.getMethod();RequestLock requestLock = method.getAnnotation(RequestLock.class);if (StringUtils.isEmpty(requestLock.prefix())) {throw new BizException(ResponseCodeEnum.BIZ_CHECK_FAIL, "重复提交前缀不能为空");}//获取自定义keyfinal String lockKey = RequestKeyGenerator.getLockKey(joinPoint);// 使用RedisCallback接口执行set命令,设置锁键;设置额外选项:过期时间和SET_IF_ABSENT选项final Boolean success = stringRedisTemplate.execute((RedisCallback<Boolean>)connection -> connection.set(lockKey.getBytes(), new byte[0],Expiration.from(requestLock.expire(), requestLock.timeUnit()),RedisStringCommands.SetOption.SET_IF_ABSENT));if (!success) {throw new BizException(ResponseCodeEnum.BIZ_CHECK_FAIL, "您的操作太快了,请稍后重试");}try {return joinPoint.proceed();} catch (Throwable throwable) {throw new BizException(ResponseCodeEnum.BIZ_CHECK_FAIL, "系统异常");}}
}

 接口方法和实体类

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;@RestController
public class UserController {@PostMapping("/add")@RequestLock(prefix = "user_add_")public ResponseEntity<String> addUser(@RequestBody AddUserRequest addUserRequest) {// 这里假设调用服务层方法添加用户,实际需注入 userService 并确保其有 add 方法// 示例中先注释掉,实际使用要补充服务层调用逻辑// userService.add(addUserRequest);return ResponseEntity.ok("添加用户成功");}
}import lombok.Data;@Data
public class AddUserRequest {@RequestKeyParamprivate String userName;@RequestKeyParamprivate String userPhone;// 其他字段...
}


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/89669.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/89669.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/89669.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Java工程师面试全攻略】Day10:系统性能优化全链路实践

一、性能优化的多维视角 系统性能优化是区分普通开发者与高级工程师的关键能力指标。根据Google的研究&#xff0c;性能优化带来的用户体验改善可以直接转化为商业收益——页面加载时间每减少100ms&#xff0c;亚马逊的销售额就增加1%。今天我们将从全链路视角剖析性能优化的方…

在kotlin中如何更好的理解 高阶函数

在 Kotlin 中&#xff0c;高阶函数的本质是「将函数作为商品流通的交易模式」。 核心需求&#xff1a;传统函数只能操作数据&#xff08;如数字、字符串&#xff09;&#xff0c;但实际开发中常需复用逻辑流程&#xff08;如「先校验参数&#xff0c;再执行操作」的流程适用于…

15-C#的scottplot控件库绘制曲线图

C#的scottplot控件库绘制曲线图 1.使用Nuget 安装scottplot控件库2.绘制柱状图private void button54_Click(object sender, EventArgs e){double[] values { 5, 10, 7, 13, 22, 18, 33, 16 };formsPlot1.Plot.Add.Bars(values);formsPlot1.Refresh();}3.中文标题显示问题 for…

使用jiaminghi/data-view-react, 本地调试能显示,发布就不显示|不成功(版本冲突)

你遇到的问题是&#xff1a; 使用 jiaminghi/data-view-react&#xff08;也就是 DataV 可视化组件库&#xff09;&#xff0c;本地调试没问题&#xff0c;但发布后打包上线却不显示图表/组件。 ✅ 常见原因&#xff08;很大概率命中&#xff09; 1. CSS 或字体资源路径丢失 …

网络层:ip协议 与数据链路层

目录 网络层 引子与前置知识 一、协议格式 二、网段划分(重要) 三、特殊的IP地址 四、IP地址的数量限制 五、私有IP地址和公网IP地址 六、理解运营商和全球网络 七、路由 八、协议格式补充 数据链路层 一、以太网帧格式 二、局域网的通信原理 三、认识MTU 四、…

Nginx入门进阶:从零到高手的实战指南

Nginx 入门与进阶玩法指南 一、什么是 Nginx&#xff1f; Nginx&#xff08;Engine X&#xff09;是一个高性能的 HTTP 和反向代理服务器&#xff0c;同时也可以作为 IMAP/POP3/SMTP 邮件代理服务器。它最初由俄罗斯程序员 Igor Sysoev 开发&#xff0c;用于解决高并发下 Apa…

NPM组件 alan-baileys 等窃取主机敏感信息

【高危】NPM组件 alan-baileys 等窃取主机敏感信息 漏洞描述 当用户安装受影响版本的 alan-baileys 组件包时会窃取用户的主机名、用户名、工作目录、IP地址等信息并发送到攻击者可控的服务器地址。 MPS编号MPS-wkyd-5v7r处置建议强烈建议修复发现时间2025-07-02投毒仓库npm…

Python爬虫实战:研究httplib2库相关技术

1. 引言 1.1 研究背景与意义 随着互联网的快速发展,网络上的信息量呈爆炸式增长。如何从海量的网页中高效地获取有价值的数据,成为了当前信息技术领域的一个重要研究课题。网络爬虫作为一种自动获取互联网信息的程序,能够按照一定的规则,自动地抓取网页内容并提取和整理信…

【C++】简单学——模板初阶

模板&#xff08;template&#xff09; 泛型编程&#xff0c;让编译器把我们不想干的事情给干了 类似于typedef&#xff0c;解决了typedef使用不方便地原因&#xff08;虽然看似写少了&#xff0c;其实只是编译器做多了&#xff09; 例如&#xff1a; 生成两个栈&#xff0c;…

X-Search:Spring AI实现的AI智能搜索

X-Search AI智能搜索 X-Search使用Spring AI和Spring AI Alibab Graph实现的AI智能搜索系统。 gitee:https://gitee.com/java-ben/x-search github:https://github.com/renpengben/x-search 核心功能 快速开始 git clone https://github.com/renpengben/x-search.git 1.申请…

一台香港原生ip站群服务器多少钱?

一台香港原生ip站群服务器多少钱&#xff1f;在香港地区租用原生 IP 站群服务器的价格受多重因素影响&#xff0c;不同配置和服务的组合会导致费用差异显著。以下是详细分析&#xff1a;一、影响香港原生 IP 站群服务器价格的核心因素IP 资源成本&#xff1a;原生 IP 由于其注册…

JavaScript性能优化实战:从理论到实践的全方位指南

Hi&#xff0c;我是布兰妮甜 &#xff01;JavaScript作为现代Web开发的核心语言&#xff0c;其性能直接影响用户体验、转化率和搜索引擎排名。本文将深入探讨JavaScript性能优化的各个方面&#xff0c;从基础原则到高级技巧&#xff0c;提供一套完整的实战指南。 文章目录 一、…

MCU的晶振匹配测试,是否匹配跟哪些因素相关?

晶振能否与目标电路良好匹配&#xff0c;取决于多个相互作用的因素。这些因素可归纳为以下四大类&#xff1a; 【】一、晶振自身特性&#xff08;核心基础&#xff09; 标称频率与公差&#xff1a;晶振的基频精度&#xff08;如 10ppm&#xff09;是匹配起点。 负载电容 (CL)&…

前端单元测试覆盖率工具有哪些,分别有什么优缺点

以下是主流的前端单元测试覆盖率工具及其优缺点对比&#xff0c;帮助你在项目中根据需求选择合适的工具&#xff1a;1. Istanbul&#xff08;NYC&#xff09; 类型&#xff1a;JavaScript 覆盖率工具适用框架&#xff1a;通用&#xff08;React/Vue/Node.js 等&#xff09;原理…

C语言常用转换函数实现原理

编程时&#xff0c;经常用到进制转换、字符转换。比如软件界面输入的数字字符串&#xff0c;如何将字符串处理成数字呢&#xff1f;今天就和大家分享一下。01 字符串转十六进制 代码实现&#xff1a; void StrToHex(char *pbDest, char *pbSrc, int nLen) {char h1,h2;char s…

办公文档批量打印器 Word、PPT、Excel、PDF、图片和文本,它都支持批量打印。

办公文档批量打印器是一款可以批量打印文档的工具&#xff0c;其是绿色单文件版&#xff0c;支持添加文件、文件夹。 我之前也介绍过批量打印的软件&#xff0c;但是都是只支持Office的文档打印&#xff0c;详情可移步至——>>大小只有700K的软件&#xff0c;永久免费&am…

大事件项目记录13-文章管理接口开发-总

一、文章管理接口。 共有5个&#xff0c;分别为&#xff1a; 1.新增文章&#xff1b; 2.文章列表(条件分页) &#xff1b; 3.获取文章详情&#xff1b; 4.更新文章&#xff1b; 5.删除文章。 二、详解。 1.新增文章。 ArticleController.java&#xff1a; PostMappingpublic R…

如何防止内部威胁:服务器访问控制与审计策略

内部威胁是指来自组织内部的用户或设备对服务器和数据的潜在安全威胁。这些威胁可能是由于恶意行为、疏忽或配置错误造成的。为了防止内部威胁&#xff0c;必须建立强大的访问控制和审计策略&#xff0c;确保服务器的安全性和数据完整性。 1. 什么是内部威胁&#xff1f; 1.1 …

科技赋能电网安全:解析绝缘子污秽度在线监测装置的核心技术与应用价值

绝缘子是电力系统中保障输电线路安全运行的关键设备&#xff0c;其表面污秽积累可能引发闪络事故&#xff0c;导致线路跳闸甚至电网瘫痪。传统的人工巡检方式存在效率低、时效性差等问题&#xff0c;而绝缘子污秽度在线监测装置通过实时数据采集与分析&#xff0c;为电网安全运…

实际开发如何快速定位和解决死锁?

一、死锁的本质与常见场景 1. 死锁的四大必要条件 互斥:资源同一时间只能被一个线程持有。占有并等待:线程持有资源的同时请求其他资源。不可抢占:资源只能被持有者主动释放。循环等待:多个线程形成资源的循环依赖链。2. 常见死锁场景 数据库事务死锁:-- 事务1 BEGIN; UP…