日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
SpringCloud—使用分布式鎖實(shí)現(xiàn)微服務(wù)重復(fù)請求控制

通常我們可以在前端通過防抖和節(jié)流來解決短時間內(nèi)請求重復(fù)提交的問題,如果因網(wǎng)絡(luò)問題、Nginx重試機(jī)制、微服務(wù)Feign重試機(jī)制或者用戶故意繞過前端防抖和節(jié)流設(shè)置,直接頻繁發(fā)起請求,都會導(dǎo)致系統(tǒng)防重請求失敗,甚至導(dǎo)致后臺產(chǎn)生多條重復(fù)記錄,此時我們需要考慮在后臺增加防重設(shè)置。

站在用戶的角度思考問題,與客戶深入溝通,找到玉泉網(wǎng)站設(shè)計(jì)與玉泉網(wǎng)站推廣的解決方案,憑借多年的經(jīng)驗(yàn),讓設(shè)計(jì)與互聯(lián)網(wǎng)技術(shù)結(jié)合,創(chuàng)造個性化、用戶體驗(yàn)好的作品,建站類型包括:網(wǎng)站設(shè)計(jì)制作、網(wǎng)站設(shè)計(jì)、企業(yè)官網(wǎng)、英文網(wǎng)站、手機(jī)端網(wǎng)站、網(wǎng)站推廣、空間域名、網(wǎng)頁空間、企業(yè)郵箱。業(yè)務(wù)覆蓋玉泉地區(qū)。

考慮到微服務(wù)分布式的場景,這里通過使用Redisson分布式鎖+自定義注解+AOP的方式來實(shí)現(xiàn)后臺防止重復(fù)請求的功能,基本實(shí)現(xiàn)思路:通過在需要防重的接口添加自定義防重注解,設(shè)置防重參數(shù),通過AOP攔截請求參數(shù),根據(jù)注解配置,生成分布式鎖的Key,并設(shè)置有效時間。每次請求訪問時,都會嘗試獲取鎖,如果獲取到,則執(zhí)行,如果獲取不到,那么說明請求在設(shè)置的重復(fù)請求間隔內(nèi),返回請勿頻繁請求提示信息。

1、自定義防止重復(fù)請求注解,根據(jù)業(yè)務(wù)場景設(shè)置了以下參數(shù):

  • interval: 防止重復(fù)提交的時間間隔。
  • timeUnit: 防止重復(fù)提交的時間間隔的單位。
  • currentSession: 是否將sessionId作為防重參數(shù)(微服務(wù)及跨域前后端分離時,無法使用,Chrome等瀏覽器跨域時禁止攜帶cookie,每次sessionId都是新的)。
  • currentUser: 是否將用戶id作為防重參數(shù)。
  • keys: 可以作為防重參數(shù)的字段(通過Spring Expression表達(dá)式,可以做到多參數(shù)時,具體取哪個參數(shù)的值)。
  • ignoreKeys: 需要忽略的防重參數(shù)字段,例如有些參數(shù)中的時間戳,此和keys互斥,當(dāng)keys配置了之后,ignoreKeys失效。
  • conditions:當(dāng)參數(shù)中的某個字段達(dá)到條件時,執(zhí)行防重配置,默認(rèn)不需要配置。
  • argsIndex: 當(dāng)沒有配置keys參數(shù)時,防重?cái)r截后會對所有參數(shù)取值作為分布式鎖的key,這里時,當(dāng)多參數(shù)時,配置取哪一個參數(shù)作為key,可以多個。此和keys互斥,當(dāng)keys配置了之后,argsIndex配置失效。
package com.gitegg.platform.base.annotation.resubmit;
import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;
/**
* 防止重復(fù)提交注解
* 1、當(dāng)設(shè)置了keys時,通過表達(dá)式確定取哪幾個參數(shù)作為防重key
* 2、當(dāng)未設(shè)置keys時,可以設(shè)置argsIndex設(shè)置取哪幾個參數(shù)作為防重key
* 3、argsIndex和ignoreKeys是未設(shè)置keys時生效,排除不需要防重的參數(shù)
* 4、因部分瀏覽器在跨域請求時,不允許request請求攜帶cookie,導(dǎo)致每次sessionId都是新的,所以這里默認(rèn)使用用戶id作為key的一部分,不使用sessionId
* @author GitEgg
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResubmitLock {
/**
* 防重復(fù)提交校驗(yàn)的時間間隔
*/
long interval() default 5;
/**
* 防重復(fù)提交校驗(yàn)的時間間隔的單位
*/
TimeUnit timeUnit() default TimeUnit.SECONDS;
/**
* 是否僅在當(dāng)前session內(nèi)進(jìn)行防重復(fù)提交校驗(yàn)
*/
boolean currentSession() default false;
/**
* 是否選用當(dāng)前操作用戶的信息作為防重復(fù)提交校驗(yàn)key的一部分
*/
boolean currentUser() default true;
/**
* keys和ignoreKeys不能同時使用
* 參數(shù)Spring EL表達(dá)式例如 #{param.name},表達(dá)式的值作為防重復(fù)校驗(yàn)key的一部分
*/
String[] keys() default {};
/**
* keys和ignoreKeys不能同時使用
* ignoreKeys不區(qū)分入?yún)?,所有入?yún)碛邢嗤淖侄螘r,都將過濾掉
*/
String[] ignoreKeys() default {};
/**
* Spring EL表達(dá)式,決定是否進(jìn)行重復(fù)提交校驗(yàn),多個條件之間為且的關(guān)系,默認(rèn)是進(jìn)行校驗(yàn)
*/
String[] conditions() default {"true"};
/**
* 當(dāng)未配置key時,設(shè)置哪幾個參數(shù)作為防重對象,默認(rèn)取所有參數(shù)
*
* @return
*/
int[] argsIndex() default {};
}

2、自定義AOP攔截防重請求的業(yè)務(wù)邏輯處理,詳細(xì)邏輯處理請看代碼注釋??梢栽贜acos中增加配置resubmit-lock: enable: false 使防重配置失效,默認(rèn)不配置為生效狀態(tài)。因?yàn)槭荝esubmitLockAspect是否初始化的ConditionalOnProperty配置,此配置修改需要重啟服務(wù)生效。

package com.gitegg.platform.boot.aspect;
import com.gitegg.platform.base.annotation.resubmit.ResubmitLock;
import com.gitegg.platform.base.enums.ResultCodeEnum;
import com.gitegg.platform.base.exception.SystemException;
import com.gitegg.platform.base.util.JsonUtils;
import com.gitegg.platform.boot.util.ExpressionUtils;
import com.gitegg.platform.boot.util.GitEggAuthUtils;
import com.gitegg.platform.boot.util.GitEggWebUtils;
import com.gitegg.platform.redis.lock.IDistributedLockService;
import com.google.common.collect.Maps;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.ArrayUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import org.springframework.util.DigestUtils;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Map;
import java.util.TreeMap;
/**
* @author GitEgg
* @date 2022-4-10
*/
@Log4j2
@Component
@Aspect
@RequiredArgsConstructor(onConstructor_ = @Autowired)
@ConditionalOnProperty(name = "enabled", prefix = "resubmit-lock", havingValue = "true", matchIfMissing = true)
public class ResubmitLockAspect {
private static final String REDIS_SEPARATOR = ":";
private static final String RESUBMIT_CHECK_KEY_PREFIX = "resubmit_lock" + REDIS_SEPARATOR;
private final IDistributedLockService distributedLockService;
/**
* Before切點(diǎn)
*/
@Pointcut("@annotation(com.gitegg.platform.base.annotation.resubmit.ResubmitLock)")
public void resubmitLock() {
}
/**
* 前置通知 防止重復(fù)提交
*
* @param joinPoint 切點(diǎn)
* @param resubmitLock 注解配置
*/
@Before("@annotation(resubmitLock)")
public Object resubmitCheck(JoinPoint joinPoint, ResubmitLock resubmitLock) throws Throwable {
final Object[] args = joinPoint.getArgs();
final String[] conditions = resubmitLock.conditions();
//根據(jù)條件判斷是否需要進(jìn)行防重復(fù)提交檢查
if (!ExpressionUtils.getConditionValue(args, conditions) || ArrayUtils.isEmpty(args)) {
return ((ProceedingJoinPoint) joinPoint).proceed();
}
doCheck(resubmitLock, args);
return ((ProceedingJoinPoint) joinPoint).proceed();
}
/**
* key的組成為: resubmit_lock:userId:sessionId:uri:method:(根據(jù)spring EL表達(dá)式對參數(shù)進(jìn)行拼接)
*
* @param resubmitLock 注解
* @param args 方法入?yún)?br> */
private void doCheck(@NonNull ResubmitLock resubmitLock, Object[] args) {
final String[] keys = resubmitLock.keys();
final boolean currentUser = resubmitLock.currentUser();
final boolean currentSession = resubmitLock.currentSession();
String method = GitEggWebUtils.getRequest().getMethod();
String uri = GitEggWebUtils.getRequest().getRequestURI();
StringBuffer lockKeyBuffer = new StringBuffer(RESUBMIT_CHECK_KEY_PREFIX);
if (null != GitEggAuthUtils.getTenantId())
{
lockKeyBuffer.append( GitEggAuthUtils.getTenantId()).append(REDIS_SEPARATOR);
}
// 此判斷暫時預(yù)留,適配后續(xù)無用戶登錄場景,因部分瀏覽器在跨域請求時,不允許request請求攜帶cookie,導(dǎo)致每次sessionId都是新的,所以這里默認(rèn)使用用戶id作為key的一部分,不使用sessionId
if (currentSession)
{
lockKeyBuffer.append( GitEggWebUtils.getSessionId()).append(REDIS_SEPARATOR);
}
// 默認(rèn)沒有將user數(shù)據(jù)作為防重key
if (currentUser && null != GitEggAuthUtils.getCurrentUser())
{
lockKeyBuffer.append( GitEggAuthUtils.getCurrentUser().getId() ).append(REDIS_SEPARATOR);
}
lockKeyBuffer.append(uri).append(REDIS_SEPARATOR).append(method);
StringBuffer parametersBuffer = new StringBuffer();
// 優(yōu)先判斷是否設(shè)置防重字段,因keys試數(shù)組,取值時是按照順序排列的,這里不需要重新排序
if (ArrayUtils.isNotEmpty(keys))
{
Object[] argsForKey = ExpressionUtils.getExpressionValue(args, keys);
for (Object obj : argsForKey) {
parametersBuffer.append(REDIS_SEPARATOR).append(String.valueOf(obj));
}
}
// 如果沒有設(shè)置防重的字段,那么需要把所有的字段和值作為key,因通過反射獲取字段時,順序時不確定的,這里取出來之后需要進(jìn)行排序
else{
// 只有當(dāng)keys為空時,ignoreKeys和argsIndex生效
final String[] ignoreKeys = resubmitLock.ignoreKeys();
final int[] argsIndex = resubmitLock.argsIndex();
if (ArrayUtils.isNotEmpty(argsIndex))
{
for(int index : argsIndex){
parametersBuffer.append(REDIS_SEPARATOR).append( getKeyAndValueJsonStr(args[index], ignoreKeys));
}
}
else
{
for(Object obj : args){
parametersBuffer.append(REDIS_SEPARATOR).append( getKeyAndValueJsonStr(obj, ignoreKeys) );
}
}
}
// 將請求參數(shù)取md5值作為key的一部分,MD5理論上會重復(fù),但是key中還包含session或者用戶id,所以同用戶在極端時間內(nèi)請參數(shù)不同生成的相同md5值的概率極低
String parametersKey = DigestUtils.md5DigestAsHex(parametersBuffer.toString().getBytes());
lockKeyBuffer.append(parametersKey);
try {
boolean isLock = distributedLockService.tryLock(lockKeyBuffer.toString(), 0, resubmitLock.interval(), resubmitLock.timeUnit());
if (!isLock)
{
throw new SystemException(ResultCodeEnum.RESUBMIT_LOCK.code, ResultCodeEnum.RESUBMIT_LOCK.msg);
}
} catch (InterruptedException e) {
throw new SystemException(ResultCodeEnum.RESUBMIT_LOCK.code, ResultCodeEnum.RESUBMIT_LOCK.msg);
}
}
/**
* 將字段轉(zhuǎn)換為json字符串
* @param obj
* @return
*/
public static String getKeyAndValueJsonStr(Object obj, String[] ignoreKeys) {
Map map = Maps.newHashMap();
// 得到類對象
Class objCla = (Class) obj.getClass();
/* 得到類中的所有屬性集合 */
Field[] fs = objCla.getDeclaredFields();
for (int i = 0; i < fs.length; i++) {
Field f = fs[i];
// 設(shè)置些屬性是可以訪問的
f.setAccessible(true);
Object val = new Object();
try {
String filedName = f.getName();
// 如果字段在排除列表,那么不將字段放入map
if (null != ignoreKeys && Arrays.asList(ignoreKeys).contains(filedName))
{
continue;
}
val = f.get(obj);
// 得到此屬性的值
// 設(shè)置鍵值
map.put(filedName, val);
} catch (IllegalArgumentException e) {
log.error("getKeyAndValue IllegalArgumentException", e);
throw new RuntimeException("您的操作太頻繁,請稍后再試");
} catch (IllegalAccessException e) {
log.error("getKeyAndValue IllegalAccessException", e);
throw new RuntimeException("您的操作太頻繁,請稍后再試");
}
}
Map sortMap = sortMapByKey(map);
String mapStr = JsonUtils.mapToJson(sortMap);
return mapStr;
}
private static Map sortMapByKey(Map map) {
if (map == null || map.isEmpty()) {
return null;
}
Map sortMap = new TreeMap(new Comparator() {
@Override
public int compare(String o1,String o2) {
return ((String)o1).compareTo((String) o2);
}
});
sortMap.putAll(map);
return sortMap;
}
}

3、Redisson分布式鎖自定義接口。

package com.gitegg.platform.redis.lock;
import java.util.concurrent.TimeUnit;
/**
* 分布式鎖接口
* @author GitEgg
* @date 2022-4-10
*/
public interface IDistributedLockService {
/**
* 加鎖
* @param lockKey key
*/
void lock(String lockKey);
/**
* 釋放鎖
*
* @param lockKey key
*/
void unlock(String lockKey);
/**
* 加鎖并設(shè)置有效期
*
* @param lockKey key
* @param timeout 有效時間,默認(rèn)時間單位在實(shí)現(xiàn)類傳入
*/
void lock(String lockKey, int timeout);
/**
* 加鎖并設(shè)置有效期指定時間單位
* @param lockKey key
* @param timeout 有效時間
* @param unit 時間單位
*/
void lock(String lockKey, int timeout, TimeUnit unit);
/**
* 嘗試獲取鎖,獲取到則持有該鎖返回true,未獲取到立即返回false
* @param lockKey
* @return true-獲取鎖成功 false-獲取鎖失敗
*/
boolean tryLock(String lockKey);
/**
* 嘗試獲取鎖,獲取到則持有該鎖leaseTime時間.
* 若未獲取到,在waitTime時間內(nèi)一直嘗試獲取,超過watiTime還未獲取到則返回false
* @param lockKey key
* @param waitTime 嘗試獲取時間
* @param leaseTime 鎖持有時間
* @param unit 時間單位
* @return true-獲取鎖成功 false-獲取鎖失敗
* @throws InterruptedException
*/
boolean tryLock(String lockKey, long waitTime, long leaseTime, TimeUnit unit)
throws InterruptedException;
/**
* 鎖是否被任意一個線程鎖持有
* @param lockKey
* @return true-被鎖 false-未被鎖
*/
boolean isLocked(String lockKey);
}

4、Redisson分布式鎖自定義接口實(shí)現(xiàn)類。

package com.gitegg.platform.redis.lock.impl;
import com.gitegg.platform.redis.lock.IDistributedLockService;
import lombok.RequiredArgsConstructor;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
/**
* 分布式鎖的 Redisson 接口實(shí)現(xiàn)
* @author GitEgg
* @date 2022-4-10
*/
@Service
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class DistributedLockServiceImpl implements IDistributedLockService {
private final RedissonClient redissonClient;
@Override
public void lock(String lockKey) {
RLock lock = redissonClient.getLock(lockKey);
lock.lock();
}
@Override
public void unlock(String lockKey) {
RLock lock = redissonClient.getLock(lockKey);
lock.unlock();
}
@Override
public void lock(String lockKey, int timeout) {
RLock lock = redissonClient.getLock(lockKey);
lock.lock(timeout, TimeUnit.MILLISECONDS);
}
@Override
public void lock(String lockKey, int timeout, TimeUnit unit) {
RLock lock = redissonClient.getLock(lockKey);
lock.lock(timeout, unit);
}
@Override
public boolean tryLock(String lockKey) {
RLock lock = redissonClient.getLock(lockKey);
return lock.tryLock();
}
@Override
public boolean tryLock(String lockKey, long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
RLock lock = redissonClient.getLock(lockKey);
return lock<
新聞名稱:SpringCloud—使用分布式鎖實(shí)現(xiàn)微服務(wù)重復(fù)請求控制
當(dāng)前URL:http://www.dlmjj.cn/article/ccsjosg.html