Commit 2cef84bdc836de9041b5c68dcba0f0e59f666277

Authored by YunaiV
1 parent 15245310

* 【修复】自定义 DefaultStreamMessageListenerContainerX 实现,解决 Redisson Stream 读取不到数据返回 `null` 导致 NPE 问题

yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/config/YudaoMQAutoConfiguration.java
@@ -18,6 +18,7 @@ import org.springframework.data.redis.core.RedisTemplate; @@ -18,6 +18,7 @@ import org.springframework.data.redis.core.RedisTemplate;
18 import org.springframework.data.redis.core.StringRedisTemplate; 18 import org.springframework.data.redis.core.StringRedisTemplate;
19 import org.springframework.data.redis.listener.ChannelTopic; 19 import org.springframework.data.redis.listener.ChannelTopic;
20 import org.springframework.data.redis.listener.RedisMessageListenerContainer; 20 import org.springframework.data.redis.listener.RedisMessageListenerContainer;
  21 +import org.springframework.data.redis.stream.DefaultStreamMessageListenerContainerX;
21 import org.springframework.data.redis.stream.StreamMessageListenerContainer; 22 import org.springframework.data.redis.stream.StreamMessageListenerContainer;
22 23
23 import java.util.List; 24 import java.util.List;
@@ -81,11 +82,12 @@ public class YudaoMQAutoConfiguration { @@ -81,11 +82,12 @@ public class YudaoMQAutoConfiguration {
81 .build(); 82 .build();
82 // 创建 container 对象 83 // 创建 container 对象
83 StreamMessageListenerContainer<String, ObjectRecord<String, String>> container = 84 StreamMessageListenerContainer<String, ObjectRecord<String, String>> container =
84 - StreamMessageListenerContainer.create(redisTemplate.getRequiredConnectionFactory(), containerOptions); 85 +// StreamMessageListenerContainer.create(redisTemplate.getRequiredConnectionFactory(), containerOptions);
  86 + DefaultStreamMessageListenerContainerX.create(redisMQTemplate.getRedisTemplate().getRequiredConnectionFactory(), containerOptions);
85 87
86 // 第二步,注册监听器,消费对应的 Stream 主题 88 // 第二步,注册监听器,消费对应的 Stream 主题
87 String consumerName = buildConsumerName(); 89 String consumerName = buildConsumerName();
88 - listeners.forEach(listener -> { 90 + listeners.parallelStream().forEach(listener -> {
89 // 创建 listener 对应的消费者分组 91 // 创建 listener 对应的消费者分组
90 try { 92 try {
91 redisTemplate.opsForStream().createGroup(listener.getStreamKey(), listener.getGroup()); 93 redisTemplate.opsForStream().createGroup(listener.getStreamKey(), listener.getGroup());
yudao-framework/yudao-spring-boot-starter-mq/src/main/java/org/springframework/data/redis/stream/DefaultStreamMessageListenerContainerX.java 0 → 100644
  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 +
更新日志.md
@@ -37,6 +37,7 @@ TODO @@ -37,6 +37,7 @@ TODO
37 * 【修复】Knife4j 接口文档 404 的问题,原因是 `spring.mvc.static-path-pattern` 配置项,影响了基础路径 [commit](https://gitee.com/zhijiantianya/ruoyi-vue-pro/commit/f966fae0606178ec5ffa47dd487f7984867da557) 37 * 【修复】Knife4j 接口文档 404 的问题,原因是 `spring.mvc.static-path-pattern` 配置项,影响了基础路径 [commit](https://gitee.com/zhijiantianya/ruoyi-vue-pro/commit/f966fae0606178ec5ffa47dd487f7984867da557)
38 * 【修复】修复文件访问地址错误 [#68](https://github.com/YunaiV/ruoyi-vue-pro/pull/68) 38 * 【修复】修复文件访问地址错误 [#68](https://github.com/YunaiV/ruoyi-vue-pro/pull/68)
39 * 【修复】工作流程发起以及审批异常,由 `@NotEmpty` 校验、和 Long 类型异常导致 [#73](https://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/73) 39 * 【修复】工作流程发起以及审批异常,由 `@NotEmpty` 校验、和 Long 类型异常导致 [#73](https://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/73)
  40 +* 【修复】自定义 DefaultStreamMessageListenerContainerX 实现,解决 Redisson Stream 读取不到数据返回 `null` 导致 NPE 问题 [commit]()
40 41
41 ### 🔨 Dependency Upgrades 42 ### 🔨 Dependency Upgrades
42 43