SpringBoot接口防重復(fù)提交的三種解決方案
前言
在Web開(kāi)發(fā)中,防止用戶重復(fù)提交表單是一個(gè)常見(jiàn)的需求。用戶可能會(huì)因?yàn)榫W(wǎng)絡(luò)延遲、誤操作等原因多次點(diǎn)擊提交按鈕,導(dǎo)致后臺(tái)接收到多個(gè)相同的請(qǐng)求。這不僅會(huì)浪費(fèi)服務(wù)器資源,還可能導(dǎo)致數(shù)據(jù)不一致等問(wèn)題。本文將介紹幾種在Spring Boot中實(shí)現(xiàn)接口防重復(fù)提交的方法。
使用Token機(jī)制
Token機(jī)制是一種常見(jiàn)的防重復(fù)提交方法。具體步驟如下:
生成Token:用戶每次請(qǐng)求表單頁(yè)面時(shí),服務(wù)器生成一個(gè)唯一的Token,并將其存儲(chǔ)在Session中。
傳遞Token:將Token嵌入到表單中,隨表單一起提交。
驗(yàn)證Token:服務(wù)器接收到請(qǐng)求后,首先驗(yàn)證Token是否有效,如果有效則繼續(xù)處理請(qǐng)求,并從Session中移除該Token;如果無(wú)效,則返回錯(cuò)誤信息。
實(shí)現(xiàn)步驟
1.生成Token
在Controller中生成Token并存儲(chǔ)在Session中:
/**
* form
* @param session
* @author senfel
* @date 2024/11/12 11:29
* @return org.springframework.web.servlet.ModelAndView
*/
@GetMapping("/form")
public ModelAndView showForm(HttpSession session) {
ModelAndView form = new ModelAndView("form");
String token = UUID.randomUUID().toString();
session.setAttribute("token", token);
form.addObject("token", token);
return form;
}
2.傳遞Token
在表單中添加隱藏字段來(lái)傳遞Token:
<form action="/base/submit" method="post">
<input type="hidden" name="token" th:value="${token}">
<!-- 其他表單字段 -->
<button type="submit">Submit</button>
</form>
3.驗(yàn)證Token
在Controller中驗(yàn)證Token:
/**
* handleForm
* @param token
* @param session
* @author senfel
* @date 2024/11/12 11:34
* @return java.lang.String
*/
@PostMapping("/submit")
public String handleForm(@RequestParam String token, HttpSession session) {
String sessionToken = (String) session.getAttribute("token");
if (sessionToken == null || !sessionToken.equals(token)) {
throw new RuntimeException("Duplicate submit detected");
}
// 移除Token
session.removeAttribute("token");
// 處理表單數(shù)據(jù)
return "success";
}
使用Redis
Redis是一個(gè)高性能的鍵值存儲(chǔ)系統(tǒng),可以用來(lái)存儲(chǔ)和驗(yàn)證Token。具體步驟如下:
生成Token:用戶每次請(qǐng)求表單頁(yè)面時(shí),服務(wù)器生成一個(gè)唯一的Token,并將其存儲(chǔ)在Redis中。
傳遞Token:將Token嵌入到表單中,隨表單一起提交。
驗(yàn)證Token:服務(wù)器接收到請(qǐng)求后,首先驗(yàn)證Token是否存在于Redis中,如果存在則繼續(xù)處理請(qǐng)求,并從Redis中刪除該Token;如果不存在,則返回錯(cuò)誤信息。
實(shí)現(xiàn)步驟
1.引入Redis依賴
在 pom.xml 中添加Redis依賴:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
2.生成Token
在Controller中生成Token并存儲(chǔ)在Redis中:
@Autowired
private StringRedisTemplate redisTemplate;
/**
* formByRedis
* @author senfel
* @date 2024/11/12 11:50
* @return org.springframework.web.servlet.ModelAndView
*/
@GetMapping("/formByRedis")
public ModelAndView showFormByRedis() {
ModelAndView form = new ModelAndView("form");
String token = UUID.randomUUID().toString();
// 設(shè)置過(guò)期時(shí)間
redisTemplate.opsForValue().set(token, token, 5, TimeUnit.MINUTES);
form.addObject("token", token);
return form;
}
3.傳遞Token
在表單中添加隱藏字段來(lái)傳遞Token:
<form action="/base/submitByRedis" method="post">
<input type="hidden" name="token" th:value="${token}">
<!-- 其他表單字段 -->
<button type="submit">Submit</button>
</form>
4.驗(yàn)證Token
在Controller中驗(yàn)證Token:
/**
* submitByRedis
* @param token
* @author senfel
* @date 2024/11/12 11:50
* @return java.lang.String
*/
@PostMapping("/submitByRedis")
public String handleFormByRedis(@RequestParam String token) {
String redisToken = redisTemplate.opsForValue().get(token);
if (redisToken == null) {
throw new RuntimeException("Duplicate submit detected");
}
// 刪除Token
redisTemplate.delete(token);
// 處理表單數(shù)據(jù)
return "success";
}
使用Spring AOP
Spring AOP(Aspect-Oriented Programming)可以用來(lái)實(shí)現(xiàn)切面編程,從而在多個(gè)方法中復(fù)用防重復(fù)提交的邏輯。
實(shí)現(xiàn)步驟
1.定義注解
創(chuàng)建一個(gè)自定義注解 @PreventDuplicateSubmit:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* PreventDuplicateSubmit
* @author senfel
* @date 2024/11/12 11:56
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PreventDuplicateSubmit {
/**重復(fù)請(qǐng)求時(shí)間*/
int expireSeconds() default 10;
}
2.創(chuàng)建切面
創(chuàng)建一個(gè)切面類 DuplicateSubmitAspect:
import lombok.extern.slf4j.Slf4j;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
/**
* DuplicateSubmitAspect
* @author senfel
* @version 1.0
* @date 2024/11/12 11:57
*/
@Slf4j
@Aspect
@Component
public class DuplicateSubmitAspect {
protected static final Logger logger = LoggerFactory.getLogger(DuplicateSubmitAspect.class);
@Autowired
private StringRedisTemplate redisTemplate;
/**
* around
* @param joinPoint
* @author senfel
* @date 2024/11/12 15:45
* @return java.lang.Object
*/
@Around("@annotation(com.example.ccedemo.aop.PreventDuplicateSubmit)")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
StringBuilder key = new StringBuilder();
//獲取class
String simpleName = joinPoint.getTarget().getClass().getSimpleName();
key.append(simpleName);
// 獲取請(qǐng)求方法
MethodSignature signature=(MethodSignature)joinPoint.getSignature();
Method method = signature.getMethod();
String methodName = method.getName();
key.append(":").append(methodName);
//獲取請(qǐng)求參數(shù)
Object[] args=joinPoint.getArgs();
for (Object arg : args) {
key.append(":").append(arg.toString());
}
//TODO 獲取客戶端IP
// 獲取注解信息
PreventDuplicateSubmit annotation = method.getAnnotation(PreventDuplicateSubmit.class);
// 判斷是否已經(jīng)請(qǐng)求過(guò)
if(redisTemplate.hasKey(key.toString())){
throw new RuntimeException("請(qǐng)勿重復(fù)提交");
}
//標(biāo)記請(qǐng)求已經(jīng)處理過(guò)
redisTemplate.opsForValue().set(key.toString(),"1",annotation.expireSeconds(), TimeUnit.SECONDS);
return joinPoint.proceed();
}
}
3.使用注解
在Controller方法上使用 @PreventDuplicateSubmit 注解:
/**
* handleFormByAnnotation
* @param param
* @author senfel
* @date 2024/11/12 11:59
* @return java.lang.String
*/
@PostMapping("/submitByAnnotation")
@PreventDuplicateSubmit
public String handleFormByAnnotation(@RequestParam String param) {
// 處理表單數(shù)據(jù)
return "success";
}
總結(jié)
本文介紹了三種在Spring Boot中實(shí)現(xiàn)接口防重復(fù)提交的方法:使用Token機(jī)制、使用Redis和使用Spring AOP。每種方法都有其適用場(chǎng)景和優(yōu)缺點(diǎn),可以根據(jù)實(shí)際需求選擇合適的方法。通過(guò)這些方法,可以有效防止用戶重復(fù)提交表單,提高系統(tǒng)的穩(wěn)定性和用戶體驗(yàn)。
以上就是SpringBoot接口防重復(fù)提交的三種解決方案的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot接口防重復(fù)提交的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Jedis出現(xiàn)connection timeout問(wèn)題解決方法(JedisPool連接池使用實(shí)例)
這篇文章主要介紹了Jedis出現(xiàn)connection timeout問(wèn)題解決方法,使用Jedis的JedisPool連接池解決了這個(gè)問(wèn)題,需要的朋友可以參考下2014-05-05
解決mybatis 執(zhí)行mapper的方法時(shí)報(bào)空指針問(wèn)題
這篇文章主要介紹了解決mybatis 執(zhí)行mapper的方法時(shí)報(bào)空指針問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07
Java 基礎(chǔ) byte[]與各種數(shù)據(jù)類型互相轉(zhuǎn)換的簡(jiǎn)單示例
這篇文章主要介紹了Java 基礎(chǔ) byte[]與各種數(shù)據(jù)類型互相轉(zhuǎn)換的簡(jiǎn)單示例的相關(guān)資料,這里對(duì)byte[]類型對(duì)long,int,double,float,short,cahr,object,string類型相互轉(zhuǎn)換的實(shí)例,需要的朋友可以參考下2017-01-01
JAVA實(shí)現(xiàn)SOCKET多客戶端通信的案例
這篇文章主要介紹了JAVA實(shí)現(xiàn)SOCKET多客戶端通信的案例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-12-12
使用SpringBoot開(kāi)發(fā)Restful服務(wù)實(shí)現(xiàn)增刪改查功能
Spring Boot是由Pivotal團(tuán)隊(duì)提供的全新框架,其設(shè)計(jì)目的是用來(lái)簡(jiǎn)化新Spring應(yīng)用的初始搭建以及開(kāi)發(fā)過(guò)程。這篇文章主要介紹了基于SpringBoot開(kāi)發(fā)一個(gè)Restful服務(wù),實(shí)現(xiàn)增刪改查功能,需要的朋友可以參考下2018-01-01
java中多個(gè)@Scheduled定時(shí)器不執(zhí)行的解決方法
在應(yīng)用開(kāi)發(fā)中經(jīng)常需要一些周期性的操作,比如每5分鐘執(zhí)行某一操作等,這篇文章主要給大家介紹了關(guān)于java中多個(gè)@Scheduled定時(shí)器不執(zhí)行的解決方法,需要的朋友可以參考下2023-04-04
SpringBoot項(xiàng)目不占用端口啟動(dòng)的方法
這篇文章主要介紹了SpringBoot項(xiàng)目不占用端口啟動(dòng)的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-08-08

