分布式部署下如何做接口防抖---使用分布式鎖

????????防抖也即防重復提交,那么如何確定兩次接口就是重復的呢?首先,我們需要給這兩次接口的調用加一個時間間隔,大于這個時間間隔的一定不是重復提交;其次,兩次請求提交的參數比對,不一定要全部參數,選擇標識性強的參數即可(生產環境還可以加上用戶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…