java如何防止表單重復(fù)提交的注解@RepeatSubmit
代碼解釋
@RepeatSubmit
- 是一個自定義注解,通常用于防止表單重復(fù)提交。
- 這個注解可以應(yīng)用于控制器方法上,以確保同一個請求在一定時間內(nèi)不會被多次提交。
以下是一些常見的參數(shù)和用法:
- value:注解的名稱或描述。
- interval:兩次請求之間的最小間隔時間(單位通常是毫秒)。
- message:當(dāng)檢測到重復(fù)提交時返回的提示信息。
示例代碼
假設(shè)有一個 @RepeatSubmit 注解的定義如下:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RepeatSubmit {
String value() default "";
int interval() default 3000; // 默認(rèn)3秒
String message() default "請勿重復(fù)提交";
}
使用示例
在控制器方法中使用 @RepeatSubmit 注解:
@RestController
public class UserController {
@PostMapping("/submitForm")
@RepeatSubmit(interval = 5000, message = "請等待5秒后再提交")
public ResponseEntity<String> submitForm(@RequestBody FormData formData) {
// 處理表單提交邏輯
return ResponseEntity.ok("表單提交成功");
}
}
控制流圖
以下是 @RepeatSubmit 注解的控制流圖,展示了其工作原理:
flowchart TD
A[開始] --> B[接收請求]
B --> C{檢查是否重復(fù)提交}
C -->|是| D[返回重復(fù)提交提示信息]
C -->|否| E[處理請求]
E --> F[返回成功響應(yīng)]
F --> G[結(jié)束]
說明
- A: 開始處理請求。
- B: 接收到客戶端的請求。
- C: 檢查當(dāng)前請求是否與前一次請求的時間間隔小于設(shè)定的 interval。
- D: 如果檢測到重復(fù)提交,返回提示信息(如 “請等待5秒后再提交”)。
- E: 如果沒有檢測到重復(fù)提交,繼續(xù)處理請求。
- F:請求處理成功后,返回成功響應(yīng)。
- G: 結(jié)束請求處理過程。
使用的設(shè)計模式
@RepeatSubmit 注解通常結(jié)合 AOP(面向切面編程) 和 攔截器模式 來實現(xiàn)防止表單重復(fù)提交的功能。
設(shè)計模式解析
AOP(面向切面編程):
- 目的: 將橫切關(guān)注點(如日志記錄、事務(wù)管理、安全性等)從業(yè)務(wù)邏輯中分離出來,提高代碼的模塊化和可維護性。
- 實現(xiàn): 使用 Spring AOP 或其他 AOP 框架,通過切面(Aspect)來攔截方法調(diào)用,執(zhí)行額外的邏輯(如檢查重復(fù)提交)。
攔截器模式
- 目的: 在請求到達(dá)目標(biāo)方法之前或之后執(zhí)行特定的邏輯。
- 實現(xiàn): 在 Spring 中,可以通過 HandlerInterceptor 或 MethodInterceptor 來實現(xiàn)攔截器,攔截請求并執(zhí)行預(yù)處理或后處理邏輯
定義注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RepeatSubmit {
String value() default "";
int interval() default 3000; // 默認(rèn)3秒
String message() default "請勿重復(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();
// 獲取請求上下文
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
// 獲取注解參數(shù)
int interval = repeatSubmit.interval();
String message = repeatSubmit.message();
// 從 session 中獲取上次請求的時間戳
Long lastRequestTime = (Long) request.getSession().getAttribute(method.getName());
// 檢查是否重復(fù)提交
if (lastRequestTime != null && System.currentTimeMillis() - lastRequestTime < interval) {
throw new RuntimeException(message);
}
// 記錄當(dāng)前請求的時間戳
request.getSession().setAttribute(method.getName(), System.currentTimeMillis());
// 繼續(xù)執(zhí)行目標(biāo)方法
return joinPoint.proceed();
}
}
在控制器方法中使用注解
@RestController
public class UserController {
@PostMapping("/submitForm")
@RepeatSubmit(interval = 5000, message = "請等待5秒后再提交")
public ResponseEntity<String> submitForm(@RequestBody FormData formData) {
// 處理表單提交邏輯
return ResponseEntity.ok("表單提交成功");
}
}
使用@RepeatSubmit需要注意什么
使用 @RepeatSubmit 注解來防止表單重復(fù)提交時,需要注意以下幾個方面:
1. 注解參數(shù)配置
- interval:設(shè)置合理的間隔時間。過短的間隔時間可能導(dǎo)致用戶頻繁遇到重復(fù)提交的提示,影響用戶體驗;過長的間隔時間可能無法有效防止快速連續(xù)提交。
- message: 提供明確的提示信息,告知用戶為什么請求被拒絕,幫助用戶理解并采取正確的操作。
2. 并發(fā)處理
- 線程安全: 在高并發(fā)環(huán)境下,確保時間戳的讀取和寫入操作是線程安全的。
- 可以使用 ConcurrentHashMap 或 AtomicLong 等線程安全的數(shù)據(jù)結(jié)構(gòu)來存儲時間戳。
- 分布式環(huán)境: 如果應(yīng)用部署在多個服務(wù)器上,需要考慮如何在分布式環(huán)境中共享時間戳信息。
- 可以使用 Redis 等分布式緩存來存儲時間戳。
3. 用戶體驗
- 前端提示: 在前端頁面上添加防重復(fù)提交的機制,如禁用提交按鈕、顯示加載動畫等,減少用戶誤操作的可能性。
- 錯誤處理:提供友好的錯誤處理機制,當(dāng)檢測到重復(fù)提交時,返回清晰的錯誤信息,并引導(dǎo)用戶重新嘗試或聯(lián)系支持人員。
4. 性能考慮
- 性能開銷: 防重復(fù)提交的檢查會增加一定的性能開銷,特別是在高并發(fā)場景下。確保這些檢查不會成為系統(tǒng)性能的瓶頸。
- 緩存策略:使用緩存來存儲時間戳信息,減少對數(shù)據(jù)庫或會話的頻繁訪問,提高性能。
5. 安全性
- 會話管理: 確保會話管理的安全性,防止會話劫持等攻擊。
- 時間戳驗證: 驗證時間戳的有效性和合法性,防止惡意用戶篡改時間戳。
6. 日志記錄
- 日志記錄: 記錄每次請求的時間戳和處理結(jié)果,便于后續(xù)的審計和問題排查。
示例代碼
以下是一個更完善的 @RepeatSubmit 注解和切面實現(xiàn),考慮了上述注意事項:
定義注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RepeatSubmit {
String value() default "";
int interval() default 3000; // 默認(rèn)3秒
String message() default "請勿重復(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();
// 獲取請求上下文
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
// 獲取注解參數(shù)
int interval = repeatSubmit.interval();
String message = repeatSubmit.message();
// 生成唯一的請求標(biāo)識
String key = method.getName() + ":" + request.getRemoteAddr();
// 從 Redis 中獲取上次請求的時間戳
Long lastRequestTime = redisTemplate.opsForValue().get(key);
// 檢查是否重復(fù)提交
if (lastRequestTime != null && System.currentTimeMillis() - lastRequestTime < interval) {
throw new RuntimeException(message);
}
// 記錄當(dāng)前請求的時間戳
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 = "請等待5秒后再提交")
public ResponseEntity<String> submitForm(@RequestBody FormData formData) {
// 處理表單提交邏輯
return ResponseEntity.ok("表單提交成功");
}
}
控制流圖
以下是 @RepeatSubmit 注解的控制流圖,展示了其工作原理:
flowchart TD
A[開始] --> B[接收請求]
B --> C[生成唯一請求標(biāo)識]
C --> D[從 Redis 獲取上次請求時間戳]
D -->|存在且未過期| E[返回重復(fù)提交提示信息]
D -->|不存在或已過期| F[記錄當(dāng)前請求時間戳]
F --> G[繼續(xù)執(zhí)行目標(biāo)方法]
G --> H[返回成功響應(yīng)]
H --> I[結(jié)束]
說明
- A: 開始處理請求。
- B: 接收到客戶端的請求。
- C: 生成唯一的請求標(biāo)識,通常包括方法名和客戶端 IP 地址。
- D: 從 Redis 中獲取上次請求的時間戳。
- E: 如果存在且未過期,返回重復(fù)提交提示信息。
- F: 如果不存在或已過期,記錄當(dāng)前請求的時間戳。
- G: 繼續(xù)執(zhí)行目標(biāo)方法。
- H: 請求處理成功后,返回成功響應(yīng)。
- I: 結(jié)束請求處理過程。
使用@RepeatSubmit會失效嗎
使用 @RepeatSubmit 注解來防止表單重復(fù)提交時,確實可能會遇到一些情況下失效的問題。
以下是一些常見的失效原因及解決方案:
1. 前端快速連續(xù)點擊
- 原因: 用戶在短時間內(nèi)快速連續(xù)點擊提交按鈕,導(dǎo)致后端無法及時響應(yīng)和處理。
- 解決方案: 前端禁用按鈕:
- 在用戶點擊提交按鈕后,立即禁用按鈕,防止多次點擊。 前端顯示加載動畫: 顯示加載動畫,告知用戶請求正在處理中。
2. 網(wǎng)絡(luò)延遲
- 原因: 網(wǎng)絡(luò)延遲可能導(dǎo)致用戶認(rèn)為請求失敗,從而再次提交。
- 解決方案: 前端超時提示: 設(shè)置合理的請求超時時間,并在超時后提示用戶。
- 后端重試機制: 在后端實現(xiàn)重試機制,但需謹(jǐn)慎處理,避免無限重試。
3. 會話失效
- 原因: 如果使用會話(Session)來存儲時間戳,會話可能因超時或服務(wù)器重啟而失效。
- 解決方案: 使用分布式緩存: 使用 Redis等分布式緩存來存儲時間戳,確保在多服務(wù)器環(huán)境下也能正常工作。
4. 并發(fā)請求
- 原因: 在高并發(fā)環(huán)境下,多個請求可能同時到達(dá),導(dǎo)致時間戳檢查失效。
- 解決方案: 線程安全: 使用線程安全的數(shù)據(jù)結(jié)構(gòu)(如ConcurrentHashMap 或 AtomicLong)來存儲時間戳。
- 分布式鎖: 在分布式環(huán)境下,使用分布式鎖(如 Redis分布式鎖)來確保時間戳的讀取和寫入操作是原子性的。
5. 時間戳精度問題
- 原因: 時間戳的精度可能不夠高,導(dǎo)致短時間內(nèi)多次請求被視為同一請求。
- 解決方案: 提高時間戳精度:使用更高精度的時間戳(如納秒)來減少沖突。
6. 代碼邏輯錯誤
- 原因: 切面或攔截器的邏輯錯誤可能導(dǎo)致 @RepeatSubmit 注解失效。
- 解決方案: 代碼審查:仔細(xì)審查切面或攔截器的代碼,確保邏輯正確。
- 單元測試: 編寫單元測試,覆蓋各種邊界情況,確保 @RepeatSubmit 注解按預(yù)期工作。
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
- java后端如何實現(xiàn)防止接口重復(fù)提交
- Java使用注解實現(xiàn)防止重復(fù)提交實例
- java后臺防止表單重復(fù)提交方法詳解
- java開發(fā)中防止重復(fù)提交的幾種解決方案
- Java防止頻繁請求、重復(fù)提交的操作代碼(后端防抖操作)
- Java后端限制頻繁請求和重復(fù)提交的實現(xiàn)
- Java中防止數(shù)據(jù)重復(fù)提交超簡單的6種方法
- Java結(jié)合redis實現(xiàn)接口防重復(fù)提交
- Java表單重復(fù)提交的避免方法
- JAVA防止重復(fù)提交Web表單的方法
- Java防止重復(fù)提交訂單的實現(xiàn)示例
相關(guān)文章
SpringBoot使用FreeMarker模板發(fā)送郵件
這篇文章主要為大家詳細(xì)介紹了SpringBoot使用FreeMarker模板發(fā)送郵件,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-04-04
SpringBoot跨系統(tǒng)單點登陸的實現(xiàn)方法
這篇文章主要介紹了SpringBoot跨系統(tǒng)單點登陸的實現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08
優(yōu)化Java虛擬機總結(jié)(jvm調(diào)優(yōu))
這篇文章主要介紹了優(yōu)化Java虛擬機總結(jié)(jvm調(diào)優(yōu)),具有一定借鑒價值,需要的朋友可以參考下2018-01-01
解決工具接口調(diào)用報錯:error:Unsupported Media Type問題
當(dāng)遇到"UnsupportedMediaType"錯誤時,意味著HTTP請求的Content-Type與服務(wù)器期望的不匹配,比如服務(wù)器期待接收J(rèn)SON格式數(shù)據(jù),而發(fā)送了純文本格式,常見的Content-Type類型包括text/html、application/json、multipart/form-data等2024-10-10
java中的HashSet與 == 和 equals的區(qū)別示例解析
HashSet是Java中基于哈希表實現(xiàn)的集合類,特點包括:元素唯一、無序和可包含null,本文給大家介紹java中的HashSet與 == 和 equals的區(qū)別,感興趣的朋友一起看看吧2025-02-02

