Java結(jié)合redis實(shí)現(xiàn)接口防重復(fù)提交
redis 接口防重
技術(shù)點(diǎn):redis/aop
說(shuō)明:
簡(jiǎn)易版本實(shí)現(xiàn)防止重復(fù)提交,適用范圍為所有接口適用,采用注解方式,在需要防重的接口上使用注解,可以設(shè)置防重時(shí)效。
場(chǎng)景:
在系統(tǒng)中,經(jīng)常會(huì)有一些接口會(huì)莫名其妙的被調(diào)用兩次,可能在冪等接口中不會(huì)存在太大的問題,但是非冪等接口的處理就會(huì)導(dǎo)致出現(xiàn)臟數(shù)據(jù),甚至影響系統(tǒng)的正確性。
選型參考:
在常見的防重處理分為多種,粗分為前端處理,后端處理
前端處理分為:
- 在按鈕觸發(fā)后便將按鈕置灰,設(shè)置為不可用,在接口調(diào)用成功后回調(diào)處理,將按鈕恢復(fù)
- 發(fā)送請(qǐng)求時(shí),設(shè)置一個(gè)狀態(tài),在接口請(qǐng)求時(shí)去獲取狀態(tài),查看在保護(hù)期是否已經(jīng)有調(diào)用,思路與第一條類似
后端處理分為:
- 版本號(hào),在數(shù)據(jù)表中增加version字段,在我們需要進(jìn)行防重的接口請(qǐng)求到達(dá)后端后,sql處理時(shí)增加版本號(hào)條件(切記:每次在修改操作后需要對(duì)版本號(hào)進(jìn)行加1哦),如果不一致則不進(jìn)行處理。這也是樂觀鎖實(shí)現(xiàn)的一種思路。
- redis,即本文所述方式
選型原因
- 在系統(tǒng)安全中,防重復(fù)提交也是比較重要的一個(gè)指標(biāo),也就是接口冪等性。所以我們?cè)谌粘5南到y(tǒng)開發(fā)中,一般使用的是簡(jiǎn)化版的放重復(fù)。也就是僅僅通過(guò)前端來(lái)進(jìn)行防重控制,但是這樣也是具有風(fēng)險(xiǎn)性的。如果在涉及比較重要的數(shù)據(jù)的時(shí)候,可能往往會(huì)有熱心同行來(lái)幫我們找bug,對(duì)于他們可以直接通過(guò)抓報(bào)文的方式拿到我們的交互信息,以此來(lái)進(jìn)行各種騷操作(羊毛黨做派,當(dāng)然了,如果要避免接口攻擊,我們還要設(shè)置ip請(qǐng)求限制,小黑屋,防DDOS等各種防御工作,此處只講防重咯)。所以我們?cè)谥匾獢?shù)據(jù)處理時(shí)在后端也是同樣需要進(jìn)行防重處理的。
- 防重提交的方式非常非常多,如上提出了四種方式,也只是冰山一角了。針對(duì)于后端側(cè)防重,如上簡(jiǎn)述了兩種方式。個(gè)人覺得在不同的時(shí)機(jī)可以進(jìn)行不同的選擇。如果我們?cè)陧?xiàng)目初期,個(gè)人覺得使用版本號(hào)處理更為合適一點(diǎn),這樣會(huì)降低對(duì)第三方工具的依賴,因?yàn)槲覀冊(cè)诿考尤胍粋€(gè)新東西的時(shí)候都是會(huì)增加系統(tǒng)的復(fù)雜性的。我們?cè)诳紤]性能,安全,可靠的時(shí)候就會(huì)多出一個(gè)事項(xiàng),有點(diǎn)給自己找事做的樣子。但是如果我們的系統(tǒng)已經(jīng)較為平穩(wěn)了,那么此時(shí)對(duì)數(shù)據(jù)庫(kù)進(jìn)行增加字段雖然也不會(huì)太難,但是會(huì)改動(dòng)一些代碼。驚喜總是從這些地方來(lái)的。使用redis我覺得就要合適一些了,我們只需要面向切面進(jìn)行編程,一處編寫,處處可用。從代碼和擴(kuò)展來(lái)講redis就更為合適了。
以上內(nèi)容都是瞎BB的,其實(shí)我也是個(gè)菜鳥,歡迎各位大佬提建議或者意見,大家共同進(jìn)步,共同完善,讓java圈充滿激情四射的愛。
代碼樣例
@PostMapping("/user/update") @ApiOperation(value = "修改用戶信息", notes = "修改用戶信息", tags = "user module") @AvoidReSubmit(expireTime = 1000 * 3) public void update(@RequestBody User user){ userMapper.updateById(user); }
具體代碼實(shí)現(xiàn)
// 定義自定義注解,設(shè)置注解參數(shù)默認(rèn)值 package top.withu.gaof.freehope.annotate; import java.lang.annotation.*; /** * @author Gaofan * @date 2019年10月12日 下午2:54:45 * @describe 防止重復(fù)提交 */ @Target({ ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface AvoidReSubmit { /** * 失效時(shí)間,即可以第二次提交間隔時(shí)長(zhǎng) * @return */ long expireTime() default 30 * 1000L; }
// 定義切面進(jìn)行處理 package top.withu.gaof.freehope.aspect; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ValueOperations; import org.springframework.stereotype.Component; import top.withu.gaof.freehope.annotate.AvoidReSubmit; import javax.annotation.Resource; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.concurrent.TimeUnit; /** * @Description: TODO * @Author: gaofan * @Date: 2019/10/12 16:10 * @Copyright: 2019 www.blog.freehope.top Inc. All rights reserved. **/ @Aspect @Component public class AvoidResumitAspect { @Resource private RedisTemplate redisTemplate; /** * 定義匹配規(guī)則,以便于后續(xù)攔截直接攔截submit方法,不用重復(fù)表達(dá)式 */ @Pointcut(value = "@annotation(top.withu.gaof.freehope.annotate.AvoidReSubmit)") public void submit() { } @Before("submit()&&@annotation(avoidReSubmit)") public void doBefore(JoinPoint joinPoint, AvoidReSubmit avoidReSubmit) { // 拼裝參數(shù) StringBuffer sb = new StringBuffer(); for(Object object : joinPoint.getArgs()){ sb.append(object); } String key = md5(sb.toString()); long expireTime = avoidReSubmit.expireTime(); ValueOperations valueOperations = redisTemplate.opsForValue(); Object object = valueOperations.get(key); if(null != object){ throw new RuntimeException("您已經(jīng)提交了請(qǐng)求,請(qǐng)不要重復(fù)提交哦!"); } valueOperations.set(key, 1, expireTime, TimeUnit.MILLISECONDS); } @Around("submit()&&@annotation(avoidReSubmit)") public Object doAround(ProceedingJoinPoint proceedingJoinPoint, AvoidReSubmit avoidReSubmit) throws Throwable { System.out.println("環(huán)繞通知:"); Object result = null; result = proceedingJoinPoint.proceed(); return result; } @After("submit()") public void doAfter() { System.out.println("******攔截后的邏輯******"); } private String md5(String str){ if (str == null || str.length() == 0) { throw new IllegalArgumentException("String to encript cannot be null or zero length"); } StringBuffer hexString = new StringBuffer(); try { MessageDigest md = MessageDigest.getInstance("MD5"); md.update(str.getBytes()); byte[] hash = md.digest(); for (int i = 0; i < hash.length; i++) { if ((0xff & hash[i]) < 0x10) { hexString.append("0" + Integer.toHexString((0xFF & hash[i]))); } else { hexString.append(Integer.toHexString(0xFF & hash[i])); } } } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } return hexString.toString(); } }
思路:
簡(jiǎn)單的通過(guò)redis實(shí)現(xiàn),估計(jì)版本在網(wǎng)上非常多了。這里的一個(gè)思路還是mark一下,現(xiàn)在我這代碼只有我和上帝知道什么意思,我怕一個(gè)月以后就只有上帝知道了。
- 自定義注解,注解中申明有效時(shí)間
- 使用aop切面攔截自定義注解,獲取注解中有效時(shí)間參數(shù),此處默認(rèn)設(shè)置保護(hù)期為30 * 1000L,單位毫秒
- 在切面中獲取接口請(qǐng)求的參數(shù),將參數(shù)拼接成string,然后進(jìn)行md5,這樣操作是因?yàn)榻档蚹ey長(zhǎng)度,避免看起來(lái)過(guò)于惡心。但是這里有一個(gè)情況沒有進(jìn)行測(cè)試,那就是key碰撞的問題。在大量數(shù)據(jù)操作下是否會(huì)產(chǎn)生相同key值。
- 使用md5加密后的key值到redis中查詢,如果存在記錄則表明已經(jīng)有接口訪問且處于保護(hù)期,不可繼續(xù)提交。此處使用異常處理。如果不存在記錄,則表明此接口在保護(hù)期內(nèi)沒有訪問過(guò),則不操作。此處的場(chǎng)景在使用時(shí)可以根據(jù)自己需求而定。
- 此處在環(huán)繞通知和after通知均沒有操作,因?yàn)槲覀冎皇菍?duì)于放重復(fù)提交處理,業(yè)務(wù)場(chǎng)景中不存在后處理的情況,故而沒有具體實(shí)現(xiàn)。
到此這篇關(guān)于Java結(jié)合redis實(shí)現(xiàn)接口防重復(fù)提交的文章就介紹到這了,更多相關(guān)redis 接口防重復(fù)提交內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java?IO網(wǎng)絡(luò)模型實(shí)現(xiàn)解析
這篇文章主要為大家介紹了Java?IO網(wǎng)絡(luò)模型實(shí)現(xiàn)解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03通過(guò)volatile驗(yàn)證線程之間的可見性
這篇文章主要介紹了通過(guò)volatile驗(yàn)證線程之間的可見性,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-10-10Java中SimpleDateFormat方法超詳細(xì)分析
這篇文章主要給大家介紹了關(guān)于Java中SimpleDateFormat方法超詳細(xì)分析的相關(guān)資料,SimpleDateFormat 是一個(gè)以國(guó)別敏感的方式格式化和分析數(shù)據(jù)的具體類,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-08-08Java對(duì)象集合按照指定元素順序排序的實(shí)現(xiàn)
最近在對(duì)一個(gè)集合列表的數(shù)據(jù)進(jìn)行排序,需求是要集合數(shù)據(jù)按照一個(gè)排序狀態(tài)值進(jìn)行排序,而這個(gè)狀態(tài)值,不是按照從小到大這樣的順序排序的,而是要按照特定的順序,所以本文給大家介紹了Java對(duì)象集合按照指定元素順序排序的實(shí)現(xiàn),需要的朋友可以參考下2024-07-07JavaWeb中的簡(jiǎn)單分頁(yè)完整代碼(推薦)
這次主要是講解一下通過(guò)登錄后對(duì)得到的數(shù)據(jù)進(jìn)行分頁(yè),首先我們新建一個(gè)登錄頁(yè)面login.jsp,因?yàn)槲覀冎饕獙W(xué)習(xí)的分頁(yè),所以登錄驗(yàn)證的部分沒有提到。關(guān)于javaweb中的分頁(yè)代碼大家通過(guò)本文學(xué)習(xí)吧2016-11-11Mybatis中SqlSession下的四大對(duì)象之執(zhí)行器(executor)
mybatis中sqlsession下的四大對(duì)象是指:executor, statementHandler,parameterHandler,resultHandler對(duì)象。這篇文章主要介紹了Mybatis中SqlSession下的四大對(duì)象之執(zhí)行器(executor),需要的朋友可以參考下2019-04-04Spring Security實(shí)現(xiàn)驗(yàn)證碼登錄功能
這篇文章主要介紹了Spring Security實(shí)現(xiàn)驗(yàn)證碼登錄功能,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-01-01Spring?@Scheduled定時(shí)器注解使用方式
這篇文章主要介紹了Spring?@Scheduled定時(shí)器注解使用方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-08-08Javaweb請(qǐng)求轉(zhuǎn)發(fā)及重定向?qū)崿F(xiàn)詳解
這篇文章主要介紹了Javaweb請(qǐng)求轉(zhuǎn)發(fā)及重定向?qū)崿F(xiàn)詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-07-07