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

          優(yōu)雅的接口防刷處理方案

          共 32354字,需瀏覽 65分鐘

           ·

          2023-11-09 04:32

          程序員的成長之路
          互聯(lián)網(wǎng)/程序員/技術(shù)/資料共享 
          關(guān)注


          閱讀本文大概需要 14 分鐘。

          來自:juejin.cn/post/7200366809407750181

          • 前言
          • 原理
          • 工程
          • 自我提問
          • 接口自由
          • 時(shí)間邏輯漏洞
          • 路徑參數(shù)問題
          • 真實(shí)ip獲取
          • 總結(jié)

          前言

          本文為描述通過Interceptor以及Redis實(shí)現(xiàn)接口訪問防刷Demo
          這里會(huì)通過逐步找問題,逐步去完善的形式展示

          原理

          • 通過ip地址+uri拼接用以作為訪問者訪問接口區(qū)分
          • 通過在Interceptor中攔截請(qǐng)求,從Redis中統(tǒng)計(jì)用戶訪問接口次數(shù)從而達(dá)到接口防刷目的
          如下圖所示

          工程

          項(xiàng)目地址:
          https://github.com/Tonciy/interface-brush-protection
          Apifox地址:Apifox 密碼:Lyh3j2Rv
          其中,Interceptor處代碼處理邏輯最為重要



          /**
           * @author: Zero
           * @time: 2023/2/14
           * @description: 接口防刷攔截處理
           */

          @Slf4j
          public class AccessLimintInterceptor  implements HandlerInterceptor {
              @Resource
              private RedisTemplate<String, Object> redisTemplate;

              /**
               * 多長時(shí)間內(nèi)
               */

              @Value("${interfaceAccess.second}")
              private Long second = 10L;

              /**
               * 訪問次數(shù)
               */

              @Value("${interfaceAccess.time}")
              private Long time = 3L;

              /**
               * 禁用時(shí)長--單位/秒
               */

              @Value("${interfaceAccess.lockTime}")
              private Long lockTime = 60L;

              /**
               * 鎖住時(shí)的key前綴
               */

              public static final String LOCK_PREFIX = "LOCK";

              /**
               * 統(tǒng)計(jì)次數(shù)時(shí)的key前綴
               */

              public static final String COUNT_PREFIX = "COUNT";


              public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

                  String uri = request.getRequestURI();
                  String ip = request.getRemoteAddr(); // 這里忽略代理軟件方式訪問,默認(rèn)直接訪問,也就是獲取得到的就是訪問者真實(shí)ip地址
                  String lockKey = LOCK_PREFIX + ip + uri;
                  Object isLock = redisTemplate.opsForValue().get(lockKey);
                  if(Objects.isNull(isLock)){
                      // 還未被禁用
                      String countKey = COUNT_PREFIX + ip + uri;
                      Object count = redisTemplate.opsForValue().get(countKey);
                      if(Objects.isNull(count)){
                          // 首次訪問
                          log.info("首次訪問");
                          redisTemplate.opsForValue().set(countKey,1,second, TimeUnit.SECONDS);
                      }else{
                          // 此用戶前一點(diǎn)時(shí)間就訪問過該接口
                          if((Integer)count < time){
                              // 放行,訪問次數(shù) + 1
                              redisTemplate.opsForValue().increment(countKey);
                          }else{
                              log.info("{}禁用訪問{}",ip, uri);
                              // 禁用
                              redisTemplate.opsForValue().set(lockKey, 1,lockTime, TimeUnit.SECONDS);
                              // 刪除統(tǒng)計(jì)
                              redisTemplate.delete(countKey);
                              throw new CommonException(ResultCode.ACCESS_FREQUENT);
                          }
                      }
                  }else{
                      // 此用戶訪問此接口已被禁用
                      throw new CommonException(ResultCode.ACCESS_FREQUENT);
                  }
                  return true;
              }
          }

          在多長時(shí)間內(nèi)訪問接口多少次,以及禁用的時(shí)長,則是通過與配置文件配合動(dòng)態(tài)設(shè)置
          當(dāng)處于禁用時(shí)直接拋異常則是通過在ControllerAdvice處統(tǒng)一處理 (這里代碼寫的有點(diǎn)丑陋)
          下面是一些測(cè)試(可以把項(xiàng)目通過Git還原到“【初始化】”狀態(tài)進(jìn)行測(cè)試)
          • 正常訪問時(shí)
          • 訪問次數(shù)過于頻繁時(shí)

          自我提問

          上述實(shí)現(xiàn)就好像就已經(jīng)達(dá)到了我們的接口防刷目的了
          但是,還不夠
          為方便后續(xù)描述,項(xiàng)目中新增補(bǔ)充Controller,如下所示
          簡(jiǎn)單來說就是
          • PassCotrollerRefuseController
          • 每個(gè)Controller分別有對(duì)應(yīng)的get,post,put,delete類型的方法,其映射路徑與方法名稱一致

          接口自由

          • 對(duì)于上述實(shí)現(xiàn),不知道你們有沒有發(fā)現(xiàn)一個(gè)問題
          • 就是現(xiàn)在我們的接口防刷處理,針對(duì)是所有的接口(項(xiàng)目案例中我只是寫的接口比較少)
          • 而在實(shí)際開發(fā)中,說對(duì)于所有的接口都要做防刷處理,感覺上也不太可能(寫此文時(shí)目前大四,實(shí)際工作經(jīng)驗(yàn)較少,這里不敢肯定)
          • 那么問題有了,該如何解決呢?目前來說想到兩個(gè)解決方案
          攔截器映射規(guī)則
          項(xiàng)目通過Git還原到"【Interceptor設(shè)置映射規(guī)則實(shí)現(xiàn)接口自由】"版本即可得到此案例實(shí)現(xiàn)
          我們都知道攔截器是可以設(shè)置攔截規(guī)則的,從而達(dá)到攔截處理目的
          1.這個(gè)AccessInterfaceInterceptor是專門用來進(jìn)行防刷處理的,那么實(shí)際上我們可以通過設(shè)置它的映射規(guī)則去匹配需要進(jìn)行【接口防刷】的接口即可
          2.比如說下面的映射配置
          3.這樣就初步達(dá)到了我們的目的,通過映射規(guī)則的配置,只針對(duì)那些需要進(jìn)行【接口防刷】的接口才會(huì)進(jìn)行處理
          4.至于為啥說是初步呢?下面我就說說目前我想到的使用這種方式進(jìn)行【接口防刷】的不足點(diǎn):
          所有要進(jìn)行防刷處理的接口統(tǒng)一都是配置成了 x 秒內(nèi) y 次訪問次數(shù),禁用時(shí)長為 z 秒
          • 要知道就是要進(jìn)行防刷處理的接口,其 x, y, z的值也是并不一定會(huì)統(tǒng)一的
          • 某些防刷接口處理比較消耗性能的,我就把x, y, z設(shè)置的緊一點(diǎn)
          • 而某些防刷接口處理相對(duì)來說比較快,我就把x, y, z 設(shè)置的松一點(diǎn)
          • 這沒問題吧
          • 但是現(xiàn)在呢?x, y, z值全都一致了,這就不行了
          • 這就是其中一個(gè)不足點(diǎn)
          • 當(dāng)然,其實(shí)針對(duì)當(dāng)前這種情況也有解決方案
          • 那就是弄多個(gè)攔截器
          • 每個(gè)攔截器的【接口防刷】處理邏輯跟上述一致,并去映射對(duì)應(yīng)要處理的防刷接口
          • 唯一不同的就是在每個(gè)攔截器內(nèi)部,去修改對(duì)應(yīng)防刷接口需要的x, y, z值
          • 這樣就是感覺會(huì)比較麻煩
          防刷接口映射路徑修改后維護(hù)問題
          • 雖然說防刷接口的映射路徑基本上定下來后就不會(huì)改變
          • 但實(shí)際上前后端聯(lián)調(diào)開發(fā)項(xiàng)目時(shí),不會(huì)有那么嚴(yán)謹(jǐn)?shù)腁pi文檔給我們用(這個(gè)在實(shí)習(xí)中倒是碰到過,公司不是很大,開發(fā)起來也就不那么嚴(yán)謹(jǐn),啥都要自己搞,功能能實(shí)現(xiàn)就好)
          • 也就是說還是會(huì)有那種要修改接口的映射路徑需求
          • 當(dāng)防刷接口數(shù)量特別多,后面的接手人員就很痛苦了
          • 就算是項(xiàng)目是自己從0到1實(shí)現(xiàn)的,其實(shí)有時(shí)候項(xiàng)目開發(fā)到后面,自己也會(huì)忘記自己前面是如何設(shè)計(jì)的
          • 而使用當(dāng)前這種方式的話,誰維護(hù)誰蛋疼
          自定義注解 + 反射
          咋說呢
          • 就是通過自定義注解中定義 x 秒內(nèi) y 次訪問次數(shù),禁用時(shí)長為 z 秒
          • 自定義注解 + 在需要進(jìn)行防刷處理的各個(gè)接口方法上
          • 在攔截器中通過反射獲取到各個(gè)接口中的x, y, z值即可達(dá)到我們想要的接口自由目的
          下面做個(gè)實(shí)現(xiàn)
          聲明自定義注解
          Controlller中方法中使用
          Interceptor處邏輯修改(最重要是通過反射判斷此接口是否需要進(jìn)行防刷處理,以及獲取到x, y, z的值)

          /**
           * @author: Zero
           * @time: 2023/2/14
           * @description: 接口防刷攔截處理
           */

          @Slf4j
          public class AccessLimintInterceptor  implements HandlerInterceptor {
              @Resource
              private RedisTemplate<String, Object> redisTemplate;
              /**
               * 鎖住時(shí)的key前綴
               */

              public static final String LOCK_PREFIX = "LOCK";

              /**
               * 統(tǒng)計(jì)次數(shù)時(shí)的key前綴
               */

              public static final String COUNT_PREFIX = "COUNT";


              public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
          //        自定義注解 + 反射 實(shí)現(xiàn)
                  // 判斷訪問的是否是接口方法
                  if(handler instanceof HandlerMethod){
                      // 訪問的是接口方法,轉(zhuǎn)化為待訪問的目標(biāo)方法對(duì)象
                      HandlerMethod targetMethod = (HandlerMethod) handler;
                      // 取出目標(biāo)方法中的 AccessLimit 注解
                      AccessLimit accessLimit = targetMethod.getMethodAnnotation(AccessLimit.class);
                      // 判斷此方法接口是否要進(jìn)行防刷處理(方法上沒有對(duì)應(yīng)注解就代表不需要,不需要的話進(jìn)行放行)
                      if(!Objects.isNull(accessLimit)){
                          // 需要進(jìn)行防刷處理,接下來是處理邏輯
                          String ip = request.getRemoteAddr();
                          String uri = request.getRequestURI();
                          String lockKey = LOCK_PREFIX + ip + uri;
                          Object isLock = redisTemplate.opsForValue().get(lockKey);
                          // 判斷此ip用戶訪問此接口是否已經(jīng)被禁用
                          if (Objects.isNull(isLock)) {
                              // 還未被禁用
                              String countKey = COUNT_PREFIX + ip + uri;
                              Object count = redisTemplate.opsForValue().get(countKey);
                              long second = accessLimit.second();
                              long maxTime = accessLimit.maxTime();

                              if (Objects.isNull(count)) {
                                  // 首次訪問
                                  log.info("首次訪問");
                                  redisTemplate.opsForValue().set(countKey, 1, second, TimeUnit.SECONDS);
                              } else {
                                  // 此用戶前一點(diǎn)時(shí)間就訪問過該接口,且頻率沒超過設(shè)置
                                  if ((Integer) count < maxTime) {
                                      redisTemplate.opsForValue().increment(countKey);
                                  } else {

                                      log.info("{}禁用訪問{}", ip, uri);
                                      long forbiddenTime = accessLimit.forbiddenTime();
                                      // 禁用
                                      redisTemplate.opsForValue().set(lockKey, 1, forbiddenTime, TimeUnit.SECONDS);
                                      // 刪除統(tǒng)計(jì)--已經(jīng)禁用了就沒必要存在了
                                      redisTemplate.delete(countKey);
                                      throw new CommonException(ResultCode.ACCESS_FREQUENT);
                                  }
                              }
                          } else {
                              // 此用戶訪問此接口已被禁用
                              throw new CommonException(ResultCode.ACCESS_FREQUENT);
                          }
                      }
                  }
                  return  true;
              }
          }

          由于不好演示效果,這里就不貼測(cè)試結(jié)果圖片了
          項(xiàng)目通過Git還原到"【自定義主鍵+反射實(shí)現(xiàn)接口自由"版本即可得到此案例實(shí)現(xiàn),后面自己可以針對(duì)接口做下測(cè)試看看是否如同我所說的那樣實(shí)現(xiàn)自定義x, y, z 的效果
          嗯,現(xiàn)在看起來,可以針對(duì)每個(gè)要進(jìn)行防刷處理的接口進(jìn)行針對(duì)性自定義多長時(shí)間內(nèi)的最大訪問次數(shù),以及禁用時(shí)長,哪個(gè)接口需要,就直接+在那個(gè)接口方法出即可
          感覺還不錯(cuò)的樣子,現(xiàn)在網(wǎng)上挺多資料也都是這樣實(shí)現(xiàn)的
          但是還是可以有改善的地方
          先舉一個(gè)例子,以我們的PassController為例,如下是其實(shí)現(xiàn)
          下圖是其映射路徑關(guān)系
          同一個(gè)Controller的所有接口方法映射路徑的前綴都包含了/pass
          我們?cè)陬惿贤ㄟ^注解@ReqeustMapping標(biāo)記映射路徑/pass,這樣所有的接口方法前綴都包含了/pass,并且以致于后面要修改映射路徑前綴時(shí)只需改這一塊地方即可
          這也是我們使用SpringMVC最常見的用法
          那么,我們的自定義注解也可不可以這樣做呢?先無中生有個(gè)需求
          假設(shè)PassController中所有接口都是要進(jìn)行防刷處理的,并且他們的x, y, z值就一樣
          如果我們的自定義注解還是只能加載方法上的話,一個(gè)一個(gè)接口加,那么無疑這是一種很呆的做法
          要改的話,其實(shí)也很簡(jiǎn)單,首先是修改自定義注解,讓其可以作用在類上
          接著就是修改AccessLimitInterceptor的處理邏輯
          AccessLimitInterceptor中代碼修改的有點(diǎn)多,主要邏輯如下
          與之前實(shí)現(xiàn)比較,不同點(diǎn)在于x, y, z的值要首先嘗試在目標(biāo)類中獲取
          其次,一旦類中標(biāo)有此注解,即代表此類下所有接口方法都要進(jìn)行防刷處理
          如果其接口方法同樣也標(biāo)有此注解,根據(jù)就近優(yōu)先原則,以接口方法中的注解標(biāo)明的值為準(zhǔn)

          /**
           * @author: Zero
           * @time: 2023/2/14
           * @description: 接口防刷攔截處理
           */

          @Slf4j
          public class AccessLimintInterceptor implements HandlerInterceptor {
              @Resource
              private RedisTemplate<String, Object> redisTemplate;

              /**
               * 鎖住時(shí)的key前綴
               */

              public static final String LOCK_PREFIX = "LOCK";

              /**
               * 統(tǒng)計(jì)次數(shù)時(shí)的key前綴
               */

              public static final String COUNT_PREFIX = "COUNT";


              public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

          //      自定義注解 + 反射 實(shí)現(xiàn), 版本 2.0
                  if (handler instanceof HandlerMethod) {
                      // 訪問的是接口方法,轉(zhuǎn)化為待訪問的目標(biāo)方法對(duì)象
                      HandlerMethod targetMethod = (HandlerMethod) handler;
                      // 獲取目標(biāo)接口方法所在類的注解@AccessLimit
                      AccessLimit targetClassAnnotation = targetMethod.getMethod().getDeclaringClass().getAnnotation(AccessLimit.class);
                      // 特別注意不能采用下面這條語句來獲取,因?yàn)?nbsp;Spring 采用的代理方式來代理目標(biāo)方法
                      //  也就是說targetMethod.getClass()獲得是class org.springframework.web.method.HandlerMethod ,而不知我們真正想要的 Controller
          //            AccessLimit targetClassAnnotation = targetMethod.getClass().getAnnotation(AccessLimit.class);
                      // 定義標(biāo)記位,標(biāo)記此類是否加了@AccessLimit注解
                      boolean isBrushForAllInterface = false;
                      String ip = request.getRemoteAddr();
                      String uri = request.getRequestURI();
                      long second = 0L;
                      long maxTime = 0L;
                      long forbiddenTime = 0L;
                      if (!Objects.isNull(targetClassAnnotation)) {
                          log.info("目標(biāo)接口方法所在類上有@AccessLimit注解");
                          isBrushForAllInterface = true;
                          second = targetClassAnnotation.second();
                          maxTime = targetClassAnnotation.maxTime();
                          forbiddenTime = targetClassAnnotation.forbiddenTime();
                      }
                      // 取出目標(biāo)方法中的 AccessLimit 注解
                      AccessLimit accessLimit = targetMethod.getMethodAnnotation(AccessLimit.class);
                      // 判斷此方法接口是否要進(jìn)行防刷處理
                      if (!Objects.isNull(accessLimit)) {
                          // 需要進(jìn)行防刷處理,接下來是處理邏輯
                          second = accessLimit.second();
                          maxTime = accessLimit.maxTime();
                          forbiddenTime = accessLimit.forbiddenTime();
                          if (isForbindden(second, maxTime, forbiddenTime, ip, uri)) {
                              throw new CommonException(ResultCode.ACCESS_FREQUENT);
                          }
                      } else {
                          // 目標(biāo)接口方法處無@AccessLimit注解,但還要看看其類上是否加了(類上有加,代表針對(duì)此類下所有接口方法都要進(jìn)行防刷處理)
                          if (isBrushForAllInterface && isForbindden(second, maxTime, forbiddenTime, ip, uri)) {
                              throw new CommonException(ResultCode.ACCESS_FREQUENT);
                          }
                      }
                  }
                  return true;
              }

              /**
               * 判斷某用戶訪問某接口是否已經(jīng)被禁用/是否需要禁用
               *
               * @param second        多長時(shí)間  單位/秒
               * @param maxTime       最大訪問次數(shù)
               * @param forbiddenTime 禁用時(shí)長 單位/秒
               * @param ip            訪問者ip地址
               * @param uri           訪問的uri
               * @return ture為需要禁用
               */

              private boolean isForbindden(long second, long maxTime, long forbiddenTime, String ip, String uri) {
                  String lockKey = LOCK_PREFIX + ip + uri; //如果此ip訪問此uri被禁用時(shí)的存在Redis中的 key
                  Object isLock = redisTemplate.opsForValue().get(lockKey);
                  // 判斷此ip用戶訪問此接口是否已經(jīng)被禁用
                  if (Objects.isNull(isLock)) {
                      // 還未被禁用
                      String countKey = COUNT_PREFIX + ip + uri;
                      Object count = redisTemplate.opsForValue().get(countKey);
                      if (Objects.isNull(count)) {
                          // 首次訪問
                          log.info("首次訪問");
                          redisTemplate.opsForValue().set(countKey, 1, second, TimeUnit.SECONDS);
                      } else {
                          // 此用戶前一點(diǎn)時(shí)間就訪問過該接口,且頻率沒超過設(shè)置
                          if ((Integer) count < maxTime) {
                              redisTemplate.opsForValue().increment(countKey);
                          } else {
                              log.info("{}禁用訪問{}", ip, uri);
                              // 禁用
                              redisTemplate.opsForValue().set(lockKey, 1, forbiddenTime, TimeUnit.SECONDS);
                              // 刪除統(tǒng)計(jì)--已經(jīng)禁用了就沒必要存在了
                              redisTemplate.delete(countKey);
                              return true;
                          }
                      }
                  } else {
                      // 此用戶訪問此接口已被禁用
                      return true;
                  }
                  return false;
              }
          }

          好了,這樣就達(dá)到我們想要的效果了
          項(xiàng)目通過Git還原到"【自定義注解+反射實(shí)現(xiàn)接口自由-版本2.0】"版本即可得到此案例實(shí)現(xiàn),自己可以測(cè)試萬一下
          這是目前來說比較理想的做法,至于其他做法,暫時(shí)沒啥了解到

          時(shí)間邏輯漏洞

          這是我一開始都有留意到的問題
          也是一直搞不懂,就是我們現(xiàn)在的所有做法其實(shí)感覺都不是嚴(yán)格意義上的x秒內(nèi)y次訪問次數(shù)
          特別注意這個(gè)x秒,它是連續(xù),任意的(代表這個(gè)x秒時(shí)間片段其實(shí)是可以發(fā)生在任意一個(gè)時(shí)間軸上)
          我下面嘗試表達(dá)我的意思,但是我不知道能不能表達(dá)清楚
          假設(shè)我們固定某個(gè)接口5秒內(nèi)只能訪問3次,以下面例子為例
          底下的小圓圈代表此刻請(qǐng)求訪問接口
          按照我們之前所有做法的邏輯走
          1. 第2秒請(qǐng)求到,為首次訪問,Redis中統(tǒng)計(jì)次數(shù)為1(過期時(shí)間為5秒)
          2. 第7秒,此時(shí)有兩個(gè)動(dòng)作,一是請(qǐng)求到,二是剛剛第二秒Redis存的值現(xiàn)在過期
          3. 我們先假設(shè)這一刻,請(qǐng)求處理完后,Redis存的值才過期
          4. 按照這樣的邏輯走
          5. 第七秒請(qǐng)求到,Redis存在對(duì)應(yīng)key,且不大于3, 次數(shù)+1
          6. 接著這個(gè)key立馬過期
          7. 再繼續(xù)往后走,第8秒又當(dāng)做新的一個(gè)起始,就不往下說了,反正就是不會(huì)出現(xiàn)禁用的情況
          按照上述邏輯走,實(shí)際上也就是說當(dāng)出現(xiàn)首次訪問時(shí),當(dāng)做這5秒時(shí)間片段的起始
          第2秒是,第8秒也是
          但是有沒有想過,實(shí)際上這個(gè)5秒時(shí)間片段實(shí)際上是可以放置在時(shí)間軸上任意區(qū)域的
          上述情況我們是根據(jù)請(qǐng)求的到來情況人為的把它放在【2-7】,【8-13】上
          而實(shí)際上這5秒時(shí)間片段是可以放在任意區(qū)域的
          那么,這樣的話,【7-12】也可以放置
          而【7-12】這段時(shí)間有4次請(qǐng)求,就達(dá)到了我們禁用的條件了
          是不是感覺怪怪的
          想過其他做法,但是好像嚴(yán)格意義上真的做不到我所說的那樣(至少目前來說想不到)
          之前我們的做法,正常來說也夠用,至少說有達(dá)到防刷的作用
          后面有機(jī)會(huì)的話再看看,不知道我是不是鉆牛角尖了

          路徑參數(shù)問題

          假設(shè)現(xiàn)在PassController中有如下接口方法
          也就是我們?cè)诮涌诜椒ㄖ谐S玫脑谡?qǐng)求路徑中獲取參數(shù)的套路
          但是使用路徑參數(shù)的話,就會(huì)發(fā)生問題
          那就是同一個(gè)ip地址訪問此接口時(shí),我攜帶的參數(shù)值不同
          按照我們之前那種前綴+ip+uri拼接的形式作為key的話,其實(shí)是區(qū)分不了的
          下圖是訪問此接口,攜帶不同參數(shù)值時(shí)獲取的uri狀況
          這樣的話在我們之前攔截器的處理邏輯中,會(huì)認(rèn)為是此ip用戶訪問的是不同的接口方法,而實(shí)際上訪問的是同一個(gè)接口方法
          也就導(dǎo)致了【接口防刷】失效
          接下來就是解決它,目前來說有兩種
          1. 不要使用路徑參數(shù)
          這算是比較理想的做法,相當(dāng)于沒這個(gè)問題
          但有一定局限性,有時(shí)候接手別的項(xiàng)目,或者自己根本沒這個(gè)權(quán)限說不能使用路徑參數(shù)
          1. 替換uri
          • 我們獲取uri的目的,其實(shí)就是為了區(qū)別訪問接口
          • 而把uri替換成另一種可以區(qū)分訪問接口方法的標(biāo)識(shí)即可
          • 最容易想到的就是通過反射獲取到接口方法名稱,使用接口方法名稱替換成uri即可
          • 當(dāng)然,其實(shí)不同的Controller中,其接口方法名稱也有可能是相同的
          • 實(shí)際上可以再獲取接口方法所在類類名,使用類名 + 方法名稱替換uri即可
          • 實(shí)際解決方案有很多,看個(gè)人需求吧

          真實(shí)ip獲取

          在之前的代碼中,我們獲取代碼都是通過request.getRemoteAddr()獲取的
          但是后續(xù)有了解到,如果說通過代理軟件方式訪問的話,這樣是獲取不到來訪者的真實(shí)ip的
          至于如何獲取,后續(xù)我再研究下http再說,這里先提個(gè)醒

          總結(jié)

          說實(shí)話,挺有意思的,一開始自己想【接口防刷】的時(shí)候,感覺也就是轉(zhuǎn)化成統(tǒng)計(jì)下訪問次數(shù)的問題擺了。后面到網(wǎng)上看別人的寫法,又再自己給自己找點(diǎn)問題出來,后面會(huì)衍生出來一推東西出來,諸如自定義注解+反射這種實(shí)現(xiàn)方式。
          以前其實(shí)對(duì)注解 + 反射其實(shí)有點(diǎn)不太懂干嘛用的,而從之前的數(shù)據(jù)報(bào)表導(dǎo)出,再到基本權(quán)限控制實(shí)現(xiàn),最后到今天的【接口防刷】一點(diǎn)點(diǎn)來進(jìn)步去補(bǔ)充自己的知識(shí)點(diǎn),而且,感覺寫博客真的是件挺有意義的事情,它會(huì)讓你去更深入的了解某個(gè)點(diǎn),并且知識(shí)是相關(guān)聯(lián)的,探索的過程中會(huì)牽扯到其他別的知識(shí)點(diǎn),就像之前的寫的【單例模式】實(shí)現(xiàn),一開始就了解到懶漢式,餓漢式
          后面深入的話就知道其實(shí)會(huì)還有序列化/反序列化,反射調(diào)用生成實(shí)例,對(duì)象克隆這幾種方式回去破壞單例模式,又是如何解決的,這也是一個(gè)進(jìn)步的點(diǎn),后續(xù)為了保證線程安全問題,牽扯到的synchronized,voliate關(guān)鍵字,繼而又關(guān)聯(lián)到JVM,JUC,操作系統(tǒng)的東西。
          <END>

          推薦閱讀:

          某知名翻墻軟件作者疑似被抓,刪庫跑路

          10 個(gè) Java Stream 頂級(jí)技巧,大量簡(jiǎn)化代碼!

             
             
          互聯(lián)網(wǎng)初中高級(jí)大廠面試題(9個(gè)G)

          內(nèi)容包含Java基礎(chǔ)、JavaWeb、MySQL性能優(yōu)化、JVM、鎖、百萬并發(fā)、消息隊(duì)列、高性能緩存、反射、Spring全家桶原理、微服務(wù)、Zookeeper......等技術(shù)棧!

          ?戳閱讀原文領(lǐng)取!                                  朕已閱 

          瀏覽 563
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  亚洲变态欧美另类精品 | 操美女嫩逼 | 一级色黄片 | 日韩视频精品 | 青青草原视频免费在线观看 |