springboot實現(xiàn)防重復(fù)提交和防重復(fù)點擊
點擊上方藍色字體,選擇“標(biāo)星公眾號”
優(yōu)質(zhì)文章,第一時間送達
? 作者?|??daleyzou
來源 |? urlify.cn/VnAZ3y
背景
同一條數(shù)據(jù)被用戶點擊了多次,導(dǎo)致數(shù)據(jù)冗余,需要防止弱網(wǎng)絡(luò)等環(huán)境下的重復(fù)點擊
目標(biāo)
通過在指定的接口處添加注解,實現(xiàn)根據(jù)指定的接口參數(shù)來防重復(fù)點擊
說明
這里的重復(fù)點擊是指在指定的時間段內(nèi)多次點擊按鈕
技術(shù)方案
springboot + redis鎖 + 注解
使用 feign client 進行請求測試
最終的使用實例
1、根據(jù)接口收到 PathVariable 參數(shù)判斷唯一
/**
?????*??根據(jù)請求參數(shù)里的?PathVariable?里獲取的變量進行接口級別防重復(fù)點擊
?????*
?????*?@param?testId?測試id
?????*?@param?requestVo?請求參數(shù)
?????*?@return
?????*?@author?daleyzou
?????*/
????@PostMapping("/test/{testId}")
????@NoRepeatSubmit(location?=?"thisIsTestLocation",?seconds?=?6)
????public?RsVo?thisIsTestLocation(@PathVariable?Integer?testId,?@RequestBody?RequestVo?requestVo)?throws?Throwable?{
????????//?睡眠?5?秒,模擬業(yè)務(wù)邏輯
????????Thread.sleep(5);
????????return?RsVo.success("test?is?return?success");
????}
2、根據(jù)接口收到的 RequestBody 中指定變量名的值判斷唯一
/**
?????*??根據(jù)請求參數(shù)里的?RequestBody?里獲取指定名稱的變量param5的值進行接口級別防重復(fù)點擊
?????*
?????*?@param?testId?測試id
?????*?@param?requestVo?請求參數(shù)
?????*?@return
?????*?@author?daleyzou
?????*/
????@PostMapping("/test/{testId}")
????@NoRepeatSubmit(location?=?"thisIsTestBody",?seconds?=?6,?argIndex?=?1,?name?=?"param5")
????public?RsVo?thisIsTestBody(@PathVariable?Integer?testId,?@RequestBody?RequestVo?requestVo)?throws?Throwable?{
????????//?睡眠?5?秒,模擬業(yè)務(wù)邏輯
????????Thread.sleep(5);
????????return?RsVo.success("test?is?return?success");
????}
ps: jedis 2.9 和 springboot有各種兼容問題,無奈只有降低springboot的版本了
運行結(jié)果
收到響應(yīng):{"succeeded":true,"code":500,"msg":"操作過于頻繁,請稍后重試","data":null}
收到響應(yīng):{"succeeded":true,"code":500,"msg":"操作過于頻繁,請稍后重試","data":null}
收到響應(yīng):{"succeeded":true,"code":500,"msg":"操作過于頻繁,請稍后重試","data":null}
收到響應(yīng):{"succeeded":true,"code":200,"msg":"success","data":"test?is?return?success"}
測試用例
package?com.dalelyzou.preventrepeatsubmit.controller;
import?com.dalelyzou.preventrepeatsubmit.PreventrepeatsubmitApplicationTests;
import?com.dalelyzou.preventrepeatsubmit.service.AsyncFeginService;
import?com.dalelyzou.preventrepeatsubmit.vo.RequestVo;
import?org.junit.jupiter.api.Test;
import?org.springframework.beans.factory.annotation.Autowired;
import?java.io.IOException;
import?java.util.concurrent.ExecutorService;
import?java.util.concurrent.Executors;
/**
?*?TestControllerTest
?*?@description?防重復(fù)點擊測試類
?*?@author?daleyzou
?*?@date?2020年09月28日?17:13
?*?@version?1.3.1
?*/
class?TestControllerTest?extends?PreventrepeatsubmitApplicationTests?{
????@Autowired
????AsyncFeginService?asyncFeginService;
????@Test
????public?void?thisIsTestLocation()?throws?IOException?{
????????RequestVo?requestVo?=?new?RequestVo();
????????requestVo.setParam5("random");
????????ExecutorService?executorService?=?Executors.newFixedThreadPool(4);
????????for?(int?i?=?0;?i?<=?3;?i++)?{
????????????executorService.execute(()?->?{
????????????????String?kl?=?asyncFeginService.thisIsTestLocation(requestVo);
????????????????System.err.println("收到響應(yīng):"?+?kl);
????????????});
????????}
????????System.in.read();
????}
????@Test
????public?void?thisIsTestBody()?throws?IOException?{
????????RequestVo?requestVo?=?new?RequestVo();
????????requestVo.setParam5("special");
????????ExecutorService?executorService?=?Executors.newFixedThreadPool(4);
????????for?(int?i?=?0;?i?<=?3;?i++)?{
????????????executorService.execute(()?->?{
????????????????String?kl?=?asyncFeginService.thisIsTestBody(requestVo);
????????????????System.err.println("收到響應(yīng):"?+?kl);
????????????});
????????}
????????System.in.read();
????}
}
定義一個注解
package?com.dalelyzou.preventrepeatsubmit.aspect;
import?java.lang.annotation.ElementType;
import?java.lang.annotation.Retention;
import?java.lang.annotation.RetentionPolicy;
import?java.lang.annotation.Target;
/**
?*?NoRepeatSubmit
?*?@description?重復(fù)點擊的切面
?*?@author?daleyzou
?*?@date?2020年09月23日?14:35
?*?@version?1.4.8
?*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public?@interface?NoRepeatSubmit?{
????/**
?????*?鎖過期的時間
?????*?*/
????int?seconds()?default?5;
????/**
?????*?鎖的位置
?????*?*/
????String?location()?default?"NoRepeatSubmit";
????/**
?????*?要掃描的參數(shù)位置
?????*?*/
????int?argIndex()?default?0;
????/**
?????*?參數(shù)名稱
?????*?*/
????String?name()?default?"";
}
根據(jù)指定的注解定義一個切面,根據(jù)參數(shù)中的指定值來判斷請求是否重復(fù)
package?com.dalelyzou.preventrepeatsubmit.aspect;
import?com.dalelyzou.preventrepeatsubmit.constant.RedisKey;
import?com.dalelyzou.preventrepeatsubmit.service.LockService;
import?com.dalelyzou.preventrepeatsubmit.vo.RsVo;
import?com.google.common.collect.Maps;
import?com.google.gson.Gson;
import?org.aspectj.lang.ProceedingJoinPoint;
import?org.aspectj.lang.annotation.Around;
import?org.aspectj.lang.annotation.Aspect;
import?org.aspectj.lang.annotation.Pointcut;
import?org.slf4j.Logger;
import?org.slf4j.LoggerFactory;
import?org.springframework.beans.factory.annotation.Autowired;
import?org.springframework.stereotype.Component;
import?org.springframework.util.StringUtils;
import?java.lang.reflect.Field;
import?java.util.Map;
@Aspect
@Component
public?class?NoRepeatSubmitAspect?{
????private?static?final?Logger?logger?=?LoggerFactory.getLogger(NoRepeatSubmitAspect.class);
????private?static?Gson?gson?=?new?Gson();
????private?static?final?String?SUFFIX?=?"SUFFIX";
????@Autowired
????LockService?lockService;
????/**
?????*?橫切點
?????*/
????@Pointcut("@annotation(noRepeatSubmit)")
????public?void?repeatPoint(NoRepeatSubmit?noRepeatSubmit)?{
????}
????/**
?????*??接收請求,并記錄數(shù)據(jù)
?????*/
????@Around(value?=?"repeatPoint(noRepeatSubmit)")
????public?Object?doBefore(ProceedingJoinPoint?joinPoint,?NoRepeatSubmit?noRepeatSubmit)?{
????????String?key?=?RedisKey.NO_REPEAT_LOCK_PREFIX?+?noRepeatSubmit.location();
????????Object[]?args?=?joinPoint.getArgs();
????????String?name?=?noRepeatSubmit.name();
????????int?argIndex?=?noRepeatSubmit.argIndex();
????????String?suffix;
????????if?(StringUtils.isEmpty(name))?{
????????????suffix?=?String.valueOf(args[argIndex]);
????????}?else?{
????????????Map?keyAndValue?=?getKeyAndValue(args[argIndex]);
????????????Object?valueObj?=?keyAndValue.get(name);
????????????if?(valueObj?==?null)?{
????????????????suffix?=?SUFFIX;
????????????}?else?{
????????????????suffix?=?String.valueOf(valueObj);
????????????}
????????}
????????key?=?key?+?":"?+?suffix;
????????logger.info("==================================================");
????????for?(Object?arg?:?args)?{
????????????logger.info(gson.toJson(arg));
????????}
????????logger.info("==================================================");
????????int?seconds?=?noRepeatSubmit.seconds();
????????logger.info("lock?key?:?"?+?key);
????????if?(!lockService.isLock(key,?seconds))?{
????????????return?RsVo.fail("操作過于頻繁,請稍后重試");
????????}
????????try?{
????????????Object?proceed?=?joinPoint.proceed();
????????????return?proceed;
????????}?catch?(Throwable?throwable)?{
????????????logger.error("運行業(yè)務(wù)代碼出錯",?throwable);
????????????throw?new?RuntimeException(throwable.getMessage());
????????}?finally?{
????????????lockService.unLock(key);
????????}
????}
????public?static?Map?getKeyAndValue(Object?obj)?{
????????Map?map?=?Maps.newHashMap();
????????//?得到類對象
????????Class?userCla?=?(Class)?obj.getClass();
????????/*?得到類中的所有屬性集合?*/
????????Field[]?fs?=?userCla.getDeclaredFields();
????????for?(int?i?=?0;?i?????????????Field?f?=?fs[i];
????????????//?設(shè)置些屬性是可以訪問的
????????????f.setAccessible(true);
????????????Object?val?=?new?Object();
????????????try?{
????????????????val?=?f.get(obj);
????????????????//?得到此屬性的值
????????????????//?設(shè)置鍵值
????????????????map.put(f.getName(),?val);
????????????}?catch?(IllegalArgumentException?e)?{
????????????????logger.error("getKeyAndValue?IllegalArgumentException",?e);
????????????}?catch?(IllegalAccessException?e)?{
????????????????logger.error("getKeyAndValue?IllegalAccessException",?e);
????????????}
????????}
????????logger.info("掃描結(jié)果:"?+?gson.toJson(map));
????????return?map;
????}
}
粉絲福利:108本java從入門到大神精選電子書領(lǐng)取
???
?長按上方鋒哥微信二維碼?2 秒 備注「1234」即可獲取資料以及 可以進入java1234官方微信群
感謝點贊支持下哈?
評論
圖片
表情
