mirror of
https://gitee.com/dromara/RuoYi-Vue-Plus.git
synced 2026-04-10 12:33:15 +08:00
update 重构 将 idempotent 与 ratelimiter 模块统一合并到 redis 模块 降级模块使用复杂度
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
package org.dromara.common.redis.annotation;
|
||||
|
||||
import org.dromara.common.redis.enums.LimitType;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 限流注解
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface RateLimiter {
|
||||
/**
|
||||
* 限流key,支持使用Spring el表达式来动态获取方法上的参数值
|
||||
* 格式类似于 #code.id #{#code}
|
||||
*/
|
||||
String key() default "";
|
||||
|
||||
/**
|
||||
* 限流时间,单位秒
|
||||
*/
|
||||
int time() default 60;
|
||||
|
||||
/**
|
||||
* 限流次数
|
||||
*/
|
||||
int count() default 100;
|
||||
|
||||
/**
|
||||
* 限流类型
|
||||
*/
|
||||
LimitType limitType() default LimitType.DEFAULT;
|
||||
|
||||
/**
|
||||
* 提示消息 支持国际化 格式为 {code}
|
||||
*/
|
||||
String message() default "{rate.limiter.message}";
|
||||
|
||||
/**
|
||||
* 限流策略超时时间 默认一天(策略存活时间 会清除已存在的策略数据)
|
||||
*/
|
||||
int timeout() default 86400;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package org.dromara.common.redis.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 自定义注解防止表单重复提交
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Inherited
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface RepeatSubmit {
|
||||
|
||||
/**
|
||||
* 间隔时间(ms),小于此时间视为重复提交
|
||||
*/
|
||||
int interval() default 5000;
|
||||
|
||||
TimeUnit timeUnit() default TimeUnit.MILLISECONDS;
|
||||
|
||||
/**
|
||||
* 提示消息 支持国际化 格式为 {code}
|
||||
*/
|
||||
String message() default "{repeat.submit.message}";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package org.dromara.common.redis.aspectj;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Before;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
import org.dromara.common.core.constant.GlobalConstants;
|
||||
import org.dromara.common.core.exception.ServiceException;
|
||||
import org.dromara.common.core.utils.MessageUtils;
|
||||
import org.dromara.common.core.utils.ServletUtils;
|
||||
import org.dromara.common.core.utils.SpringUtils;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.redis.annotation.RateLimiter;
|
||||
import org.dromara.common.redis.enums.LimitType;
|
||||
import org.dromara.common.redis.utils.RedisUtils;
|
||||
import org.redisson.api.RateType;
|
||||
import org.springframework.context.expression.BeanFactoryResolver;
|
||||
import org.springframework.context.expression.MethodBasedEvaluationContext;
|
||||
import org.springframework.core.DefaultParameterNameDiscoverer;
|
||||
import org.springframework.core.ParameterNameDiscoverer;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
import org.springframework.expression.ParserContext;
|
||||
import org.springframework.expression.common.TemplateParserContext;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* 限流处理
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Slf4j
|
||||
@Aspect
|
||||
public class RateLimiterAspect {
|
||||
|
||||
/**
|
||||
* 定义spel表达式解析器
|
||||
*/
|
||||
private final ExpressionParser parser = new SpelExpressionParser();
|
||||
/**
|
||||
* 定义spel解析模版
|
||||
*/
|
||||
private final ParserContext parserContext = new TemplateParserContext();
|
||||
/**
|
||||
* 方法参数解析器
|
||||
*/
|
||||
private final ParameterNameDiscoverer pnd = new DefaultParameterNameDiscoverer();
|
||||
|
||||
|
||||
@Before("@annotation(rateLimiter)")
|
||||
public void doBefore(JoinPoint point, RateLimiter rateLimiter) {
|
||||
int time = rateLimiter.time();
|
||||
int count = rateLimiter.count();
|
||||
int timeout = rateLimiter.timeout();
|
||||
try {
|
||||
String combineKey = getCombineKey(rateLimiter, point);
|
||||
RateType rateType = RateType.OVERALL;
|
||||
if (rateLimiter.limitType() == LimitType.CLUSTER) {
|
||||
rateType = RateType.PER_CLIENT;
|
||||
}
|
||||
long number = RedisUtils.rateLimiter(combineKey, rateType, count, time, timeout);
|
||||
if (number == -1) {
|
||||
String message = rateLimiter.message();
|
||||
if (StringUtils.startsWith(message, "{") && StringUtils.endsWith(message, "}")) {
|
||||
message = MessageUtils.message(StringUtils.substring(message, 1, message.length() - 1));
|
||||
}
|
||||
throw new ServiceException(message);
|
||||
}
|
||||
log.info("限制令牌 => {}, 剩余令牌 => {}, 缓存key => '{}'", count, number, combineKey);
|
||||
} catch (Exception e) {
|
||||
if (e instanceof ServiceException) {
|
||||
throw e;
|
||||
} else {
|
||||
throw new RuntimeException("服务器限流异常,请稍候再试", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String getCombineKey(RateLimiter rateLimiter, JoinPoint point) {
|
||||
String key = rateLimiter.key();
|
||||
// 判断 key 不为空 和 不是表达式
|
||||
if (StringUtils.isNotBlank(key) && StringUtils.containsAny(key, "#")) {
|
||||
MethodSignature signature = (MethodSignature) point.getSignature();
|
||||
Method targetMethod = signature.getMethod();
|
||||
Object[] args = point.getArgs();
|
||||
MethodBasedEvaluationContext context =
|
||||
new MethodBasedEvaluationContext(null, targetMethod, args, pnd);
|
||||
context.setBeanResolver(new BeanFactoryResolver(SpringUtils.getBeanFactory()));
|
||||
Expression expression;
|
||||
if (StringUtils.startsWith(key, parserContext.getExpressionPrefix())
|
||||
&& StringUtils.endsWith(key, parserContext.getExpressionSuffix())) {
|
||||
expression = parser.parseExpression(key, parserContext);
|
||||
} else {
|
||||
expression = parser.parseExpression(key);
|
||||
}
|
||||
key = expression.getValue(context, String.class);
|
||||
}
|
||||
StringBuilder stringBuffer = new StringBuilder(GlobalConstants.RATE_LIMIT_KEY);
|
||||
stringBuffer.append(ServletUtils.getRequest().getRequestURI()).append(":");
|
||||
if (rateLimiter.limitType() == LimitType.IP) {
|
||||
// 获取请求ip
|
||||
stringBuffer.append(ServletUtils.getClientIP()).append(":");
|
||||
} else if (rateLimiter.limitType() == LimitType.CLUSTER) {
|
||||
// 获取客户端实例id
|
||||
stringBuffer.append(RedisUtils.getClient().getId()).append(":");
|
||||
}
|
||||
return stringBuffer.append(key).toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
package org.dromara.common.redis.aspectj;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.crypto.SecureUtil;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.aspectj.lang.annotation.AfterReturning;
|
||||
import org.aspectj.lang.annotation.AfterThrowing;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Before;
|
||||
import org.dromara.common.core.constant.GlobalConstants;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.core.exception.ServiceException;
|
||||
import org.dromara.common.core.utils.MessageUtils;
|
||||
import org.dromara.common.core.utils.ServletUtils;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.json.utils.JsonUtils;
|
||||
import org.dromara.common.redis.annotation.RepeatSubmit;
|
||||
import org.dromara.common.redis.utils.RedisUtils;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
/**
|
||||
* 防止重复提交(参考美团GTIS防重系统)
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Aspect
|
||||
public class RepeatSubmitAspect {
|
||||
|
||||
private static final ThreadLocal<String> KEY_CACHE = new ThreadLocal<>();
|
||||
|
||||
@Before("@annotation(repeatSubmit)")
|
||||
public void doBefore(JoinPoint point, RepeatSubmit repeatSubmit) throws Throwable {
|
||||
// 如果注解不为0 则使用注解数值
|
||||
long interval = repeatSubmit.timeUnit().toMillis(repeatSubmit.interval());
|
||||
|
||||
if (interval < 1000) {
|
||||
throw new ServiceException("重复提交间隔时间不能小于'1'秒");
|
||||
}
|
||||
HttpServletRequest request = ServletUtils.getRequest();
|
||||
String nowParams = argsArrayToString(point.getArgs());
|
||||
|
||||
// 请求地址(作为存放cache的key值)
|
||||
String url = request.getRequestURI();
|
||||
|
||||
// 唯一值(没有消息头则使用请求地址)
|
||||
String submitKey = StringUtils.trimToEmpty(request.getHeader(SaManager.getConfig().getTokenName()));
|
||||
|
||||
submitKey = SecureUtil.md5(submitKey + ":" + nowParams);
|
||||
// 唯一标识(指定key + url + 消息头)
|
||||
String cacheRepeatKey = GlobalConstants.REPEAT_SUBMIT_KEY + url + submitKey;
|
||||
if (RedisUtils.setObjectIfAbsent(cacheRepeatKey, "", Duration.ofMillis(interval))) {
|
||||
KEY_CACHE.set(cacheRepeatKey);
|
||||
} else {
|
||||
String message = repeatSubmit.message();
|
||||
if (StringUtils.startsWith(message, "{") && StringUtils.endsWith(message, "}")) {
|
||||
message = MessageUtils.message(StringUtils.substring(message, 1, message.length() - 1));
|
||||
}
|
||||
throw new ServiceException(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理完请求后执行
|
||||
*
|
||||
* @param joinPoint 切点
|
||||
*/
|
||||
@AfterReturning(pointcut = "@annotation(repeatSubmit)", returning = "jsonResult")
|
||||
public void doAfterReturning(JoinPoint joinPoint, RepeatSubmit repeatSubmit, Object jsonResult) {
|
||||
if (jsonResult instanceof R<?> r) {
|
||||
try {
|
||||
// 成功则不删除redis数据 保证在有效时间内无法重复提交
|
||||
if (r.getCode() == R.SUCCESS) {
|
||||
return;
|
||||
}
|
||||
RedisUtils.deleteObject(KEY_CACHE.get());
|
||||
} finally {
|
||||
KEY_CACHE.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 拦截异常操作
|
||||
*
|
||||
* @param joinPoint 切点
|
||||
* @param e 异常
|
||||
*/
|
||||
@AfterThrowing(value = "@annotation(repeatSubmit)", throwing = "e")
|
||||
public void doAfterThrowing(JoinPoint joinPoint, RepeatSubmit repeatSubmit, Exception e) {
|
||||
RedisUtils.deleteObject(KEY_CACHE.get());
|
||||
KEY_CACHE.remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* 参数拼装
|
||||
*/
|
||||
private String argsArrayToString(Object[] paramsArray) {
|
||||
StringJoiner params = new StringJoiner(" ");
|
||||
if (ArrayUtil.isEmpty(paramsArray)) {
|
||||
return params.toString();
|
||||
}
|
||||
for (Object o : paramsArray) {
|
||||
if (ObjectUtil.isNotNull(o) && !isFilterObject(o)) {
|
||||
params.add(JsonUtils.toJsonString(o));
|
||||
}
|
||||
}
|
||||
return params.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否需要过滤的对象。
|
||||
*
|
||||
* @param o 对象信息。
|
||||
* @return 如果是需要过滤的对象,则返回true;否则返回false。
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public boolean isFilterObject(final Object o) {
|
||||
Class<?> clazz = o.getClass();
|
||||
if (clazz.isArray()) {
|
||||
return MultipartFile.class.isAssignableFrom(clazz.getComponentType());
|
||||
} else if (Collection.class.isAssignableFrom(clazz)) {
|
||||
Collection collection = (Collection) o;
|
||||
for (Object value : collection) {
|
||||
return value instanceof MultipartFile;
|
||||
}
|
||||
} else if (Map.class.isAssignableFrom(clazz)) {
|
||||
Map map = (Map) o;
|
||||
for (Object value : map.values()) {
|
||||
return value instanceof MultipartFile;
|
||||
}
|
||||
}
|
||||
return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
|
||||
|| o instanceof BindingResult;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package org.dromara.common.redis.config;
|
||||
|
||||
import org.dromara.common.redis.aspectj.RepeatSubmitAspect;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.data.redis.connection.RedisConfiguration;
|
||||
|
||||
/**
|
||||
* 幂等功能配置
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@AutoConfiguration(after = RedisConfiguration.class)
|
||||
public class IdempotentConfig {
|
||||
|
||||
@Bean
|
||||
public RepeatSubmitAspect repeatSubmitAspect() {
|
||||
return new RepeatSubmitAspect();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package org.dromara.common.redis.config;
|
||||
|
||||
import org.dromara.common.redis.aspectj.RateLimiterAspect;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.data.redis.connection.RedisConfiguration;
|
||||
|
||||
/**
|
||||
* @author guangxin
|
||||
* @date 2023/1/18
|
||||
*/
|
||||
@AutoConfiguration(after = RedisConfiguration.class)
|
||||
public class RateLimiterConfig {
|
||||
|
||||
@Bean
|
||||
public RateLimiterAspect rateLimiterAspect() {
|
||||
return new RateLimiterAspect();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package org.dromara.common.redis.enums;
|
||||
|
||||
/**
|
||||
* 限流类型
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
|
||||
public enum LimitType {
|
||||
/**
|
||||
* 默认策略全局限流
|
||||
*/
|
||||
DEFAULT,
|
||||
|
||||
/**
|
||||
* 根据请求者IP进行限流
|
||||
*/
|
||||
IP,
|
||||
|
||||
/**
|
||||
* 实例限流(集群多后端实例)
|
||||
*/
|
||||
CLUSTER
|
||||
}
|
||||
@@ -3,10 +3,11 @@ package org.dromara.common.redis.utils;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.dromara.common.core.utils.SpringUtils;
|
||||
import org.redisson.api.*;
|
||||
import org.redisson.api.RBlockingQueue;
|
||||
import org.redisson.api.RPriorityBlockingQueue;
|
||||
import org.redisson.api.RedissonClient;
|
||||
|
||||
import java.util.concurrent.CompletionStage;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
@@ -16,9 +17,7 @@ import java.util.function.Function;
|
||||
*
|
||||
* @author Lion Li
|
||||
* @version 3.6.0 新增
|
||||
* @deprecated redisson 新版本已经将队列功能标记删除 一些技术问题无法解决 建议搭建MQ使用
|
||||
*/
|
||||
@Deprecated
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class QueueUtils {
|
||||
|
||||
@@ -69,60 +68,6 @@ public class QueueUtils {
|
||||
return queue.delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加延迟队列数据 默认毫秒
|
||||
*
|
||||
* @param queueName 队列名
|
||||
* @param data 数据
|
||||
* @param time 延迟时间
|
||||
*/
|
||||
public static <T> void addDelayedQueueObject(String queueName, T data, long time) {
|
||||
addDelayedQueueObject(queueName, data, time, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加延迟队列数据
|
||||
*
|
||||
* @param queueName 队列名
|
||||
* @param data 数据
|
||||
* @param time 延迟时间
|
||||
* @param timeUnit 单位
|
||||
*/
|
||||
public static <T> void addDelayedQueueObject(String queueName, T data, long time, TimeUnit timeUnit) {
|
||||
RBlockingQueue<T> queue = CLIENT.getBlockingQueue(queueName);
|
||||
RDelayedQueue<T> delayedQueue = CLIENT.getDelayedQueue(queue);
|
||||
delayedQueue.offer(data, time, timeUnit);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取一个延迟队列数据 没有数据返回 null
|
||||
*
|
||||
* @param queueName 队列名
|
||||
*/
|
||||
public static <T> T getDelayedQueueObject(String queueName) {
|
||||
RBlockingQueue<T> queue = CLIENT.getBlockingQueue(queueName);
|
||||
RDelayedQueue<T> delayedQueue = CLIENT.getDelayedQueue(queue);
|
||||
return delayedQueue.poll();
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除延迟队列数据
|
||||
*/
|
||||
public static <T> boolean removeDelayedQueueObject(String queueName, T data) {
|
||||
RBlockingQueue<T> queue = CLIENT.getBlockingQueue(queueName);
|
||||
RDelayedQueue<T> delayedQueue = CLIENT.getDelayedQueue(queue);
|
||||
return delayedQueue.remove(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁延迟队列 所有阻塞监听 报错
|
||||
*/
|
||||
public static <T> void destroyDelayedQueue(String queueName) {
|
||||
RBlockingQueue<T> queue = CLIENT.getBlockingQueue(queueName);
|
||||
RDelayedQueue<T> delayedQueue = CLIENT.getDelayedQueue(queue);
|
||||
delayedQueue.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加优先队列数据
|
||||
*
|
||||
@@ -161,78 +106,15 @@ public class QueueUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* 尝试设置 有界队列 容量 用于限制数量
|
||||
*
|
||||
* @param queueName 队列名
|
||||
* @param capacity 容量
|
||||
* 订阅阻塞队列(可订阅所有实现类)
|
||||
*/
|
||||
public static <T> boolean trySetBoundedQueueCapacity(String queueName, int capacity) {
|
||||
RBoundedBlockingQueue<T> boundedBlockingQueue = CLIENT.getBoundedBlockingQueue(queueName);
|
||||
return boundedBlockingQueue.trySetCapacity(capacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 尝试设置 有界队列 容量 用于限制数量
|
||||
*
|
||||
* @param queueName 队列名
|
||||
* @param capacity 容量
|
||||
* @param destroy 是否销毁
|
||||
*/
|
||||
public static <T> boolean trySetBoundedQueueCapacity(String queueName, int capacity, boolean destroy) {
|
||||
RBoundedBlockingQueue<T> boundedBlockingQueue = CLIENT.getBoundedBlockingQueue(queueName);
|
||||
if (destroy) {
|
||||
boundedBlockingQueue.delete();
|
||||
}
|
||||
return boundedBlockingQueue.trySetCapacity(capacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加有界队列数据
|
||||
*
|
||||
* @param queueName 队列名
|
||||
* @param data 数据
|
||||
* @return 添加成功 true 已达到界限 false
|
||||
*/
|
||||
public static <T> boolean addBoundedQueueObject(String queueName, T data) {
|
||||
RBoundedBlockingQueue<T> boundedBlockingQueue = CLIENT.getBoundedBlockingQueue(queueName);
|
||||
return boundedBlockingQueue.offer(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 有界队列获取一个队列数据 没有数据返回 null(不支持延迟队列)
|
||||
*
|
||||
* @param queueName 队列名
|
||||
*/
|
||||
public static <T> T getBoundedQueueObject(String queueName) {
|
||||
RBoundedBlockingQueue<T> queue = CLIENT.getBoundedBlockingQueue(queueName);
|
||||
return queue.poll();
|
||||
}
|
||||
|
||||
/**
|
||||
* 有界队列删除队列数据(不支持延迟队列)
|
||||
*/
|
||||
public static <T> boolean removeBoundedQueueObject(String queueName, T data) {
|
||||
RBoundedBlockingQueue<T> queue = CLIENT.getBoundedBlockingQueue(queueName);
|
||||
return queue.remove(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 有界队列销毁队列 所有阻塞监听 报错(不支持延迟队列)
|
||||
*/
|
||||
public static <T> boolean destroyBoundedQueue(String queueName) {
|
||||
RBoundedBlockingQueue<T> queue = CLIENT.getBoundedBlockingQueue(queueName);
|
||||
return queue.delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* 订阅阻塞队列(可订阅所有实现类 例如: 延迟 优先 有界 等)
|
||||
*/
|
||||
public static <T> void subscribeBlockingQueue(String queueName, Function<T, CompletionStage<Void>> consumer, boolean isDelayed) {
|
||||
public static <T> void subscribeBlockingQueue(String queueName, Function<T, CompletionStage<Void>> consumer) {
|
||||
RBlockingQueue<T> queue = CLIENT.getBlockingQueue(queueName);
|
||||
if (isDelayed) {
|
||||
// 订阅延迟队列
|
||||
CLIENT.getDelayedQueue(queue);
|
||||
}
|
||||
// 延迟队列已经被redisson官方废弃不建议使用
|
||||
// if (isDelayed) {
|
||||
// // 订阅延迟队列
|
||||
// CLIENT.getDelayedQueue(queue);
|
||||
// }
|
||||
queue.subscribeOnElements(consumer);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
org.dromara.common.redis.config.RedisConfig
|
||||
org.dromara.common.redis.config.CacheConfig
|
||||
org.dromara.common.redis.config.IdempotentConfig
|
||||
org.dromara.common.redis.config.RateLimiterConfig
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"org.dromara.common.ratelimiter.annotation.RateLimiter@key": {
|
||||
"method": {
|
||||
"parameters": true
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user