Commit 497d826ac42a0b7cf2fa7c079fcce437b3bef5d6

Authored by 芋道源码
Committed by Gitee
2 parents df8a81f6 c886e7ef

!711 增加 RocketMQ、Kafka、RabbitMQ 消息队列的支持

Merge pull request !711 from 芋道源码/feature/mq-optimize
Showing 57 changed files with 843 additions and 229 deletions
yudao-dependencies/pom.xml
@@ -30,6 +30,8 @@ @@ -30,6 +30,8 @@
30 <mybatis-plus-join.version>1.4.7</mybatis-plus-join.version> 30 <mybatis-plus-join.version>1.4.7</mybatis-plus-join.version>
31 <redisson.version>3.18.0</redisson.version> 31 <redisson.version>3.18.0</redisson.version>
32 <dm8.jdbc.version>8.1.3.62</dm8.jdbc.version> 32 <dm8.jdbc.version>8.1.3.62</dm8.jdbc.version>
  33 + <!-- 消息队列 -->
  34 + <rocketmq-spring.version>2.2.3</rocketmq-spring.version>
33 <!-- 服务保障相关 --> 35 <!-- 服务保障相关 -->
34 <lock4j.version>2.2.5</lock4j.version> 36 <lock4j.version>2.2.5</lock4j.version>
35 <resilience4j.version>1.7.1</resilience4j.version> 37 <resilience4j.version>1.7.1</resilience4j.version>
@@ -98,11 +100,6 @@ @@ -98,11 +100,6 @@
98 </dependency> 100 </dependency>
99 <dependency> 101 <dependency>
100 <groupId>cn.iocoder.boot</groupId> 102 <groupId>cn.iocoder.boot</groupId>
101 - <artifactId>yudao-spring-boot-starter-biz-trade</artifactId>  
102 - <version>${revision}</version>  
103 - </dependency>  
104 - <dependency>  
105 - <groupId>cn.iocoder.boot</groupId>  
106 <artifactId>yudao-spring-boot-starter-biz-dict</artifactId> 103 <artifactId>yudao-spring-boot-starter-biz-dict</artifactId>
107 <version>${revision}</version> 104 <version>${revision}</version>
108 </dependency> 105 </dependency>
@@ -260,6 +257,12 @@ @@ -260,6 +257,12 @@
260 <version>${revision}</version> 257 <version>${revision}</version>
261 </dependency> 258 </dependency>
262 259
  260 + <dependency>
  261 + <groupId>org.apache.rocketmq</groupId>
  262 + <artifactId>rocketmq-spring-boot-starter</artifactId>
  263 + <version>${rocketmq-spring.version}</version>
  264 + </dependency>
  265 +
263 <!-- 服务保障相关 --> 266 <!-- 服务保障相关 -->
264 <dependency> 267 <dependency>
265 <groupId>cn.iocoder.boot</groupId> 268 <groupId>cn.iocoder.boot</groupId>
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/core/KeyValue.java
@@ -4,6 +4,8 @@ import lombok.AllArgsConstructor; @@ -4,6 +4,8 @@ import lombok.AllArgsConstructor;
4 import lombok.Data; 4 import lombok.Data;
5 import lombok.NoArgsConstructor; 5 import lombok.NoArgsConstructor;
6 6
  7 +import java.io.Serializable;
  8 +
7 /** 9 /**
8 * Key Value 的键值对 10 * Key Value 的键值对
9 * 11 *
@@ -12,7 +14,7 @@ import lombok.NoArgsConstructor; @@ -12,7 +14,7 @@ import lombok.NoArgsConstructor;
12 @Data 14 @Data
13 @NoArgsConstructor 15 @NoArgsConstructor
14 @AllArgsConstructor 16 @AllArgsConstructor
15 -public class KeyValue<K, V> { 17 +public class KeyValue<K, V> implements Serializable {
16 18
17 private K key; 19 private K key;
18 private V value; 20 private V value;
yudao-framework/yudao-spring-boot-starter-biz-tenant/pom.xml
@@ -48,6 +48,22 @@ @@ -48,6 +48,22 @@
48 <dependency> 48 <dependency>
49 <groupId>cn.iocoder.boot</groupId> 49 <groupId>cn.iocoder.boot</groupId>
50 <artifactId>yudao-spring-boot-starter-mq</artifactId> 50 <artifactId>yudao-spring-boot-starter-mq</artifactId>
  51 + <optional>true</optional>
  52 + </dependency>
  53 + <dependency>
  54 + <groupId>org.springframework.kafka</groupId>
  55 + <artifactId>spring-kafka</artifactId>
  56 + <optional>true</optional>
  57 + </dependency>
  58 + <dependency>
  59 + <groupId>org.springframework.amqp</groupId>
  60 + <artifactId>spring-rabbit</artifactId>
  61 + <optional>true</optional>
  62 + </dependency>
  63 + <dependency>
  64 + <groupId>org.apache.rocketmq</groupId>
  65 + <artifactId>rocketmq-spring-boot-starter</artifactId>
  66 + <optional>true</optional>
51 </dependency> 67 </dependency>
52 68
53 <!-- Test 测试相关 --> 69 <!-- Test 测试相关 -->
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/YudaoTenantAutoConfiguration.java
@@ -6,7 +6,9 @@ import cn.iocoder.yudao.framework.redis.config.YudaoCacheProperties; @@ -6,7 +6,9 @@ import cn.iocoder.yudao.framework.redis.config.YudaoCacheProperties;
6 import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnoreAspect; 6 import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnoreAspect;
7 import cn.iocoder.yudao.framework.tenant.core.db.TenantDatabaseInterceptor; 7 import cn.iocoder.yudao.framework.tenant.core.db.TenantDatabaseInterceptor;
8 import cn.iocoder.yudao.framework.tenant.core.job.TenantJobAspect; 8 import cn.iocoder.yudao.framework.tenant.core.job.TenantJobAspect;
9 -import cn.iocoder.yudao.framework.tenant.core.mq.TenantRedisMessageInterceptor; 9 +import cn.iocoder.yudao.framework.tenant.core.mq.rabbitmq.TenantRabbitMQInitializer;
  10 +import cn.iocoder.yudao.framework.tenant.core.mq.redis.TenantRedisMessageInterceptor;
  11 +import cn.iocoder.yudao.framework.tenant.core.mq.rocketmq.TenantRocketMQInitializer;
10 import cn.iocoder.yudao.framework.tenant.core.redis.TenantRedisCacheManager; 12 import cn.iocoder.yudao.framework.tenant.core.redis.TenantRedisCacheManager;
11 import cn.iocoder.yudao.framework.tenant.core.security.TenantSecurityWebFilter; 13 import cn.iocoder.yudao.framework.tenant.core.security.TenantSecurityWebFilter;
12 import cn.iocoder.yudao.framework.tenant.core.service.TenantFrameworkService; 14 import cn.iocoder.yudao.framework.tenant.core.service.TenantFrameworkService;
@@ -18,6 +20,7 @@ import cn.iocoder.yudao.module.system.api.tenant.TenantApi; @@ -18,6 +20,7 @@ import cn.iocoder.yudao.module.system.api.tenant.TenantApi;
18 import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; 20 import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
19 import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor; 21 import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
20 import org.springframework.boot.autoconfigure.AutoConfiguration; 22 import org.springframework.boot.autoconfigure.AutoConfiguration;
  23 +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
21 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 24 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
22 import org.springframework.boot.context.properties.EnableConfigurationProperties; 25 import org.springframework.boot.context.properties.EnableConfigurationProperties;
23 import org.springframework.boot.web.servlet.FilterRegistrationBean; 26 import org.springframework.boot.web.servlet.FilterRegistrationBean;
@@ -92,6 +95,18 @@ public class YudaoTenantAutoConfiguration { @@ -92,6 +95,18 @@ public class YudaoTenantAutoConfiguration {
92 return new TenantRedisMessageInterceptor(); 95 return new TenantRedisMessageInterceptor();
93 } 96 }
94 97
  98 + @Bean
  99 + @ConditionalOnClass(name = "org.springframework.amqp.rabbit.core.RabbitTemplate")
  100 + public TenantRabbitMQInitializer tenantRabbitMQInitializer() {
  101 + return new TenantRabbitMQInitializer();
  102 + }
  103 +
  104 + @Bean
  105 + @ConditionalOnClass(name = "org.apache.rocketmq.spring.core.RocketMQTemplate")
  106 + public TenantRocketMQInitializer tenantRocketMQInitializer() {
  107 + return new TenantRocketMQInitializer();
  108 + }
  109 +
95 // ========== Job ========== 110 // ========== Job ==========
96 111
97 @Bean 112 @Bean
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/mq/kafka/TenantKafkaEnvironmentPostProcessor.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.tenant.core.mq.kafka;
  2 +
  3 +import cn.hutool.core.util.StrUtil;
  4 +import lombok.extern.slf4j.Slf4j;
  5 +import org.springframework.boot.SpringApplication;
  6 +import org.springframework.boot.env.EnvironmentPostProcessor;
  7 +import org.springframework.core.env.ConfigurableEnvironment;
  8 +
  9 +/**
  10 + * 多租户的 Kafka 的 {@link EnvironmentPostProcessor} 实现类
  11 + *
  12 + * Kafka Producer 发送消息时,增加 {@link TenantKafkaProducerInterceptor} 拦截器
  13 + *
  14 + * @author 芋道源码
  15 + */
  16 +@Slf4j
  17 +public class TenantKafkaEnvironmentPostProcessor implements EnvironmentPostProcessor {
  18 +
  19 + private static final String PROPERTY_KEY_INTERCEPTOR_CLASSES = "spring.kafka.producer.properties.interceptor.classes";
  20 +
  21 + @Override
  22 + public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
  23 + // 添加 TenantKafkaProducerInterceptor 拦截器
  24 + try {
  25 + String value = environment.getProperty(PROPERTY_KEY_INTERCEPTOR_CLASSES);
  26 + if (StrUtil.isEmpty(value)) {
  27 + value = TenantKafkaProducerInterceptor.class.getName();
  28 + } else {
  29 + value += "," + TenantKafkaProducerInterceptor.class.getName();
  30 + }
  31 + environment.getSystemProperties().put(PROPERTY_KEY_INTERCEPTOR_CLASSES, value);
  32 + } catch (NoClassDefFoundError ignore) {
  33 + // 如果触发 NoClassDefFoundError 异常,说明 TenantKafkaProducerInterceptor 类不存在,即没引入 kafka-spring 依赖
  34 + }
  35 + }
  36 +
  37 +}
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/mq/kafka/TenantKafkaProducerInterceptor.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.tenant.core.mq.kafka;
  2 +
  3 +import cn.hutool.core.util.ReflectUtil;
  4 +import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
  5 +import org.apache.kafka.clients.producer.ProducerInterceptor;
  6 +import org.apache.kafka.clients.producer.ProducerRecord;
  7 +import org.apache.kafka.clients.producer.RecordMetadata;
  8 +import org.apache.kafka.common.header.Headers;
  9 +import org.springframework.messaging.handler.invocation.InvocableHandlerMethod;
  10 +
  11 +import java.util.Map;
  12 +
  13 +import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.HEADER_TENANT_ID;
  14 +
  15 +/**
  16 + * Kafka 消息队列的多租户 {@link ProducerInterceptor} 实现类
  17 + *
  18 + * 1. Producer 发送消息时,将 {@link TenantContextHolder} 租户编号,添加到消息的 Header 中
  19 + * 2. Consumer 消费消息时,将消息的 Header 的租户编号,添加到 {@link TenantContextHolder} 中,通过 {@link InvocableHandlerMethod} 实现
  20 + *
  21 + * @author 芋道源码
  22 + */
  23 +public class TenantKafkaProducerInterceptor implements ProducerInterceptor<Object, Object> {
  24 +
  25 + @Override
  26 + public ProducerRecord<Object, Object> onSend(ProducerRecord<Object, Object> record) {
  27 + Long tenantId = TenantContextHolder.getTenantId();
  28 + if (tenantId != null) {
  29 + Headers headers = (Headers) ReflectUtil.getFieldValue(record, "headers"); // private 属性,没有 get 方法,智能反射
  30 + headers.add(HEADER_TENANT_ID, tenantId.toString().getBytes());
  31 + }
  32 + return record;
  33 + }
  34 +
  35 + @Override
  36 + public void onAcknowledgement(RecordMetadata metadata, Exception exception) {
  37 + }
  38 +
  39 + @Override
  40 + public void close() {
  41 + }
  42 +
  43 + @Override
  44 + public void configure(Map<String, ?> configs) {
  45 + }
  46 +
  47 +}
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/mq/rabbitmq/TenantRabbitMQInitializer.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.tenant.core.mq.rabbitmq;
  2 +
  3 +import org.springframework.amqp.rabbit.core.RabbitTemplate;
  4 +import org.springframework.beans.BeansException;
  5 +import org.springframework.beans.factory.config.BeanPostProcessor;
  6 +
  7 +/**
  8 + * 多租户的 RabbitMQ 初始化器
  9 + *
  10 + * @author 芋道源码
  11 + */
  12 +public class TenantRabbitMQInitializer implements BeanPostProcessor {
  13 +
  14 + @Override
  15 + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
  16 + if (bean instanceof RabbitTemplate) {
  17 + RabbitTemplate rabbitTemplate = (RabbitTemplate) bean;
  18 + rabbitTemplate.addBeforePublishPostProcessors(new TenantRabbitMQMessagePostProcessor());
  19 + }
  20 + return bean;
  21 + }
  22 +
  23 +}
0 \ No newline at end of file 24 \ No newline at end of file
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/mq/rabbitmq/TenantRabbitMQMessagePostProcessor.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.tenant.core.mq.rabbitmq;
  2 +
  3 +import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
  4 +import org.apache.kafka.clients.producer.ProducerInterceptor;
  5 +import org.springframework.amqp.AmqpException;
  6 +import org.springframework.amqp.core.Message;
  7 +import org.springframework.amqp.core.MessagePostProcessor;
  8 +import org.springframework.messaging.handler.invocation.InvocableHandlerMethod;
  9 +
  10 +import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.HEADER_TENANT_ID;
  11 +
  12 +/**
  13 + * RabbitMQ 消息队列的多租户 {@link ProducerInterceptor} 实现类
  14 + *
  15 + * 1. Producer 发送消息时,将 {@link TenantContextHolder} 租户编号,添加到消息的 Header 中
  16 + * 2. Consumer 消费消息时,将消息的 Header 的租户编号,添加到 {@link TenantContextHolder} 中,通过 {@link InvocableHandlerMethod} 实现
  17 + *
  18 + * @author 芋道源码
  19 + */
  20 +public class TenantRabbitMQMessagePostProcessor implements MessagePostProcessor {
  21 +
  22 + @Override
  23 + public Message postProcessMessage(Message message) throws AmqpException {
  24 + Long tenantId = TenantContextHolder.getTenantId();
  25 + if (tenantId != null) {
  26 + message.getMessageProperties().getHeaders().put(HEADER_TENANT_ID, tenantId);
  27 + }
  28 + return message;
  29 + }
  30 +
  31 +}
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/mq/TenantRedisMessageInterceptor.java renamed to yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/mq/redis/TenantRedisMessageInterceptor.java
1 -package cn.iocoder.yudao.framework.tenant.core.mq; 1 +package cn.iocoder.yudao.framework.tenant.core.mq.redis;
2 2
3 import cn.hutool.core.util.StrUtil; 3 import cn.hutool.core.util.StrUtil;
4 -import cn.iocoder.yudao.framework.mq.core.interceptor.RedisMessageInterceptor;  
5 -import cn.iocoder.yudao.framework.mq.core.message.AbstractRedisMessage; 4 +import cn.iocoder.yudao.framework.mq.redis.core.interceptor.RedisMessageInterceptor;
  5 +import cn.iocoder.yudao.framework.mq.redis.core.message.AbstractRedisMessage;
6 import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; 6 import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
7 7
8 import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.HEADER_TENANT_ID; 8 import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.HEADER_TENANT_ID;
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/mq/rocketmq/TenantRocketMQConsumeMessageHook.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.tenant.core.mq.rocketmq;
  2 +
  3 +import cn.hutool.core.lang.Assert;
  4 +import cn.hutool.core.util.StrUtil;
  5 +import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
  6 +import org.apache.rocketmq.client.hook.ConsumeMessageContext;
  7 +import org.apache.rocketmq.client.hook.ConsumeMessageHook;
  8 +import org.apache.rocketmq.common.message.MessageExt;
  9 +import org.springframework.messaging.handler.invocation.InvocableHandlerMethod;
  10 +
  11 +import java.util.List;
  12 +
  13 +import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.HEADER_TENANT_ID;
  14 +
  15 +/**
  16 + * RocketMQ 消息队列的多租户 {@link ConsumeMessageHook} 实现类
  17 + *
  18 + * Consumer 消费消息时,将消息的 Header 的租户编号,添加到 {@link TenantContextHolder} 中,通过 {@link InvocableHandlerMethod} 实现
  19 + *
  20 + * @author 芋道源码
  21 + */
  22 +public class TenantRocketMQConsumeMessageHook implements ConsumeMessageHook {
  23 +
  24 + @Override
  25 + public String hookName() {
  26 + return getClass().getSimpleName();
  27 + }
  28 +
  29 + @Override
  30 + public void consumeMessageBefore(ConsumeMessageContext context) {
  31 + // 校验,消息必须是单条,不然设置租户可能不正确
  32 + List<MessageExt> messages = context.getMsgList();
  33 + Assert.isTrue(messages.size() == 1, "消息条数({})不正确", messages.size());
  34 + // 设置租户编号
  35 + String tenantId = messages.get(0).getUserProperty(HEADER_TENANT_ID);
  36 + if (StrUtil.isNotEmpty(tenantId)) {
  37 + TenantContextHolder.setTenantId(Long.parseLong(tenantId));
  38 + }
  39 + }
  40 +
  41 + @Override
  42 + public void consumeMessageAfter(ConsumeMessageContext context) {
  43 + TenantContextHolder.clear();
  44 + }
  45 +
  46 +}
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/mq/rocketmq/TenantRocketMQInitializer.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.tenant.core.mq.rocketmq;
  2 +
  3 +import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
  4 +import org.apache.rocketmq.client.impl.consumer.DefaultMQPushConsumerImpl;
  5 +import org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl;
  6 +import org.apache.rocketmq.client.producer.DefaultMQProducer;
  7 +import org.apache.rocketmq.spring.core.RocketMQTemplate;
  8 +import org.apache.rocketmq.spring.support.DefaultRocketMQListenerContainer;
  9 +import org.springframework.beans.BeansException;
  10 +import org.springframework.beans.factory.config.BeanPostProcessor;
  11 +
  12 +/**
  13 + * 多租户的 RocketMQ 初始化器
  14 + *
  15 + * @author 芋道源码
  16 + */
  17 +public class TenantRocketMQInitializer implements BeanPostProcessor {
  18 +
  19 + @Override
  20 + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
  21 + if (bean instanceof DefaultRocketMQListenerContainer) {
  22 + DefaultRocketMQListenerContainer container = (DefaultRocketMQListenerContainer) bean;
  23 + initTenantConsumer(container.getConsumer());
  24 + } else if (bean instanceof RocketMQTemplate) {
  25 + RocketMQTemplate template = (RocketMQTemplate) bean;
  26 + initTenantProducer(template.getProducer());
  27 + }
  28 + return bean;
  29 + }
  30 +
  31 + private void initTenantProducer(DefaultMQProducer producer) {
  32 + if (producer == null) {
  33 + return;
  34 + }
  35 + DefaultMQProducerImpl producerImpl = producer.getDefaultMQProducerImpl();
  36 + if (producerImpl == null) {
  37 + return;
  38 + }
  39 + producerImpl.registerSendMessageHook(new TenantRocketMQSendMessageHook());
  40 + }
  41 +
  42 + private void initTenantConsumer(DefaultMQPushConsumer consumer) {
  43 + if (consumer == null) {
  44 + return;
  45 + }
  46 + DefaultMQPushConsumerImpl consumerImpl = consumer.getDefaultMQPushConsumerImpl();
  47 + if (consumerImpl == null) {
  48 + return;
  49 + }
  50 + consumerImpl.registerConsumeMessageHook(new TenantRocketMQConsumeMessageHook());
  51 + }
  52 +
  53 +}
0 \ No newline at end of file 54 \ No newline at end of file
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/mq/rocketmq/TenantRocketMQSendMessageHook.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.tenant.core.mq.rocketmq;
  2 +
  3 +import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
  4 +import org.apache.rocketmq.client.hook.SendMessageContext;
  5 +import org.apache.rocketmq.client.hook.SendMessageHook;
  6 +
  7 +import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.HEADER_TENANT_ID;
  8 +
  9 +/**
  10 + * RocketMQ 消息队列的多租户 {@link SendMessageHook} 实现类
  11 + *
  12 + * Producer 发送消息时,将 {@link TenantContextHolder} 租户编号,添加到消息的 Header 中
  13 + *
  14 + * @author 芋道源码
  15 + */
  16 +public class TenantRocketMQSendMessageHook implements SendMessageHook {
  17 +
  18 + @Override
  19 + public String hookName() {
  20 + return getClass().getSimpleName();
  21 + }
  22 +
  23 + @Override
  24 + public void sendMessageBefore(SendMessageContext sendMessageContext) {
  25 + Long tenantId = TenantContextHolder.getTenantId();
  26 + if (tenantId == null) {
  27 + return;
  28 + }
  29 + sendMessageContext.getMessage().putUserProperty(HEADER_TENANT_ID, tenantId.toString());
  30 + }
  31 +
  32 + @Override
  33 + public void sendMessageAfter(SendMessageContext sendMessageContext) {
  34 + }
  35 +
  36 +}
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/org/springframework/messaging/handler/invocation/InvocableHandlerMethod.java 0 → 100644
  1 +/*
  2 + * Copyright 2002-2021 the original author or authors.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * https://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package org.springframework.messaging.handler.invocation;
  18 +
  19 +import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
  20 +import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
  21 +import org.springframework.core.DefaultParameterNameDiscoverer;
  22 +import org.springframework.core.MethodParameter;
  23 +import org.springframework.core.ParameterNameDiscoverer;
  24 +import org.springframework.core.ResolvableType;
  25 +import org.springframework.lang.Nullable;
  26 +import org.springframework.messaging.Message;
  27 +import org.springframework.messaging.handler.HandlerMethod;
  28 +import org.springframework.util.ObjectUtils;
  29 +
  30 +import java.lang.reflect.InvocationTargetException;
  31 +import java.lang.reflect.Method;
  32 +import java.lang.reflect.Type;
  33 +import java.util.Arrays;
  34 +
  35 +import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.HEADER_TENANT_ID;
  36 +
  37 +/**
  38 + * Extension of {@link HandlerMethod} that invokes the underlying method with
  39 + * argument values resolved from the current HTTP request through a list of
  40 + * {@link HandlerMethodArgumentResolver}.
  41 + *
  42 + * 针对 rabbitmq-spring 和 kafka-spring,不存在合适的拓展点,可以实现 Consumer 消费前,读取 Header 中的 tenant-id 设置到 {@link TenantContextHolder} 中
  43 + * TODO 芋艿:持续跟进,看看有没新的拓展点
  44 + *
  45 + * @author Rossen Stoyanchev
  46 + * @author Juergen Hoeller
  47 + * @since 4.0
  48 + */
  49 +public class InvocableHandlerMethod extends HandlerMethod {
  50 +
  51 + private static final Object[] EMPTY_ARGS = new Object[0];
  52 +
  53 + private HandlerMethodArgumentResolverComposite resolvers = new HandlerMethodArgumentResolverComposite();
  54 +
  55 + private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
  56 +
  57 + /**
  58 + * Create an instance from a {@code HandlerMethod}.
  59 + */
  60 + public InvocableHandlerMethod(HandlerMethod handlerMethod) {
  61 + super(handlerMethod);
  62 + }
  63 +
  64 + /**
  65 + * Create an instance from a bean instance and a method.
  66 + */
  67 + public InvocableHandlerMethod(Object bean, Method method) {
  68 + super(bean, method);
  69 + }
  70 +
  71 + /**
  72 + * Construct a new handler method with the given bean instance, method name and parameters.
  73 + * @param bean the object bean
  74 + * @param methodName the method name
  75 + * @param parameterTypes the method parameter types
  76 + * @throws NoSuchMethodException when the method cannot be found
  77 + */
  78 + public InvocableHandlerMethod(Object bean, String methodName, Class<?>... parameterTypes)
  79 + throws NoSuchMethodException {
  80 +
  81 + super(bean, methodName, parameterTypes);
  82 + }
  83 +
  84 + /**
  85 + * Set {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers} to use for resolving method argument values.
  86 + */
  87 + public void setMessageMethodArgumentResolvers(HandlerMethodArgumentResolverComposite argumentResolvers) {
  88 + this.resolvers = argumentResolvers;
  89 + }
  90 +
  91 + /**
  92 + * Set the ParameterNameDiscoverer for resolving parameter names when needed
  93 + * (e.g. default request attribute name).
  94 + * <p>Default is a {@link DefaultParameterNameDiscoverer}.
  95 + */
  96 + public void setParameterNameDiscoverer(ParameterNameDiscoverer parameterNameDiscoverer) {
  97 + this.parameterNameDiscoverer = parameterNameDiscoverer;
  98 + }
  99 +
  100 + /**
  101 + * Invoke the method after resolving its argument values in the context of the given message.
  102 + * <p>Argument values are commonly resolved through
  103 + * {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers}.
  104 + * The {@code providedArgs} parameter however may supply argument values to be used directly,
  105 + * i.e. without argument resolution.
  106 + * <p>Delegates to {@link #getMethodArgumentValues} and calls {@link #doInvoke} with the
  107 + * resolved arguments.
  108 + * @param message the current message being processed
  109 + * @param providedArgs "given" arguments matched by type, not resolved
  110 + * @return the raw value returned by the invoked method
  111 + * @throws Exception raised if no suitable argument resolver can be found,
  112 + * or if the method raised an exception
  113 + * @see #getMethodArgumentValues
  114 + * @see #doInvoke
  115 + */
  116 + @Nullable
  117 + public Object invoke(Message<?> message, Object... providedArgs) throws Exception {
  118 + Object[] args = getMethodArgumentValues(message, providedArgs);
  119 + if (logger.isTraceEnabled()) {
  120 + logger.trace("Arguments: " + Arrays.toString(args));
  121 + }
  122 + // 注意:如下是本类的改动点!!!
  123 + // 情况一:无租户编号的情况
  124 + Long tenantId= parseTenantId(message);
  125 + if (tenantId == null) {
  126 + return doInvoke(args);
  127 + }
  128 + // 情况二:有租户的情况下
  129 + return TenantUtils.execute(tenantId, () -> doInvoke(args));
  130 + }
  131 +
  132 + private Long parseTenantId(Message<?> message) {
  133 + Object tenantId = message.getHeaders().get(HEADER_TENANT_ID);
  134 + if (tenantId == null) {
  135 + return null;
  136 + }
  137 + if (tenantId instanceof Long) {
  138 + return (Long) tenantId;
  139 + }
  140 + if (tenantId instanceof Number) {
  141 + return ((Number) tenantId).longValue();
  142 + }
  143 + if (tenantId instanceof String) {
  144 + return Long.parseLong((String) tenantId);
  145 + }
  146 + if (tenantId instanceof byte[]) {
  147 + return Long.parseLong(new String((byte[]) tenantId));
  148 + }
  149 + throw new IllegalArgumentException("未知的数据类型:" + tenantId);
  150 + }
  151 +
  152 + /**
  153 + * Get the method argument values for the current message, checking the provided
  154 + * argument values and falling back to the configured argument resolvers.
  155 + * <p>The resulting array will be passed into {@link #doInvoke}.
  156 + * @since 5.1.2
  157 + */
  158 + protected Object[] getMethodArgumentValues(Message<?> message, Object... providedArgs) throws Exception {
  159 + MethodParameter[] parameters = getMethodParameters();
  160 + if (ObjectUtils.isEmpty(parameters)) {
  161 + return EMPTY_ARGS;
  162 + }
  163 +
  164 + Object[] args = new Object[parameters.length];
  165 + for (int i = 0; i < parameters.length; i++) {
  166 + MethodParameter parameter = parameters[i];
  167 + parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
  168 + args[i] = findProvidedArgument(parameter, providedArgs);
  169 + if (args[i] != null) {
  170 + continue;
  171 + }
  172 + if (!this.resolvers.supportsParameter(parameter)) {
  173 + throw new MethodArgumentResolutionException(
  174 + message, parameter, formatArgumentError(parameter, "No suitable resolver"));
  175 + }
  176 + try {
  177 + args[i] = this.resolvers.resolveArgument(parameter, message);
  178 + }
  179 + catch (Exception ex) {
  180 + // Leave stack trace for later, exception may actually be resolved and handled...
  181 + if (logger.isDebugEnabled()) {
  182 + String exMsg = ex.getMessage();
  183 + if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
  184 + logger.debug(formatArgumentError(parameter, exMsg));
  185 + }
  186 + }
  187 + throw ex;
  188 + }
  189 + }
  190 + return args;
  191 + }
  192 +
  193 + /**
  194 + * Invoke the handler method with the given argument values.
  195 + */
  196 + @Nullable
  197 + protected Object doInvoke(Object... args) throws Exception {
  198 + try {
  199 + return getBridgedMethod().invoke(getBean(), args);
  200 + }
  201 + catch (IllegalArgumentException ex) {
  202 + assertTargetBean(getBridgedMethod(), getBean(), args);
  203 + String text = (ex.getMessage() != null ? ex.getMessage() : "Illegal argument");
  204 + throw new IllegalStateException(formatInvokeError(text, args), ex);
  205 + }
  206 + catch (InvocationTargetException ex) {
  207 + // Unwrap for HandlerExceptionResolvers ...
  208 + Throwable targetException = ex.getTargetException();
  209 + if (targetException instanceof RuntimeException) {
  210 + throw (RuntimeException) targetException;
  211 + }
  212 + else if (targetException instanceof Error) {
  213 + throw (Error) targetException;
  214 + }
  215 + else if (targetException instanceof Exception) {
  216 + throw (Exception) targetException;
  217 + }
  218 + else {
  219 + throw new IllegalStateException(formatInvokeError("Invocation failure", args), targetException);
  220 + }
  221 + }
  222 + }
  223 +
  224 + MethodParameter getAsyncReturnValueType(@Nullable Object returnValue) {
  225 + return new AsyncResultMethodParameter(returnValue);
  226 + }
  227 +
  228 + private class AsyncResultMethodParameter extends HandlerMethodParameter {
  229 +
  230 + @Nullable
  231 + private final Object returnValue;
  232 +
  233 + private final ResolvableType returnType;
  234 +
  235 + public AsyncResultMethodParameter(@Nullable Object returnValue) {
  236 + super(-1);
  237 + this.returnValue = returnValue;
  238 + this.returnType = ResolvableType.forType(super.getGenericParameterType()).getGeneric();
  239 + }
  240 +
  241 + protected AsyncResultMethodParameter(AsyncResultMethodParameter original) {
  242 + super(original);
  243 + this.returnValue = original.returnValue;
  244 + this.returnType = original.returnType;
  245 + }
  246 +
  247 + @Override
  248 + public Class<?> getParameterType() {
  249 + if (this.returnValue != null) {
  250 + return this.returnValue.getClass();
  251 + }
  252 + if (!ResolvableType.NONE.equals(this.returnType)) {
  253 + return this.returnType.toClass();
  254 + }
  255 + return super.getParameterType();
  256 + }
  257 +
  258 + @Override
  259 + public Type getGenericParameterType() {
  260 + return this.returnType.getType();
  261 + }
  262 +
  263 + @Override
  264 + public AsyncResultMethodParameter clone() {
  265 + return new AsyncResultMethodParameter(this);
  266 + }
  267 + }
  268 +
  269 +}
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/resources/META-INF/spring.factories 0 → 100644
  1 +org.springframework.boot.env.EnvironmentPostProcessor=\
  2 + cn.iocoder.yudao.framework.tenant.core.mq.kafka.TenantKafkaEnvironmentPostProcessor
yudao-framework/yudao-spring-boot-starter-mq/pom.xml
@@ -12,7 +12,7 @@ @@ -12,7 +12,7 @@
12 <packaging>jar</packaging> 12 <packaging>jar</packaging>
13 13
14 <name>${project.artifactId}</name> 14 <name>${project.artifactId}</name>
15 - <description>消息队列,基于 Redis Pub/Sub 实现广播消费,基于 Stream 实现集群消费</description> 15 + <description>消息队列,支持 Redis、RocketMQ、RabbitMQ、Kafka 四种</description>
16 <url>https://github.com/YunaiV/ruoyi-vue-pro</url> 16 <url>https://github.com/YunaiV/ruoyi-vue-pro</url>
17 17
18 <dependencies> 18 <dependencies>
@@ -21,6 +21,23 @@ @@ -21,6 +21,23 @@
21 <groupId>cn.iocoder.boot</groupId> 21 <groupId>cn.iocoder.boot</groupId>
22 <artifactId>yudao-spring-boot-starter-redis</artifactId> 22 <artifactId>yudao-spring-boot-starter-redis</artifactId>
23 </dependency> 23 </dependency>
  24 +
  25 + <!-- 消息队列相关 -->
  26 + <dependency>
  27 + <groupId>org.springframework.kafka</groupId>
  28 + <artifactId>spring-kafka</artifactId>
  29 + <optional>true</optional>
  30 + </dependency>
  31 + <dependency>
  32 + <groupId>org.springframework.amqp</groupId>
  33 + <artifactId>spring-rabbit</artifactId>
  34 + <optional>true</optional>
  35 + </dependency>
  36 + <dependency>
  37 + <groupId>org.apache.rocketmq</groupId>
  38 + <artifactId>rocketmq-spring-boot-starter</artifactId>
  39 + <optional>true</optional>
  40 + </dependency>
24 </dependencies> 41 </dependencies>
25 42
26 -</project> 43 -</project>
  44 +</project>
27 \ No newline at end of file 45 \ No newline at end of file
yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/package-info.java
1 /** 1 /**
2 - * 消息队列,基于 Redis 提供:  
3 - * 1. 基于 Pub/Sub 实现广播消费  
4 - * 2. 基于 Stream 实现集群消费 2 + * 消息队列,支持 Redis、RocketMQ、RabbitMQ、Kafka 四种
5 */ 3 */
6 package cn.iocoder.yudao.framework.mq; 4 package cn.iocoder.yudao.framework.mq;
yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/rabbitmq/config/YudaoRabbitMQAutoConfiguration.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.mq.rabbitmq.config;
  2 +
  3 +import cn.hutool.core.util.ReflectUtil;
  4 +import lombok.extern.slf4j.Slf4j;
  5 +import org.springframework.amqp.utils.SerializationUtils;
  6 +import org.springframework.boot.autoconfigure.AutoConfiguration;
  7 +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
  8 +
  9 +import java.lang.reflect.Field;
  10 +
  11 +/**
  12 + * RabbitMQ 消息队列配置类
  13 + *
  14 + * @author 芋道源码
  15 + */
  16 +@AutoConfiguration
  17 +@Slf4j
  18 +@ConditionalOnClass(name = "org.springframework.amqp.rabbit.core.RabbitTemplate")
  19 +public class YudaoRabbitMQAutoConfiguration {
  20 +
  21 + static {
  22 + // 强制设置 SerializationUtils 的 TRUST_ALL 为 true,避免 RabbitMQ Consumer 反序列化消息报错
  23 + // 为什么不通过设置 spring.amqp.deserialization.trust.all 呢?因为可能在 SerializationUtils static 初始化后
  24 + Field trustAllField = ReflectUtil.getField(SerializationUtils.class, "TRUST_ALL");
  25 + ReflectUtil.removeFinalModify(trustAllField);
  26 + ReflectUtil.setFieldValue(SerializationUtils.class, trustAllField, true);
  27 + }
  28 +
  29 +}
yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/rabbitmq/core/package-info.java 0 → 100644
  1 +/**
  2 + * 占位符,无特殊逻辑
  3 + */
  4 +package cn.iocoder.yudao.framework.mq.rabbitmq.core;
0 \ No newline at end of file 5 \ No newline at end of file
yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/rabbitmq/package-info.java 0 → 100644
  1 +/**
  2 + * 消息队列,基于 RabbitMQ 提供
  3 + */
  4 +package cn.iocoder.yudao.framework.mq.rabbitmq;
yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/config/YudaoMQAutoConfiguration.java renamed to yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/config/YudaoRedisMQAutoConfiguration.java
1 -package cn.iocoder.yudao.framework.mq.config; 1 +package cn.iocoder.yudao.framework.mq.redis.config;
2 2
3 import cn.hutool.core.map.MapUtil; 3 import cn.hutool.core.map.MapUtil;
4 import cn.hutool.core.util.StrUtil; 4 import cn.hutool.core.util.StrUtil;
5 import cn.hutool.system.SystemUtil; 5 import cn.hutool.system.SystemUtil;
6 import cn.iocoder.yudao.framework.common.enums.DocumentEnum; 6 import cn.iocoder.yudao.framework.common.enums.DocumentEnum;
7 -import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;  
8 -import cn.iocoder.yudao.framework.mq.core.interceptor.RedisMessageInterceptor;  
9 -import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener;  
10 -import cn.iocoder.yudao.framework.mq.core.stream.AbstractStreamMessageListener;  
11 -import cn.iocoder.yudao.framework.mq.job.RedisPendingMessageResendJob; 7 +import cn.iocoder.yudao.framework.mq.redis.core.RedisMQTemplate;
  8 +import cn.iocoder.yudao.framework.mq.redis.core.interceptor.RedisMessageInterceptor;
  9 +import cn.iocoder.yudao.framework.mq.redis.core.job.RedisPendingMessageResendJob;
  10 +import cn.iocoder.yudao.framework.mq.redis.core.pubsub.AbstractRedisChannelMessageListener;
  11 +import cn.iocoder.yudao.framework.mq.redis.core.stream.AbstractRedisStreamMessageListener;
12 import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration; 12 import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration;
13 import lombok.extern.slf4j.Slf4j; 13 import lombok.extern.slf4j.Slf4j;
14 import org.redisson.api.RedissonClient; 14 import org.redisson.api.RedissonClient;
15 import org.springframework.beans.factory.annotation.Value; 15 import org.springframework.beans.factory.annotation.Value;
16 import org.springframework.boot.autoconfigure.AutoConfiguration; 16 import org.springframework.boot.autoconfigure.AutoConfiguration;
17 import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; 17 import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
18 -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;  
19 import org.springframework.context.annotation.Bean; 18 import org.springframework.context.annotation.Bean;
20 import org.springframework.data.redis.connection.RedisServerCommands; 19 import org.springframework.data.redis.connection.RedisServerCommands;
21 import org.springframework.data.redis.connection.stream.Consumer; 20 import org.springframework.data.redis.connection.stream.Consumer;
@@ -27,7 +26,6 @@ import org.springframework.data.redis.core.RedisTemplate; @@ -27,7 +26,6 @@ import org.springframework.data.redis.core.RedisTemplate;
27 import org.springframework.data.redis.core.StringRedisTemplate; 26 import org.springframework.data.redis.core.StringRedisTemplate;
28 import org.springframework.data.redis.listener.ChannelTopic; 27 import org.springframework.data.redis.listener.ChannelTopic;
29 import org.springframework.data.redis.listener.RedisMessageListenerContainer; 28 import org.springframework.data.redis.listener.RedisMessageListenerContainer;
30 -import org.springframework.data.redis.stream.DefaultStreamMessageListenerContainerX;  
31 import org.springframework.data.redis.stream.StreamMessageListenerContainer; 29 import org.springframework.data.redis.stream.StreamMessageListenerContainer;
32 import org.springframework.scheduling.annotation.EnableScheduling; 30 import org.springframework.scheduling.annotation.EnableScheduling;
33 31
@@ -42,7 +40,7 @@ import java.util.Properties; @@ -42,7 +40,7 @@ import java.util.Properties;
42 @Slf4j 40 @Slf4j
43 @EnableScheduling // 启用定时任务,用于 RedisPendingMessageResendJob 重发消息 41 @EnableScheduling // 启用定时任务,用于 RedisPendingMessageResendJob 重发消息
44 @AutoConfiguration(after = YudaoRedisAutoConfiguration.class) 42 @AutoConfiguration(after = YudaoRedisAutoConfiguration.class)
45 -public class YudaoMQAutoConfiguration { 43 +public class YudaoRedisMQAutoConfiguration {
46 44
47 @Bean 45 @Bean
48 public RedisMQTemplate redisMQTemplate(StringRedisTemplate redisTemplate, 46 public RedisMQTemplate redisMQTemplate(StringRedisTemplate redisTemplate,
@@ -59,10 +57,9 @@ public class YudaoMQAutoConfiguration { @@ -59,10 +57,9 @@ public class YudaoMQAutoConfiguration {
59 * 创建 Redis Pub/Sub 广播消费的容器 57 * 创建 Redis Pub/Sub 广播消费的容器
60 */ 58 */
61 @Bean(initMethod = "start", destroyMethod = "stop") 59 @Bean(initMethod = "start", destroyMethod = "stop")
62 - @ConditionalOnBean(AbstractChannelMessageListener.class) // 只有 AbstractChannelMessageListener 存在的时候,才需要注册 Redis pubsub 监听  
63 - @ConditionalOnProperty(prefix = "yudao.mq.redis.pubsub", value = "enable", matchIfMissing = true) // 允许使用 yudao.mq.redis.pubsub.enable=false 禁用多租户 60 + @ConditionalOnBean(AbstractRedisChannelMessageListener.class) // 只有 AbstractChannelMessageListener 存在的时候,才需要注册 Redis pubsub 监听
64 public RedisMessageListenerContainer redisMessageListenerContainer( 61 public RedisMessageListenerContainer redisMessageListenerContainer(
65 - RedisMQTemplate redisMQTemplate, List<AbstractChannelMessageListener<?>> listeners) { 62 + RedisMQTemplate redisMQTemplate, List<AbstractRedisChannelMessageListener<?>> listeners) {
66 // 创建 RedisMessageListenerContainer 对象 63 // 创建 RedisMessageListenerContainer 对象
67 RedisMessageListenerContainer container = new RedisMessageListenerContainer(); 64 RedisMessageListenerContainer container = new RedisMessageListenerContainer();
68 // 设置 RedisConnection 工厂。 65 // 设置 RedisConnection 工厂。
@@ -81,9 +78,8 @@ public class YudaoMQAutoConfiguration { @@ -81,9 +78,8 @@ public class YudaoMQAutoConfiguration {
81 * 创建 Redis Stream 重新消费的任务 78 * 创建 Redis Stream 重新消费的任务
82 */ 79 */
83 @Bean 80 @Bean
84 - @ConditionalOnBean(AbstractStreamMessageListener.class) // 只有 AbstractStreamMessageListener 存在的时候,才需要注册 Redis pubsub 监听  
85 - @ConditionalOnProperty(prefix = "yudao.mq.redis.stream", value = "enable", matchIfMissing = true) // 允许使用 yudao.mq.redis.stream.enable=false 禁用多租户  
86 - public RedisPendingMessageResendJob redisPendingMessageResendJob(List<AbstractStreamMessageListener<?>> listeners, 81 + @ConditionalOnBean(AbstractRedisStreamMessageListener.class) // 只有 AbstractStreamMessageListener 存在的时候,才需要注册 Redis pubsub 监听
  82 + public RedisPendingMessageResendJob redisPendingMessageResendJob(List<AbstractRedisStreamMessageListener<?>> listeners,
87 RedisMQTemplate redisTemplate, 83 RedisMQTemplate redisTemplate,
88 @Value("${spring.application.name}") String groupName, 84 @Value("${spring.application.name}") String groupName,
89 RedissonClient redissonClient) { 85 RedissonClient redissonClient) {
@@ -92,14 +88,13 @@ public class YudaoMQAutoConfiguration { @@ -92,14 +88,13 @@ public class YudaoMQAutoConfiguration {
92 88
93 /** 89 /**
94 * 创建 Redis Stream 集群消费的容器 90 * 创建 Redis Stream 集群消费的容器
95 - * <p>  
96 - * Redis Stream 的 xreadgroup 命令:https://www.geek-book.com/src/docs/redis/redis/redis.io/commands/xreadgroup.html 91 + *
  92 + * 基础知识:<a href="https://www.geek-book.com/src/docs/redis/redis/redis.io/commands/xreadgroup.html">Redis Stream 的 xreadgroup 命令</a>
97 */ 93 */
98 @Bean(initMethod = "start", destroyMethod = "stop") 94 @Bean(initMethod = "start", destroyMethod = "stop")
99 - @ConditionalOnBean(AbstractStreamMessageListener.class) // 只有 AbstractStreamMessageListener 存在的时候,才需要注册 Redis pubsub 监听  
100 - @ConditionalOnProperty(prefix = "yudao.mq.redis.stream", value = "enable", matchIfMissing = true) // 允许使用 yudao.mq.redis.stream.enable=false 禁用多租户 95 + @ConditionalOnBean(AbstractRedisStreamMessageListener.class) // 只有 AbstractStreamMessageListener 存在的时候,才需要注册 Redis pubsub 监听
101 public StreamMessageListenerContainer<String, ObjectRecord<String, String>> redisStreamMessageListenerContainer( 96 public StreamMessageListenerContainer<String, ObjectRecord<String, String>> redisStreamMessageListenerContainer(
102 - RedisMQTemplate redisMQTemplate, List<AbstractStreamMessageListener<?>> listeners) { 97 + RedisMQTemplate redisMQTemplate, List<AbstractRedisStreamMessageListener<?>> listeners) {
103 RedisTemplate<String, ?> redisTemplate = redisMQTemplate.getRedisTemplate(); 98 RedisTemplate<String, ?> redisTemplate = redisMQTemplate.getRedisTemplate();
104 checkRedisVersion(redisTemplate); 99 checkRedisVersion(redisTemplate);
105 // 第一步,创建 StreamMessageListenerContainer 容器 100 // 第一步,创建 StreamMessageListenerContainer 容器
@@ -111,8 +106,7 @@ public class YudaoMQAutoConfiguration { @@ -111,8 +106,7 @@ public class YudaoMQAutoConfiguration {
111 .build(); 106 .build();
112 // 创建 container 对象 107 // 创建 container 对象
113 StreamMessageListenerContainer<String, ObjectRecord<String, String>> container = 108 StreamMessageListenerContainer<String, ObjectRecord<String, String>> container =
114 -// StreamMessageListenerContainer.create(redisTemplate.getRequiredConnectionFactory(), containerOptions);  
115 - DefaultStreamMessageListenerContainerX.create(redisMQTemplate.getRedisTemplate().getRequiredConnectionFactory(), containerOptions); 109 + StreamMessageListenerContainer.create(redisMQTemplate.getRedisTemplate().getRequiredConnectionFactory(), containerOptions);
116 110
117 // 第二步,注册监听器,消费对应的 Stream 主题 111 // 第二步,注册监听器,消费对应的 Stream 主题
118 String consumerName = buildConsumerName(); 112 String consumerName = buildConsumerName();
yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/core/RedisMQTemplate.java renamed to yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/core/RedisMQTemplate.java
1 -package cn.iocoder.yudao.framework.mq.core; 1 +package cn.iocoder.yudao.framework.mq.redis.core;
2 2
3 import cn.iocoder.yudao.framework.common.util.json.JsonUtils; 3 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
4 -import cn.iocoder.yudao.framework.mq.core.interceptor.RedisMessageInterceptor;  
5 -import cn.iocoder.yudao.framework.mq.core.message.AbstractRedisMessage;  
6 -import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessage;  
7 -import cn.iocoder.yudao.framework.mq.core.stream.AbstractStreamMessage; 4 +import cn.iocoder.yudao.framework.mq.redis.core.interceptor.RedisMessageInterceptor;
  5 +import cn.iocoder.yudao.framework.mq.redis.core.message.AbstractRedisMessage;
  6 +import cn.iocoder.yudao.framework.mq.redis.core.pubsub.AbstractRedisChannelMessage;
  7 +import cn.iocoder.yudao.framework.mq.redis.core.stream.AbstractRedisStreamMessage;
8 import lombok.AllArgsConstructor; 8 import lombok.AllArgsConstructor;
9 import lombok.Getter; 9 import lombok.Getter;
10 import org.springframework.data.redis.connection.stream.RecordId; 10 import org.springframework.data.redis.connection.stream.RecordId;
@@ -35,7 +35,7 @@ public class RedisMQTemplate { @@ -35,7 +35,7 @@ public class RedisMQTemplate {
35 * 35 *
36 * @param message 消息 36 * @param message 消息
37 */ 37 */
38 - public <T extends AbstractChannelMessage> void send(T message) { 38 + public <T extends AbstractRedisChannelMessage> void send(T message) {
39 try { 39 try {
40 sendMessageBefore(message); 40 sendMessageBefore(message);
41 // 发送消息 41 // 发送消息
@@ -51,7 +51,7 @@ public class RedisMQTemplate { @@ -51,7 +51,7 @@ public class RedisMQTemplate {
51 * @param message 消息 51 * @param message 消息
52 * @return 消息记录的编号对象 52 * @return 消息记录的编号对象
53 */ 53 */
54 - public <T extends AbstractStreamMessage> RecordId send(T message) { 54 + public <T extends AbstractRedisStreamMessage> RecordId send(T message) {
55 try { 55 try {
56 sendMessageBefore(message); 56 sendMessageBefore(message);
57 // 发送消息 57 // 发送消息
yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/core/interceptor/RedisMessageInterceptor.java renamed to yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/core/interceptor/RedisMessageInterceptor.java
1 -package cn.iocoder.yudao.framework.mq.core.interceptor; 1 +package cn.iocoder.yudao.framework.mq.redis.core.interceptor;
2 2
3 -import cn.iocoder.yudao.framework.mq.core.message.AbstractRedisMessage; 3 +import cn.iocoder.yudao.framework.mq.redis.core.message.AbstractRedisMessage;
4 4
5 /** 5 /**
6 * {@link AbstractRedisMessage} 消息拦截器 6 * {@link AbstractRedisMessage} 消息拦截器
yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/job/RedisPendingMessageResendJob.java renamed to yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/core/job/RedisPendingMessageResendJob.java
1 -package cn.iocoder.yudao.framework.mq.job; 1 +package cn.iocoder.yudao.framework.mq.redis.core.job;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 -import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;  
5 -import cn.iocoder.yudao.framework.mq.core.stream.AbstractStreamMessageListener; 4 +import cn.iocoder.yudao.framework.mq.redis.core.RedisMQTemplate;
  5 +import cn.iocoder.yudao.framework.mq.redis.core.stream.AbstractRedisStreamMessageListener;
6 import lombok.AllArgsConstructor; 6 import lombok.AllArgsConstructor;
7 import lombok.extern.slf4j.Slf4j; 7 import lombok.extern.slf4j.Slf4j;
8 import org.redisson.api.RLock; 8 import org.redisson.api.RLock;
@@ -33,7 +33,7 @@ public class RedisPendingMessageResendJob { @@ -33,7 +33,7 @@ public class RedisPendingMessageResendJob {
33 */ 33 */
34 private static final int EXPIRE_TIME = 5 * 60; 34 private static final int EXPIRE_TIME = 5 * 60;
35 35
36 - private final List<AbstractStreamMessageListener<?>> listeners; 36 + private final List<AbstractRedisStreamMessageListener<?>> listeners;
37 private final RedisMQTemplate redisTemplate; 37 private final RedisMQTemplate redisTemplate;
38 private final String groupName; 38 private final String groupName;
39 private final RedissonClient redissonClient; 39 private final RedissonClient redissonClient;
yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/core/message/AbstractRedisMessage.java renamed to yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/core/message/AbstractRedisMessage.java
1 -package cn.iocoder.yudao.framework.mq.core.message; 1 +package cn.iocoder.yudao.framework.mq.redis.core.message;
2 2
3 import lombok.Data; 3 import lombok.Data;
4 4
yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/core/pubsub/AbstractChannelMessage.java renamed to yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/core/pubsub/AbstractRedisChannelMessage.java
1 -package cn.iocoder.yudao.framework.mq.core.pubsub; 1 +package cn.iocoder.yudao.framework.mq.redis.core.pubsub;
2 2
3 -import cn.iocoder.yudao.framework.mq.core.message.AbstractRedisMessage; 3 +import cn.iocoder.yudao.framework.mq.redis.core.message.AbstractRedisMessage;
4 import com.fasterxml.jackson.annotation.JsonIgnore; 4 import com.fasterxml.jackson.annotation.JsonIgnore;
5 5
6 /** 6 /**
@@ -8,14 +8,16 @@ import com.fasterxml.jackson.annotation.JsonIgnore; @@ -8,14 +8,16 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
8 * 8 *
9 * @author 芋道源码 9 * @author 芋道源码
10 */ 10 */
11 -public abstract class AbstractChannelMessage extends AbstractRedisMessage { 11 +public abstract class AbstractRedisChannelMessage extends AbstractRedisMessage {
12 12
13 /** 13 /**
14 - * 获得 Redis Channel 14 + * 获得 Redis Channel,默认使用类名
15 * 15 *
16 * @return Channel 16 * @return Channel
17 */ 17 */
18 @JsonIgnore // 避免序列化。原因是,Redis 发布 Channel 消息的时候,已经会指定。 18 @JsonIgnore // 避免序列化。原因是,Redis 发布 Channel 消息的时候,已经会指定。
19 - public abstract String getChannel(); 19 + public String getChannel() {
  20 + return getClass().getSimpleName();
  21 + }
20 22
21 } 23 }
yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/core/pubsub/AbstractChannelMessageListener.java renamed to yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/core/pubsub/AbstractRedisChannelMessageListener.java
1 -package cn.iocoder.yudao.framework.mq.core.pubsub; 1 +package cn.iocoder.yudao.framework.mq.redis.core.pubsub;
2 2
3 import cn.hutool.core.util.TypeUtil; 3 import cn.hutool.core.util.TypeUtil;
4 import cn.iocoder.yudao.framework.common.util.json.JsonUtils; 4 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
5 -import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;  
6 -import cn.iocoder.yudao.framework.mq.core.interceptor.RedisMessageInterceptor;  
7 -import cn.iocoder.yudao.framework.mq.core.message.AbstractRedisMessage; 5 +import cn.iocoder.yudao.framework.mq.redis.core.RedisMQTemplate;
  6 +import cn.iocoder.yudao.framework.mq.redis.core.interceptor.RedisMessageInterceptor;
  7 +import cn.iocoder.yudao.framework.mq.redis.core.message.AbstractRedisMessage;
8 import lombok.Setter; 8 import lombok.Setter;
9 import lombok.SneakyThrows; 9 import lombok.SneakyThrows;
10 import org.springframework.data.redis.connection.Message; 10 import org.springframework.data.redis.connection.Message;
@@ -20,7 +20,7 @@ import java.util.List; @@ -20,7 +20,7 @@ import java.util.List;
20 * 20 *
21 * @author 芋道源码 21 * @author 芋道源码
22 */ 22 */
23 -public abstract class AbstractChannelMessageListener<T extends AbstractChannelMessage> implements MessageListener { 23 +public abstract class AbstractRedisChannelMessageListener<T extends AbstractRedisChannelMessage> implements MessageListener {
24 24
25 /** 25 /**
26 * 消息类型 26 * 消息类型
@@ -37,7 +37,7 @@ public abstract class AbstractChannelMessageListener&lt;T extends AbstractChannelMe @@ -37,7 +37,7 @@ public abstract class AbstractChannelMessageListener&lt;T extends AbstractChannelMe
37 private RedisMQTemplate redisMQTemplate; 37 private RedisMQTemplate redisMQTemplate;
38 38
39 @SneakyThrows 39 @SneakyThrows
40 - protected AbstractChannelMessageListener() { 40 + protected AbstractRedisChannelMessageListener() {
41 this.messageType = getMessageClass(); 41 this.messageType = getMessageClass();
42 this.channel = messageType.getDeclaredConstructor().newInstance().getChannel(); 42 this.channel = messageType.getDeclaredConstructor().newInstance().getChannel();
43 } 43 }
yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/core/stream/AbstractStreamMessage.java renamed to yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/core/stream/AbstractRedisStreamMessage.java
1 -package cn.iocoder.yudao.framework.mq.core.stream; 1 +package cn.iocoder.yudao.framework.mq.redis.core.stream;
2 2
3 -import cn.iocoder.yudao.framework.mq.core.message.AbstractRedisMessage; 3 +import cn.iocoder.yudao.framework.mq.redis.core.message.AbstractRedisMessage;
4 import com.fasterxml.jackson.annotation.JsonIgnore; 4 import com.fasterxml.jackson.annotation.JsonIgnore;
5 5
6 /** 6 /**
@@ -8,14 +8,16 @@ import com.fasterxml.jackson.annotation.JsonIgnore; @@ -8,14 +8,16 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
8 * 8 *
9 * @author 芋道源码 9 * @author 芋道源码
10 */ 10 */
11 -public abstract class AbstractStreamMessage extends AbstractRedisMessage { 11 +public abstract class AbstractRedisStreamMessage extends AbstractRedisMessage {
12 12
13 /** 13 /**
14 - * 获得 Redis Stream Key 14 + * 获得 Redis Stream Key,默认使用类名
15 * 15 *
16 * @return Channel 16 * @return Channel
17 */ 17 */
18 @JsonIgnore // 避免序列化 18 @JsonIgnore // 避免序列化
19 - public abstract String getStreamKey(); 19 + public String getStreamKey() {
  20 + return getClass().getSimpleName();
  21 + }
20 22
21 } 23 }
yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/core/stream/AbstractStreamMessageListener.java renamed to yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/core/stream/AbstractRedisStreamMessageListener.java
1 -package cn.iocoder.yudao.framework.mq.core.stream; 1 +package cn.iocoder.yudao.framework.mq.redis.core.stream;
2 2
3 import cn.hutool.core.util.TypeUtil; 3 import cn.hutool.core.util.TypeUtil;
4 import cn.iocoder.yudao.framework.common.util.json.JsonUtils; 4 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
5 -import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;  
6 -import cn.iocoder.yudao.framework.mq.core.interceptor.RedisMessageInterceptor;  
7 -import cn.iocoder.yudao.framework.mq.core.message.AbstractRedisMessage; 5 +import cn.iocoder.yudao.framework.mq.redis.core.RedisMQTemplate;
  6 +import cn.iocoder.yudao.framework.mq.redis.core.interceptor.RedisMessageInterceptor;
  7 +import cn.iocoder.yudao.framework.mq.redis.core.message.AbstractRedisMessage;
8 import lombok.Getter; 8 import lombok.Getter;
9 import lombok.Setter; 9 import lombok.Setter;
10 import lombok.SneakyThrows; 10 import lombok.SneakyThrows;
@@ -22,7 +22,7 @@ import java.util.List; @@ -22,7 +22,7 @@ import java.util.List;
22 * 22 *
23 * @author 芋道源码 23 * @author 芋道源码
24 */ 24 */
25 -public abstract class AbstractStreamMessageListener<T extends AbstractStreamMessage> 25 +public abstract class AbstractRedisStreamMessageListener<T extends AbstractRedisStreamMessage>
26 implements StreamListener<String, ObjectRecord<String, String>> { 26 implements StreamListener<String, ObjectRecord<String, String>> {
27 27
28 /** 28 /**
@@ -48,7 +48,7 @@ public abstract class AbstractStreamMessageListener&lt;T extends AbstractStreamMess @@ -48,7 +48,7 @@ public abstract class AbstractStreamMessageListener&lt;T extends AbstractStreamMess
48 private RedisMQTemplate redisMQTemplate; 48 private RedisMQTemplate redisMQTemplate;
49 49
50 @SneakyThrows 50 @SneakyThrows
51 - protected AbstractStreamMessageListener() { 51 + protected AbstractRedisStreamMessageListener() {
52 this.messageType = getMessageClass(); 52 this.messageType = getMessageClass();
53 this.streamKey = messageType.getDeclaredConstructor().newInstance().getStreamKey(); 53 this.streamKey = messageType.getDeclaredConstructor().newInstance().getStreamKey();
54 } 54 }
yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/package-info.java 0 → 100644
  1 +/**
  2 + * 消息队列,基于 Redis 提供:
  3 + * 1. 基于 Pub/Sub 实现广播消费
  4 + * 2. 基于 Stream 实现集群消费
  5 + */
  6 +package cn.iocoder.yudao.framework.mq.redis;
yudao-framework/yudao-spring-boot-starter-mq/src/main/java/org/springframework/data/redis/stream/DefaultStreamMessageListenerContainerX.java deleted
1 -package org.springframework.data.redis.stream;  
2 -  
3 -import cn.hutool.core.util.ReflectUtil;  
4 -import org.springframework.data.redis.connection.RedisConnectionFactory;  
5 -import org.springframework.data.redis.connection.stream.ByteRecord;  
6 -import org.springframework.data.redis.connection.stream.ReadOffset;  
7 -import org.springframework.data.redis.connection.stream.Record;  
8 -import org.springframework.util.Assert;  
9 -  
10 -import java.util.Collections;  
11 -import java.util.List;  
12 -import java.util.function.Function;  
13 -  
14 -/**  
15 - * 拓展 DefaultStreamMessageListenerContainer 实现,解决 Spring Data Redis + Redisson 结合使用时,Redisson 在 Stream 获得不到数据时,返回 null 而不是空 List,导致 NPE 异常。  
16 - * 对应 issue:https://github.com/spring-projects/spring-data-redis/issues/2147 和 https://github.com/redisson/redisson/issues/4006  
17 - * 目前看下来 Spring Data Redis 不肯加 null 判断,Redisson 暂时也没改返回 null 到空 List 的打算,所以暂时只能自己改,哽咽!  
18 - *  
19 - * @author 芋道源码  
20 - */  
21 -public class DefaultStreamMessageListenerContainerX<K, V extends Record<K, ?>> extends DefaultStreamMessageListenerContainer<K, V> {  
22 -  
23 - /**  
24 - * 参考 {@link StreamMessageListenerContainer#create(RedisConnectionFactory, StreamMessageListenerContainerOptions)} 的实现  
25 - */  
26 - public static <K, V extends Record<K, ?>> StreamMessageListenerContainer<K, V> create(RedisConnectionFactory connectionFactory, StreamMessageListenerContainer.StreamMessageListenerContainerOptions<K, V> options) {  
27 - Assert.notNull(connectionFactory, "RedisConnectionFactory must not be null!");  
28 - Assert.notNull(options, "StreamMessageListenerContainerOptions must not be null!");  
29 - return new DefaultStreamMessageListenerContainerX<>(connectionFactory, options);  
30 - }  
31 -  
32 - public DefaultStreamMessageListenerContainerX(RedisConnectionFactory connectionFactory, StreamMessageListenerContainerOptions<K, V> containerOptions) {  
33 - super(connectionFactory, containerOptions);  
34 - }  
35 -  
36 - /**  
37 - * 参考 {@link DefaultStreamMessageListenerContainer#register(StreamReadRequest, StreamListener)} 的实现  
38 - */  
39 - @Override  
40 - public Subscription register(StreamReadRequest<K> streamRequest, StreamListener<K, V> listener) {  
41 - return this.doRegisterX(getReadTaskX(streamRequest, listener));  
42 - }  
43 -  
44 - @SuppressWarnings("unchecked")  
45 - private StreamPollTask<K, V> getReadTaskX(StreamReadRequest<K> streamRequest, StreamListener<K, V> listener) {  
46 - StreamPollTask<K, V> task = ReflectUtil.invoke(this, "getReadTask", streamRequest, listener);  
47 - // 修改 readFunction 方法  
48 - Function<ReadOffset, List<ByteRecord>> readFunction = (Function<ReadOffset, List<ByteRecord>>) ReflectUtil.getFieldValue(task, "readFunction");  
49 - ReflectUtil.setFieldValue(task, "readFunction", (Function<ReadOffset, List<ByteRecord>>) readOffset -> {  
50 - List<ByteRecord> records = readFunction.apply(readOffset);  
51 - //【重点】保证 records 不是空,避免 NPE 的问题!!!  
52 - return records != null ? records : Collections.emptyList();  
53 - });  
54 - return task;  
55 - }  
56 -  
57 - private Subscription doRegisterX(Task task) {  
58 - return ReflectUtil.invoke(this, "doRegister", task);  
59 - }  
60 -  
61 -}  
62 -  
yudao-framework/yudao-spring-boot-starter-mq/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
1 -cn.iocoder.yudao.framework.mq.config.YudaoMQAutoConfiguration  
2 \ No newline at end of file 1 \ No newline at end of file
  2 +cn.iocoder.yudao.framework.mq.redis.config.YudaoRedisMQAutoConfiguration
  3 +cn.iocoder.yudao.framework.mq.rabbitmq.config.YudaoRabbitMQAutoConfiguration
3 \ No newline at end of file 4 \ No newline at end of file
yudao-framework/yudao-spring-boot-starter-mq/《芋道 Spring Boot 事件机制 Event 入门》.md 0 → 100644
  1 +<http://www.iocoder.cn/Spring-Boot/RocketMQ/?yudao>
yudao-framework/yudao-spring-boot-starter-mq/《芋道 Spring Boot 消息队列 Kafka 入门》.md 0 → 100644
  1 +<http://www.iocoder.cn/Spring-Boot/Kafka/?yudao>
yudao-framework/yudao-spring-boot-starter-mq/《芋道 Spring Boot 消息队列 RabbitMQ 入门》.md 0 → 100644
  1 +<http://www.iocoder.cn/Spring-Boot/RabbitMQ/?yudao>
yudao-framework/yudao-spring-boot-starter-mq/《芋道 Spring Boot 消息队列 RocketMQ 入门》.md 0 → 100644
  1 +<http://www.iocoder.cn/Spring-Boot/RocketMQ/?yudao>
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/mq/consumer/coupon/UserCreateConsumer.java renamed to yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/mq/consumer/coupon/CouponTakeByRegisterConsumer.java
1 package cn.iocoder.yudao.module.promotion.mq.consumer.coupon; 1 package cn.iocoder.yudao.module.promotion.mq.consumer.coupon;
2 2
3 -import cn.iocoder.yudao.framework.mq.core.stream.AbstractStreamMessageListener;  
4 -import cn.iocoder.yudao.module.promotion.mq.message.coupon.UserCreateMessage; 3 +import cn.iocoder.yudao.module.member.message.user.MemberUserCreateMessage;
5 import cn.iocoder.yudao.module.promotion.service.coupon.CouponService; 4 import cn.iocoder.yudao.module.promotion.service.coupon.CouponService;
6 import lombok.extern.slf4j.Slf4j; 5 import lombok.extern.slf4j.Slf4j;
  6 +import org.springframework.context.event.EventListener;
  7 +import org.springframework.scheduling.annotation.Async;
7 import org.springframework.stereotype.Component; 8 import org.springframework.stereotype.Component;
8 9
9 import javax.annotation.Resource; 10 import javax.annotation.Resource;
10 11
11 /** 12 /**
12 - * 针对 {@link UserCreateMessage} 的消费者 13 + * 用户注册时,发送优惠劵的消费者,基 {@link MemberUserCreateMessage} 消息
13 * 14 *
14 * @author owen 15 * @author owen
15 */ 16 */
16 @Component 17 @Component
17 @Slf4j 18 @Slf4j
18 -public class UserCreateConsumer extends AbstractStreamMessageListener<UserCreateMessage> { 19 +public class CouponTakeByRegisterConsumer {
19 20
20 @Resource 21 @Resource
21 private CouponService couponService; 22 private CouponService couponService;
22 23
23 - @Override  
24 - public void onMessage(UserCreateMessage message) { 24 + @EventListener
  25 + @Async // Spring Event 默认在 Producer 发送的线程,通过 @Async 实现异步
  26 + public void onMessage(MemberUserCreateMessage message) {
25 log.info("[onMessage][消息内容({})]", message); 27 log.info("[onMessage][消息内容({})]", message);
26 couponService.takeCouponByRegister(message.getUserId()); 28 couponService.takeCouponByRegister(message.getUserId());
27 } 29 }
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/mq/consumer/package-info.java 0 → 100644
  1 +/**
  2 + * 消息队列的消费者
  3 + */
  4 +package cn.iocoder.yudao.module.promotion.mq.consumer;
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/mq/message/package-info.java 0 → 100644
  1 +/**
  2 + * 消息队列的消息
  3 + */
  4 +package cn.iocoder.yudao.module.promotion.mq.message;
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/mq/producer/package-info.java 0 → 100644
  1 +/**
  2 + * 消息队列的生产者
  3 + */
  4 +package cn.iocoder.yudao.module.promotion.mq.producer;
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/message/package-info.java 0 → 100644
  1 +/**
  2 + * 消息队列的消息
  3 + */
  4 +package cn.iocoder.yudao.module.member.message;
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/mq/message/coupon/UserCreateMessage.java renamed to yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/message/user/MemberUserCreateMessage.java
1 -package cn.iocoder.yudao.module.promotion.mq.message.coupon; 1 +package cn.iocoder.yudao.module.member.message.user;
2 2
3 -import cn.iocoder.yudao.framework.mq.core.stream.AbstractStreamMessage;  
4 import lombok.Data; 3 import lombok.Data;
5 -import lombok.EqualsAndHashCode;  
6 4
7 import javax.validation.constraints.NotNull; 5 import javax.validation.constraints.NotNull;
8 6
@@ -12,8 +10,7 @@ import javax.validation.constraints.NotNull; @@ -12,8 +10,7 @@ import javax.validation.constraints.NotNull;
12 * @author owen 10 * @author owen
13 */ 11 */
14 @Data 12 @Data
15 -@EqualsAndHashCode(callSuper = true)  
16 -public class UserCreateMessage extends AbstractStreamMessage { 13 +public class MemberUserCreateMessage {
17 14
18 /** 15 /**
19 * 用户编号 16 * 用户编号
@@ -21,9 +18,4 @@ public class UserCreateMessage extends AbstractStreamMessage { @@ -21,9 +18,4 @@ public class UserCreateMessage extends AbstractStreamMessage {
21 @NotNull(message = "用户编号不能为空") 18 @NotNull(message = "用户编号不能为空")
22 private Long userId; 19 private Long userId;
23 20
24 - @Override  
25 - public String getStreamKey() {  
26 - return "member.user.create";  
27 - }  
28 -  
29 } 21 }
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/auth/AppAuthController.http
@@ -4,7 +4,7 @@ Content-Type: application/json @@ -4,7 +4,7 @@ Content-Type: application/json
4 tenant-id: {{appTenentId}} 4 tenant-id: {{appTenentId}}
5 5
6 { 6 {
7 - "mobile": "15601691300", 7 + "mobile": "15601691388",
8 "password": "admin123" 8 "password": "admin123"
9 } 9 }
10 10
@@ -14,7 +14,7 @@ Content-Type: application/json @@ -14,7 +14,7 @@ Content-Type: application/json
14 tenant-id: {{appTenentId}} 14 tenant-id: {{appTenentId}}
15 15
16 { 16 {
17 - "mobile": "15601691399", 17 + "mobile": "15601691388",
18 "scene": 1 18 "scene": 1
19 } 19 }
20 20
@@ -22,9 +22,10 @@ tenant-id: {{appTenentId}} @@ -22,9 +22,10 @@ tenant-id: {{appTenentId}}
22 POST {{appApi}}/member/auth/sms-login 22 POST {{appApi}}/member/auth/sms-login
23 Content-Type: application/json 23 Content-Type: application/json
24 tenant-id: {{appTenentId}} 24 tenant-id: {{appTenentId}}
  25 +terminal: 30
25 26
26 { 27 {
27 - "mobile": "15601691301", 28 + "mobile": "15601691388",
28 "code": 9999 29 "code": 9999
29 } 30 }
30 31
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/mq/consumer/package-info.java 0 → 100644
  1 +/**
  2 + * 消息队列的消费者
  3 + */
  4 +package cn.iocoder.yudao.module.member.mq.consumer;
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/mq/message/package-info.java 0 → 100644
  1 +/**
  2 + * 消息队列的消息
  3 + */
  4 +package cn.iocoder.yudao.module.member.mq.message;
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/mq/message/user/MemberUserCreateMessage.java deleted
1 -package cn.iocoder.yudao.module.member.mq.message.user;  
2 -  
3 -import cn.iocoder.yudao.framework.mq.core.stream.AbstractStreamMessage;  
4 -import lombok.Data;  
5 -import lombok.EqualsAndHashCode;  
6 -  
7 -import javax.validation.constraints.NotNull;  
8 -  
9 -/**  
10 - * 会员用户创建消息  
11 - *  
12 - * @author owen  
13 - */  
14 -@Data  
15 -@EqualsAndHashCode(callSuper = true)  
16 -public class MemberUserCreateMessage extends AbstractStreamMessage {  
17 -  
18 - /**  
19 - * 用户编号  
20 - */  
21 - @NotNull(message = "用户编号不能为空")  
22 - private Long userId;  
23 -  
24 - @Override  
25 - public String getStreamKey() {  
26 - return "member.user.create";  
27 - }  
28 -  
29 -}  
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/mq/producer/package-info.java 0 → 100644
  1 +/**
  2 + * 消息队列的生产者
  3 + */
  4 +package cn.iocoder.yudao.module.member.mq.producer;
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/mq/producer/user/MemberUserProducer.java
1 package cn.iocoder.yudao.module.member.mq.producer.user; 1 package cn.iocoder.yudao.module.member.mq.producer.user;
2 2
3 -import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;  
4 -import cn.iocoder.yudao.module.member.mq.message.user.MemberUserCreateMessage; 3 +import cn.iocoder.yudao.module.member.message.user.MemberUserCreateMessage;
5 import lombok.extern.slf4j.Slf4j; 4 import lombok.extern.slf4j.Slf4j;
  5 +import org.springframework.context.ApplicationContext;
6 import org.springframework.stereotype.Component; 6 import org.springframework.stereotype.Component;
7 7
8 import javax.annotation.Resource; 8 import javax.annotation.Resource;
@@ -17,7 +17,7 @@ import javax.annotation.Resource; @@ -17,7 +17,7 @@ import javax.annotation.Resource;
17 public class MemberUserProducer { 17 public class MemberUserProducer {
18 18
19 @Resource 19 @Resource
20 - private RedisMQTemplate redisMQTemplate; 20 + private ApplicationContext applicationContext;
21 21
22 /** 22 /**
23 * 发送 {@link MemberUserCreateMessage} 消息 23 * 发送 {@link MemberUserCreateMessage} 消息
@@ -25,7 +25,7 @@ public class MemberUserProducer { @@ -25,7 +25,7 @@ public class MemberUserProducer {
25 * @param userId 用户编号 25 * @param userId 用户编号
26 */ 26 */
27 public void sendUserCreateMessage(Long userId) { 27 public void sendUserCreateMessage(Long userId) {
28 - redisMQTemplate.send(new MemberUserCreateMessage().setUserId(userId)); 28 + applicationContext.publishEvent(new MemberUserCreateMessage().setUserId(userId));
29 } 29 }
30 30
31 } 31 }
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sms/SmsTemplateController.http
@@ -6,9 +6,9 @@ tenant-id: {{adminTenentId}} @@ -6,9 +6,9 @@ tenant-id: {{adminTenentId}}
6 6
7 { 7 {
8 "templateCode": "test_01", 8 "templateCode": "test_01",
9 - "mobile": "156016913900",  
10 - "params": {  
11 - "key01": "value01",  
12 - "key02": "value02" 9 + "mobile": "15601691390",
  10 + "templateParams": {
  11 + "operation": "value01",
  12 + "code": "value02"
13 } 13 }
14 } 14 }
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/mail/MailSendConsumer.java
1 package cn.iocoder.yudao.module.system.mq.consumer.mail; 1 package cn.iocoder.yudao.module.system.mq.consumer.mail;
2 2
3 -import cn.iocoder.yudao.framework.mq.core.stream.AbstractStreamMessageListener;  
4 import cn.iocoder.yudao.module.system.mq.message.mail.MailSendMessage; 3 import cn.iocoder.yudao.module.system.mq.message.mail.MailSendMessage;
5 -import cn.iocoder.yudao.module.system.mq.message.sms.SmsSendMessage;  
6 import cn.iocoder.yudao.module.system.service.mail.MailSendService; 4 import cn.iocoder.yudao.module.system.service.mail.MailSendService;
7 import lombok.extern.slf4j.Slf4j; 5 import lombok.extern.slf4j.Slf4j;
  6 +import org.springframework.context.event.EventListener;
  7 +import org.springframework.scheduling.annotation.Async;
8 import org.springframework.stereotype.Component; 8 import org.springframework.stereotype.Component;
9 9
10 import javax.annotation.Resource; 10 import javax.annotation.Resource;
@@ -16,12 +16,13 @@ import javax.annotation.Resource; @@ -16,12 +16,13 @@ import javax.annotation.Resource;
16 */ 16 */
17 @Component 17 @Component
18 @Slf4j 18 @Slf4j
19 -public class MailSendConsumer extends AbstractStreamMessageListener<MailSendMessage> { 19 +public class MailSendConsumer {
20 20
21 @Resource 21 @Resource
22 private MailSendService mailSendService; 22 private MailSendService mailSendService;
23 23
24 - @Override 24 + @EventListener
  25 + @Async // Spring Event 默认在 Producer 发送的线程,通过 @Async 实现异步
25 public void onMessage(MailSendMessage message) { 26 public void onMessage(MailSendMessage message) {
26 log.info("[onMessage][消息内容({})]", message); 27 log.info("[onMessage][消息内容({})]", message);
27 mailSendService.doSendMail(message); 28 mailSendService.doSendMail(message);
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/consumer/sms/SmsSendConsumer.java
@@ -2,8 +2,9 @@ package cn.iocoder.yudao.module.system.mq.consumer.sms; @@ -2,8 +2,9 @@ package cn.iocoder.yudao.module.system.mq.consumer.sms;
2 2
3 import cn.iocoder.yudao.module.system.mq.message.sms.SmsSendMessage; 3 import cn.iocoder.yudao.module.system.mq.message.sms.SmsSendMessage;
4 import cn.iocoder.yudao.module.system.service.sms.SmsSendService; 4 import cn.iocoder.yudao.module.system.service.sms.SmsSendService;
5 -import cn.iocoder.yudao.framework.mq.core.stream.AbstractStreamMessageListener;  
6 import lombok.extern.slf4j.Slf4j; 5 import lombok.extern.slf4j.Slf4j;
  6 +import org.springframework.context.event.EventListener;
  7 +import org.springframework.scheduling.annotation.Async;
7 import org.springframework.stereotype.Component; 8 import org.springframework.stereotype.Component;
8 9
9 import javax.annotation.Resource; 10 import javax.annotation.Resource;
@@ -15,12 +16,13 @@ import javax.annotation.Resource; @@ -15,12 +16,13 @@ import javax.annotation.Resource;
15 */ 16 */
16 @Component 17 @Component
17 @Slf4j 18 @Slf4j
18 -public class SmsSendConsumer extends AbstractStreamMessageListener<SmsSendMessage> { 19 +public class SmsSendConsumer {
19 20
20 @Resource 21 @Resource
21 private SmsSendService smsSendService; 22 private SmsSendService smsSendService;
22 23
23 - @Override 24 + @EventListener
  25 + @Async // Spring Event 默认在 Producer 发送的线程,通过 @Async 实现异步
24 public void onMessage(SmsSendMessage message) { 26 public void onMessage(SmsSendMessage message) {
25 log.info("[onMessage][消息内容({})]", message); 27 log.info("[onMessage][消息内容({})]", message);
26 smsSendService.doSendSms(message); 28 smsSendService.doSendSms(message);
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/mail/MailSendMessage.java
1 package cn.iocoder.yudao.module.system.mq.message.mail; 1 package cn.iocoder.yudao.module.system.mq.message.mail;
2 2
3 -import cn.iocoder.yudao.framework.mq.core.stream.AbstractStreamMessage;  
4 import lombok.Data; 3 import lombok.Data;
5 -import lombok.EqualsAndHashCode;  
6 4
7 import javax.validation.constraints.NotEmpty; 5 import javax.validation.constraints.NotEmpty;
8 import javax.validation.constraints.NotNull; 6 import javax.validation.constraints.NotNull;
@@ -13,8 +11,7 @@ import javax.validation.constraints.NotNull; @@ -13,8 +11,7 @@ import javax.validation.constraints.NotNull;
13 * @author 芋道源码 11 * @author 芋道源码
14 */ 12 */
15 @Data 13 @Data
16 -@EqualsAndHashCode(callSuper = true)  
17 -public class MailSendMessage extends AbstractStreamMessage { 14 +public class MailSendMessage {
18 15
19 /** 16 /**
20 * 邮件日志编号 17 * 邮件日志编号
@@ -47,9 +44,4 @@ public class MailSendMessage extends AbstractStreamMessage { @@ -47,9 +44,4 @@ public class MailSendMessage extends AbstractStreamMessage {
47 @NotEmpty(message = "邮件内容不能为空") 44 @NotEmpty(message = "邮件内容不能为空")
48 private String content; 45 private String content;
49 46
50 - @Override  
51 - public String getStreamKey() {  
52 - return "system.mail.send";  
53 - }  
54 -  
55 } 47 }
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/message/sms/SmsSendMessage.java
1 package cn.iocoder.yudao.module.system.mq.message.sms; 1 package cn.iocoder.yudao.module.system.mq.message.sms;
2 2
3 import cn.iocoder.yudao.framework.common.core.KeyValue; 3 import cn.iocoder.yudao.framework.common.core.KeyValue;
4 -import cn.iocoder.yudao.framework.mq.core.stream.AbstractStreamMessage;  
5 import lombok.Data; 4 import lombok.Data;
6 -import lombok.EqualsAndHashCode;  
7 5
8 import javax.validation.constraints.NotNull; 6 import javax.validation.constraints.NotNull;
9 import java.util.List; 7 import java.util.List;
@@ -14,8 +12,7 @@ import java.util.List; @@ -14,8 +12,7 @@ import java.util.List;
14 * @author 芋道源码 12 * @author 芋道源码
15 */ 13 */
16 @Data 14 @Data
17 -@EqualsAndHashCode(callSuper = true)  
18 -public class SmsSendMessage extends AbstractStreamMessage { 15 +public class SmsSendMessage {
19 16
20 /** 17 /**
21 * 短信日志编号 18 * 短信日志编号
@@ -42,9 +39,4 @@ public class SmsSendMessage extends AbstractStreamMessage { @@ -42,9 +39,4 @@ public class SmsSendMessage extends AbstractStreamMessage {
42 */ 39 */
43 private List<KeyValue<String, Object>> templateParams; 40 private List<KeyValue<String, Object>> templateParams;
44 41
45 - @Override  
46 - public String getStreamKey() {  
47 - return "system.sms.send";  
48 - }  
49 -  
50 } 42 }
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/producer/mail/MailProducer.java
1 package cn.iocoder.yudao.module.system.mq.producer.mail; 1 package cn.iocoder.yudao.module.system.mq.producer.mail;
2 2
3 -import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;  
4 import cn.iocoder.yudao.module.system.mq.message.mail.MailSendMessage; 3 import cn.iocoder.yudao.module.system.mq.message.mail.MailSendMessage;
5 import lombok.extern.slf4j.Slf4j; 4 import lombok.extern.slf4j.Slf4j;
  5 +import org.springframework.context.ApplicationContext;
6 import org.springframework.stereotype.Component; 6 import org.springframework.stereotype.Component;
7 7
8 import javax.annotation.Resource; 8 import javax.annotation.Resource;
@@ -18,7 +18,7 @@ import javax.annotation.Resource; @@ -18,7 +18,7 @@ import javax.annotation.Resource;
18 public class MailProducer { 18 public class MailProducer {
19 19
20 @Resource 20 @Resource
21 - private RedisMQTemplate redisMQTemplate; 21 + private ApplicationContext applicationContext;
22 22
23 /** 23 /**
24 * 发送 {@link MailSendMessage} 消息 24 * 发送 {@link MailSendMessage} 消息
@@ -35,7 +35,7 @@ public class MailProducer { @@ -35,7 +35,7 @@ public class MailProducer {
35 MailSendMessage message = new MailSendMessage() 35 MailSendMessage message = new MailSendMessage()
36 .setLogId(sendLogId).setMail(mail).setAccountId(accountId) 36 .setLogId(sendLogId).setMail(mail).setAccountId(accountId)
37 .setNickname(nickname).setTitle(title).setContent(content); 37 .setNickname(nickname).setTitle(title).setContent(content);
38 - redisMQTemplate.send(message); 38 + applicationContext.publishEvent(message);
39 } 39 }
40 40
41 } 41 }
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/mq/producer/sms/SmsProducer.java
1 package cn.iocoder.yudao.module.system.mq.producer.sms; 1 package cn.iocoder.yudao.module.system.mq.producer.sms;
2 2
3 import cn.iocoder.yudao.framework.common.core.KeyValue; 3 import cn.iocoder.yudao.framework.common.core.KeyValue;
4 -import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;  
5 import cn.iocoder.yudao.module.system.mq.message.sms.SmsSendMessage; 4 import cn.iocoder.yudao.module.system.mq.message.sms.SmsSendMessage;
6 import lombok.extern.slf4j.Slf4j; 5 import lombok.extern.slf4j.Slf4j;
  6 +import org.springframework.context.ApplicationContext;
7 import org.springframework.stereotype.Component; 7 import org.springframework.stereotype.Component;
8 8
9 import javax.annotation.Resource; 9 import javax.annotation.Resource;
@@ -20,7 +20,7 @@ import java.util.List; @@ -20,7 +20,7 @@ import java.util.List;
20 public class SmsProducer { 20 public class SmsProducer {
21 21
22 @Resource 22 @Resource
23 - private RedisMQTemplate redisMQTemplate; 23 + private ApplicationContext applicationContext;
24 24
25 /** 25 /**
26 * 发送 {@link SmsSendMessage} 消息 26 * 发送 {@link SmsSendMessage} 消息
@@ -35,7 +35,7 @@ public class SmsProducer { @@ -35,7 +35,7 @@ public class SmsProducer {
35 Long channelId, String apiTemplateId, List<KeyValue<String, Object>> templateParams) { 35 Long channelId, String apiTemplateId, List<KeyValue<String, Object>> templateParams) {
36 SmsSendMessage message = new SmsSendMessage().setLogId(logId).setMobile(mobile); 36 SmsSendMessage message = new SmsSendMessage().setLogId(logId).setMobile(mobile);
37 message.setChannelId(channelId).setApiTemplateId(apiTemplateId).setTemplateParams(templateParams); 37 message.setChannelId(channelId).setApiTemplateId(apiTemplateId).setTemplateParams(templateParams);
38 - redisMQTemplate.send(message); 38 + applicationContext.publishEvent(message);
39 } 39 }
40 40
41 } 41 }
yudao-server/src/main/resources/application-dev.yaml
@@ -94,6 +94,23 @@ spring: @@ -94,6 +94,23 @@ spring:
94 jdbc: # 使用 JDBC 的 JobStore 的时候,JDBC 的配置 94 jdbc: # 使用 JDBC 的 JobStore 的时候,JDBC 的配置
95 initialize-schema: NEVER # 是否自动使用 SQL 初始化 Quartz 表结构。这里设置成 never ,我们手动创建表结构。 95 initialize-schema: NEVER # 是否自动使用 SQL 初始化 Quartz 表结构。这里设置成 never ,我们手动创建表结构。
96 96
  97 +--- #################### 消息队列相关 ####################
  98 +
  99 +# rocketmq 配置项,对应 RocketMQProperties 配置类
  100 +rocketmq:
  101 + name-server: 127.0.0.1:9876 # RocketMQ Namesrv
  102 +
  103 +spring:
  104 + # RabbitMQ 配置项,对应 RabbitProperties 配置类
  105 + rabbitmq:
  106 + host: 127.0.0.1 # RabbitMQ 服务的地址
  107 + port: 5672 # RabbitMQ 服务的端口
  108 + username: guest # RabbitMQ 服务的账号
  109 + password: guest # RabbitMQ 服务的密码
  110 + # Kafka 配置项,对应 KafkaProperties 配置类
  111 + kafka:
  112 + bootstrap-servers: 127.0.0.1:9092 # 指定 Kafka Broker 地址,可以设置多个,以逗号分隔
  113 +
97 --- #################### 服务保障相关配置 #################### 114 --- #################### 服务保障相关配置 ####################
98 115
99 # Lock4j 配置项 116 # Lock4j 配置项
yudao-server/src/main/resources/application-local.yaml
@@ -109,6 +109,23 @@ spring: @@ -109,6 +109,23 @@ spring:
109 jdbc: # 使用 JDBC 的 JobStore 的时候,JDBC 的配置 109 jdbc: # 使用 JDBC 的 JobStore 的时候,JDBC 的配置
110 initialize-schema: NEVER # 是否自动使用 SQL 初始化 Quartz 表结构。这里设置成 never ,我们手动创建表结构。 110 initialize-schema: NEVER # 是否自动使用 SQL 初始化 Quartz 表结构。这里设置成 never ,我们手动创建表结构。
111 111
  112 +--- #################### 消息队列相关 ####################
  113 +
  114 +# rocketmq 配置项,对应 RocketMQProperties 配置类
  115 +rocketmq:
  116 + name-server: 127.0.0.1:9876 # RocketMQ Namesrv
  117 +
  118 +spring:
  119 + # RabbitMQ 配置项,对应 RabbitProperties 配置类
  120 + rabbitmq:
  121 + host: 127.0.0.1 # RabbitMQ 服务的地址
  122 + port: 5672 # RabbitMQ 服务的端口
  123 + username: guest # RabbitMQ 服务的账号
  124 + password: guest # RabbitMQ 服务的密码
  125 + # Kafka 配置项,对应 KafkaProperties 配置类
  126 + kafka:
  127 + bootstrap-servers: 127.0.0.1:9092 # 指定 Kafka Broker 地址,可以设置多个,以逗号分隔
  128 +
112 --- #################### 服务保障相关配置 #################### 129 --- #################### 服务保障相关配置 ####################
113 130
114 # Lock4j 配置项 131 # Lock4j 配置项
yudao-server/src/main/resources/application.yaml
@@ -106,6 +106,32 @@ aj: @@ -106,6 +106,32 @@ aj:
106 req-check-minute-limit: 60 # check 接口一分钟内请求数限制 106 req-check-minute-limit: 60 # check 接口一分钟内请求数限制
107 req-verify-minute-limit: 60 # verify 接口一分钟内请求数限制 107 req-verify-minute-limit: 60 # verify 接口一分钟内请求数限制
108 108
  109 +--- #################### 消息队列相关 ####################
  110 +
  111 +# rocketmq 配置项,对应 RocketMQProperties 配置类
  112 +rocketmq:
  113 + # Producer 配置项
  114 + producer:
  115 + group: ${spring.application.name}_PRODUCER # 生产者分组
  116 +
  117 +spring:
  118 + # Kafka 配置项,对应 KafkaProperties 配置类
  119 + kafka:
  120 + # Kafka Producer 配置项
  121 + producer:
  122 + acks: 1 # 0-不应答。1-leader 应答。all-所有 leader 和 follower 应答。
  123 + retries: 3 # 发送失败时,重试发送的次数
  124 + value-serializer: org.springframework.kafka.support.serializer.JsonSerializer # 消息的 value 的序列化
  125 + # Kafka Consumer 配置项
  126 + consumer:
  127 + auto-offset-reset: earliest # 设置消费者分组最初的消费进度为 earliest 。可参考博客 https://blog.csdn.net/lishuangzhe7047/article/details/74530417 理解
  128 + value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer
  129 + properties:
  130 + spring.json.trusted.packages: '*'
  131 + # Kafka Consumer Listener 监听器配置
  132 + listener:
  133 + missing-topics-fatal: false # 消费监听接口监听的主题不存在时,默认会报错。所以通过设置为 false ,解决报错
  134 +
109 --- #################### 芋道相关配置 #################### 135 --- #################### 芋道相关配置 ####################
110 136
111 yudao: 137 yudao:
@@ -145,12 +171,6 @@ yudao: @@ -145,12 +171,6 @@ yudao:
145 - cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants 171 - cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants
146 - cn.iocoder.yudao.module.system.enums.ErrorCodeConstants 172 - cn.iocoder.yudao.module.system.enums.ErrorCodeConstants
147 - cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants 173 - cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants
148 - mq:  
149 - redis:  
150 - pubsub:  
151 - enable: false # 是否开启 Redis pubsub 广播消费,默认为 true。这里设置成 false,可以按需开启  
152 - stream:  
153 - enable: false # 是否开启 Redis stream 集群消费,默认为 true。这里设置成 false,可以按需开启  
154 tenant: # 多租户相关配置项 174 tenant: # 多租户相关配置项
155 enable: true 175 enable: true
156 ignore-urls: 176 ignore-urls: