<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

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

          共 6869字,需瀏覽 14分鐘

           ·

          2020-10-07 20:02

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

          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樂園

          有用!分享+在看?
          瀏覽 60
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日韩无码小电影 | 操逼动漫免费观看 | 伊人久久狠狠操 | 一级黄色片免费看在干嘛呢 | 欧美大奶一区二区 |