Commit d6cc9e23a306f0d0ecb13810de7317aee1b4b010

Authored by YunaiV
1 parent 9c76fd4b

完成基础的 stream 封装

src/main/java/cn/iocoder/dashboard/framework/redis/config/RedisConfig.java
@@ -83,12 +83,15 @@ public class RedisConfig { @@ -83,12 +83,15 @@ public class RedisConfig {
83 redisTemplate.getRequiredConnectionFactory(), containerOptions); 83 redisTemplate.getRequiredConnectionFactory(), containerOptions);
84 84
85 // 第二步,注册监听器,消费对应的 Stream 主题 85 // 第二步,注册监听器,消费对应的 Stream 主题
86 - String consumerName = buildConsumerName(); 86 +// String consumerName = buildConsumerName();
  87 + String consumerName = "110";
87 listeners.forEach(listener -> { 88 listeners.forEach(listener -> {
88 // 创建 listener 对应的消费者分组 89 // 创建 listener 对应的消费者分组
89 try { 90 try {
90 redisTemplate.opsForStream().createGroup(listener.getStreamKey(), listener.getGroup()); 91 redisTemplate.opsForStream().createGroup(listener.getStreamKey(), listener.getGroup());
91 } catch (Exception ignore) {} 92 } catch (Exception ignore) {}
  93 + // 设置 listener 对应的 redisTemplate
  94 + listener.setRedisTemplate(redisTemplate);
92 // 创建 Consumer 对象 95 // 创建 Consumer 对象
93 Consumer consumer = Consumer.from(listener.getGroup(), consumerName); 96 Consumer consumer = Consumer.from(listener.getGroup(), consumerName);
94 // 设置 Consumer 消费进度,以最小消费进度为准 97 // 设置 Consumer 消费进度,以最小消费进度为准
src/main/java/cn/iocoder/dashboard/framework/redis/core/pubsub/AbstractChannelMessageListener.java
1 package cn.iocoder.dashboard.framework.redis.core.pubsub; 1 package cn.iocoder.dashboard.framework.redis.core.pubsub;
2 2
3 -import cn.hutool.core.util.ArrayUtil; 3 +import cn.hutool.core.util.TypeUtil;
4 import cn.iocoder.dashboard.util.json.JsonUtils; 4 import cn.iocoder.dashboard.util.json.JsonUtils;
5 import lombok.SneakyThrows; 5 import lombok.SneakyThrows;
6 import org.springframework.data.redis.connection.Message; 6 import org.springframework.data.redis.connection.Message;
7 import org.springframework.data.redis.connection.MessageListener; 7 import org.springframework.data.redis.connection.MessageListener;
8 -import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl;  
9 8
10 import java.lang.reflect.Type; 9 import java.lang.reflect.Type;
11 10
@@ -62,21 +61,11 @@ public abstract class AbstractChannelMessageListener<T extends ChannelMessage> i @@ -62,21 +61,11 @@ public abstract class AbstractChannelMessageListener<T extends ChannelMessage> i
62 */ 61 */
63 @SuppressWarnings("unchecked") 62 @SuppressWarnings("unchecked")
64 private Class<T> getMessageClass() { 63 private Class<T> getMessageClass() {
65 - Class<?> targetClass = getClass();  
66 - while (targetClass.getSuperclass() != null) {  
67 - // 如果不是 AbstractMessageListener 父类,继续向上查找  
68 - if (targetClass.getSuperclass() != AbstractChannelMessageListener.class) {  
69 - targetClass = targetClass.getSuperclass();  
70 - continue;  
71 - }  
72 - // 如果是 AbstractMessageListener 父类,则解析泛型  
73 - Type[] types = ((ParameterizedTypeImpl) targetClass.getGenericSuperclass()).getActualTypeArguments();  
74 - if (ArrayUtil.isEmpty(types)) {  
75 - throw new IllegalStateException(String.format("类型(%s) 需要设置消息类型", getClass().getName()));  
76 - }  
77 - return (Class<T>) types[0]; 64 + Type type = TypeUtil.getTypeArgument(getClass(), 0);
  65 + if (type == null) {
  66 + throw new IllegalStateException(String.format("类型(%s) 需要设置消息类型", getClass().getName()));
78 } 67 }
79 - throw new IllegalStateException(String.format("类型(%s) 找不到 AbstractMessageListener 父类", getClass().getName())); 68 + return (Class<T>) type;
80 } 69 }
81 70
82 } 71 }
src/main/java/cn/iocoder/dashboard/framework/redis/core/stream/AbstractStreamMessageListener.java
1 package cn.iocoder.dashboard.framework.redis.core.stream; 1 package cn.iocoder.dashboard.framework.redis.core.stream;
2 2
3 -import cn.hutool.core.util.ArrayUtil; 3 +import cn.hutool.core.util.TypeUtil;
  4 +import cn.iocoder.dashboard.util.json.JsonUtils;
4 import lombok.Getter; 5 import lombok.Getter;
  6 +import lombok.Setter;
5 import lombok.SneakyThrows; 7 import lombok.SneakyThrows;
6 import org.springframework.beans.factory.annotation.Value; 8 import org.springframework.beans.factory.annotation.Value;
7 import org.springframework.data.redis.connection.stream.ObjectRecord; 9 import org.springframework.data.redis.connection.stream.ObjectRecord;
  10 +import org.springframework.data.redis.core.RedisTemplate;
8 import org.springframework.data.redis.stream.StreamListener; 11 import org.springframework.data.redis.stream.StreamListener;
9 -import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl;  
10 12
11 import java.lang.reflect.Type; 13 import java.lang.reflect.Type;
12 14
@@ -33,9 +35,14 @@ public abstract class AbstractStreamMessageListener&lt;T extends StreamMessage&gt; @@ -33,9 +35,14 @@ public abstract class AbstractStreamMessageListener&lt;T extends StreamMessage&gt;
33 /** 35 /**
34 * Redis 消费者分组,默认使用 spring.application.name 名字 36 * Redis 消费者分组,默认使用 spring.application.name 名字
35 */ 37 */
36 - @Value("spring.application.name") 38 + @Value("${spring.application.name}")
37 @Getter 39 @Getter
38 private String group; 40 private String group;
  41 + /**
  42 + *
  43 + */
  44 + @Setter
  45 + private RedisTemplate<String, ?> redisTemplate;
39 46
40 @SneakyThrows 47 @SneakyThrows
41 protected AbstractStreamMessageListener() { 48 protected AbstractStreamMessageListener() {
@@ -45,10 +52,16 @@ public abstract class AbstractStreamMessageListener&lt;T extends StreamMessage&gt; @@ -45,10 +52,16 @@ public abstract class AbstractStreamMessageListener&lt;T extends StreamMessage&gt;
45 52
46 @Override 53 @Override
47 public void onMessage(ObjectRecord<String, String> message) { 54 public void onMessage(ObjectRecord<String, String> message) {
48 - System.out.println(message);  
49 - if (true) {  
50 -// throw new IllegalStateException("测试下");  
51 - } 55 + // 消费消息
  56 + T messageObj = JsonUtils.parseObject(message.getValue(), messageType);
  57 + this.onMessage(messageObj);
  58 + // ack 消息消费完成
  59 + redisTemplate.opsForStream().acknowledge(group, message);
  60 + // TODO 芋艿:需要额外考虑以下几个点:
  61 + // 1. 处理异常的情况
  62 + // 2. 发送日志;以及事务的结合
  63 + // 3. 消费日志;以及通用的幂等性
  64 + // 4. 消费失败的重试,https://zhuanlan.zhihu.com/p/60501638
52 } 65 }
53 66
54 /** 67 /**
@@ -64,23 +77,12 @@ public abstract class AbstractStreamMessageListener&lt;T extends StreamMessage&gt; @@ -64,23 +77,12 @@ public abstract class AbstractStreamMessageListener&lt;T extends StreamMessage&gt;
64 * @return 消息类型 77 * @return 消息类型
65 */ 78 */
66 @SuppressWarnings("unchecked") 79 @SuppressWarnings("unchecked")
67 - // TODO 芋艿:稍后重构  
68 private Class<T> getMessageClass() { 80 private Class<T> getMessageClass() {
69 - Class<?> targetClass = getClass();  
70 - while (targetClass.getSuperclass() != null) {  
71 - // 如果不是 AbstractMessageListener 父类,继续向上查找  
72 - if (targetClass.getSuperclass() != AbstractStreamMessageListener.class) {  
73 - targetClass = targetClass.getSuperclass();  
74 - continue;  
75 - }  
76 - // 如果是 AbstractMessageListener 父类,则解析泛型  
77 - Type[] types = ((ParameterizedTypeImpl) targetClass.getGenericSuperclass()).getActualTypeArguments();  
78 - if (ArrayUtil.isEmpty(types)) {  
79 - throw new IllegalStateException(String.format("类型(%s) 需要设置消息类型", getClass().getName()));  
80 - }  
81 - return (Class<T>) types[0]; 81 + Type type = TypeUtil.getTypeArgument(getClass(), 0);
  82 + if (type == null) {
  83 + throw new IllegalStateException(String.format("类型(%s) 需要设置消息类型", getClass().getName()));
82 } 84 }
83 - throw new IllegalStateException(String.format("类型(%s) 找不到 AbstractStreamMessageListener 父类", getClass().getName())); 85 + return (Class<T>) type;
84 } 86 }
85 87
86 } 88 }
src/main/java/cn/iocoder/dashboard/modules/system/mq/consumer/mail/SysMailSendConsumer.java
@@ -2,14 +2,16 @@ package cn.iocoder.dashboard.modules.system.mq.consumer.mail; @@ -2,14 +2,16 @@ package cn.iocoder.dashboard.modules.system.mq.consumer.mail;
2 2
3 import cn.iocoder.dashboard.framework.redis.core.stream.AbstractStreamMessageListener; 3 import cn.iocoder.dashboard.framework.redis.core.stream.AbstractStreamMessageListener;
4 import cn.iocoder.dashboard.modules.system.mq.message.mail.SysMailSendMessage; 4 import cn.iocoder.dashboard.modules.system.mq.message.mail.SysMailSendMessage;
  5 +import lombok.extern.slf4j.Slf4j;
5 import org.springframework.stereotype.Component; 6 import org.springframework.stereotype.Component;
6 7
7 @Component 8 @Component
  9 +@Slf4j
8 public class SysMailSendConsumer extends AbstractStreamMessageListener<SysMailSendMessage> { 10 public class SysMailSendConsumer extends AbstractStreamMessageListener<SysMailSendMessage> {
9 11
10 @Override 12 @Override
11 public void onMessage(SysMailSendMessage message) { 13 public void onMessage(SysMailSendMessage message) {
12 - 14 + log.info("[onMessage][消息内容({})]", message);
13 } 15 }
14 16
15 } 17 }
src/main/java/cn/iocoder/dashboard/modules/system/mq/consumer/sms/SysSmsSendConsumer.java
@@ -2,14 +2,16 @@ package cn.iocoder.dashboard.modules.system.mq.consumer.sms; @@ -2,14 +2,16 @@ package cn.iocoder.dashboard.modules.system.mq.consumer.sms;
2 2
3 import cn.iocoder.dashboard.framework.redis.core.stream.AbstractStreamMessageListener; 3 import cn.iocoder.dashboard.framework.redis.core.stream.AbstractStreamMessageListener;
4 import cn.iocoder.dashboard.modules.system.mq.message.sms.SysSmsSendMessage; 4 import cn.iocoder.dashboard.modules.system.mq.message.sms.SysSmsSendMessage;
  5 +import lombok.extern.slf4j.Slf4j;
5 import org.springframework.stereotype.Component; 6 import org.springframework.stereotype.Component;
6 7
7 @Component 8 @Component
  9 +@Slf4j
8 public class SysSmsSendConsumer extends AbstractStreamMessageListener<SysSmsSendMessage> { 10 public class SysSmsSendConsumer extends AbstractStreamMessageListener<SysSmsSendMessage> {
9 11
10 @Override 12 @Override
11 public void onMessage(SysSmsSendMessage message) { 13 public void onMessage(SysSmsSendMessage message) {
12 - System.out.println(message); 14 + log.info("[onMessage][消息内容({})]", message);
13 } 15 }
14 16
15 } 17 }