背景
最近在小組同學(xué)卷的受不了的情況下,我決定換一個(gè)方向卷去,在算法上還是認(rèn)命吧,跟他們差距太大了, 在最近一段時(shí)間偶然看到網(wǎng)上關(guān)于接口冪等性校驗(yàn)的文章,在我一番思索下,發(fā)現(xiàn)他們的實(shí)現(xiàn)原理各有不同而且每種實(shí)現(xiàn)原理各有不同,加之最近恰好在學(xué)設(shè)計(jì)模式,我就在想怎樣利用設(shè)計(jì)模式讓我們可以隨意選擇不同的實(shí)現(xiàn)方式。在此聲明一下,筆者僅僅是一個(gè)學(xué)生,對(duì)于正式的業(yè)務(wù)流程開發(fā)并不太懂,只是利用自己現(xiàn)有的知識(shí)儲(chǔ)備,打造一個(gè)讓自己使用起來更方便的小demo, 如果有大佬覺得哪兒有問題,歡迎指出。
什么是冪等性
在數(shù)學(xué)領(lǐng)域中對(duì)于冪等性的解釋是 f(f(x)) = f(x) 即冪等元素x在函數(shù)f的多次作用下,其效果和在f的一次作用下相同。在編程上可以理解為,如果某個(gè)函數(shù)(方法)或接口被調(diào)用多次其行為結(jié)果和被調(diào)用一次相同,則這種函數(shù)或接口就具有冪等性。簡(jiǎn)單舉個(gè)例子,==天然冪等性==:
假設(shè)對(duì)象Person中有個(gè)name屬性,有個(gè)
setName(Stringname){ this.name=name }
的方法,那這個(gè)方法就是天然冪等的哦,你輸入相同的“小明”參數(shù),不論你重復(fù)調(diào)用多少次都是將名字設(shè)置為“小明”,其對(duì)對(duì)象Person的影響都是一樣的。這就是天然冪等性。
==非冪等性==:還是拿對(duì)象Person舉例子,假設(shè)對(duì)象中有個(gè)age屬性,有個(gè)
increaseAge(){ this.age++; }
方法,我們按正常的步驟一次一次調(diào)用是不會(huì)有問題的,如果調(diào)用者沒有控制好邏輯,一次流程重復(fù)調(diào)用好幾次,這時(shí)候影響效果和一次是有非常大區(qū)別,代碼編寫者以為它只會(huì)調(diào)用一次,結(jié)果出現(xiàn)了意外調(diào)用了很多次,恰好方法不具有冪等性,于是就會(huì)出現(xiàn)和預(yù)期不一樣的效果。這個(gè)方法本身是不具備冪等性的,我們可以修改這個(gè)方法,讓其傳入一個(gè)標(biāo)識(shí)符,每一次重復(fù)的請(qǐng)求會(huì)有相同的標(biāo)識(shí)符,方法內(nèi)部可以根據(jù)標(biāo)識(shí)符查數(shù)據(jù)庫(kù)是不是已經(jīng)處理過,如果處理過就不重復(fù)處理。這樣方法就具備了冪等性。
更通俗一點(diǎn)就是:當(dāng)在進(jìn)行轉(zhuǎn)賬的時(shí)候,我們分了兩個(gè)系統(tǒng)來處理這個(gè)轉(zhuǎn)賬的流程:
①系統(tǒng)A負(fù)責(zé)收集轉(zhuǎn)賬人和接收人還有金額的信息然后傳給系統(tǒng)B進(jìn)行轉(zhuǎn)賬,將控制邏輯留在系統(tǒng)A。
②系統(tǒng)B讀取系統(tǒng)A傳過來的信息,負(fù)責(zé)更改數(shù)據(jù)庫(kù)的金額。如果操作成功,就回復(fù)系統(tǒng)A成功,如果失敗就回復(fù)系統(tǒng)A失敗。
③系統(tǒng)A可以接受系統(tǒng)B操作成功或失敗的回復(fù),但是我們知道,系統(tǒng)A這個(gè)交易流程是有等待時(shí)間的,如果等待超時(shí),它不確認(rèn)是否是轉(zhuǎn)賬成功或失敗,于是系統(tǒng)A會(huì)重試調(diào)用直到得到一個(gè)明確的回復(fù)。
這是系統(tǒng)大致的交易流程。這個(gè)流程是有問題的,系統(tǒng)B提供的操作接口不是冪等性的,因?yàn)锳會(huì)重復(fù)調(diào)用接口,導(dǎo)致出現(xiàn)一個(gè)接口被同一個(gè)數(shù)據(jù)源發(fā)送相同數(shù)據(jù)切想要達(dá)到請(qǐng)求一次接口的效果的現(xiàn)象。
常見請(qǐng)求方式的冪等性
√ 滿足冪等
x 不滿足冪等
可能滿足也可能不滿足冪等,根據(jù)實(shí)際業(yè)務(wù)邏輯有關(guān)
方法類型 | 是否冪等 | 描述 |
---|---|---|
Get | √ | Get 方法用于獲取資源。其一般不會(huì)也不應(yīng)當(dāng)對(duì)系統(tǒng)資源進(jìn)行改變,所以是冪等的。 |
Post | x | Post 方法一般用于創(chuàng)建新的資源。其每次執(zhí)行都會(huì)新增數(shù)據(jù),所以不是冪等的。 |
Put | _ | Put 方法一般用于修改資源。該操作則分情況來判斷是不是滿足冪等,更新操作中直接根據(jù)某個(gè)值進(jìn)行更新,也能保持冪等。不過執(zhí)行累加操作的更新是非冪等。 |
Delete | _ | Delete 方法一般用于刪除資源。該操作則分情況來判斷是不是滿足冪等,當(dāng)根據(jù)唯一值進(jìn)行刪除時(shí),刪除同一個(gè)數(shù)據(jù)多次執(zhí)行效果一樣。不過需要注意,帶查詢條件的刪除則就不一定滿足冪等了。例如在根據(jù)條件刪除一批數(shù)據(jù)后,這時(shí)候新增加了一條數(shù)據(jù)也滿足條件,然后又執(zhí)行了一次刪除,那么將會(huì)導(dǎo)致新增加的這條滿足條件數(shù)據(jù)也被刪除。 |
為什么要實(shí)現(xiàn)冪等性校驗(yàn)
在接口調(diào)用時(shí)一般情況下都能正常返回信息不會(huì)重復(fù)提交,不過在遇見以下情況時(shí)可以就會(huì)出現(xiàn)問題,如:
前端重復(fù)提交表單:在填寫一些表格時(shí)候,用戶填寫完成提交,很多時(shí)候會(huì)因網(wǎng)絡(luò)波動(dòng)沒有及時(shí)對(duì)用戶做出提交成功響應(yīng),致使用戶認(rèn)為沒有成功提交,然后一直點(diǎn)提交按鈕,這時(shí)就會(huì)發(fā)生重復(fù)提交表單請(qǐng)求。
用戶惡意進(jìn)行刷單:例如在實(shí)現(xiàn)用戶投票這種功能時(shí),如果用戶針對(duì)一個(gè)用戶進(jìn)行重復(fù)提交投票,這樣會(huì)導(dǎo)致接口接收到用戶重復(fù)提交的投票信息,這樣會(huì)使投票結(jié)果與事實(shí)嚴(yán)重不符。
接口超時(shí)重復(fù)提交:很多時(shí)候 HTTP 客戶端工具都默認(rèn)開啟超時(shí)重試的機(jī)制,尤其是第三方調(diào)用接口時(shí)候,為了防止網(wǎng)絡(luò)波動(dòng)超時(shí)等造成的請(qǐng)求失敗,都會(huì)添加重試機(jī)制,導(dǎo)致一個(gè)請(qǐng)求提交多次。
消息進(jìn)行重復(fù)消費(fèi):當(dāng)使用 MQ 消息中間件時(shí)候,如果發(fā)生消息中間件出現(xiàn)錯(cuò)誤未及時(shí)提交消費(fèi)信息,導(dǎo)致發(fā)生重復(fù)消費(fèi)。
使用冪等性最大的優(yōu)勢(shì)在于使接口保證任何冪等性操作,免去因重試等造成系統(tǒng)產(chǎn)生的未知的問題。
如何實(shí)現(xiàn)接口的冪等性校驗(yàn)
網(wǎng)上流傳最多的應(yīng)該是四種方式去實(shí)現(xiàn)接口的冪等性校驗(yàn),接下來我們來一個(gè)個(gè)盤點(diǎn)。
數(shù)據(jù)庫(kù)唯一主鍵
「方案描述」 數(shù)據(jù)庫(kù)唯一主鍵的實(shí)現(xiàn)主要是利用數(shù)據(jù)庫(kù)中主鍵唯一約束的特性,一般來說唯一主鍵比較適用于“插入”時(shí)的冪等性,其能保證一張表中只能存在一條帶該唯一主鍵的記錄。使用數(shù)據(jù)庫(kù)唯一主鍵完成冪等性時(shí)需要注意的是,該主鍵一般來說并不是使用數(shù)據(jù)庫(kù)中自增主鍵,而是使用分布式 ID 充當(dāng)主鍵(或者使用其他算法生成的全局唯一的id),這樣才能能保證在分布式環(huán)境下 ID 的全局唯一性。
「適用操作:」 插入操作 刪除操作
「使用限制:」 需要生成全局唯一主鍵 ID;
「主要流程:」 ① 客戶端執(zhí)行創(chuàng)建請(qǐng)求,調(diào)用服務(wù)端接口。② 服務(wù)端執(zhí)行業(yè)務(wù)邏輯,生成一個(gè)分布式 ID,將該 ID 充當(dāng)待插入數(shù)據(jù)的主鍵,然后執(zhí)數(shù)據(jù)插入操作,運(yùn)行對(duì)應(yīng)的 SQL 語句。③ 服務(wù)端將該條數(shù)據(jù)插入數(shù)據(jù)庫(kù)中,如果插入成功則表示沒有重復(fù)調(diào)用接口。如果拋出主鍵重復(fù)異常,則表示數(shù)據(jù)庫(kù)中已經(jīng)存在該條記錄,返回錯(cuò)誤信息到客戶端。
數(shù)據(jù)庫(kù)樂觀鎖
「方案描述:」 數(shù)據(jù)庫(kù)樂觀鎖方案一般只能適用于執(zhí)行“更新操作”的過程,我們可以提前在對(duì)應(yīng)的數(shù)據(jù)表中多添加一個(gè)字段,充當(dāng)當(dāng)前數(shù)據(jù)的版本標(biāo)識(shí)。這樣每次對(duì)該數(shù)據(jù)庫(kù)該表的這條數(shù)據(jù)執(zhí)行更新時(shí),都會(huì)將該版本標(biāo)識(shí)作為一個(gè)條件,值為上次待更新數(shù)據(jù)中的版本標(biāo)識(shí)的值。「適用操作:」 更新操作
「使用限制:」 需要數(shù)據(jù)庫(kù)對(duì)應(yīng)業(yè)務(wù)表中添加額外字段;
防重 Token 令牌
「方案描述:」 針對(duì)客戶端連續(xù)點(diǎn)擊或者調(diào)用方的超時(shí)重試等情況,例如提交訂單,此種操作就可以用 Token 的機(jī)制實(shí)現(xiàn)防止重復(fù)提交。簡(jiǎn)單的說就是調(diào)用方在調(diào)用接口的時(shí)候先向后端請(qǐng)求一個(gè)全局 ID(Token),請(qǐng)求的時(shí)候攜帶這個(gè)全局 ID 一起請(qǐng)求(Token 最好將其放到 Headers 中),后端需要對(duì)這個(gè) Token 作為 Key,用戶信息作為 Value 到 Redis 中進(jìn)行鍵值內(nèi)容校驗(yàn),如果 Key 存在且 Value 匹配就執(zhí)行刪除命令,然后正常執(zhí)行后面的業(yè)務(wù)邏輯。如果不存在對(duì)應(yīng)的 Key 或 Value 不匹配就返回重復(fù)執(zhí)行的錯(cuò)誤信息,這樣來保證冪等操作。
「適用操作:」 插入操作 更新操作 刪除操作
「使用限制:」 需要生成全局唯一 Token 串;需要使用第三方組件 Redis 進(jìn)行數(shù)據(jù)效驗(yàn);
redis
「方案描述:」
第四種是我覺著用著挺方便的,但是實(shí)用性應(yīng)該不大,而且和第三種類似,我們可以把接口名加請(qǐng)求參數(shù)通過算法生成一個(gè)全局唯一的id,然后 存到redis中,如果在一定時(shí)間請(qǐng)求多次,我們就直接拒絕。
「適用操作:」 插入操作 更新操作 刪除操作
「使用限制:」 需要使用第三方組件 Redis 進(jìn)行數(shù)據(jù)效驗(yàn);
如何將這幾種方式都組裝到一起
我使用了Java自帶的注解以及設(shè)計(jì)模式中的策略模式,我們可以在注解中直接指定冪等性校驗(yàn)的方式,當(dāng)然也可以在配置文件中指定,但是直接在注解中指定更加靈活。
但是,由于最近時(shí)間比較忙,天天被某些人卷,很少有時(shí)間去完善,目前只是實(shí)現(xiàn)了redis和防重 Token 令牌兩種方式的。以下是部分代碼
「自定義注解」
packageorg.example.annotation; importjava.lang.annotation.*; /** *@authorzrq */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @Documented public@interfaceRequestMany{ /** *策略 *@return */ Stringvalue()default""; /** *過期時(shí)間 *@return */ longexpireTime()default0; }
「定義切面」
packageorg.example.aop; importorg.aspectj.lang.ProceedingJoinPoint; importorg.aspectj.lang.annotation.Around; importorg.aspectj.lang.annotation.Aspect; importorg.aspectj.lang.reflect.MethodSignature; importorg.example.annotation.RequestMany; importorg.example.factory.RequestManyStrategy; importorg.springframework.beans.factory.annotation.Autowired; importorg.springframework.stereotype.Component; importorg.springframework.util.DigestUtils; importorg.springframework.web.context.request.RequestContextHolder; importorg.springframework.web.context.request.ServletRequestAttributes; importjavax.servlet.http.HttpServletRequest; importjava.lang.reflect.Method; importjava.util.Arrays; importjava.util.Map; importjava.util.stream.Collectors; /** *@authorzrq *@ClassNameRequestManyValidationAspect *@date2023/11/229:14 *@DescriptionTODO */ @Aspect @Component publicclassRequestManyValidationAspect{ @Autowired privateMapidempotentStrategies; @Around("@annotation(org.example.annotation.RequestMany)") publicObjectvalidateIdempotent(ProceedingJoinPointjoinPoint)throwsThrowable{ MethodSignaturemethodSignature=(MethodSignature)joinPoint.getSignature(); Methodmethod=methodSignature.getMethod(); RequestManyrequestMany=method.getAnnotation(RequestMany.class); Stringstrategy=requestMany.value();//獲取注解中配置的策略名稱 Integertime=(int)requestMany.expireTime();//獲取注解中配置的策略名稱 if(!idempotentStrategies.containsKey(strategy)){ thrownewIllegalArgumentException("Invalididempotentstrategy:"+strategy); } Stringkey=generateKey(joinPoint);//根據(jù)方法參數(shù)等生成唯一的key RequestManyStrategyidempotentStrategy=idempotentStrategies.get(strategy); idempotentStrategy.validate(key,time); returnjoinPoint.proceed(); } privateStringgenerateKey(ProceedingJoinPointjoinPoint){ //獲取類名 StringclassName=joinPoint.getTarget().getClass().getSimpleName(); //獲取方法名 MethodSignaturemethodSignature=(MethodSignature)joinPoint.getSignature(); StringmethodName=methodSignature.getMethod().getName(); //獲取方法參數(shù) Object[]args=joinPoint.getArgs(); StringargString=Arrays.stream(args) .map(Object::toString) .collect(Collectors.joining(",")); //獲取請(qǐng)求攜帶的Token HttpServletRequestrequest=((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest(); Stringtoken=request.getHeader("token"); //生成唯一的key Stringkey=className+":"+methodName+":"+argString+":"+token; Stringmd5Password=DigestUtils.md5DigestAsHex(key.getBytes()); returnmd5Password; } }
「處理異?!?/strong>
packageorg.example.exception; /** *運(yùn)行時(shí)異常 *@authorbinbin.hou *@since0.0.1 */ publicclassRequestManyValidationExceptionextendsRuntimeException{ publicRequestManyValidationException(){ } publicRequestManyValidationException(Stringmessage){ super(message); } publicRequestManyValidationException(Stringmessage,Throwablecause){ super(message,cause); } publicRequestManyValidationException(Throwablecause){ super(cause); } publicRequestManyValidationException(Stringmessage,Throwablecause,booleanenableSuppression,booleanwritableStackTrace){ super(message,cause,enableSuppression,writableStackTrace); } }
「模式工廠」
packageorg.example.factory; importorg.example.exception.RequestManyValidationException; /** *@authorzrq *@ClassNameRequestManyStrategy *@date2023/11/229:04 *@DescriptionTODO */ publicinterfaceRequestManyStrategy{ voidvalidate(Stringkey,Integertime)throwsRequestManyValidationException; }
「模式實(shí)現(xiàn)01」
packageorg.example.factory.impl; importorg.example.exception.RequestManyValidationException; importorg.example.factory.RequestManyStrategy; importorg.example.utils.RedisCache; importorg.springframework.stereotype.Component; importjavax.annotation.Resource; importjava.util.concurrent.TimeUnit; /** *@authorzrq *@ClassNameRedisIdempotentStrategy *@date2023/11/229:07 *@DescriptionTODO */ @Component publicclassRedisIdempotentStrategyimplementsRequestManyStrategy{ @Resource privateRedisCacheredisCache; @Override publicvoidvalidate(Stringkey,Integertime)throwsRequestManyValidationException{ if(redisCache.hasKey(key)){ thrownewRequestManyValidationException("請(qǐng)求次數(shù)過多"); }else{ redisCache.setCacheObject(key,"1",time,TimeUnit.MINUTES); } } }
「模式實(shí)現(xiàn)02」
packageorg.example.factory.impl; importorg.example.exception.RequestManyValidationException; importorg.example.factory.RequestManyStrategy; importorg.example.utils.RedisCache; importorg.springframework.data.redis.connection.RedisConnectionFactory; importorg.springframework.data.redis.connection.jedis.JedisConnectionFactory; importorg.springframework.data.redis.core.RedisTemplate; importorg.springframework.data.redis.serializer.StringRedisSerializer; importorg.springframework.stereotype.Component; importorg.springframework.web.context.request.RequestContextHolder; importorg.springframework.web.context.request.ServletRequestAttributes; importjavax.annotation.Resource; importjavax.servlet.http.HttpServletRequest; /** *@authorzrq *@ClassNameTokenIdempotentStrategy *@date2023/11/229:13 *@DescriptionTODO */ @Component publicclassTokenIdempotentStrategyimplementsRequestManyStrategy{ @Resource privateRedisCacheredisCache; @Override publicvoidvalidate(Stringkey,Integertime)throwsRequestManyValidationException{ HttpServletRequestrequest=((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest(); Stringtoken=request.getHeader("token"); if(token==null||token.isEmpty()){ thrownewRequestManyValidationException("未授權(quán)的token"); } //根據(jù)key和token執(zhí)行冪等性校驗(yàn) booleanisDuplicateRequest=performTokenValidation(key,token); if(!isDuplicateRequest){ thrownewRequestManyValidationException("多次請(qǐng)求"); } } privatebooleanperformTokenValidation(Stringkey,Stringtoken){ //執(zhí)行根據(jù)Token進(jìn)行冪等性校驗(yàn)的邏輯 //這里可以使用你選擇的合適的方法,比如將Token存儲(chǔ)到數(shù)據(jù)庫(kù)或緩存中,然后檢查是否已存在 StringstoredToken=redisCache.getCacheObject(key); //比較存儲(chǔ)的Token和當(dāng)前請(qǐng)求的Token是否一致 returntoken.equals(storedToken); } }
「redisutil類」
packageorg.example.utils; importlombok.extern.slf4j.Slf4j; importorg.springframework.beans.factory.annotation.Autowired; importorg.springframework.data.redis.connection.BitFieldSubCommands; importorg.springframework.data.redis.core.*; importorg.springframework.stereotype.Component; importjava.util.*; importjava.util.concurrent.TimeUnit; @SuppressWarnings(value={"unchecked","rawtypes"}) @Component @Slf4j publicclassRedisCache { @Autowired publicRedisTemplateredisTemplate; @Autowired privateStringRedisTemplatestringRedisTemplate; /** *緩存基本的對(duì)象,Integer、String、實(shí)體類等 * *@paramkey緩存的鍵值 *@paramvalue緩存的值 */ publicvoidsetCacheObject(finalStringkey,finalTvalue) { redisTemplate.opsForValue().set(key,value); } /** *緩存基本的對(duì)象,Integer、String、實(shí)體類等 * *@paramkey緩存的鍵值 *@paramvalue緩存的值 *@paramtimeout時(shí)間 *@paramtimeUnit時(shí)間顆粒度 */ public voidsetCacheObject(finalStringkey,finalTvalue,finalIntegertimeout,finalTimeUnittimeUnit) { redisTemplate.opsForValue().set(key,value,timeout,timeUnit); } /** *設(shè)置有效時(shí)間 * *@paramkeyRedis鍵 *@paramtimeout超時(shí)時(shí)間 *@returntrue=設(shè)置成功;false=設(shè)置失敗 */ publicbooleanexpire(finalStringkey,finallongtimeout) { returnexpire(key,timeout,TimeUnit.SECONDS); } publicbooleanhasKey(finalStringkey) { returnBoolean.TRUE.equals(redisTemplate.hasKey(key)); } /** *設(shè)置有效時(shí)間 * *@paramkeyRedis鍵 *@paramtimeout超時(shí)時(shí)間 *@paramunit時(shí)間單位 *@returntrue=設(shè)置成功;false=設(shè)置失敗 */ publicbooleanexpire(finalStringkey,finallongtimeout,finalTimeUnitunit) { returnredisTemplate.expire(key,timeout,unit); } /** *獲得緩存的基本對(duì)象。 * *@paramkey緩存鍵值 *@return緩存鍵值對(duì)應(yīng)的數(shù)據(jù) */ public TgetCacheObject(finalStringkey) { ValueOperations operation=redisTemplate.opsForValue(); returnoperation.get(key); } /** *刪除單個(gè)對(duì)象 * *@paramkey */ publicbooleandeleteObject(finalStringkey) { returnredisTemplate.delete(key); } /** *刪除集合對(duì)象 * *@paramcollection多個(gè)對(duì)象 *@return */ publiclongdeleteObject(finalCollectioncollection) { returnredisTemplate.delete(collection); } /** *緩存List數(shù)據(jù) * *@paramkey緩存的鍵值 *@paramdataList待緩存的List數(shù)據(jù) *@return緩存的對(duì)象 */ public longsetCacheList(finalStringkey,finalList dataList) { Longcount=redisTemplate.opsForList().rightPushAll(key,dataList); returncount==null?0:count; } /** *獲得緩存的list對(duì)象 * *@paramkey緩存的鍵值 *@return緩存鍵值對(duì)應(yīng)的數(shù)據(jù) */ public List getCacheList(finalStringkey) { returnredisTemplate.opsForList().range(key,0,-1); } /** *緩存Set * *@paramkey緩存鍵值 *@paramdataSet緩存的數(shù)據(jù) *@return緩存數(shù)據(jù)的對(duì)象 */ public BoundSetOperations setCacheSet(finalStringkey,finalSet dataSet) { BoundSetOperations setOperation=redisTemplate.boundSetOps(key); Iterator it=dataSet.iterator(); while(it.hasNext()) { setOperation.add(it.next()); } returnsetOperation; } /** *獲得緩存的set * *@paramkey *@return */ public Set getCacheSet(finalStringkey) { returnredisTemplate.opsForSet().members(key); } /** *緩存Map * *@paramkey *@paramdataMap */ public voidsetCacheMap(finalStringkey,finalMap dataMap) { if(dataMap!=null){ redisTemplate.opsForHash().putAll(key,dataMap); } } /** *獲得緩存的Map * *@paramkey *@return */ public Map getCacheMap(finalStringkey) { returnredisTemplate.opsForHash().entries(key); } /** *往Hash中存入數(shù)據(jù) * *@paramkeyRedis鍵 *@paramhKeyHash鍵 *@paramvalue值 */ public voidsetCacheMapValue(finalStringkey,finalStringhKey,finalTvalue) { redisTemplate.opsForHash().put(key,hKey,value); } /** *獲取Hash中的數(shù)據(jù) * *@paramkeyRedis鍵 *@paramhKeyHash鍵 *@returnHash中的對(duì)象 */ public TgetCacheMapValue(finalStringkey,finalStringhKey) { HashOperations opsForHash=redisTemplate.opsForHash(); returnopsForHash.get(key,hKey); } /** *刪除Hash中的數(shù)據(jù) * *@paramkey *@paramhkey */ publicvoiddelCacheMapValue(finalStringkey,finalStringhkey) { HashOperationshashOperations=redisTemplate.opsForHash(); hashOperations.delete(key,hkey); } /** *獲取多個(gè)Hash中的數(shù)據(jù) * *@paramkeyRedis鍵 *@paramhKeysHash鍵集合 *@returnHash對(duì)象集合 */ public List getMultiCacheMapValue(finalStringkey,finalCollection
「配置文件」
如果要實(shí)現(xiàn)其他方式的話只需要實(shí)現(xiàn)下RequestManyStrategy模板方法,然后編寫自己的校驗(yàn)邏輯就可以。
以上代碼已經(jīng)上傳到github :https://github.com/Lumos-i/tools-and-frameworks
結(jié)語
大學(xué)過的可真快,轉(zhuǎn)眼就大三了,自己的技術(shù)還是不行,跟別人的差距還有很大距離,希望自己能在有限的時(shí)間里學(xué)到更多有用的知識(shí),同時(shí)也希望在明年的這個(gè)時(shí)候可以坐在辦公室里敲代碼。突然想到高中時(shí)中二的一句話“聽聞少年二字,應(yīng)與平庸相斥”,誰不希望這樣呢,奈何身邊大佬太多,現(xiàn)在只能追趕別人的腳步。。。
審核編輯:黃飛
-
接口
+關(guān)注
關(guān)注
33文章
8447瀏覽量
150723 -
數(shù)據(jù)庫(kù)
+關(guān)注
關(guān)注
7文章
3752瀏覽量
64233 -
Redis
+關(guān)注
關(guān)注
0文章
370瀏覽量
10830
原文標(biāo)題:策略模式實(shí)現(xiàn)接口的冪等性校驗(yàn)
文章出處:【微信號(hào):芋道源碼,微信公眾號(hào):芋道源碼】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論