在SpringBoot使用AOP防止接口重復提交

前言

防止接口重復提交有跟多種方法,可以在前端做處理。同樣在后端也能處理,而且后端的處理也有很多中方法。最先能想到的就是加鎖,也可以直接在該接口的實現過程中進行處理(可以參考防止數據重復提交的6種方法(超簡單)!),本文主要介紹另一種借助AOP實現的方法。

AOP

關于AOP就不做過多贅述,可以參考我的另一篇文章Spring框架(下半部分 -AOP)。主要是借助它能增強方法的功能,對接口做以下處理,這個方法跟直接在接口種處理相似,話不多說,我們直接開始吧。

自定義注解

我們要靈活的使用AOP,注解是必不可少的,能幫我們更加便捷靈活的處理。我們先創建一個Submit注解,有該注解的接口就是我們要使用AOP處理的接口。

package com.blog.annotation;import java.lang.annotation.*;@Documented
@Retention(RetentionPolicy.RUNTIME) // 注解的存活時間
@Target(ElementType.METHOD) // 作用在方法上
public @interface Submit {/*** 提交的間隔時間* 默認是10s* @return*/long expire() default 10000;
}

AOP的實現

其實使用AOP都有一個很創建的模板,我先貼出來,然后解釋。

@Aspect
@Component
@Slf4j
public class SubmitAspect {@Pointcut("@annotation(com.blog.annotation.Submit)")public void pt() {}@Around("pt()")public Object around(ProceedingJoinPoint point) {}
}

@Pointcut("@annotation(com.blog.annotation.Submit)")就是切入點表達式,它的參數就是指定我們要處理,@Around("pt()")表明我們使用環繞通知來處理。具體的在我剛剛提到的另一篇博客中,感興趣的可以仔細的了解一下。

接下來我們就要考慮該如何實現,防止接口重復提交就是說如果該接口提交過了,再來一次提交我們就不讓他去執行,直接返回。現在就有一個問題了,我們該如何知道這個接口提交沒提交過?我們是不是可以把提交過的接口保存下來,如果來了一個提交我們就去查找,如果找到了我們就不如他提交。

if (接口 not in 接口集合) {return "請勿重復提交";
}
// 說明接口沒有提交,我們就執行該接口的方法
// 最重要的一點是把該接口存儲到接口集合中
...執行提交操作...
接口集合.insert(接口)

所以我們就需要考慮使用哪些集合?這個接口該怎么存儲?怎么執行原方法的操作?什么時候用戶還能再次提交代碼?等等,這些都是我們要考慮的問題。

關于集合的使用,我們首先能想到的是list、set、map等等,但是考慮到并發安全,我們應該使用線程安全的集合例如ConcurrentHashMap、CopyOnWriteArrayList等等。我們還要解決什么時候用戶還能再次提交代碼,我們可以設置一個實現,所以更加推薦ConcurrentHashMap,其key值就是我們為每一個接口構建的key(使用類名+方法名),value就是我們設置的時間。

想到這還有一個問題,我們為每一個接口構建key,如果有多個用戶那么他們的key就是一樣的,可事實上每個用戶的同一接口的key一定是不能一樣的,否則他提交了我提交不了,這憑什么?所以我們再構建每一個接口的key時加上當前用戶的唯一標識,使用該用戶的id就行。

那么又該如何獲取到當前用戶的id呢? 在這里我們ThreadLocal就可以,ThreadLocal也是很重要的,如果不是很了解,建議花點時間去認識它。在這里我們只需要知道,他是獨立于線程之外的,每一個線程又一個獨自的ThreadLocal ,也就是說,我們把每一個用戶都存儲在ThreadLocal 中。要的時候直接get就行。

到這里其實核心的問題都已經解決了,剩下的就是一些細節問題,在自己寫的時候就能注意到。這里給出我的實現。我使用的redis實現,因為它設置過期時間會自動清除,不需要我們手動去清除,再加上redis是天生支持高并發。
SUBMIT_KEY_PREFIX和NOT_SUBMIT_REPEATEDLY都是一個常量而已,不用過多注意。

package com.blog.aspect;import com.alibaba.fastjson.JSON;
import com.blog.annotation.Submit;
import com.blog.utils.JWTUtils;
import com.blog.utils.Result;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
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.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;import java.lang.reflect.Method;
import java.time.Duration;import static com.blog.domain.vo.ErrorCode.NOT_SUBMIT_REPEATEDLY;
import static com.blog.utils.ConstantValue.SUBMIT_KEY_PREFIX;@Aspect
@Component
@Slf4j
public class SubmitAspect {@Autowiredprivate RedisTemplate<String, String> redisTemplate;@Pointcut("@annotation(com.blog.annotation.Submit)")public void pt() {}@Around("pt()")public Object around(ProceedingJoinPoint point) {try {
//            User user = UserThreadLocal.get();
//            String UserId = user.getId();// 假設這里是從ThreadLocal獲取到的用戶id。String UserId = "123456";Signature signature = point.getSignature();// 獲取當前類名String className = point.getTarget().getClass().getSimpleName();// 獲取當前方法名String methodName = signature.getName();// 拿到該方法Method method = ((MethodSignature) signature).getMethod();// 獲取Submit注解Submit annotation = method.getAnnotation(Submit.class);// 獲取過期時間long expire = annotation.expire();// 設置key值,每個用戶對與每一個接口的key都是一樣的String key = SUBMIT_KEY_PREFIX + DigestUtils.md5Hex(UserId) + "::" + className + "::" + methodName;// 首先查看是否已經提交過String value = redisTemplate.opsForValue().get(key);if (StringUtils.isNoneEmpty(value)) {return Result.error(NOT_SUBMIT_REPEATEDLY.getCode(), NOT_SUBMIT_REPEATEDLY.getMsg());}// 沒有提交過就執行原方法Object proceed = point.proceed();redisTemplate.opsForValue().set(key, JSON.toJSONString(proceed), Duration.ofMillis(expire));return proceed;} catch (Throwable throwable) {throwable.printStackTrace();}return Result.error(-999, "系統異常");}
}

測試

接下來我們使用ApiPost進行測試,由于我們給定了id,所以我們只能測試單用戶的,如果想測試多用戶的,可以在請求路徑中加上一個id,來模擬多用戶。

間隔0ms,調用5次,只有一次成功,失敗的幾次,這里就不截圖了。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-e2fHCLrs-1720583474961)(https://i-blog.csdnimg.cn/direct/e0b09889def54e4494172c9edc1571e1.png)]

間隔11000ms,調用2次,每次都成功,這是因為我們的冷靜窗口是10000ms。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-zG3MVA4q-1720583474962)(https://i-blog.csdnimg.cn/direct/a0153a775a524fd0821df825fa3154ff.png)]

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/43580.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/43580.shtml
英文地址,請注明出處:http://en.pswp.cn/web/43580.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

動手學Avalonia:基于硅基流動構建一個文生圖應用(一)

文生圖 文生圖&#xff0c;全稱“文字生成圖像”&#xff08;Text-to-Image&#xff09;&#xff0c;是一種AI技術&#xff0c;能夠根據給定的文本描述生成相應的圖像。這種技術利用深度學習模型&#xff0c;如生成對抗網絡&#xff08;GANs&#xff09;或變換器&#xff08;T…

【Mac】Charles for Mac(HTTP協議抓包工具)及同類型軟件介紹

軟件介紹 Charles for Mac 是一款功能強大的網絡調試工具&#xff0c;主要用于HTTP代理/HTTP監視器。以下是它的一些主要特點和功能&#xff1a; 1.HTTP代理&#xff1a;Charles 可以作為HTTP代理服務器&#xff0c;允許你查看客戶端和服務器之間的所有HTTP和SSL/TLS通信。 …

金航標kinghelm宋仕強在介紹自己公司時說

金航標kinghelm宋仕強在介紹自己公司時說&#xff0c;金航標成立于2007年&#xff0c;成立地點在華強北雷圳大廈803室&#xff0c;后搬到華強北廣業大廈24樓CD室&#xff0c;后搬遷到龍華展滔科技大廈C座C809和C817室&#xff0c;現在的辦公地址為龍崗區坂田街道百瑞達大廈&…

WSL安裝USB驅動

wsl用不了USB盤&#xff0c;需要安裝驅動 1、安裝windows驅動 https://github.com/dorssel/usbipd-win/releases 下載msi&#xff0c;并且安裝 2、linux里面安裝 sudo apt install linux-tools-5.4.0-77-generic hwdata sudo update-alternatives --install /usr/local/bin/usb…

PageDTO<T>,PageQuery,BeanUtils,CollUtils的封裝

一、PageDTO<T> import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.fasterxml.jackson.annotation.JsonIgnore; import com.tianji.common.utils.BeanUtils; import com.tianji.common.utils.CollUtils; import com.tianji.common.utils.…

C#中的MD5摘要算法與哈希算法

文章目錄 一、哈希算法基礎二、MD5 算法原理三、MD5摘要算法四、哈希算法五、C#實現示例MD5算法示例哈希算法示例字符串MD5值對比 六、總結 一、哈希算法基礎 哈希算法是一種單向密碼體制&#xff0c;它將任意長度的數據轉換成固定長度的字符串。這種轉換是不可逆的&#xff0…

IDEA中配置代理,解決Codearts Snap登陸不了的問題

問題描述&#xff1a;在mac電腦中的idea中安裝了華為的codearts snap插件&#xff0c;一直登錄不了&#xff0c;賬號是沒問題的&#xff0c;后來我懷疑是我的代理有問題&#xff0c;找到IDEA中的代理設置先是有這個問題“You have JVM property "https.proxyHost" se…

千呼新零售2.0分銷商城視頻介紹

千呼新零售2.0系統是零售行業連鎖店一體化收銀系統&#xff0c;包括線下收銀線上商城連鎖店管理ERP管理商品管理供應商管理會員營銷等功能為一體&#xff0c;線上線下數據全部打通。 適用于商超、便利店、水果、生鮮、母嬰、服裝、零食、百貨、寵物等連鎖店使用。 詳細介紹請…

C語言 將兩個字符串連接起來,不用strcat函數

編一個程序,將兩個字符串連接起來,不要用strcat函數。 #include <stdio.h>void my_strcat(char *s1, const char *s2) {while (*s1) {s1;}while (*s2) {*s1 *s2;s1;s2;}*s1 \0; }int main() {char s1[100] "Hello, ";char s2[] "World!";my_str…

Android初學者書籍推薦

書單 1.《Android應用開發項目式教程》&#xff0c;機械工業出版社&#xff0c;2024年出版2.《第一行代碼Android》第二版3.《第一行代碼Android》第三版4.《瘋狂Android講義》第四版5.《Android移動應用基礎教程&#xff08;Android Studio 第2版&#xff09;》 從學安卓到用安…

uniapp 打包成安卓APP預覽base64pdf實現方法

下載PDF.js 問題描述 在uniapp中預覽base64的PDF&#xff0c;可以使用web-view組件嵌入一個PDF.js的實例。以下是一個簡單的示例&#xff1a; 解決方案&#xff1a; 1.在頁面的.vue文件中添加web-view組件&#xff1a; <template><view style"width: 50%;&qu…

【機器學習】支持向量機與主成分分析在機器學習中的應用

文章目錄 一、支持向量機概述什么是支持向量機&#xff1f;超平面和支持向量大邊距直覺 二、數據預處理與可視化數據集的基本信息導入必要的庫加載數據集數據概況數據可視化特征對的散點圖矩陣類別分布條形圖平均面積與平均光滑度的散點圖變量之間的相關性熱圖 三、模型訓練&am…

JS【詳解】類 class ( ES6 新增語法 )

本質上&#xff0c;類只是一種特殊的函數。 console.log(typeof 某類); //"function"聲明類 class 方式 1 – 類聲明 class Car {constructor(model, year) {this.model model;this.year year;} }方式 2 – 類表達式 匿名式 const Car class {constructor(mod…

在conda的環境中安裝Jupyter及其他軟件包

Pytorch版本、安裝和檢驗 大多數軟件包都是隨Anaconda安裝的&#xff0c;也可以根據需要手動安裝一些其他軟件包。 目錄 創建虛擬環境 進入虛擬環境 安裝Jupyter notebook 安裝matplotlib 安裝 pandas 創建虛擬環境 基于conda包的環境創建、激活、管理與刪除http://t.cs…

podman 替代 docker ? centos Stream 10 已經棄用docker,開始用podman了!

&#x1f468;?&#x1f393;博主簡介 &#x1f3c5;CSDN博客專家 ??&#x1f3c5;云計算領域優質創作者 ??&#x1f3c5;華為云開發者社區專家博主 ??&#x1f3c5;阿里云開發者社區專家博主 &#x1f48a;交流社區&#xff1a;運維交流社區 歡迎大家的加入&#xff01…

淺談React

forwardRef和useImperativeHandle的聯動使用 import React, { useImperativeHandle, useRef } from "react" import { forwardRef } from "react"const CustomInput forwardRef((props, ref) > {const inputRef useRef<HTMLInputElement>(null…

Java中鎖的分類、原理、使用場景、注意事項、優缺點等詳解

Java開發中&#xff0c;鎖是保證多線程安全的重要手段。Java提供了多種類型的鎖來滿足不同的同步需求。在這篇文章中&#xff0c;我將為您介紹以下幾種常見的鎖類型&#xff1a; 偏向鎖/輕量級鎖/重量級鎖 偏向鎖&#xff1a;當一個線程獲取一個對象的鎖時&#xff0c;如果發現…

解決MCM功率電源模塊EMC的關鍵

對MCM功率電源而言&#xff0c;由于其工作在幾百kHz的高頻開關狀態&#xff0c;故易成為干擾源。電磁兼容性EMC&#xff08;Electro Magnetic Compatibility&#xff09;&#xff0c;是指設備或系統在其電磁環境中符合要求運行并不對其環境中的任何設備產生無法忍受的電磁干擾的…

react父調用子的方法,子調用父的方法

父調用子的方法 // 子組件 import React, { useRef, useEffect } from react;const ChildComponent ({ childMethodRef }) > {const childMethod useRef(null);useEffect(() > {childMethodRef.current childMethod;}, []);const someMethod () > {console.log(子…

量化交易的實戰操作與心得

量化交易&#xff0c;作為一種基于數學模型和算法執行交易的方法&#xff0c;已經在全球金融市場中取得了廣泛的應用。對于從事量化交易的投資者而言&#xff0c;了解實戰操作的具體細節及相關心得是至關重要的&#xff0c;它可以幫助投資者優化策略&#xff0c;提高交易效率&a…