?
??個(gè)人主頁:? ? ?蒾酒
??系列專欄:《spring boot實(shí)戰(zhàn)》
??山高路遠(yuǎn),行路漫漫,終有歸途
目錄
寫在前面
實(shí)現(xiàn)思路
實(shí)現(xiàn)步驟
1.定義防重復(fù)提交注解
2.編寫一個(gè)切面去發(fā)現(xiàn)該注解然后執(zhí)行防重復(fù)提交邏輯
3.測(cè)試
依賴條件
1.接口上標(biāo)記防重復(fù)提交注解
2.接口測(cè)試
寫在最后
寫在前面
本文介紹了springboot開發(fā)后端服務(wù)中,防重復(fù)提交功能的設(shè)計(jì)與實(shí)現(xiàn),堅(jiān)持看完相信對(duì)你有幫助。
同時(shí)歡迎訂閱springboot系列專欄,持續(xù)分享spring boot的使用經(jīng)驗(yàn)。
實(shí)現(xiàn)思路
通過定義一個(gè)防重復(fù)提交的自定義注解,再通過AOP的前置通知攔截帶有該注解的方法,執(zhí)行防重復(fù)提交邏輯,需要拼接一個(gè)唯一的key,如果redis中不存在則代表第一次請(qǐng)求,將這個(gè)key存入redis,設(shè)置注解類中指定的過期時(shí)間,遇到下次重復(fù)提交請(qǐng)求,直接拋出對(duì)應(yīng)異常,全局異常處理返回對(duì)應(yīng)信息即可。
需要注意
這個(gè)key的生成需要考慮有token和無token情況,同時(shí)滿足唯一性。
- 有 token;可以用 token+請(qǐng)求參數(shù),做為唯一值!
- 無 token:可以用請(qǐng)求路徑+請(qǐng)求參數(shù),做為唯一值!
實(shí)現(xiàn)步驟
1.定義防重復(fù)提交注解
import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;
/**
* @author mijiupro
*/
@Inherited
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RepeatSubmit {
/**
* 鎖定時(shí)間,默認(rèn)5000毫秒
*/
int interval() default 5000;
/**
* 鎖定時(shí)間單位,默認(rèn)毫秒
*/
TimeUnit timeUnit() default TimeUnit.MILLISECONDS;
/**
* 提示信息
*/
String message() default "不允許重復(fù)提交,請(qǐng)稍后再試!";
}
2.編寫一個(gè)切面去發(fā)現(xiàn)該注解然后執(zhí)行防重復(fù)提交邏輯
因?yàn)榫彺娴膋ey有拼接請(qǐng)求參數(shù),所以遇到文件類型的參數(shù)需要進(jìn)行過濾,拼接邏輯以及參數(shù)過濾方法都在下面代碼中。
import cn.hutool.crypto.SecureUtil;
import cn.hutool.json.JSONUtil;
import com.mijiu.commom.aop.annotation.RepeatSubmit;
import com.mijiu.commom.exception.GeneralBusinessException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import java.util.Collection;
import java.util.Map;
import java.util.Objects;
/**
* @author mijiupro
*/
@Aspect
@Component
@Slf4j
public class RepeatSubmitAspect {
private final StringRedisTemplate redisTemplate;
public RepeatSubmitAspect(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
@Before("@annotation(repeatSubmit)")
public void before(JoinPoint joinPoint, RepeatSubmit repeatSubmit) {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = null;
if (attributes != null) {
request = attributes.getRequest();
}
//請(qǐng)求參數(shù)拼接
String requestParams = argsArrayToString(joinPoint.getArgs());
String authorizationHeader = null;
if (request != null) {
authorizationHeader = request.getHeader("Authorization");
}
String submitKey = null;
if (authorizationHeader != null) {
//如果存在token則通過token+請(qǐng)求參數(shù)生成唯一標(biāo)識(shí)
String token = StringUtils.removeStart(authorizationHeader, "Bearer ");
submitKey= SecureUtil.md5(token+":"+requestParams);
} else{
//不存在token則通過請(qǐng)求url+參數(shù)生成唯一標(biāo)識(shí)
if (request != null) {
submitKey = SecureUtil.md5(request.getRequestURL().toString()+":"+requestParams);
}
}
//緩存key
String cacheKey = "repeat_submit:"+submitKey;
if (Boolean.TRUE.equals(redisTemplate.hasKey(cacheKey))) {
throw new GeneralBusinessException(repeatSubmit.message());
}
redisTemplate.opsForValue().set(cacheKey, "1", repeatSubmit.interval(), repeatSubmit.timeUnit());
}
/**
* 參數(shù)拼接
* @param args 參數(shù)數(shù)組
* @return 拼接后的字符串
*/
private String argsArrayToString(Object[] args){
StringBuilder params = new StringBuilder();
if(args!= null && args.length > 0){
for(Object o:args){
if(Objects.nonNull(o)&&!isFilterObject(o)){
try {
params.append(JSONUtil.toJsonStr(o)).append(" ");
}catch (Exception e){
log.error("參數(shù)拼接異常:{}",e.getMessage());
}
}
}
}
return params.toString().trim();
}
/**
* 判斷是否需要過濾的對(duì)象。
* @param o 對(duì)象
* @return true:需要過濾;false:不需要過濾
*/
private boolean isFilterObject(final Object o) {
Class<?> c = o.getClass();
//如果是數(shù)組且類型為文件類型的需要過濾
if(c.isArray()){
return c.getComponentType().isAssignableFrom(MultipartFile.class);
}
//如果是集合且類型為文件類型的需要過濾
else if(Collection.class.isAssignableFrom(c)){
Collection collection = (Collection) o;
for(Object value:collection){
return value instanceof MultipartFile;
}
}
//如果是Map且類型為文件類型的需要過濾
else if(Map.class.isAssignableFrom(c)){
Map map = (Map) o;
for(Object value:map.entrySet()){
Map.Entry entry = (Map.Entry) value;
return entry.getValue() instanceof MultipartFile;
}
}
//如果是文件類型的需要過濾
return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
|| o instanceof BindingResult;
}
}
3.測(cè)試
依賴條件
redis:
Spring Boot3整合Redis_springboot3整合redis-CSDN博客https://blog.csdn.net/qq_62262918/article/details/136067550?spm=1001.2014.3001.5502
全局異常捕獲:
Spring Boot3自定義異常及全局異常捕獲_全局異常捕獲 自定義異常-CSDN博客https://blog.csdn.net/qq_62262918/article/details/136110267?spm=1001.2014.3001.5502
swagger3:
Spring Boot3整合knife4j(swagger3)_springboot3 knife4j-CSDN博客https://blog.csdn.net/qq_62262918/article/details/135761392?spm=1001.2014.3001.5502
hutool工具包:
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.25</version>
</dependency>
1.接口上標(biāo)記防重復(fù)提交注解
隨便寫個(gè)測(cè)試接口添加防重復(fù)提交注解設(shè)置間隔5000毫秒
@PostMapping("/add")
@RepeatSubmit(interval= 5000)
public void test(@RequestBody User user){
//添加用戶的操作邏輯。。。
}
2.接口測(cè)試
第一次提交
可以看到對(duì)應(yīng)緩存已經(jīng)存入redis了
5s內(nèi)第二次提交
文章來源:http://www.zghlxwxcb.cn/news/detail-847983.html
寫在最后
springboot使用自定義注解+AOP+redis優(yōu)雅實(shí)現(xiàn)防重復(fù)提交到這里就結(jié)束了,本文介紹了一種通用的防重復(fù)提交的實(shí)現(xiàn)方式,代碼邏輯清晰。任何問題評(píng)論區(qū)或私信討論,歡迎指正。文章來源地址http://www.zghlxwxcb.cn/news/detail-847983.html
到了這里,關(guān)于springboot3使用自定義注解+AOP+redis優(yōu)雅實(shí)現(xiàn)防重復(fù)提交的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!