feat: 增加 IoT Kafka 消息总线实现

- 新增 IotKafkaMessageBus,支持 IoT gateway 与 yudao-server 通过 Kafka 通信
- 增加 type=kafka 自动配置,并补充消息总线类型说明
- 为 iot-gateway 补充 spring-kafka 依赖和 Kafka 连接配置
- 将 iot-gateway 与 yudao-server 的 IoT message-bus 默认切换为 kafka
This commit is contained in:
YunaiV
2026-06-07 19:14:07 +08:00
parent 6d3d097383
commit 93dadc14f6
@@ -0,0 +1,127 @@
package cn.iocoder.yudao.module.iot.core.messagebus.core.kafka;
import cn.hutool.core.util.TypeUtil;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageSubscriber;
import jakarta.annotation.PreDestroy;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.apache.kafka.common.serialization.StringSerializer;
import org.springframework.boot.autoconfigure.kafka.KafkaProperties;
import org.springframework.kafka.core.DefaultKafkaConsumerFactory;
import org.springframework.kafka.core.DefaultKafkaProducerFactory;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.listener.AcknowledgingMessageListener;
import org.springframework.kafka.listener.ConcurrentMessageListenerContainer;
import org.springframework.kafka.listener.ContainerProperties;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
/**
* 基于 Kafka 的 {@link IotMessageBus} 实现类
*
* @author 芋道源码
*/
@Slf4j
public class IotKafkaMessageBus implements IotMessageBus {
private final KafkaTemplate<String, String> kafkaTemplate;
private final KafkaProperties kafkaProperties;
@Getter
private final List<IotMessageSubscriber<?>> subscribers = new ArrayList<>();
private final List<ConcurrentMessageListenerContainer<String, String>> containers = new ArrayList<>();
public IotKafkaMessageBus(KafkaProperties kafkaProperties) {
this.kafkaProperties = kafkaProperties;
this.kafkaTemplate = new KafkaTemplate<>(new DefaultKafkaProducerFactory<>(buildProducerProperties(kafkaProperties)));
}
@Override
public void post(String topic, Object message) {
String messageJson = JsonUtils.toJsonString(message);
try {
kafkaTemplate.send(topic, messageJson).get();
log.info("[post][topic({}) 发送消息({})]", topic, message);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new IllegalStateException(String.format("发送 Kafka 消息失败,topic(%s) message(%s)", topic, message), e);
} catch (ExecutionException e) {
throw new IllegalStateException(String.format("发送 Kafka 消息失败,topic(%s) message(%s)", topic, message), e);
}
}
@Override
public void register(IotMessageSubscriber<?> subscriber) {
Type type = TypeUtil.getTypeArgument(subscriber.getClass(), 0);
if (type == null) {
throw new IllegalStateException(String.format("类型(%s) 需要设置消息类型", getClass().getName()));
}
// 1. 创建消费容器
ContainerProperties containerProperties = new ContainerProperties(subscriber.getTopic());
containerProperties.setGroupId(subscriber.getGroup());
containerProperties.setAckMode(ContainerProperties.AckMode.MANUAL_IMMEDIATE);
containerProperties.setMissingTopicsFatal(false);
containerProperties.setMessageListener((AcknowledgingMessageListener<String, String>) (message, acknowledgment) -> {
try {
subscriber.onMessage(JsonUtils.parseObject(message.value(), type));
acknowledgment.acknowledge();
} catch (Exception ex) {
log.error("[onMessage][topic({}/{}) message({}) 消费者({}) 处理异常]",
subscriber.getTopic(), subscriber.getGroup(), message, subscriber.getClass().getName(), ex);
throw ex;
}
});
ConcurrentMessageListenerContainer<String, String> container = new ConcurrentMessageListenerContainer<>(
new DefaultKafkaConsumerFactory<>(buildConsumerProperties(kafkaProperties, subscriber.getGroup())),
containerProperties);
container.start();
// 2. 保存消费者引用
containers.add(container);
subscribers.add(subscriber);
}
@PreDestroy
public void destroy() {
for (ConcurrentMessageListenerContainer<String, String> container : containers) {
try {
container.stop();
log.info("[destroy][关闭 Kafka 消费者容器成功]");
} catch (Exception e) {
log.error("[destroy][关闭 Kafka 消费者容器异常]", e);
}
}
kafkaTemplate.destroy();
}
private static Map<String, Object> buildProducerProperties(KafkaProperties kafkaProperties) {
Map<String, Object> properties = new HashMap<>(kafkaProperties.buildProducerProperties());
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
return properties;
}
private static Map<String, Object> buildConsumerProperties(KafkaProperties kafkaProperties, String group) {
Map<String, Object> properties = new HashMap<>(kafkaProperties.buildConsumerProperties());
properties.put(ConsumerConfig.GROUP_ID_CONFIG, group);
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);
properties.putIfAbsent(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");
return properties;
}
}