實現(xiàn)防重復提交和防重復點擊

點擊上方「藍字」關注我們

0x01: 背景
同一條數(shù)據(jù)被用戶點擊了多次,導致數(shù)據(jù)冗余,需要防止弱網(wǎng)絡等環(huán)境下的重復點擊
0x02: 目標
通過在指定的接口處添加注解,實現(xiàn)根據(jù)指定的接口參數(shù)來防重復點擊
0x03: 說明
這里的重復點擊是指在指定的時間段內(nèi)多次點擊按鈕
0x04: 技術(shù)方案
springboot + redis鎖 + 注解
使用 feign client 進行請求測試
0x05:實戰(zhàn)演練
1、根據(jù)接口收到 PathVariable 參數(shù)判斷唯一
/**
?????*??根據(jù)請求參數(shù)里的?PathVariable?里獲取的變量進行接口級別防重復點擊
?????*
?????*?@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è)務邏輯
????????Thread.sleep(5);
????????return?RsVo.success("test?is?return?success");
????}
2、根據(jù)接口收到的 RequestBody 中指定變量名的值判斷唯一
/**
?????*??根據(jù)請求參數(shù)里的?RequestBody?里獲取指定名稱的變量param5的值進行接口級別防重復點擊
?????*
?????*?@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è)務邏輯
????????Thread.sleep(5);
????????return?RsVo.success("test?is?return?success");
????}
ps: jedis 2.9 和 springboot有各種兼容問題,無奈只有降低springboot的版本了
運行結(jié)果
收到響應:{"succeeded":true,"code":500,"msg":"操作過于頻繁,請稍后重試","data":null}
收到響應:{"succeeded":true,"code":500,"msg":"操作過于頻繁,請稍后重試","data":null}
收到響應:{"succeeded":true,"code":500,"msg":"操作過于頻繁,請稍后重試","data":null}
收到響應:{"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?防重復點擊測試類
?*?@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("收到響應:"?+?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("收到響應:"?+?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?重復點擊的切面
?*?@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ù)中的指定值來判斷請求是否重復
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<String,?Object>?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è)務代碼出錯",?throwable);
????????????throw?new?RuntimeException(throwable.getMessage());
????????}?finally?{
????????????lockService.unLock(key);
????????}
????}
????public?static?Map<String,?Object>?getKeyAndValue(Object?obj)?{
????????Map<String,?Object>?map?=?Maps.newHashMap();
????????//?得到類對象
????????Class?userCla?=?(Class)?obj.getClass();
????????/*?得到類中的所有屬性集合?*/
????????Field[]?fs?=?userCla.getDeclaredFields();
????????for?(int?i?=?0;?i?????????????Field?f?=?fs[i];
????????????//?設置些屬性是可以訪問的
????????????f.setAccessible(true);
????????????Object?val?=?new?Object();
????????????try?{
????????????????val?=?f.get(obj);
????????????????//?得到此屬性的值
????????????????//?設置鍵值
????????????????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;
????}
}
項目完整代碼
https://github.com/daleyzou/PreventRepeatSubmit
作者:DaleyZou
出處:https://www.cnblogs.com/daleyzou/p/noSubmitRepeat.html
掃碼二維碼
獲取更多精彩
Java樂園

評論
圖片
表情
