0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

為什么要實(shí)現(xiàn)冪等性校驗(yàn) 如何實(shí)現(xiàn)接口的冪等性校驗(yàn)

jf_ro2CN3Fa ? 來源:芋道源碼 ? 2024-02-20 14:14 ? 次閱讀

背景

最近在小組同學(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í)間顆粒度
*/
publicvoidsetCacheObject(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ù)
*/
publicTgetCacheObject(finalStringkey)
{
ValueOperationsoperation=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ì)象
*/
publiclongsetCacheList(finalStringkey,finalListdataList)
{
Longcount=redisTemplate.opsForList().rightPushAll(key,dataList);
returncount==null?0:count;
}

/**
*獲得緩存的list對(duì)象
*
*@paramkey緩存的鍵值
*@return緩存鍵值對(duì)應(yīng)的數(shù)據(jù)
*/
publicListgetCacheList(finalStringkey)
{
returnredisTemplate.opsForList().range(key,0,-1);
}

/**
*緩存Set
*
*@paramkey緩存鍵值
*@paramdataSet緩存的數(shù)據(jù)
*@return緩存數(shù)據(jù)的對(duì)象
*/
publicBoundSetOperationssetCacheSet(finalStringkey,finalSetdataSet)
{
BoundSetOperationssetOperation=redisTemplate.boundSetOps(key);
Iteratorit=dataSet.iterator();
while(it.hasNext())
{
setOperation.add(it.next());
}
returnsetOperation;
}

/**
*獲得緩存的set
*
*@paramkey
*@return
*/
publicSetgetCacheSet(finalStringkey)
{
returnredisTemplate.opsForSet().members(key);
}

/**
*緩存Map
*
*@paramkey
*@paramdataMap
*/
publicvoidsetCacheMap(finalStringkey,finalMapdataMap)
{
if(dataMap!=null){
redisTemplate.opsForHash().putAll(key,dataMap);
}
}

/**
*獲得緩存的Map
*
*@paramkey
*@return
*/
publicMapgetCacheMap(finalStringkey)
{
returnredisTemplate.opsForHash().entries(key);
}

/**
*往Hash中存入數(shù)據(jù)
*
*@paramkeyRedis鍵
*@paramhKeyHash鍵
*@paramvalue值
*/
publicvoidsetCacheMapValue(finalStringkey,finalStringhKey,finalTvalue)
{
redisTemplate.opsForHash().put(key,hKey,value);
}

/**
*獲取Hash中的數(shù)據(jù)
*
*@paramkeyRedis鍵
*@paramhKeyHash鍵
*@returnHash中的對(duì)象
*/
publicTgetCacheMapValue(finalStringkey,finalStringhKey)
{
HashOperationsopsForHash=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ì)象集合
*/
publicListgetMultiCacheMapValue(finalStringkey,finalCollectionhKeys)
{
returnredisTemplate.opsForHash().multiGet(key,hKeys);
}

/**
*獲得緩存的基本對(duì)象列表
*
*@parampattern字符串前綴
*@return對(duì)象列表
*/
publicCollectionkeys(finalStringpattern)
{
returnredisTemplate.keys(pattern);
}

publicBooleansign(Stringkey,intday,booleansign){
returnredisTemplate.opsForValue().setBit(key,day,sign);
}

publicListresult(Stringkey,intday,intnum){
Listresult=stringRedisTemplate.opsForValue().bitField(key,BitFieldSubCommands.create()
.get(BitFieldSubCommands.BitFieldType.unsigned(day)).valueAt(0)
);
returnresult;
}

publicLongbigCount(Stringkey){
Longexecute=(Long)redisTemplate.execute((RedisCallback)cbk->cbk.bitCount(key.getBytes()));
returnexecute;
}
publicvoidsetCacheZSet(Stringname,Tkey,doublenum){
if(key!=null){
redisTemplate.opsForZSet().add(name,key,num);
}
}
publicLonggetCacheZSetRanking(Stringname,Stringkey){
LongaLong=null;
if(key!=null){
aLong=redisTemplate.opsForZSet().reverseRank(name,key);
}
returnaLong;
}
publicDoublegetCacheZSetScore(Stringname,Tkey){
Doublescore=null;
if(key!=null){
score=redisTemplate.opsForZSet().score(name,key);
}
returnscore;
}
publicSetgetCacheZSetLookTop(Stringname,intnums){
Setset=null;
if(name!=null){
set=redisTemplate.opsForZSet().reverseRange(name,0,nums);
}
returnset;
}
publicLonggetCacheZSetSize(Stringkey){
LongaLong=null;
if(key!=null){
aLong=redisTemplate.opsForZSet().zCard(key);
}
returnaLong;
}

publicLongdeleteCacheZSet(Stringkey,Tvalue){
Longremove=null;
if(key!=null){
remove=redisTemplate.opsForZSet().remove(key,value);
}
returnremove;
}
publicListgetBitMap(Stringkey,Integerday){
ListbitFieldList=(List)redisTemplate.execute((RedisCallback>)cbk
->cbk.bitField(key.getBytes(),BitFieldSubCommands.create().get(BitFieldSubCommands.BitFieldType.unsigned(day)).valueAt(0)));
returnbitFieldList;
}
publicLongincrExpire(Stringkey,longtime){
Longcount=redisTemplate.opsForValue().increment(key,1);
if(count!=null&&count==1){
redisTemplate.expire(key,time,TimeUnit.SECONDS);
}
returncount;
}
publicbooleanremoveList(StringlistName,Integercount,Stringvalue){
redisTemplate.opsForList().remove(listName,count,value);
returntrue;
}
}

「配置文件」

43e1f616-cf1d-11ee-a297-92fbcf53809c.jpg

如果要實(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)在只能追趕別人的腳步。。。

審核編輯:黃飛

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 接口
    +關(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)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    離線計(jì)算中的和DataWorks中的相關(guān)事項(xiàng)

    比較容易。只需要設(shè)置導(dǎo)入的MaxCompute表的清洗規(guī)則為“寫入前清理已有數(shù)據(jù)Insert Overwr”即可。這樣數(shù)據(jù)在導(dǎo)入的過程中會(huì)先清空數(shù)據(jù)后再導(dǎo)入,從而實(shí)現(xiàn)
    發(fā)表于 02-27 13:24

    循環(huán)冗余校驗(yàn)碼的單片機(jī)及CPLD 實(shí)現(xiàn)

    循環(huán)冗余碼校驗(yàn)(CRC)是一種可靠很高的串行數(shù)據(jù)校驗(yàn)方法。介紹循環(huán)冗余碼校驗(yàn)的基本原理,并分別用單片機(jī)和CPLD 作了循環(huán)冗余碼校驗(yàn)的軟件
    發(fā)表于 04-16 14:19 ?16次下載

    容錯(cuò)系統(tǒng)中的自校驗(yàn)技術(shù)及實(shí)現(xiàn)方法

    : 闡述了自校驗(yàn)技術(shù)在容錯(cuò)系統(tǒng)中的作用,給出了自校驗(yàn)網(wǎng)絡(luò)實(shí)現(xiàn)原理及實(shí)現(xiàn)方法,指出用VHDL語言結(jié)合FPGA/CPLD是
    發(fā)表于 06-20 15:46 ?599次閱讀
    容錯(cuò)系統(tǒng)中的自<b class='flag-5'>校驗(yàn)</b>技術(shù)及<b class='flag-5'>實(shí)現(xiàn)</b>方法

    LTE系統(tǒng)的CRC校驗(yàn)算法及DSP實(shí)現(xiàn)

    通過對(duì)兩種常用CRC校驗(yàn)算法的研究分析,為TD-LTE測(cè)試儀表系統(tǒng)選擇了一種最優(yōu)的CRC校驗(yàn)算法,并在TMS320C64xDSP中實(shí)現(xiàn)。將CRC校驗(yàn)程序在CCS3.3中運(yùn)行,其結(jié)果驗(yàn)證
    發(fā)表于 02-23 14:58 ?30次下載

    電量測(cè)量裝置校驗(yàn)接口電路的實(shí)現(xiàn)方法

    :本文敘述在進(jìn)行電量測(cè)量裝置的高精度校驗(yàn)中,采用數(shù)字信號(hào)處理器TMS320F206及其與工業(yè)控制PC機(jī)(IPC)的ISA總線、雙口SRAM、高精度A/D轉(zhuǎn)換器接口電路的
    發(fā)表于 10-16 10:26 ?0次下載
    電量測(cè)量裝置<b class='flag-5'>校驗(yàn)</b>中<b class='flag-5'>接口</b>電路的<b class='flag-5'>實(shí)現(xiàn)</b>方法

    在高并發(fā)下怎么保證接口?

    前言 接口性問題,對(duì)于開發(fā)人員來說,是一個(gè)跟語言無關(guān)的公共問題。本文分享了一些解決這類問題非常實(shí)用的辦法,絕大部分內(nèi)容我在項(xiàng)目中實(shí)踐過的,給有需要的小伙伴一個(gè)參考。 不知道你有沒有遇到過這些場(chǎng)景
    的頭像 發(fā)表于 05-14 10:23 ?1765次閱讀
    在高并發(fā)下怎么保證<b class='flag-5'>接口</b>的<b class='flag-5'>冪</b><b class='flag-5'>等</b><b class='flag-5'>性</b>?

    CRC校驗(yàn)原理及實(shí)現(xiàn)

    作者:王超首發(fā):電子電路開發(fā)學(xué)習(xí)目錄前言CRC算法簡(jiǎn)介CRC計(jì)算CRC校驗(yàn)CRC計(jì)算的C語言實(shí)現(xiàn)CRC計(jì)算工具總結(jié)前言最近的工作中,實(shí)現(xiàn)對(duì)通...
    發(fā)表于 01-26 17:37 ?30次下載
    CRC<b class='flag-5'>校驗(yàn)</b>原理及<b class='flag-5'>實(shí)現(xiàn)</b>

    什么是?關(guān)于接口的解決方案

    這里的樂觀鎖指的是用樂觀鎖的原理去實(shí)現(xiàn),為數(shù)據(jù)字段增加一個(gè)version字段,當(dāng)數(shù)據(jù)需要更新時(shí),先去數(shù)據(jù)庫(kù)里獲取此時(shí)的version版本號(hào)
    發(fā)表于 10-09 10:19 ?1895次閱讀

    分析解決)的方法

    這個(gè)概念,是一個(gè)數(shù)學(xué)上的概念,即:f……(f(f(x))) = f(x)。用在計(jì)算機(jī)領(lǐng)域,指的是系統(tǒng)里的接口或方法對(duì)外的一種承諾,使用相同參數(shù)對(duì)同一資源重復(fù)調(diào)用某個(gè)接口或方法的結(jié)果
    的頭像 發(fā)表于 10-14 10:08 ?898次閱讀

    Spring Boot實(shí)現(xiàn)接口的4種方案

    是一個(gè)數(shù)學(xué)與計(jì)算機(jī)學(xué)概念,在數(shù)學(xué)中某一元運(yùn)算為時(shí),其作用在任一元素兩次后會(huì)和其作用一次的結(jié)果相同。
    的頭像 發(fā)表于 11-08 10:21 ?964次閱讀

    什么是實(shí)現(xiàn)原理

    在編程中一個(gè)操作的特點(diǎn)是其任意多次執(zhí)行所產(chǎn)生的影響均與一次執(zhí)行的影響相同。函數(shù),或
    發(fā)表于 01-05 10:40 ?5976次閱讀

    FPGA奇偶校驗(yàn)的基本原理及實(shí)現(xiàn)方法

    在數(shù)字電路中,數(shù)據(jù)的正確非常重要。為了保證數(shù)據(jù)的正確,在傳輸數(shù)據(jù)時(shí)需要添加一些冗余信息,以便在接收端進(jìn)行校驗(yàn)。其中一種常用的校驗(yàn)方式是奇偶校驗(yàn)
    的頭像 發(fā)表于 05-14 14:59 ?2848次閱讀
    FPGA奇偶<b class='flag-5'>校驗(yàn)</b>的基本原理及<b class='flag-5'>實(shí)現(xiàn)</b>方法

    一個(gè)注解,優(yōu)雅的實(shí)現(xiàn)接口!

    除了查詢和刪除之外,還有更新操作,同樣的更新操作在大多數(shù)場(chǎng)景下也是天然的,其例外是也會(huì)存在ABA的問題,更重要的是,比如執(zhí)行update table set a = a + 1 where v = 1這樣的更新就非等了。
    的頭像 發(fā)表于 08-26 14:36 ?845次閱讀
    一個(gè)注解,優(yōu)雅的<b class='flag-5'>實(shí)現(xiàn)</b><b class='flag-5'>接口</b><b class='flag-5'>冪</b><b class='flag-5'>等</b><b class='flag-5'>性</b>!

    基于接口解決方案

    接口是指無論調(diào)用接口的次數(shù)是一次還是多次,對(duì)于同一資源的操作都只會(huì)產(chǎn)生一次結(jié)果。換句話說,多次重復(fù)調(diào)用相同的
    的頭像 發(fā)表于 09-30 16:27 ?406次閱讀
    基于<b class='flag-5'>接口</b><b class='flag-5'>冪</b><b class='flag-5'>等</b><b class='flag-5'>性</b>解決方案

    探索LabVIEW編程接口原理與實(shí)踐

    原來是數(shù)學(xué)上的概念,在編程領(lǐng)域可以理解為:多次請(qǐng)求某一個(gè)資源或執(zhí)行某一個(gè)操作時(shí)應(yīng)該具有唯一同樣結(jié)果,也就是說,其任意多次執(zhí)行對(duì)資源
    的頭像 發(fā)表于 02-29 10:24 ?544次閱讀
    探索LabVIEW編程<b class='flag-5'>接口</b><b class='flag-5'>冪</b><b class='flag-5'>等</b><b class='flag-5'>性</b>原理與實(shí)踐