Commit be3fac75420232887eaad05c66ee028ec8baf52e
1 parent
f5331ce6
1. 简单 redis stream 的 StreamMessage 和对应的消费者
2. 跑通 Redis Stream 的流程
Showing
13 changed files
with
458 additions
and
3 deletions
src/main/java/cn/iocoder/dashboard/framework/redis/config/RedisConfig.java
| 1 | package cn.iocoder.dashboard.framework.redis.config; | 1 | package cn.iocoder.dashboard.framework.redis.config; |
| 2 | 2 | ||
| 3 | +import cn.hutool.core.net.NetUtil; | ||
| 3 | import cn.iocoder.dashboard.framework.redis.core.pubsub.AbstractChannelMessageListener; | 4 | import cn.iocoder.dashboard.framework.redis.core.pubsub.AbstractChannelMessageListener; |
| 5 | +import cn.iocoder.dashboard.framework.redis.core.stream.AbstractStreamMessageListener; | ||
| 4 | import lombok.extern.slf4j.Slf4j; | 6 | import lombok.extern.slf4j.Slf4j; |
| 5 | import org.springframework.context.annotation.Bean; | 7 | import org.springframework.context.annotation.Bean; |
| 6 | import org.springframework.context.annotation.Configuration; | 8 | import org.springframework.context.annotation.Configuration; |
| 7 | import org.springframework.data.redis.connection.RedisConnectionFactory; | 9 | import org.springframework.data.redis.connection.RedisConnectionFactory; |
| 10 | +import org.springframework.data.redis.connection.stream.*; | ||
| 8 | import org.springframework.data.redis.core.RedisTemplate; | 11 | import org.springframework.data.redis.core.RedisTemplate; |
| 9 | import org.springframework.data.redis.listener.ChannelTopic; | 12 | import org.springframework.data.redis.listener.ChannelTopic; |
| 10 | import org.springframework.data.redis.listener.RedisMessageListenerContainer; | 13 | import org.springframework.data.redis.listener.RedisMessageListenerContainer; |
| 11 | import org.springframework.data.redis.serializer.RedisSerializer; | 14 | import org.springframework.data.redis.serializer.RedisSerializer; |
| 15 | +import org.springframework.data.redis.stream.StreamMessageListenerContainer; | ||
| 16 | +import org.springframework.util.ErrorHandler; | ||
| 12 | 17 | ||
| 18 | +import java.time.Duration; | ||
| 13 | import java.util.List; | 19 | import java.util.List; |
| 14 | 20 | ||
| 15 | /** | 21 | /** |
| @@ -48,4 +54,52 @@ public class RedisConfig { | @@ -48,4 +54,52 @@ public class RedisConfig { | ||
| 48 | return container; | 54 | return container; |
| 49 | } | 55 | } |
| 50 | 56 | ||
| 57 | + @Bean(initMethod = "start", destroyMethod = "stop") | ||
| 58 | + public StreamMessageListenerContainer<String, ObjectRecord<String, String>> redisStreamMessageListenerContainer( | ||
| 59 | + RedisConnectionFactory factory, List<AbstractStreamMessageListener<?>> listeners) { | ||
| 60 | + // 创建配置对象 | ||
| 61 | + StreamMessageListenerContainer.StreamMessageListenerContainerOptions<String, ObjectRecord<String, String>> | ||
| 62 | + streamMessageListenerContainerOptions = StreamMessageListenerContainer.StreamMessageListenerContainerOptions | ||
| 63 | + .builder() | ||
| 64 | + // 一次性最多拉取多少条消息 | ||
| 65 | + .batchSize(10) | ||
| 66 | + // 执行消息轮询的执行器 | ||
| 67 | + // .executor(this.threadPoolTaskExecutor) | ||
| 68 | + // 消息消费异常的handler | ||
| 69 | + .errorHandler(new ErrorHandler() { | ||
| 70 | + @Override | ||
| 71 | + public void handleError(Throwable t) { | ||
| 72 | + // throw new RuntimeException(t); | ||
| 73 | + t.printStackTrace(); | ||
| 74 | + } | ||
| 75 | + }) | ||
| 76 | + // 超时时间,设置为0,表示不超时(超时后会抛出异常) | ||
| 77 | + .pollTimeout(Duration.ZERO) | ||
| 78 | + // 序列化器 | ||
| 79 | + .serializer(RedisSerializer.string()) | ||
| 80 | + .targetType(String.class) | ||
| 81 | + .build(); | ||
| 82 | + | ||
| 83 | + // 根据配置对象创建监听容器对象 | ||
| 84 | + StreamMessageListenerContainer<String, ObjectRecord<String, String>> container = StreamMessageListenerContainer | ||
| 85 | + .create(factory, streamMessageListenerContainerOptions); | ||
| 86 | + | ||
| 87 | + RedisTemplate<String, Object> redisTemplate = redisTemplate(factory); | ||
| 88 | + | ||
| 89 | + // 使用监听容器对象开始监听消费(使用的是手动确认方式) | ||
| 90 | + String consumerName = NetUtil.getLocalHostName(); // TODO 需要优化下,晚点参考下 rocketmq consumer 的 | ||
| 91 | + for (AbstractStreamMessageListener<?> listener : listeners) { | ||
| 92 | + try { | ||
| 93 | + redisTemplate.opsForStream().createGroup(listener.getStreamKey(), listener.getGroup()); | ||
| 94 | + } catch (Exception ignore) { | ||
| 95 | +// ignore.printStackTrace(); | ||
| 96 | + } | ||
| 97 | + | ||
| 98 | + container.receive(Consumer.from(listener.getGroup(), consumerName), | ||
| 99 | + StreamOffset.create(listener.getStreamKey(), ReadOffset.lastConsumed()), listener); | ||
| 100 | + } | ||
| 101 | + | ||
| 102 | + return container; | ||
| 103 | + } | ||
| 104 | + | ||
| 51 | } | 105 | } |
src/main/java/cn/iocoder/dashboard/framework/redis/core/pubsub/ChannelMessage.java
| @@ -4,6 +4,8 @@ import com.fasterxml.jackson.annotation.JsonIgnore; | @@ -4,6 +4,8 @@ import com.fasterxml.jackson.annotation.JsonIgnore; | ||
| 4 | 4 | ||
| 5 | /** | 5 | /** |
| 6 | * Redis Channel Message 接口 | 6 | * Redis Channel Message 接口 |
| 7 | + * | ||
| 8 | + * @author 芋道源码 | ||
| 7 | */ | 9 | */ |
| 8 | public interface ChannelMessage { | 10 | public interface ChannelMessage { |
| 9 | 11 | ||
| @@ -12,7 +14,7 @@ public interface ChannelMessage { | @@ -12,7 +14,7 @@ public interface ChannelMessage { | ||
| 12 | * | 14 | * |
| 13 | * @return Channel | 15 | * @return Channel |
| 14 | */ | 16 | */ |
| 15 | - @JsonIgnore // 必须序列化 | 17 | + @JsonIgnore // 避免序列化 |
| 16 | String getChannel(); | 18 | String getChannel(); |
| 17 | 19 | ||
| 18 | } | 20 | } |
src/main/java/cn/iocoder/dashboard/framework/redis/core/stream/AbstractStreamMessageListener.java
0 → 100644
| 1 | +package cn.iocoder.dashboard.framework.redis.core.stream; | ||
| 2 | + | ||
| 3 | +import cn.hutool.core.util.ArrayUtil; | ||
| 4 | +import lombok.Getter; | ||
| 5 | +import lombok.SneakyThrows; | ||
| 6 | +import org.springframework.beans.factory.annotation.Value; | ||
| 7 | +import org.springframework.data.redis.connection.stream.ObjectRecord; | ||
| 8 | +import org.springframework.data.redis.stream.StreamListener; | ||
| 9 | +import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl; | ||
| 10 | + | ||
| 11 | +import java.lang.reflect.Type; | ||
| 12 | + | ||
| 13 | +/** | ||
| 14 | + * Redis Stream 监听器抽象类,用于实现集群消费 | ||
| 15 | + * | ||
| 16 | + * @param <T> 消息类型。一定要填写噢,不然会报错 | ||
| 17 | + * | ||
| 18 | + * @author 芋道源码 | ||
| 19 | + */ | ||
| 20 | +public abstract class AbstractStreamMessageListener<T extends StreamMessage> | ||
| 21 | + implements StreamListener<String, ObjectRecord<String, String>> { | ||
| 22 | + | ||
| 23 | + /** | ||
| 24 | + * 消息类型 | ||
| 25 | + */ | ||
| 26 | + private final Class<T> messageType; | ||
| 27 | + /** | ||
| 28 | + * Redis Channel | ||
| 29 | + */ | ||
| 30 | + @Getter | ||
| 31 | + private final String streamKey; | ||
| 32 | + | ||
| 33 | + /** | ||
| 34 | + * Redis 消费者分组,默认使用 spring.application.name 名字 | ||
| 35 | + */ | ||
| 36 | + @Value("spring.application.name") | ||
| 37 | + @Getter | ||
| 38 | + private String group; | ||
| 39 | + | ||
| 40 | + @SneakyThrows | ||
| 41 | + protected AbstractStreamMessageListener() { | ||
| 42 | + this.messageType = getMessageClass(); | ||
| 43 | + this.streamKey = messageType.newInstance().getStreamKey(); | ||
| 44 | + } | ||
| 45 | + | ||
| 46 | + @Override | ||
| 47 | + public void onMessage(ObjectRecord<String, String> message) { | ||
| 48 | + System.out.println(message); | ||
| 49 | + } | ||
| 50 | + | ||
| 51 | + /** | ||
| 52 | + * 处理消息 | ||
| 53 | + * | ||
| 54 | + * @param message 消息 | ||
| 55 | + */ | ||
| 56 | + public abstract void onMessage(T message); | ||
| 57 | + | ||
| 58 | + /** | ||
| 59 | + * 通过解析类上的泛型,获得消息类型 | ||
| 60 | + * | ||
| 61 | + * @return 消息类型 | ||
| 62 | + */ | ||
| 63 | + @SuppressWarnings("unchecked") | ||
| 64 | + // TODO 芋艿:稍后重构 | ||
| 65 | + private Class<T> getMessageClass() { | ||
| 66 | + Class<?> targetClass = getClass(); | ||
| 67 | + while (targetClass.getSuperclass() != null) { | ||
| 68 | + // 如果不是 AbstractMessageListener 父类,继续向上查找 | ||
| 69 | + if (targetClass.getSuperclass() != AbstractStreamMessageListener.class) { | ||
| 70 | + targetClass = targetClass.getSuperclass(); | ||
| 71 | + continue; | ||
| 72 | + } | ||
| 73 | + // 如果是 AbstractMessageListener 父类,则解析泛型 | ||
| 74 | + Type[] types = ((ParameterizedTypeImpl) targetClass.getGenericSuperclass()).getActualTypeArguments(); | ||
| 75 | + if (ArrayUtil.isEmpty(types)) { | ||
| 76 | + throw new IllegalStateException(String.format("类型(%s) 需要设置消息类型", getClass().getName())); | ||
| 77 | + } | ||
| 78 | + return (Class<T>) types[0]; | ||
| 79 | + } | ||
| 80 | + throw new IllegalStateException(String.format("类型(%s) 找不到 AbstractStreamMessageListener 父类", getClass().getName())); | ||
| 81 | + } | ||
| 82 | + | ||
| 83 | +} |
src/main/java/cn/iocoder/dashboard/framework/redis/core/stream/StreamMessage.java
0 → 100644
| 1 | +package cn.iocoder.dashboard.framework.redis.core.stream; | ||
| 2 | + | ||
| 3 | +import com.fasterxml.jackson.annotation.JsonIgnore; | ||
| 4 | + | ||
| 5 | +/** | ||
| 6 | + * Redis Stream Message 接口 | ||
| 7 | + * | ||
| 8 | + * @author 芋道源码 | ||
| 9 | + */ | ||
| 10 | +public interface StreamMessage { | ||
| 11 | + | ||
| 12 | + /** | ||
| 13 | + * 获得 Redis Stream Key | ||
| 14 | + * | ||
| 15 | + * @return Channel | ||
| 16 | + */ | ||
| 17 | + @JsonIgnore // 避免序列化 | ||
| 18 | + String getStreamKey(); | ||
| 19 | + | ||
| 20 | +} |
src/main/java/cn/iocoder/dashboard/framework/redis/core/util/RedisMessageUtils.java
| 1 | package cn.iocoder.dashboard.framework.redis.core.util; | 1 | package cn.iocoder.dashboard.framework.redis.core.util; |
| 2 | 2 | ||
| 3 | import cn.iocoder.dashboard.framework.redis.core.pubsub.ChannelMessage; | 3 | import cn.iocoder.dashboard.framework.redis.core.pubsub.ChannelMessage; |
| 4 | +import cn.iocoder.dashboard.framework.redis.core.stream.StreamMessage; | ||
| 4 | import cn.iocoder.dashboard.util.json.JsonUtils; | 5 | import cn.iocoder.dashboard.util.json.JsonUtils; |
| 6 | +import org.springframework.data.redis.connection.stream.RecordId; | ||
| 7 | +import org.springframework.data.redis.connection.stream.StreamRecords; | ||
| 5 | import org.springframework.data.redis.core.RedisTemplate; | 8 | import org.springframework.data.redis.core.RedisTemplate; |
| 6 | 9 | ||
| 7 | /** | 10 | /** |
| @@ -17,8 +20,21 @@ public class RedisMessageUtils { | @@ -17,8 +20,21 @@ public class RedisMessageUtils { | ||
| 17 | * @param redisTemplate Redis 操作模板 | 20 | * @param redisTemplate Redis 操作模板 |
| 18 | * @param message 消息 | 21 | * @param message 消息 |
| 19 | */ | 22 | */ |
| 20 | - public static <T extends ChannelMessage> void sendChannelMessage(RedisTemplate<?, ?> redisTemplate, T message) { | 23 | + public static <T extends ChannelMessage> void sendChannelMessage(RedisTemplate<?, ?> redisTemplate, T message) { |
| 21 | redisTemplate.convertAndSend(message.getChannel(), JsonUtils.toJsonString(message)); | 24 | redisTemplate.convertAndSend(message.getChannel(), JsonUtils.toJsonString(message)); |
| 22 | } | 25 | } |
| 23 | 26 | ||
| 27 | + /** | ||
| 28 | + * 发送 Redis 消息,基于 Redis Stream 实现 | ||
| 29 | + * | ||
| 30 | + * @param redisTemplate Redis 操作模板 | ||
| 31 | + * @param message 消息 | ||
| 32 | + * @return 消息记录的编号对象 | ||
| 33 | + */ | ||
| 34 | + public static <T extends StreamMessage> RecordId sendStreamMessage(RedisTemplate<String, String> redisTemplate, T message) { | ||
| 35 | + return redisTemplate.opsForStream().add(StreamRecords.newRecord() | ||
| 36 | + .ofObject(JsonUtils.toJsonString(message)) // 设置内容 | ||
| 37 | + .withStreamKey(message.getStreamKey())); // 设置 stream key | ||
| 38 | + } | ||
| 39 | + | ||
| 24 | } | 40 | } |
src/main/java/cn/iocoder/dashboard/modules/system/mq/consumer/mail/SysMailSendConsumer.java
0 → 100644
| 1 | +package cn.iocoder.dashboard.modules.system.mq.consumer.mail; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.dashboard.framework.redis.core.stream.AbstractStreamMessageListener; | ||
| 4 | +import cn.iocoder.dashboard.modules.system.mq.message.mail.SysMailSendMessage; | ||
| 5 | +import org.springframework.stereotype.Component; | ||
| 6 | + | ||
| 7 | +@Component | ||
| 8 | +public class SysMailSendConsumer extends AbstractStreamMessageListener<SysMailSendMessage> { | ||
| 9 | + | ||
| 10 | + @Override | ||
| 11 | + public void onMessage(SysMailSendMessage message) { | ||
| 12 | + | ||
| 13 | + } | ||
| 14 | + | ||
| 15 | +} |
src/main/java/cn/iocoder/dashboard/modules/system/mq/consumer/sms/SysSmsSendConsumer.java
0 → 100644
| 1 | +package cn.iocoder.dashboard.modules.system.mq.consumer.sms; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.dashboard.framework.redis.core.stream.AbstractStreamMessageListener; | ||
| 4 | +import cn.iocoder.dashboard.modules.system.mq.message.sms.SysSmsSendMessage; | ||
| 5 | +import org.springframework.stereotype.Component; | ||
| 6 | + | ||
| 7 | +@Component | ||
| 8 | +public class SysSmsSendConsumer extends AbstractStreamMessageListener<SysSmsSendMessage> { | ||
| 9 | + | ||
| 10 | + @Override | ||
| 11 | + public void onMessage(SysSmsSendMessage message) { | ||
| 12 | + System.out.println(message); | ||
| 13 | + } | ||
| 14 | + | ||
| 15 | +} |
src/main/java/cn/iocoder/dashboard/modules/system/mq/message/mail/SysMailSendMessage.java
0 → 100644
| 1 | +package cn.iocoder.dashboard.modules.system.mq.message.mail; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.dashboard.framework.redis.core.stream.StreamMessage; | ||
| 4 | +import lombok.Data; | ||
| 5 | + | ||
| 6 | +import javax.validation.constraints.NotNull; | ||
| 7 | +import java.util.Map; | ||
| 8 | + | ||
| 9 | +/** | ||
| 10 | + * 邮箱发送消息 | ||
| 11 | + * | ||
| 12 | + * @author 芋道源码 | ||
| 13 | + */ | ||
| 14 | +@Data | ||
| 15 | +public class SysMailSendMessage implements StreamMessage { | ||
| 16 | + | ||
| 17 | + /** | ||
| 18 | + * 邮箱地址 | ||
| 19 | + */ | ||
| 20 | + @NotNull(message = "邮箱地址不能为空") | ||
| 21 | + private String address; | ||
| 22 | + /** | ||
| 23 | + * 短信模板编号 | ||
| 24 | + */ | ||
| 25 | + @NotNull(message = "短信模板编号不能为空") | ||
| 26 | + private String templateCode; | ||
| 27 | + /** | ||
| 28 | + * 短信模板参数 | ||
| 29 | + */ | ||
| 30 | + private Map<String, Object> templateParams; | ||
| 31 | + | ||
| 32 | + /** | ||
| 33 | + * 用户编号,允许空 | ||
| 34 | + */ | ||
| 35 | + private Integer userId; | ||
| 36 | + /** | ||
| 37 | + * 用户类型,允许空 | ||
| 38 | + */ | ||
| 39 | + private Integer userType; | ||
| 40 | + | ||
| 41 | + @Override | ||
| 42 | + public String getStreamKey() { | ||
| 43 | + return "system.mail.send"; | ||
| 44 | + } | ||
| 45 | + | ||
| 46 | +} |
src/main/java/cn/iocoder/dashboard/modules/system/mq/message/sms/SysSmsSendMessage.java
0 → 100644
| 1 | +package cn.iocoder.dashboard.modules.system.mq.message.sms; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.dashboard.framework.redis.core.stream.StreamMessage; | ||
| 4 | +import lombok.Data; | ||
| 5 | + | ||
| 6 | +import javax.validation.constraints.NotNull; | ||
| 7 | +import java.util.Map; | ||
| 8 | + | ||
| 9 | +/** | ||
| 10 | + * 短信发送消息 | ||
| 11 | + * | ||
| 12 | + * @author 芋道源码 | ||
| 13 | + */ | ||
| 14 | +@Data | ||
| 15 | +public class SysSmsSendMessage implements StreamMessage { | ||
| 16 | + | ||
| 17 | + /** | ||
| 18 | + * 手机号 | ||
| 19 | + */ | ||
| 20 | + @NotNull(message = "手机号不能为空") | ||
| 21 | + private String mobile; | ||
| 22 | + /** | ||
| 23 | + * 短信模板编号 | ||
| 24 | + */ | ||
| 25 | + @NotNull(message = "短信模板编号不能为空") | ||
| 26 | + private String templateCode; | ||
| 27 | + /** | ||
| 28 | + * 短信模板参数 | ||
| 29 | + */ | ||
| 30 | + private Map<String, Object> templateParams; | ||
| 31 | + | ||
| 32 | + /** | ||
| 33 | + * 用户编号,允许空 | ||
| 34 | + */ | ||
| 35 | + private Integer userId; | ||
| 36 | + /** | ||
| 37 | + * 用户类型,允许空 | ||
| 38 | + */ | ||
| 39 | + private Integer userType; | ||
| 40 | + | ||
| 41 | + @Override | ||
| 42 | + public String getStreamKey() { | ||
| 43 | + return "system.sms.send"; | ||
| 44 | + } | ||
| 45 | + | ||
| 46 | +} |
src/test-integration/java/cn/iocoder/dashboard/BaseRedisIntegrationTest.java
0 → 100644
| 1 | +package cn.iocoder.dashboard; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.dashboard.framework.redis.config.RedisConfig; | ||
| 4 | +import org.redisson.spring.starter.RedissonAutoConfiguration; | ||
| 5 | +import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; | ||
| 6 | +import org.springframework.boot.test.context.SpringBootTest; | ||
| 7 | +import org.springframework.context.annotation.Import; | ||
| 8 | +import org.springframework.test.context.ActiveProfiles; | ||
| 9 | + | ||
| 10 | +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = BaseRedisIntegrationTest.Application.class) | ||
| 11 | +@ActiveProfiles("integration-test") // 设置使用 application-integration-test 配置文件 | ||
| 12 | +public class BaseRedisIntegrationTest { | ||
| 13 | + | ||
| 14 | + @Import({ | ||
| 15 | + // Redis 配置类 | ||
| 16 | + RedisAutoConfiguration.class, // Spring Redis 自动配置类 | ||
| 17 | + RedisConfig.class, // 自己的 Redis 配置类 | ||
| 18 | + RedissonAutoConfiguration.class, // Redisson 自动高配置类 | ||
| 19 | + }) | ||
| 20 | + public static class Application { | ||
| 21 | + } | ||
| 22 | + | ||
| 23 | +} |
src/test-integration/java/cn/iocoder/dashboard/framework/redis/core/stream/RedisStreamTest.java
0 → 100644
| 1 | +package cn.iocoder.dashboard.framework.redis.core.stream; | ||
| 2 | + | ||
| 3 | +import cn.hutool.core.thread.ThreadUtil; | ||
| 4 | +import cn.iocoder.dashboard.BaseRedisIntegrationTest; | ||
| 5 | +import cn.iocoder.dashboard.framework.redis.core.util.RedisMessageUtils; | ||
| 6 | +import cn.iocoder.dashboard.modules.system.mq.consumer.mail.SysMailSendConsumer; | ||
| 7 | +import cn.iocoder.dashboard.modules.system.mq.consumer.sms.SysSmsSendConsumer; | ||
| 8 | +import cn.iocoder.dashboard.modules.system.mq.message.mail.SysMailSendMessage; | ||
| 9 | +import cn.iocoder.dashboard.modules.system.mq.message.sms.SysSmsSendMessage; | ||
| 10 | +import org.junit.jupiter.api.Test; | ||
| 11 | +import org.springframework.context.annotation.Import; | ||
| 12 | +import org.springframework.data.redis.core.StringRedisTemplate; | ||
| 13 | + | ||
| 14 | +import javax.annotation.Resource; | ||
| 15 | +import java.util.concurrent.TimeUnit; | ||
| 16 | + | ||
| 17 | +public class RedisStreamTest { | ||
| 18 | + | ||
| 19 | + @Import({SysSmsSendConsumer.class, SysMailSendConsumer.class}) | ||
| 20 | + public static class ConsumerTest extends BaseRedisIntegrationTest { | ||
| 21 | + | ||
| 22 | + @Test | ||
| 23 | + public void testConsumer() { | ||
| 24 | + ThreadUtil.sleep(1, TimeUnit.DAYS); | ||
| 25 | + } | ||
| 26 | + | ||
| 27 | + } | ||
| 28 | + | ||
| 29 | + public static class ProducerTest extends BaseRedisIntegrationTest { | ||
| 30 | + | ||
| 31 | + @Resource | ||
| 32 | + private StringRedisTemplate stringRedisTemplate; | ||
| 33 | + | ||
| 34 | + @Test | ||
| 35 | + public void testProducer01() { | ||
| 36 | + // 创建消息 | ||
| 37 | + SysSmsSendMessage message = new SysSmsSendMessage(); | ||
| 38 | + message.setMobile("15601691300").setTemplateCode("test"); | ||
| 39 | + // 发送消息 | ||
| 40 | + RedisMessageUtils.sendStreamMessage(stringRedisTemplate, message); | ||
| 41 | + } | ||
| 42 | + | ||
| 43 | + @Test | ||
| 44 | + public void testProducer02() { | ||
| 45 | + // 创建消息 | ||
| 46 | + SysMailSendMessage message = new SysMailSendMessage(); | ||
| 47 | + message.setAddress("fangfang@mihayou.com").setTemplateCode("test"); | ||
| 48 | + // 发送消息 | ||
| 49 | + RedisMessageUtils.sendStreamMessage(stringRedisTemplate, message); | ||
| 50 | + } | ||
| 51 | + | ||
| 52 | + } | ||
| 53 | + | ||
| 54 | +} |
src/test-integration/resources/application-integration-test.yaml
0 → 100644
| 1 | +spring: | ||
| 2 | + main: | ||
| 3 | + lazy-initialization: true # 开启懒加载,加快速度 | ||
| 4 | + banner-mode: off # 单元测试,禁用 Banner | ||
| 5 | + | ||
| 6 | +--- #################### 数据库相关配置 #################### | ||
| 7 | + | ||
| 8 | +spring: | ||
| 9 | + # 数据源配置项 | ||
| 10 | + datasource: | ||
| 11 | + name: ruoyi-vue-pro | ||
| 12 | + url: jdbc:h2:mem:testdb;MODE=MYSQL;DATABASE_TO_UPPER=false; # MODE 使用 MySQL 模式;DATABASE_TO_UPPER 配置表和字段使用小写 | ||
| 13 | + driver-class-name: org.h2.Driver | ||
| 14 | + username: sa | ||
| 15 | + password: | ||
| 16 | + schema: classpath:sql/create_tables.sql # MySQL 转 H2 的语句,使用 https://www.jooq.org/translate/ 工具 | ||
| 17 | + druid: | ||
| 18 | + async-init: true # 单元测试,异步初始化 Druid 连接池,提升启动速度 | ||
| 19 | + initial-size: 1 # 单元测试,配置为 1,提升启动速度 | ||
| 20 | + | ||
| 21 | + # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优 | ||
| 22 | + redis: | ||
| 23 | + host: 127.0.0.1 # 地址 | ||
| 24 | + port: 6379 # 端口(单元测试,使用 16379 端口) | ||
| 25 | + database: 0 # 数据库索引 | ||
| 26 | + | ||
| 27 | +mybatis: | ||
| 28 | + lazy-initialization: true # 单元测试,设置 MyBatis Mapper 延迟加载,加速每个单元测试 | ||
| 29 | + | ||
| 30 | +--- #################### 定时任务相关配置 #################### | ||
| 31 | + | ||
| 32 | +--- #################### 配置中心相关配置 #################### | ||
| 33 | + | ||
| 34 | +--- #################### 服务保障相关配置 #################### | ||
| 35 | + | ||
| 36 | +# Lock4j 配置项(单元测试,禁用 Lock4j) | ||
| 37 | + | ||
| 38 | +# Resilience4j 配置项 | ||
| 39 | +resilience4j: | ||
| 40 | + ratelimiter: | ||
| 41 | + instances: | ||
| 42 | + backendA: | ||
| 43 | + limit-for-period: 1 # 每个周期内,允许的请求数。默认为 50 | ||
| 44 | + limit-refresh-period: 60s # 每个周期的时长,单位:微秒。默认为 500 | ||
| 45 | + timeout-duration: 1s # 被限流时,阻塞等待的时长,单位:微秒。默认为 5s | ||
| 46 | + register-health-indicator: true # 是否注册到健康监测 | ||
| 47 | + | ||
| 48 | +--- #################### 监控相关配置 #################### | ||
| 49 | + | ||
| 50 | +--- #################### 芋道相关配置 #################### | ||
| 51 | + | ||
| 52 | +# 芋道配置项,设置当前项目所有自定义的配置 | ||
| 53 | +yudao: | ||
| 54 | + info: | ||
| 55 | + version: 1.0.0 | ||
| 56 | + base-package: cn.iocoder.dashboard | ||
| 57 | + web: | ||
| 58 | + api-prefix: /api | ||
| 59 | + controller-package: ${yudao.info.base-package} | ||
| 60 | + security: | ||
| 61 | + token-header: Authorization | ||
| 62 | + token-secret: abcdefghijklmnopqrstuvwxyz | ||
| 63 | + token-timeout: 1d | ||
| 64 | + session-timeout: 30m | ||
| 65 | + mock-enable: true | ||
| 66 | + mock-secret: test | ||
| 67 | + swagger: | ||
| 68 | + enable: false # 单元测试,禁用 Swagger | ||
| 69 | + captcha: | ||
| 70 | + timeout: 5m | ||
| 71 | + width: 160 | ||
| 72 | + height: 60 | ||
| 73 | + file: | ||
| 74 | + base-path: http://127.0.0.1:${server.port}/${yudao.web.api-prefix}/file/get/ | ||
| 75 | + codegen: | ||
| 76 | + base-package: ${yudao.info.base-package}.modules | ||
| 77 | + db-schemas: ${spring.datasource.name} | ||
| 78 | + xss: | ||
| 79 | + enable: false | ||
| 80 | + exclude-urls: # 如下两个 url,仅仅是为了演示,去掉配置也没关系 | ||
| 81 | + - ${spring.boot.admin.context-path}/** # 不处理 Spring Boot Admin 的请求 | ||
| 82 | + - ${management.endpoints.web.base-path}/** # 不处理 Actuator 的请求 |
src/test/java/cn/iocoder/dashboard/modules/system/service/dept/SysDeptServiceTest.java
| @@ -72,7 +72,6 @@ class SysDeptServiceTest extends BaseDbUnitTest { | @@ -72,7 +72,6 @@ class SysDeptServiceTest extends BaseDbUnitTest { | ||
| 72 | // 断言 maxUpdateTime 缓存 | 72 | // 断言 maxUpdateTime 缓存 |
| 73 | Date maxUpdateTime = (Date) getFieldValue(deptService, "maxUpdateTime"); | 73 | Date maxUpdateTime = (Date) getFieldValue(deptService, "maxUpdateTime"); |
| 74 | assertEquals(ObjectUtils.max(deptDO1.getUpdateTime(), deptDO2.getUpdateTime()), maxUpdateTime); | 74 | assertEquals(ObjectUtils.max(deptDO1.getUpdateTime(), deptDO2.getUpdateTime()), maxUpdateTime); |
| 75 | - | ||
| 76 | } | 75 | } |
| 77 | 76 | ||
| 78 | @Test | 77 | @Test |