原理
通過(guò)ip地址+uri拼接用以作為訪問(wèn)者訪問(wèn)接口區(qū)分
通過(guò)在Interceptor中攔截請(qǐng)求,從Redis中統(tǒng)計(jì)用戶訪問(wèn)接口次數(shù)從而達(dá)到接口防刷目的
如下圖所示
工程
其中,Interceptor處代碼處理邏輯最為重要
/** *@author:Zero *@time:2023/2/14 *@description:接口防刷攔截處理 */ @Slf4j publicclassAccessLimintInterceptorimplementsHandlerInterceptor{ @Resource privateRedisTemplateredisTemplate; /** *多長(zhǎng)時(shí)間內(nèi) */ @Value("${interfaceAccess.second}") privateLongsecond=10L; /** *訪問(wèn)次數(shù) */ @Value("${interfaceAccess.time}") privateLongtime=3L; /** *禁用時(shí)長(zhǎng)--單位/秒 */ @Value("${interfaceAccess.lockTime}") privateLonglockTime=60L; /** *鎖住時(shí)的key前綴 */ publicstaticfinalStringLOCK_PREFIX="LOCK"; /** *統(tǒng)計(jì)次數(shù)時(shí)的key前綴 */ publicstaticfinalStringCOUNT_PREFIX="COUNT"; publicbooleanpreHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler)throwsException{ Stringuri=request.getRequestURI(); Stringip=request.getRemoteAddr();//這里忽略代理軟件方式訪問(wèn),默認(rèn)直接訪問(wèn),也就是獲取得到的就是訪問(wèn)者真實(shí)ip地址 StringlockKey=LOCK_PREFIX+ip+uri; ObjectisLock=redisTemplate.opsForValue().get(lockKey); if(Objects.isNull(isLock)){ //還未被禁用 StringcountKey=COUNT_PREFIX+ip+uri; Objectcount=redisTemplate.opsForValue().get(countKey); if(Objects.isNull(count)){ //首次訪問(wèn) log.info("首次訪問(wèn)"); redisTemplate.opsForValue().set(countKey,1,second,TimeUnit.SECONDS); }else{ //此用戶前一點(diǎn)時(shí)間就訪問(wèn)過(guò)該接口 if((Integer)count
在多長(zhǎng)時(shí)間內(nèi)訪問(wèn)接口多少次,以及禁用的時(shí)長(zhǎng),則是通過(guò)與配置文件配合動(dòng)態(tài)設(shè)置
當(dāng)處于禁用時(shí)直接拋異常則是通過(guò)在ControllerAdvice處統(tǒng)一處理 (這里代碼寫的有點(diǎn)丑陋)
下面是一些測(cè)試(可以把項(xiàng)目通過(guò)Git還原到“【初始化】”狀態(tài)進(jìn)行測(cè)試)
正常訪問(wèn)時(shí)
訪問(wèn)次數(shù)過(guò)于頻繁時(shí)
自我提問(wèn)
上述實(shí)現(xiàn)就好像就已經(jīng)達(dá)到了我們的接口防刷目的了
但是,還不夠
為方便后續(xù)描述,項(xiàng)目中新增補(bǔ)充Controller,如下所示
簡(jiǎn)單來(lái)說(shuō)就是
PassCotroller和RefuseController
每個(gè)Controller分別有對(duì)應(yīng)的get,post,put,delete類型的方法,其映射路徑與方法名稱一致
接口自由
對(duì)于上述實(shí)現(xiàn),不知道你們有沒(méi)有發(fā)現(xiàn)一個(gè)問(wèn)題
就是現(xiàn)在我們的接口防刷處理,針對(duì)是所有的接口(項(xiàng)目案例中我只是寫的接口比較少)
而在實(shí)際開(kāi)發(fā)中,說(shuō)對(duì)于所有的接口都要做防刷處理,感覺(jué)上也不太可能(寫此文時(shí)目前大四,實(shí)際工作經(jīng)驗(yàn)較少,這里不敢肯定)
那么問(wèn)題有了,該如何解決呢?目前來(lái)說(shuō)想到兩個(gè)解決方案
攔截器映射規(guī)則
項(xiàng)目通過(guò)Git還原到"【Interceptor設(shè)置映射規(guī)則實(shí)現(xiàn)接口自由】"版本即可得到此案例實(shí)現(xiàn)
我們都知道攔截器是可以設(shè)置攔截規(guī)則的,從而達(dá)到攔截處理目的
1.這個(gè)AccessInterfaceInterceptor是專門用來(lái)進(jìn)行防刷處理的,那么實(shí)際上我們可以通過(guò)設(shè)置它的映射規(guī)則去匹配需要進(jìn)行【接口防刷】的接口即可
2.比如說(shuō)下面的映射配置
3.這樣就初步達(dá)到了我們的目的,通過(guò)映射規(guī)則的配置,只針對(duì)那些需要進(jìn)行【接口防刷】的接口才會(huì)進(jìn)行處理
4.至于為啥說(shuō)是初步呢?下面我就說(shuō)說(shuō)目前我想到的使用這種方式進(jìn)行【接口防刷】的不足點(diǎn):
所有要進(jìn)行防刷處理的接口統(tǒng)一都是配置成了 x 秒內(nèi) y 次訪問(wèn)次數(shù),禁用時(shí)長(zhǎng)為 z 秒
要知道就是要進(jìn)行防刷處理的接口,其 x, y, z的值也是并不一定會(huì)統(tǒng)一的
某些防刷接口處理比較消耗性能的,我就把x, y, z設(shè)置的緊一點(diǎn)
而某些防刷接口處理相對(duì)來(lái)說(shuō)比較快,我就把x, y, z 設(shè)置的松一點(diǎn)
這沒(méi)問(wè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值
這樣就是感覺(jué)會(huì)比較麻煩
防刷接口映射路徑修改后維護(hù)問(wèn)題
雖然說(shuō)防刷接口的映射路徑基本上定下來(lái)后就不會(huì)改變
但實(shí)際上前后端聯(lián)調(diào)開(kāi)發(fā)項(xiàng)目時(shí),不會(huì)有那么嚴(yán)謹(jǐn)?shù)腁pi文檔給我們用(這個(gè)在實(shí)習(xí)中倒是碰到過(guò),公司不是很大,開(kāi)發(fā)起來(lái)也就不那么嚴(yán)謹(jǐn),啥都要自己搞,功能能實(shí)現(xiàn)就好)
也就是說(shuō)還是會(huì)有那種要修改接口的映射路徑需求
當(dāng)防刷接口數(shù)量特別多,后面的接手人員就很痛苦了
就算是項(xiàng)目是自己從0到1實(shí)現(xiàn)的,其實(shí)有時(shí)候項(xiàng)目開(kāi)發(fā)到后面,自己也會(huì)忘記自己前面是如何設(shè)計(jì)的
而使用當(dāng)前這種方式的話,誰(shuí)維護(hù)誰(shuí)蛋疼
自定義注解 + 反射
咋說(shuō)呢
就是通過(guò)自定義注解中定義 x 秒內(nèi) y 次訪問(wèn)次數(shù),禁用時(shí)長(zhǎng)為 z 秒
自定義注解 + 在需要進(jìn)行防刷處理的各個(gè)接口方法上
在攔截器中通過(guò)反射獲取到各個(gè)接口中的x, y, z值即可達(dá)到我們想要的接口自由目的
下面做個(gè)實(shí)現(xiàn)
聲明自定義注解
Controlller中方法中使用
Interceptor處邏輯修改(最重要是通過(guò)反射判斷此接口是否需要進(jìn)行防刷處理,以及獲取到x, y, z的值)
/** *@author:Zero *@time:2023/2/14 *@description:接口防刷攔截處理 */ @Slf4j publicclassAccessLimintInterceptorimplementsHandlerInterceptor{ @Resource privateRedisTemplateredisTemplate; /** *鎖住時(shí)的key前綴 */ publicstaticfinalStringLOCK_PREFIX="LOCK"; /** *統(tǒng)計(jì)次數(shù)時(shí)的key前綴 */ publicstaticfinalStringCOUNT_PREFIX="COUNT"; publicbooleanpreHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler)throwsException{ //自定義注解+反射實(shí)現(xiàn) //判斷訪問(wèn)的是否是接口方法 if(handlerinstanceofHandlerMethod){ //訪問(wèn)的是接口方法,轉(zhuǎn)化為待訪問(wèn)的目標(biāo)方法對(duì)象 HandlerMethodtargetMethod=(HandlerMethod)handler; //取出目標(biāo)方法中的AccessLimit注解 AccessLimitaccessLimit=targetMethod.getMethodAnnotation(AccessLimit.class); //判斷此方法接口是否要進(jìn)行防刷處理(方法上沒(méi)有對(duì)應(yīng)注解就代表不需要,不需要的話進(jìn)行放行) if(!Objects.isNull(accessLimit)){ //需要進(jìn)行防刷處理,接下來(lái)是處理邏輯 Stringip=request.getRemoteAddr(); Stringuri=request.getRequestURI(); StringlockKey=LOCK_PREFIX+ip+uri; ObjectisLock=redisTemplate.opsForValue().get(lockKey); //判斷此ip用戶訪問(wèn)此接口是否已經(jīng)被禁用 if(Objects.isNull(isLock)){ //還未被禁用 StringcountKey=COUNT_PREFIX+ip+uri; Objectcount=redisTemplate.opsForValue().get(countKey); longsecond=accessLimit.second(); longmaxTime=accessLimit.maxTime(); if(Objects.isNull(count)){ //首次訪問(wèn) log.info("首次訪問(wèn)"); redisTemplate.opsForValue().set(countKey,1,second,TimeUnit.SECONDS); }else{ //此用戶前一點(diǎn)時(shí)間就訪問(wèn)過(guò)該接口,且頻率沒(méi)超過(guò)設(shè)置 if((Integer)count
由于不好演示效果,這里就不貼測(cè)試結(jié)果圖片了
項(xiàng)目通過(guò)Git還原到"【自定義主鍵+反射實(shí)現(xiàn)接口自由"版本即可得到此案例實(shí)現(xiàn),后面自己可以針對(duì)接口做下測(cè)試看看是否如同我所說(shuō)的那樣實(shí)現(xiàn)自定義x, y, z 的效果
嗯,現(xiàn)在看起來(lái),可以針對(duì)每個(gè)要進(jìn)行防刷處理的接口進(jìn)行針對(duì)性自定義多長(zhǎng)時(shí)間內(nèi)的最大訪問(wèn)次數(shù),以及禁用時(shí)長(zhǎng),哪個(gè)接口需要,就直接+在那個(gè)接口方法出即可
感覺(jué)還不錯(cuò)的樣子,現(xiàn)在網(wǎng)上挺多資料也都是這樣實(shí)現(xiàn)的
但是還是可以有改善的地方
先舉一個(gè)例子,以我們的PassController為例,如下是其實(shí)現(xiàn)
下圖是其映射路徑關(guān)系
同一個(gè)Controller的所有接口方法映射路徑的前綴都包含了/pass
我們?cè)陬惿贤ㄟ^(guò)注解@ReqeustMapping標(biāo)記映射路徑/pass,這樣所有的接口方法前綴都包含了/pass,并且以致于后面要修改映射路徑前綴時(shí)只需改這一塊地方即可
這也是我們使用SpringMVC最常見(jiàn)的用法
那么,我們的自定義注解也可不可以這樣做呢?先無(wú)中生有個(gè)需求
假設(shè)PassController中所有接口都是要進(jìn)行防刷處理的,并且他們的x, y, z值就一樣
如果我們的自定義注解還是只能加載方法上的話,一個(gè)一個(gè)接口加,那么無(wú)疑這是一種很呆的做法
要改的話,其實(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 publicclassAccessLimintInterceptorimplementsHandlerInterceptor{ @Resource privateRedisTemplateredisTemplate; /** *鎖住時(shí)的key前綴 */ publicstaticfinalStringLOCK_PREFIX="LOCK"; /** *統(tǒng)計(jì)次數(shù)時(shí)的key前綴 */ publicstaticfinalStringCOUNT_PREFIX="COUNT"; publicbooleanpreHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler)throwsException{ //自定義注解+反射實(shí)現(xiàn),版本2.0 if(handlerinstanceofHandlerMethod){ //訪問(wèn)的是接口方法,轉(zhuǎn)化為待訪問(wèn)的目標(biāo)方法對(duì)象 HandlerMethodtargetMethod=(HandlerMethod)handler; //獲取目標(biāo)接口方法所在類的注解@AccessLimit AccessLimittargetClassAnnotation=targetMethod.getMethod().getDeclaringClass().getAnnotation(AccessLimit.class); //特別注意不能采用下面這條語(yǔ)句來(lái)獲取,因?yàn)镾pring采用的代理方式來(lái)代理目標(biāo)方法 //也就是說(shuō)targetMethod.getClass()獲得是classorg.springframework.web.method.HandlerMethod,而不知我們真正想要的Controller //AccessLimittargetClassAnnotation=targetMethod.getClass().getAnnotation(AccessLimit.class); //定義標(biāo)記位,標(biāo)記此類是否加了@AccessLimit注解 booleanisBrushForAllInterface=false; Stringip=request.getRemoteAddr(); Stringuri=request.getRequestURI(); longsecond=0L; longmaxTime=0L; longforbiddenTime=0L; if(!Objects.isNull(targetClassAnnotation)){ log.info("目標(biāo)接口方法所在類上有@AccessLimit注解"); isBrushForAllInterface=true; second=targetClassAnnotation.second(); maxTime=targetClassAnnotation.maxTime(); forbiddenTime=targetClassAnnotation.forbiddenTime(); } //取出目標(biāo)方法中的AccessLimit注解 AccessLimitaccessLimit=targetMethod.getMethodAnnotation(AccessLimit.class); //判斷此方法接口是否要進(jìn)行防刷處理 if(!Objects.isNull(accessLimit)){ //需要進(jìn)行防刷處理,接下來(lái)是處理邏輯 second=accessLimit.second(); maxTime=accessLimit.maxTime(); forbiddenTime=accessLimit.forbiddenTime(); if(isForbindden(second,maxTime,forbiddenTime,ip,uri)){ thrownewCommonException(ResultCode.ACCESS_FREQUENT); } }else{ //目標(biāo)接口方法處無(wú)@AccessLimit注解,但還要看看其類上是否加了(類上有加,代表針對(duì)此類下所有接口方法都要進(jìn)行防刷處理) if(isBrushForAllInterface&&isForbindden(second,maxTime,forbiddenTime,ip,uri)){ thrownewCommonException(ResultCode.ACCESS_FREQUENT); } } } returntrue; } /** *判斷某用戶訪問(wèn)某接口是否已經(jīng)被禁用/是否需要禁用 * *@paramsecond多長(zhǎng)時(shí)間單位/秒 *@parammaxTime最大訪問(wèn)次數(shù) *@paramforbiddenTime禁用時(shí)長(zhǎng)單位/秒 *@paramip訪問(wèn)者ip地址 *@paramuri訪問(wèn)的uri *@returnture為需要禁用 */ privatebooleanisForbindden(longsecond,longmaxTime,longforbiddenTime,Stringip,Stringuri){ StringlockKey=LOCK_PREFIX+ip+uri;//如果此ip訪問(wèn)此uri被禁用時(shí)的存在Redis中的key ObjectisLock=redisTemplate.opsForValue().get(lockKey); //判斷此ip用戶訪問(wèn)此接口是否已經(jīng)被禁用 if(Objects.isNull(isLock)){ //還未被禁用 StringcountKey=COUNT_PREFIX+ip+uri; Objectcount=redisTemplate.opsForValue().get(countKey); if(Objects.isNull(count)){ //首次訪問(wèn) log.info("首次訪問(wèn)"); redisTemplate.opsForValue().set(countKey,1,second,TimeUnit.SECONDS); }else{ //此用戶前一點(diǎn)時(shí)間就訪問(wèn)過(guò)該接口,且頻率沒(méi)超過(guò)設(shè)置 if((Integer)count
好了,這樣就達(dá)到我們想要的效果了
項(xiàng)目通過(guò)Git還原到"【自定義注解+反射實(shí)現(xiàn)接口自由-版本2.0】"版本即可得到此案例實(shí)現(xiàn),自己可以測(cè)試萬(wàn)一下
這是目前來(lái)說(shuō)比較理想的做法,至于其他做法,暫時(shí)沒(méi)啥了解到
時(shí)間邏輯漏洞
這是我一開(kāi)始都有留意到的問(wèn)題
也是一直搞不懂,就是我們現(xiàn)在的所有做法其實(shí)感覺(jué)都不是嚴(yán)格意義上的x秒內(nèi)y次訪問(wèn)次數(shù)
特別注意這個(gè)x秒,它是連續(xù),任意的(代表這個(gè)x秒時(shí)間片段其實(shí)是可以發(fā)生在任意一個(gè)時(shí)間軸上)
我下面嘗試表達(dá)我的意思,但是我不知道能不能表達(dá)清楚
假設(shè)我們固定某個(gè)接口5秒內(nèi)只能訪問(wèn)3次,以下面例子為例
底下的小圓圈代表此刻請(qǐng)求訪問(wèn)接口
按照我們之前所有做法的邏輯走
第2秒請(qǐng)求到,為首次訪問(wèn),Redis中統(tǒng)計(jì)次數(shù)為1(過(guò)期時(shí)間為5秒)
第7秒,此時(shí)有兩個(gè)動(dòng)作,一是請(qǐng)求到,二是剛剛第二秒Redis存的值現(xiàn)在過(guò)期
我們先假設(shè)這一刻,請(qǐng)求處理完后,Redis存的值才過(guò)期
按照這樣的邏輯走
第七秒請(qǐng)求到,Redis存在對(duì)應(yīng)key,且不大于3, 次數(shù)+1
接著這個(gè)key立馬過(guò)期
再繼續(xù)往后走,第8秒又當(dāng)做新的一個(gè)起始,就不往下說(shuō)了,反正就是不會(huì)出現(xiàn)禁用的情況
按照上述邏輯走,實(shí)際上也就是說(shuō)當(dāng)出現(xiàn)首次訪問(wèn)時(shí),當(dāng)做這5秒時(shí)間片段的起始
第2秒是,第8秒也是
但是有沒(méi)有想過(guò),實(shí)際上這個(gè)5秒時(shí)間片段實(shí)際上是可以放置在時(shí)間軸上任意區(qū)域的
上述情況我們是根據(jù)請(qǐng)求的到來(lái)情況人為的把它放在【2-7】,【8-13】上
而實(shí)際上這5秒時(shí)間片段是可以放在任意區(qū)域的
那么,這樣的話,【7-12】也可以放置
而【7-12】這段時(shí)間有4次請(qǐng)求,就達(dá)到了我們禁用的條件了
是不是感覺(jué)怪怪的
想過(guò)其他做法,但是好像嚴(yán)格意義上真的做不到我所說(shuō)的那樣(至少目前來(lái)說(shuō)想不到)
之前我們的做法,正常來(lái)說(shuō)也夠用,至少說(shuō)有達(dá)到防刷的作用
后面有機(jī)會(huì)的話再看看,不知道我是不是鉆牛角尖了
路徑參數(shù)問(wèn)題
假設(shè)現(xiàn)在PassController中有如下接口方法
也就是我們?cè)诮涌诜椒ㄖ谐S玫脑谡?qǐng)求路徑中獲取參數(shù)的套路
但是使用路徑參數(shù)的話,就會(huì)發(fā)生問(wèn)題
那就是同一個(gè)ip地址訪問(wèn)此接口時(shí),我攜帶的參數(shù)值不同
按照我們之前那種前綴+ip+uri拼接的形式作為key的話,其實(shí)是區(qū)分不了的
下圖是訪問(wèn)此接口,攜帶不同參數(shù)值時(shí)獲取的uri狀況
這樣的話在我們之前攔截器的處理邏輯中,會(huì)認(rèn)為是此ip用戶訪問(wèn)的是不同的接口方法,而實(shí)際上訪問(wèn)的是同一個(gè)接口方法
也就導(dǎo)致了【接口防刷】失效
接下來(lái)就是解決它,目前來(lái)說(shuō)有兩種
不要使用路徑參數(shù)
這算是比較理想的做法,相當(dāng)于沒(méi)這個(gè)問(wèn)題
但有一定局限性,有時(shí)候接手別的項(xiàng)目,或者自己根本沒(méi)這個(gè)權(quán)限說(shuō)不能使用路徑參數(shù)
替換uri
我們獲取uri的目的,其實(shí)就是為了區(qū)別訪問(wèn)接口
而把uri替換成另一種可以區(qū)分訪問(wèn)接口方法的標(biāo)識(shí)即可
最容易想到的就是通過(guò)反射獲取到接口方法名稱,使用接口方法名稱替換成uri即可
當(dāng)然,其實(shí)不同的Controller中,其接口方法名稱也有可能是相同的
實(shí)際上可以再獲取接口方法所在類類名,使用類名 + 方法名稱替換uri即可
實(shí)際解決方案有很多,看個(gè)人需求吧
真實(shí)ip獲取
在之前的代碼中,我們獲取代碼都是通過(guò)request.getRemoteAddr()獲取的
但是后續(xù)有了解到,如果說(shuō)通過(guò)代理軟件方式訪問(wèn)的話,這樣是獲取不到來(lái)訪者的真實(shí)ip的
至于如何獲取,后續(xù)我再研究下http再說(shuō),這里先提個(gè)醒
總結(jié)
說(shuō)實(shí)話,挺有意思的,一開(kāi)始自己想【接口防刷】的時(shí)候,感覺(jué)也就是轉(zhuǎn)化成統(tǒng)計(jì)下訪問(wèn)次數(shù)的問(wèn)題擺了。后面到網(wǎng)上看別人的寫法,又再自己給自己找點(diǎn)問(wèn)題出來(lái),后面會(huì)衍生出來(lái)一推東西出來(lái),諸如自定義注解+反射這種實(shí)現(xiàn)方式。
以前其實(shí)對(duì)注解 + 反射其實(shí)有點(diǎn)不太懂干嘛用的,而從之前的數(shù)據(jù)報(bào)表導(dǎo)出,再到基本權(quán)限控制實(shí)現(xiàn),最后到今天的【接口防刷】一點(diǎn)點(diǎn)來(lái)進(jìn)步去補(bǔ)充自己的知識(shí)點(diǎn),而且,感覺(jué)寫博客真的是件挺有意義的事情,它會(huì)讓你去更深入的了解某個(gè)點(diǎn),并且知識(shí)是相關(guān)聯(lián)的,探索的過(guò)程中會(huì)牽扯到其他別的知識(shí)點(diǎn),就像之前的寫的【單例模式】實(shí)現(xiàn),一開(kāi)始就了解到懶漢式,餓漢式
后面深入的話就知道其實(shí)會(huì)還有序列化/反序列化,反射調(diào)用生成實(shí)例,對(duì)象克隆這幾種方式回去破壞單例模式,又是如何解決的,這也是一個(gè)進(jìn)步的點(diǎn),后續(xù)為了保證線程安全問(wèn)題,牽扯到的synchronized,voliate關(guān)鍵字,繼而又關(guān)聯(lián)到JVM,JUC,操作系統(tǒng)的東西。
審核編輯:劉清
-
計(jì)數(shù)器
+關(guān)注
關(guān)注
32文章
2259瀏覽量
94848 -
MVC
+關(guān)注
關(guān)注
0文章
73瀏覽量
13888 -
API接口
+關(guān)注
關(guān)注
1文章
84瀏覽量
10476 -
Redis
+關(guān)注
關(guān)注
0文章
377瀏覽量
10905
原文標(biāo)題:優(yōu)雅的接口防刷處理方案
文章出處:【微信號(hào):芋道源碼,微信公眾號(hào):芋道源碼】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論