網(wǎng)站首頁(yè) 編程語(yǔ)言 正文
在項(xiàng)目中,用戶在操作某些重要操作時(shí),經(jīng)常會(huì)出現(xiàn)手抖,導(dǎo)致重復(fù)提交,該怎么處理呢?
? ? ? ? ?我的答案是交給前端,用戶提交的時(shí)候禁用按鈕,等接口返回后再啟用。
? ? ? ? ?哈哈,開個(gè)玩笑,今天給大家示范一下后端如何處理。
?首先通常我們?yōu)楹我苊獗韱沃貜?fù)提交?這其實(shí)跟接口冪等有關(guān),既然說到這里,那么我們就來回顧一下什么是接口冪等吧!
1. 接口冪等的定義:
? ? ? ? 一個(gè)接口多次調(diào)用而沒有副作用,那么我們稱之為接口冪等。所謂沒有副作用簡(jiǎn)單來講就是一個(gè)接口被執(zhí)行多次數(shù)據(jù)不會(huì)亂。每次返回的結(jié)果都一致。
? 2.哪些地方天然冪等,那么地方可能導(dǎo)致接口不冪等?
? ? ? ? ? ?基本業(yè)務(wù)操作中,查詢和刪除,天然冪等。更新和插入是接口不冪等的高發(fā)地。
? 3.業(yè)務(wù)中重點(diǎn)觸發(fā)場(chǎng)景
- 用戶重復(fù)操作:例如我們的系統(tǒng)中,用戶開方后點(diǎn)擊確認(rèn),手抖觸發(fā)了兩次,發(fā)了兩次請(qǐng)求給后臺(tái)。
- 代碼重試:很多RPC框架在進(jìn)行遠(yuǎn)程調(diào)用時(shí)都有重試機(jī)制,當(dāng)?shù)谝淮握?qǐng)求發(fā)出后在指定時(shí)間內(nèi)沒有收到回復(fù),會(huì)再發(fā)一次。例如dubbo,默認(rèn)2次。
- 消息重復(fù)消費(fèi):多次觸發(fā)消費(fèi)者消費(fèi)邏輯。
- 網(wǎng)絡(luò)波動(dòng):同一個(gè)請(qǐng)求服務(wù)端收到多次。
4.常用解決方案
- Token+Redis:每次接口先獲取token,后臺(tái)將生成的token放入redis,請(qǐng)求時(shí)在header中加上token,收到請(qǐng)求后驗(yàn)證redis中是否存在改token,通過后刪除token。-------保證網(wǎng)絡(luò)波動(dòng)導(dǎo)致的重復(fù)請(qǐng)求。(最常用的解決方案)。
- 唯一索引去重:保證最終插入數(shù)據(jù)庫(kù)只有一天數(shù)據(jù)。
- 狀態(tài)機(jī):解決狀態(tài)update問題,多線程下,多用戶對(duì)同一訂單處理問題。
- 樂觀鎖:(雖然比悲觀鎖效率好點(diǎn),但任然用的不多)
- 悲觀鎖:for update (效率問題,不推薦,除非安全等級(jí)要求極高的地方)
- 先查詢后判斷+版本控制
- 建去重表
- 前端攔截
- JVM鎖:僅單機(jī)狀態(tài)
- 分布式鎖
5.我們遇到的重復(fù)請(qǐng)求問題
????????1.對(duì)于較重的請(qǐng)求,因?yàn)楸容^耗時(shí),有時(shí)候用戶會(huì)重復(fù)點(diǎn)擊,例如查詢某個(gè)復(fù)雜報(bào)表,用戶查詢后遲遲未收到回復(fù),便再次多次點(diǎn)擊,導(dǎo)致重復(fù)請(qǐng)求過多,浪費(fèi)系統(tǒng)資源。
?????????2.網(wǎng)絡(luò)波動(dòng),用戶客戶端提交了一次,服務(wù)端卻收到了多次請(qǐng)求。
? ? ? ? ?3.用戶手抖,同一時(shí)間內(nèi)點(diǎn)擊了兩次表單提交按鈕,導(dǎo)致服務(wù)端收到兩次請(qǐng)求。
6.解決思路
? ? ? ? 對(duì)于上面所訴的三個(gè)問題,重點(diǎn)就是過濾掉重復(fù)的請(qǐng)求。我們采用redis+token 的思路來處理,由于我們?cè)趃ateway中已經(jīng)在請(qǐng)求參數(shù)中追加了用戶信息(里面擁有accountId,因系統(tǒng)只允許單點(diǎn)登錄,可唯一區(qū)分是同一個(gè)用戶發(fā)出的請(qǐng)求)具體實(shí)現(xiàn)參見我的另一篇博客合理使用gateWay過濾器,實(shí)現(xiàn)Concroller自動(dòng)注入用戶信息
,因此可以省去獲取token這一步驟,我們?cè)谡?qǐng)求進(jìn)入controller之前,采用aop攔截,將獲取的請(qǐng)求的方法簽名和參數(shù)(方法參數(shù)?+ 用戶信息?)作為參數(shù) 使用MD5運(yùn)算出一個(gè)key存入redis(存入時(shí)使用redis set if not exists來保證原子性)。根據(jù)存入結(jié)果判斷當(dāng)前請(qǐng)求是否正在被處理,若正在被處理,則不再執(zhí)行當(dāng)前請(qǐng)求,直接返回,否則正常執(zhí)行,當(dāng)請(qǐng)求執(zhí)行完成后后置處理將redis中的key移除。
7.實(shí)現(xiàn)代碼
? ? ? ? ? 1.添加注解,方便后續(xù)使用。代碼如下
package com.lvyuanji.common.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FormRepeatSubmitValidation {
String value() default "表單重復(fù)提交驗(yàn)證";
//禁止重試時(shí)間
int timeout() default 8*1000;
}
2.aop切面
package com.lvyuanji.common.aspect;
import cn.hutool.crypto.digest.MD5;
import com.lvyuanji.common.annotation.FormRepeatSubmitValidation;
import com.lvyuanji.common.asserts.BusinessAsserts;
import com.lvyuanji.common.exception.Exceptions;
import org.apache.dubbo.common.utils.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
/**
* 防止表單重復(fù)提交 Aspect,僅用于Controller層
*
* @author moky
* @date 2021/04/21.
*/
@Aspect
@Component
public class FormRepeatSubmitAspect {
//日志記錄
private static final Logger logger = LoggerFactory.getLogger(FormRepeatSubmitAspect.class);
//redis客戶端
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Around("@annotation(formRepeatSubmitValidation)")
public Object doProcess(ProceedingJoinPoint joinPoint, FormRepeatSubmitValidation formRepeatSubmitValidation) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
//獲取方法簽名
String methodSignature = signature.toLongString();
// 請(qǐng)求的方法參數(shù)值,因小藍(lán)本機(jī)制,會(huì)在請(qǐng)求參數(shù)中寫入accountEntity,因此通過參數(shù)可以唯一定位到具體用戶
Object[] args = joinPoint.getArgs();
String argString = StringUtils.toArgumentString(args);
//通過MD5運(yùn)算加密
MD5 md5 = new MD5();
String digexStr = md5.digestHex(methodSignature + argString);
//將字符串存入 redis,根據(jù)返回值判斷是否該請(qǐng)求仍然正在處理
ValueOperations<String, String> operations = redisTemplate.opsForValue();
Boolean isSuccess = operations.setIfAbsent(digexStr, "lock", formRepeatSubmitValidation.timeout(), TimeUnit.MILLISECONDS);
BusinessAsserts.isTrue(isSuccess, Exceptions.System.repet_submit);
//執(zhí)行被被代理的方法
try {
return joinPoint.proceed(args);
} finally {
//方法執(zhí)行結(jié)束,解鎖
redisTemplate.delete(digexStr);
}
}
}
? ? ? ? 3.完成以上兩步,后面我們面對(duì)需要攔截重復(fù)提交的請(qǐng)求時(shí),只需要在controller中添加注解即可實(shí)現(xiàn)動(dòng)態(tài)攔截。
注意此處僅處理了客戶端重復(fù)請(qǐng)求的問題,遠(yuǎn)程服務(wù)調(diào)用重復(fù)請(qǐng)求問題我會(huì)在后續(xù)給出處理方案。
????????
????????
原文鏈接:https://blog.csdn.net/weixin_47987440/article/details/125695249
相關(guān)推薦
- 2022-10-02 C#使用is、as關(guān)鍵字以及顯式強(qiáng)轉(zhuǎn)實(shí)現(xiàn)引用類型轉(zhuǎn)換_C#教程
- 2022-03-07 深入淺析C/C++語(yǔ)言結(jié)構(gòu)體指針的使用注意事項(xiàng)_C 語(yǔ)言
- 2022-06-25 在Nginx服務(wù)器上安裝SSL證書完成HTTPS請(qǐng)求的步驟詳解(springboot項(xiàng)目)_ngin
- 2022-10-22 關(guān)于分布式鎖的三種實(shí)現(xiàn)方式_Redis
- 2022-05-10 在 VSCode 中如何設(shè)置默認(rèn)的瀏覽器為Chrome或Firefox
- 2022-04-12 Taro打包Android?apk過程詳解_Android
- 2022-03-16 .Net?6簡(jiǎn)介并和之前版本寫法做對(duì)比_基礎(chǔ)應(yīng)用
- 2022-05-22 nginx常用配置conf的示例代碼詳解_nginx
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡(jiǎn)單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支