java如何防止表單重復(fù)提交的注解@RepeatSubmit
代碼解釋
@RepeatSubmit
- 是一個(gè)自定義注解,通常用于防止表單重復(fù)提交。
- 這個(gè)注解可以應(yīng)用于控制器方法上,以確保同一個(gè)請(qǐng)求在一定時(shí)間內(nèi)不會(huì)被多次提交。
以下是一些常見的參數(shù)和用法:
- value:注解的名稱或描述。
- interval:兩次請(qǐng)求之間的最小間隔時(shí)間(單位通常是毫秒)。
- message:當(dāng)檢測(cè)到重復(fù)提交時(shí)返回的提示信息。
示例代碼
假設(shè)有一個(gè) @RepeatSubmit 注解的定義如下:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface RepeatSubmit { String value() default ""; int interval() default 3000; // 默認(rèn)3秒 String message() default "請(qǐng)勿重復(fù)提交"; }
使用示例
在控制器方法中使用 @RepeatSubmit 注解:
@RestController public class UserController { @PostMapping("/submitForm") @RepeatSubmit(interval = 5000, message = "請(qǐng)等待5秒后再提交") public ResponseEntity<String> submitForm(@RequestBody FormData formData) { // 處理表單提交邏輯 return ResponseEntity.ok("表單提交成功"); } }
控制流圖
以下是 @RepeatSubmit 注解的控制流圖,展示了其工作原理:
flowchart TD
A[開始] --> B[接收請(qǐng)求]
B --> C{檢查是否重復(fù)提交}
C -->|是| D[返回重復(fù)提交提示信息]
C -->|否| E[處理請(qǐng)求]
E --> F[返回成功響應(yīng)]
F --> G[結(jié)束]
說(shuō)明
- A: 開始處理請(qǐng)求。
- B: 接收到客戶端的請(qǐng)求。
- C: 檢查當(dāng)前請(qǐng)求是否與前一次請(qǐng)求的時(shí)間間隔小于設(shè)定的 interval。
- D: 如果檢測(cè)到重復(fù)提交,返回提示信息(如 “請(qǐng)等待5秒后再提交”)。
- E: 如果沒(méi)有檢測(cè)到重復(fù)提交,繼續(xù)處理請(qǐng)求。
- F:請(qǐng)求處理成功后,返回成功響應(yīng)。
- G: 結(jié)束請(qǐng)求處理過(guò)程。
使用的設(shè)計(jì)模式
@RepeatSubmit 注解通常結(jié)合 AOP(面向切面編程) 和 攔截器模式 來(lái)實(shí)現(xiàn)防止表單重復(fù)提交的功能。
設(shè)計(jì)模式解析
AOP(面向切面編程):
- 目的: 將橫切關(guān)注點(diǎn)(如日志記錄、事務(wù)管理、安全性等)從業(yè)務(wù)邏輯中分離出來(lái),提高代碼的模塊化和可維護(hù)性。
- 實(shí)現(xiàn): 使用 Spring AOP 或其他 AOP 框架,通過(guò)切面(Aspect)來(lái)攔截方法調(diào)用,執(zhí)行額外的邏輯(如檢查重復(fù)提交)。
攔截器模式
- 目的: 在請(qǐng)求到達(dá)目標(biāo)方法之前或之后執(zhí)行特定的邏輯。
- 實(shí)現(xiàn): 在 Spring 中,可以通過(guò) HandlerInterceptor 或 MethodInterceptor 來(lái)實(shí)現(xiàn)攔截器,攔截請(qǐng)求并執(zhí)行預(yù)處理或后處理邏輯
定義注解
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface RepeatSubmit { String value() default ""; int interval() default 3000; // 默認(rèn)3秒 String message() default "請(qǐng)勿重復(fù)提交"; }
創(chuàng)建切面
@Aspect @Component public class RepeatSubmitAspect { @Around("@annotation(repeatSubmit)") public Object around(ProceedingJoinPoint joinPoint, RepeatSubmit repeatSubmit) throws Throwable { // 獲取方法簽名 MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); // 獲取請(qǐng)求上下文 HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); // 獲取注解參數(shù) int interval = repeatSubmit.interval(); String message = repeatSubmit.message(); // 從 session 中獲取上次請(qǐng)求的時(shí)間戳 Long lastRequestTime = (Long) request.getSession().getAttribute(method.getName()); // 檢查是否重復(fù)提交 if (lastRequestTime != null && System.currentTimeMillis() - lastRequestTime < interval) { throw new RuntimeException(message); } // 記錄當(dāng)前請(qǐng)求的時(shí)間戳 request.getSession().setAttribute(method.getName(), System.currentTimeMillis()); // 繼續(xù)執(zhí)行目標(biāo)方法 return joinPoint.proceed(); } }
在控制器方法中使用注解
@RestController public class UserController { @PostMapping("/submitForm") @RepeatSubmit(interval = 5000, message = "請(qǐng)等待5秒后再提交") public ResponseEntity<String> submitForm(@RequestBody FormData formData) { // 處理表單提交邏輯 return ResponseEntity.ok("表單提交成功"); } }
使用@RepeatSubmit需要注意什么
使用 @RepeatSubmit 注解來(lái)防止表單重復(fù)提交時(shí),需要注意以下幾個(gè)方面:
1. 注解參數(shù)配置
- interval:設(shè)置合理的間隔時(shí)間。過(guò)短的間隔時(shí)間可能導(dǎo)致用戶頻繁遇到重復(fù)提交的提示,影響用戶體驗(yàn);過(guò)長(zhǎng)的間隔時(shí)間可能無(wú)法有效防止快速連續(xù)提交。
- message: 提供明確的提示信息,告知用戶為什么請(qǐng)求被拒絕,幫助用戶理解并采取正確的操作。
2. 并發(fā)處理
- 線程安全: 在高并發(fā)環(huán)境下,確保時(shí)間戳的讀取和寫入操作是線程安全的。
- 可以使用 ConcurrentHashMap 或 AtomicLong 等線程安全的數(shù)據(jù)結(jié)構(gòu)來(lái)存儲(chǔ)時(shí)間戳。
- 分布式環(huán)境: 如果應(yīng)用部署在多個(gè)服務(wù)器上,需要考慮如何在分布式環(huán)境中共享時(shí)間戳信息。
- 可以使用 Redis 等分布式緩存來(lái)存儲(chǔ)時(shí)間戳。
3. 用戶體驗(yàn)
- 前端提示: 在前端頁(yè)面上添加防重復(fù)提交的機(jī)制,如禁用提交按鈕、顯示加載動(dòng)畫等,減少用戶誤操作的可能性。
- 錯(cuò)誤處理:提供友好的錯(cuò)誤處理機(jī)制,當(dāng)檢測(cè)到重復(fù)提交時(shí),返回清晰的錯(cuò)誤信息,并引導(dǎo)用戶重新嘗試或聯(lián)系支持人員。
4. 性能考慮
- 性能開銷: 防重復(fù)提交的檢查會(huì)增加一定的性能開銷,特別是在高并發(fā)場(chǎng)景下。確保這些檢查不會(huì)成為系統(tǒng)性能的瓶頸。
- 緩存策略:使用緩存來(lái)存儲(chǔ)時(shí)間戳信息,減少對(duì)數(shù)據(jù)庫(kù)或會(huì)話的頻繁訪問(wèn),提高性能。
5. 安全性
- 會(huì)話管理: 確保會(huì)話管理的安全性,防止會(huì)話劫持等攻擊。
- 時(shí)間戳驗(yàn)證: 驗(yàn)證時(shí)間戳的有效性和合法性,防止惡意用戶篡改時(shí)間戳。
6. 日志記錄
- 日志記錄: 記錄每次請(qǐng)求的時(shí)間戳和處理結(jié)果,便于后續(xù)的審計(jì)和問(wèn)題排查。
示例代碼
以下是一個(gè)更完善的 @RepeatSubmit 注解和切面實(shí)現(xiàn),考慮了上述注意事項(xiàng):
定義注解
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface RepeatSubmit { String value() default ""; int interval() default 3000; // 默認(rèn)3秒 String message() default "請(qǐng)勿重復(fù)提交"; } // 創(chuàng)建切面 @Aspect @Component public class RepeatSubmitAspect { @Autowired private RedisTemplate<String, Long> redisTemplate; @Around("@annotation(repeatSubmit)") public Object around(ProceedingJoinPoint joinPoint, RepeatSubmit repeatSubmit) throws Throwable { // 獲取方法簽名 MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); // 獲取請(qǐng)求上下文 HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); // 獲取注解參數(shù) int interval = repeatSubmit.interval(); String message = repeatSubmit.message(); // 生成唯一的請(qǐng)求標(biāo)識(shí) String key = method.getName() + ":" + request.getRemoteAddr(); // 從 Redis 中獲取上次請(qǐng)求的時(shí)間戳 Long lastRequestTime = redisTemplate.opsForValue().get(key); // 檢查是否重復(fù)提交 if (lastRequestTime != null && System.currentTimeMillis() - lastRequestTime < interval) { throw new RuntimeException(message); } // 記錄當(dāng)前請(qǐng)求的時(shí)間戳 redisTemplate.opsForValue().set(key, System.currentTimeMillis(), interval, TimeUnit.MILLISECONDS); // 繼續(xù)執(zhí)行目標(biāo)方法 return joinPoint.proceed(); } } // 在控制器方法中使用注解 @RestController public class UserController { @PostMapping("/submitForm") @RepeatSubmit(interval = 5000, message = "請(qǐng)等待5秒后再提交") public ResponseEntity<String> submitForm(@RequestBody FormData formData) { // 處理表單提交邏輯 return ResponseEntity.ok("表單提交成功"); } }
控制流圖
以下是 @RepeatSubmit 注解的控制流圖,展示了其工作原理:
flowchart TD A[開始] --> B[接收請(qǐng)求] B --> C[生成唯一請(qǐng)求標(biāo)識(shí)] C --> D[從 Redis 獲取上次請(qǐng)求時(shí)間戳] D -->|存在且未過(guò)期| E[返回重復(fù)提交提示信息] D -->|不存在或已過(guò)期| F[記錄當(dāng)前請(qǐng)求時(shí)間戳] F --> G[繼續(xù)執(zhí)行目標(biāo)方法] G --> H[返回成功響應(yīng)] H --> I[結(jié)束]
說(shuō)明
- A: 開始處理請(qǐng)求。
- B: 接收到客戶端的請(qǐng)求。
- C: 生成唯一的請(qǐng)求標(biāo)識(shí),通常包括方法名和客戶端 IP 地址。
- D: 從 Redis 中獲取上次請(qǐng)求的時(shí)間戳。
- E: 如果存在且未過(guò)期,返回重復(fù)提交提示信息。
- F: 如果不存在或已過(guò)期,記錄當(dāng)前請(qǐng)求的時(shí)間戳。
- G: 繼續(xù)執(zhí)行目標(biāo)方法。
- H: 請(qǐng)求處理成功后,返回成功響應(yīng)。
- I: 結(jié)束請(qǐng)求處理過(guò)程。
使用@RepeatSubmit會(huì)失效嗎
使用 @RepeatSubmit 注解來(lái)防止表單重復(fù)提交時(shí),確實(shí)可能會(huì)遇到一些情況下失效的問(wèn)題。
以下是一些常見的失效原因及解決方案:
1. 前端快速連續(xù)點(diǎn)擊
- 原因: 用戶在短時(shí)間內(nèi)快速連續(xù)點(diǎn)擊提交按鈕,導(dǎo)致后端無(wú)法及時(shí)響應(yīng)和處理。
- 解決方案: 前端禁用按鈕:
- 在用戶點(diǎn)擊提交按鈕后,立即禁用按鈕,防止多次點(diǎn)擊。 前端顯示加載動(dòng)畫: 顯示加載動(dòng)畫,告知用戶請(qǐng)求正在處理中。
2. 網(wǎng)絡(luò)延遲
- 原因: 網(wǎng)絡(luò)延遲可能導(dǎo)致用戶認(rèn)為請(qǐng)求失敗,從而再次提交。
- 解決方案: 前端超時(shí)提示: 設(shè)置合理的請(qǐng)求超時(shí)時(shí)間,并在超時(shí)后提示用戶。
- 后端重試機(jī)制: 在后端實(shí)現(xiàn)重試機(jī)制,但需謹(jǐn)慎處理,避免無(wú)限重試。
3. 會(huì)話失效
- 原因: 如果使用會(huì)話(Session)來(lái)存儲(chǔ)時(shí)間戳,會(huì)話可能因超時(shí)或服務(wù)器重啟而失效。
- 解決方案: 使用分布式緩存: 使用 Redis等分布式緩存來(lái)存儲(chǔ)時(shí)間戳,確保在多服務(wù)器環(huán)境下也能正常工作。
4. 并發(fā)請(qǐng)求
- 原因: 在高并發(fā)環(huán)境下,多個(gè)請(qǐng)求可能同時(shí)到達(dá),導(dǎo)致時(shí)間戳檢查失效。
- 解決方案: 線程安全: 使用線程安全的數(shù)據(jù)結(jié)構(gòu)(如ConcurrentHashMap 或 AtomicLong)來(lái)存儲(chǔ)時(shí)間戳。
- 分布式鎖: 在分布式環(huán)境下,使用分布式鎖(如 Redis分布式鎖)來(lái)確保時(shí)間戳的讀取和寫入操作是原子性的。
5. 時(shí)間戳精度問(wèn)題
- 原因: 時(shí)間戳的精度可能不夠高,導(dǎo)致短時(shí)間內(nèi)多次請(qǐng)求被視為同一請(qǐng)求。
- 解決方案: 提高時(shí)間戳精度:使用更高精度的時(shí)間戳(如納秒)來(lái)減少?zèng)_突。
6. 代碼邏輯錯(cuò)誤
- 原因: 切面或攔截器的邏輯錯(cuò)誤可能導(dǎo)致 @RepeatSubmit 注解失效。
- 解決方案: 代碼審查:仔細(xì)審查切面或攔截器的代碼,確保邏輯正確。
- 單元測(cè)試: 編寫單元測(cè)試,覆蓋各種邊界情況,確保 @RepeatSubmit 注解按預(yù)期工作。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
舉例講解Java中synchronized關(guān)鍵字的用法
這篇文章主要介紹了Java中synchronized關(guān)鍵字的用法,針對(duì)synchronized修飾方法的使用作出了簡(jiǎn)單講解和演示,需要的朋友可以參考下2016-04-04java實(shí)現(xiàn)學(xué)生宿舍系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)學(xué)生宿舍系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03SpringBoot jar包大小優(yōu)化問(wèn)題及解決
這篇文章主要介紹了SpringBoot jar包大小優(yōu)化問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11IDEA使用Maven創(chuàng)建父與子多模塊項(xiàng)目的圖文教程
在?IntelliJ?IDEA?中使用?Maven?創(chuàng)建父與子多模塊項(xiàng)目是一個(gè)常見的開發(fā)實(shí)踐,有助于更好地組織和管理代碼,所以本文小編給大家介紹了IDEA使用Maven創(chuàng)建父與子多模塊項(xiàng)目的圖文教程,需要的小伙伴跟著小編一起來(lái)看看吧2025-03-03MyBatis使用Zookeeper保存數(shù)據(jù)庫(kù)的配置可動(dòng)態(tài)刷新的實(shí)現(xiàn)代碼
這篇文章主要介紹了MyBatis使用Zookeeper保存數(shù)據(jù)庫(kù)的配置,可動(dòng)態(tài)刷新,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-08-08selenium+java破解極驗(yàn)滑動(dòng)驗(yàn)證碼的示例代碼
本篇文章主要介紹了selenium+java破解極驗(yàn)滑動(dòng)驗(yàn)證碼的示例代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-01-01關(guān)于Lambda表達(dá)式的方法引用和構(gòu)造器引用簡(jiǎn)的單示例
這篇文章主要介紹了關(guān)于Lambda表達(dá)式的方法引用和構(gòu)造器引用簡(jiǎn)的單示例,方法引用與構(gòu)造器引用可以使?Lambda?表達(dá)式的代碼塊更加簡(jiǎn)潔<BR>,需要的朋友可以參考下2023-04-04