Commit a5a3aea522a13300973dd343aafa20a16c80ba0a

Authored by haohao
1 parent 0593dbc9

feat:【IoT 物联网】新增 TCP 协议支持,添加 TCP 连接管理、上下行消息处理及相关配置

yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/config/IotGatewayConfiguration.java
1 1 package cn.iocoder.yudao.module.iot.gateway.config;
2 2  
  3 +import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;
3 4 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
4 5 import cn.iocoder.yudao.module.iot.gateway.protocol.emqx.IotEmqxAuthEventProtocol;
5 6 import cn.iocoder.yudao.module.iot.gateway.protocol.emqx.IotEmqxDownstreamSubscriber;
6 7 import cn.iocoder.yudao.module.iot.gateway.protocol.emqx.IotEmqxUpstreamProtocol;
7 8 import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpDownstreamSubscriber;
8 9 import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpUpstreamProtocol;
  10 +import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.IotTcpConnectionManager;
  11 +import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.router.IotTcpDownstreamHandler;
  12 +import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.IotTcpDownstreamSubscriber;
  13 +import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.IotTcpUpstreamProtocol;
  14 +import cn.iocoder.yudao.module.iot.gateway.service.device.IotDeviceService;
  15 +import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
9 16 import io.vertx.core.Vertx;
10 17 import lombok.extern.slf4j.Slf4j;
11 18 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
... ... @@ -33,7 +40,7 @@ public class IotGatewayConfiguration {
33 40  
34 41 @Bean
35 42 public IotHttpDownstreamSubscriber iotHttpDownstreamSubscriber(IotHttpUpstreamProtocol httpUpstreamProtocol,
36   - IotMessageBus messageBus) {
  43 + IotMessageBus messageBus) {
37 44 return new IotHttpDownstreamSubscriber(httpUpstreamProtocol, messageBus);
38 45 }
39 46 }
... ... @@ -53,21 +60,51 @@ public class IotGatewayConfiguration {
53 60  
54 61 @Bean
55 62 public IotEmqxAuthEventProtocol iotEmqxAuthEventProtocol(IotGatewayProperties gatewayProperties,
56   - Vertx emqxVertx) {
  63 + Vertx emqxVertx) {
57 64 return new IotEmqxAuthEventProtocol(gatewayProperties.getProtocol().getEmqx(), emqxVertx);
58 65 }
59 66  
60 67 @Bean
61 68 public IotEmqxUpstreamProtocol iotEmqxUpstreamProtocol(IotGatewayProperties gatewayProperties,
62   - Vertx emqxVertx) {
  69 + Vertx emqxVertx) {
63 70 return new IotEmqxUpstreamProtocol(gatewayProperties.getProtocol().getEmqx(), emqxVertx);
64 71 }
65 72  
66 73 @Bean
67 74 public IotEmqxDownstreamSubscriber iotEmqxDownstreamSubscriber(IotEmqxUpstreamProtocol mqttUpstreamProtocol,
68   - IotMessageBus messageBus) {
  75 + IotMessageBus messageBus) {
69 76 return new IotEmqxDownstreamSubscriber(mqttUpstreamProtocol, messageBus);
70 77 }
71 78 }
72 79  
  80 + /**
  81 + * IoT 网关 TCP 协议配置类
  82 + */
  83 + @Configuration
  84 + @ConditionalOnProperty(prefix = "yudao.iot.gateway.protocol.tcp", name = "enabled", havingValue = "true")
  85 + @Slf4j
  86 + public static class TcpProtocolConfiguration {
  87 +
  88 + @Bean
  89 + public Vertx tcpVertx() {
  90 + return Vertx.vertx();
  91 + }
  92 +
  93 + @Bean
  94 + public IotTcpUpstreamProtocol iotTcpUpstreamProtocol(Vertx tcpVertx, IotGatewayProperties gatewayProperties,
  95 + IotTcpConnectionManager connectionManager, IotDeviceMessageService messageService,
  96 + IotDeviceService deviceService, IotDeviceCommonApi deviceApi) {
  97 + return new IotTcpUpstreamProtocol(tcpVertx, gatewayProperties, connectionManager,
  98 + messageService, deviceService, deviceApi);
  99 + }
  100 +
  101 + @Bean
  102 + public IotTcpDownstreamSubscriber iotTcpDownstreamSubscriber(IotTcpUpstreamProtocol tcpUpstreamProtocol,
  103 + IotMessageBus messageBus,
  104 + IotTcpDownstreamHandler downstreamHandler) {
  105 + return new IotTcpDownstreamSubscriber(tcpUpstreamProtocol, messageBus, downstreamHandler);
  106 + }
  107 +
  108 + }
  109 +
73 110 }
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/config/IotGatewayProperties.java
... ... @@ -78,6 +78,11 @@ public class IotGatewayProperties {
78 78 */
79 79 private EmqxProperties emqx;
80 80  
  81 + /**
  82 + * TCP 组件配置
  83 + */
  84 + private TcpProperties tcp;
  85 +
81 86 }
82 87  
83 88 @Data
... ... @@ -96,6 +101,25 @@ public class IotGatewayProperties {
96 101 }
97 102  
98 103 @Data
  104 + public static class TcpProperties {
  105 +
  106 + /**
  107 + * 是否开启
  108 + */
  109 + @NotNull(message = "是否开启不能为空")
  110 + private Boolean enabled;
  111 + /**
  112 + * 服务端口
  113 + */
  114 + private Integer serverPort;
  115 + /**
  116 + * 服务主机
  117 + */
  118 + private String serverHost;
  119 +
  120 + }
  121 +
  122 + @Data
99 123 public static class EmqxProperties {
100 124  
101 125 /**
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/emqx/router/IotEmqxDownstreamHandler.java
... ... @@ -97,28 +97,19 @@ public class IotEmqxDownstreamHandler {
97 97 break;
98 98  
99 99 case EVENT_POST:
100   - // TODO @haohao:不用 eventIdentifier 拼接哈,直接 data 里面,有 identifier 字段
101 100 // 事件上报:只支持回复消息(下行)
102 101 if (isReply) {
103   - String identifier = IotDeviceMessageUtils.getIdentifier(message);
104   - if (StrUtil.isNotBlank(identifier)) {
105   - return IotMqttTopicUtils.buildEventPostReplyTopic(productKey, deviceName, identifier);
106   - }
  102 + return IotMqttTopicUtils.buildEventPostReplyTopicGeneric(productKey, deviceName);
107 103 }
108 104 break;
109 105  
110 106 case SERVICE_INVOKE:
111 107 // 服务调用:支持请求和回复
112   - // TODO @haohao:不用 serviceIdentifier 拼接哈,直接 data 里面,有 identifier 字段
113   - String serviceIdentifier = IotDeviceMessageUtils.getIdentifier(message);
114   - if (StrUtil.isNotBlank(serviceIdentifier)) {
115   - if (isReply) {
116   - return IotMqttTopicUtils.buildServiceReplyTopic(productKey, deviceName, serviceIdentifier);
117   - } else {
118   - return IotMqttTopicUtils.buildServiceTopic(productKey, deviceName, serviceIdentifier);
119   - }
  108 + if (isReply) {
  109 + return IotMqttTopicUtils.buildServiceReplyTopicGeneric(productKey, deviceName);
  110 + } else {
  111 + return IotMqttTopicUtils.buildServiceTopicGeneric(productKey, deviceName);
120 112 }
121   - break;
122 113  
123 114 case CONFIG_PUSH:
124 115 // 配置推送:平台向设备推送配置(下行请求),设备回复确认(上行回复)
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/IotTcpConnectionManager.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.tcp;
  2 +
  3 +import io.vertx.core.net.NetSocket;
  4 +import lombok.extern.slf4j.Slf4j;
  5 +import org.springframework.stereotype.Component;
  6 +
  7 +import java.util.concurrent.ConcurrentHashMap;
  8 +import java.util.concurrent.ConcurrentMap;
  9 +
  10 +/**
  11 + * IoT TCP 连接管理器
  12 + *
  13 + * @author 芋道源码
  14 + */
  15 +@Component
  16 +@Slf4j
  17 +public class IotTcpConnectionManager {
  18 +
  19 + /**
  20 + * 连接集合
  21 + *
  22 + * key:设备唯一标识
  23 + */
  24 + private final ConcurrentMap<String, NetSocket> connectionMap = new ConcurrentHashMap<>();
  25 +
  26 + /**
  27 + * 添加一个新连接
  28 + *
  29 + * @param deviceId 设备唯一标识
  30 + * @param socket Netty Channel
  31 + */
  32 + public void addConnection(String deviceId, NetSocket socket) {
  33 + log.info("[addConnection][设备({}) 连接({})]", deviceId, socket.remoteAddress());
  34 + connectionMap.put(deviceId, socket);
  35 + }
  36 +
  37 + /**
  38 + * 根据设备 ID 获取连接
  39 + *
  40 + * @param deviceId 设备 ID
  41 + * @return 连接
  42 + */
  43 + public NetSocket getConnection(String deviceId) {
  44 + return connectionMap.get(deviceId);
  45 + }
  46 +
  47 + /**
  48 + * 移除指定连接
  49 + *
  50 + * @param socket Netty Channel
  51 + */
  52 + public void removeConnection(NetSocket socket) {
  53 + connectionMap.entrySet().stream()
  54 + .filter(entry -> entry.getValue().equals(socket))
  55 + .findFirst()
  56 + .ifPresent(entry -> {
  57 + log.info("[removeConnection][设备({}) 断开连接({})]", entry.getKey(), socket.remoteAddress());
  58 + connectionMap.remove(entry.getKey());
  59 + });
  60 + }
  61 +
  62 +}
0 63 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/IotTcpDownstreamSubscriber.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.tcp;
  2 +
  3 +import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
  4 +import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageSubscriber;
  5 +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
  6 +import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
  7 +import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.router.IotTcpDownstreamHandler;
  8 +import jakarta.annotation.PostConstruct;
  9 +import lombok.RequiredArgsConstructor;
  10 +import lombok.extern.slf4j.Slf4j;
  11 +
  12 +/**
  13 + * IoT 网关 TCP 订阅者:接收下行给设备的消息
  14 + *
  15 + * @author 芋道源码
  16 + */
  17 +@RequiredArgsConstructor
  18 +@Slf4j
  19 +public class IotTcpDownstreamSubscriber implements IotMessageSubscriber<IotDeviceMessage> {
  20 +
  21 + private final IotTcpUpstreamProtocol protocol;
  22 +
  23 + private final IotMessageBus messageBus;
  24 +
  25 + private final IotTcpDownstreamHandler downstreamHandler;
  26 +
  27 + @PostConstruct
  28 + public void init() {
  29 + messageBus.register(this);
  30 + }
  31 +
  32 + @Override
  33 + public String getTopic() {
  34 + return IotDeviceMessageUtils.buildMessageBusGatewayDeviceMessageTopic(protocol.getServerId());
  35 + }
  36 +
  37 + @Override
  38 + public String getGroup() {
  39 + // 保证点对点消费,需要保证独立的 Group,所以使用 Topic 作为 Group
  40 + return getTopic();
  41 + }
  42 +
  43 + @Override
  44 + public void onMessage(IotDeviceMessage message) {
  45 + log.debug("[onMessage][接收到下行消息, messageId: {}, method: {}, deviceId: {}]",
  46 + message.getId(), message.getMethod(), message.getDeviceId());
  47 + try {
  48 + // 1. 校验
  49 + String method = message.getMethod();
  50 + if (method == null) {
  51 + log.warn("[onMessage][消息方法为空, messageId: {}, deviceId: {}]",
  52 + message.getId(), message.getDeviceId());
  53 + return;
  54 + }
  55 +
  56 + // 2. 处理下行消息
  57 + downstreamHandler.handle(message);
  58 + } catch (Exception e) {
  59 + log.error("[onMessage][处理下行消息失败, messageId: {}, method: {}, deviceId: {}]",
  60 + message.getId(), message.getMethod(), message.getDeviceId(), e);
  61 + }
  62 + }
  63 +
  64 +}
0 65 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/IotTcpUpstreamProtocol.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.tcp;
  2 +
  3 +import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;
  4 +import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
  5 +import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties;
  6 +import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.router.IotTcpConnectionHandler;
  7 +import cn.iocoder.yudao.module.iot.gateway.service.device.IotDeviceService;
  8 +import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
  9 +import io.vertx.core.Vertx;
  10 +import io.vertx.core.net.NetServer;
  11 +import jakarta.annotation.PostConstruct;
  12 +import jakarta.annotation.PreDestroy;
  13 +import lombok.Getter;
  14 +import lombok.RequiredArgsConstructor;
  15 +import lombok.extern.slf4j.Slf4j;
  16 +
  17 +/**
  18 + * IoT 网关 TCP 协议:接收设备上行消息
  19 + *
  20 + * @author 芋道源码
  21 + */
  22 +@Slf4j
  23 +@RequiredArgsConstructor
  24 +public class IotTcpUpstreamProtocol {
  25 +
  26 + private final Vertx vertx;
  27 +
  28 + private final IotGatewayProperties gatewayProperties;
  29 +
  30 + private final IotTcpConnectionManager connectionManager;
  31 +
  32 + private final IotDeviceMessageService messageService;
  33 +
  34 + private final IotDeviceService deviceService;
  35 +
  36 + private final IotDeviceCommonApi deviceApi;
  37 +
  38 + @Getter
  39 + private String serverId;
  40 +
  41 + private NetServer netServer;
  42 +
  43 + @PostConstruct
  44 + public void start() {
  45 + // 1. 初始化参数
  46 + IotGatewayProperties.TcpProperties tcpProperties = gatewayProperties.getProtocol().getTcp();
  47 + this.serverId = IotDeviceMessageUtils.generateServerId(tcpProperties.getServerPort());
  48 +
  49 + // 2. 创建 TCP 服务器
  50 + netServer = vertx.createNetServer();
  51 + netServer.connectHandler(socket -> {
  52 + new IotTcpConnectionHandler(socket, connectionManager,
  53 + messageService, deviceService, deviceApi, serverId).start();
  54 + });
  55 +
  56 + // 3. 启动 TCP 服务器
  57 + netServer.listen(tcpProperties.getServerPort(), tcpProperties.getServerHost())
  58 + .onSuccess(server -> log.info("[start][IoT 网关 TCP 服务启动成功,端口:{}]", server.actualPort()))
  59 + .onFailure(e -> log.error("[start][IoT 网关 TCP 服务启动失败]", e));
  60 + }
  61 +
  62 + @PreDestroy
  63 + public void stop() {
  64 + if (netServer != null) {
  65 + netServer.close()
  66 + .onSuccess(v -> log.info("[stop][IoT 网关 TCP 服务已停止]"))
  67 + .onFailure(e -> log.error("[stop][IoT 网关 TCP 服务停止失败]", e));
  68 + }
  69 + }
  70 +
  71 +}
0 72 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/router/IotTcpConnectionHandler.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.tcp.router;
  2 +
  3 +import cn.hutool.core.util.BooleanUtil;
  4 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  5 +import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;
  6 +import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
  7 +import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO;
  8 +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
  9 +import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.IotTcpConnectionManager;
  10 +import cn.iocoder.yudao.module.iot.gateway.service.device.IotDeviceService;
  11 +import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
  12 +import io.vertx.core.Handler;
  13 +import io.vertx.core.buffer.Buffer;
  14 +import io.vertx.core.net.NetSocket;
  15 +import io.vertx.core.parsetools.RecordParser;
  16 +import lombok.RequiredArgsConstructor;
  17 +import lombok.extern.slf4j.Slf4j;
  18 +
  19 +/**
  20 + * IoT TCP 连接处理器
  21 + * <p>
  22 + * 核心负责:
  23 + * 1. 【认证】创建连接后,设备需要发送认证消息,认证通过后,才能进行后续的通信
  24 + * 2. 【消息处理】接收设备发送的消息,解码后,发送到消息队列
  25 + * 3. 【断开】设备断开连接后,清理资源
  26 + *
  27 + * @author 芋道源码
  28 + */
  29 +@RequiredArgsConstructor
  30 +@Slf4j
  31 +public class IotTcpConnectionHandler implements Handler<Buffer> {
  32 +
  33 + private final NetSocket socket;
  34 + /**
  35 + * 是否已认证
  36 + */
  37 + private boolean authenticated = false;
  38 + /**
  39 + * 设备信息
  40 + */
  41 + private IotDeviceRespDTO device;
  42 +
  43 + private final IotTcpConnectionManager connectionManager;
  44 +
  45 + private final IotDeviceMessageService messageService;
  46 +
  47 + private final IotDeviceService deviceService;
  48 +
  49 + private final IotDeviceCommonApi deviceApi;
  50 +
  51 + private final String serverId;
  52 +
  53 + public void start() {
  54 + // 1. 设置解析器
  55 + final RecordParser parser = RecordParser.newDelimited("\n", this);
  56 + socket.handler(parser);
  57 +
  58 + // 2. 设置处理器
  59 + socket.closeHandler(v -> handleConnectionClose());
  60 + socket.exceptionHandler(this::handleException);
  61 + }
  62 +
  63 + @Override
  64 + public void handle(Buffer buffer) {
  65 + log.info("[handle][接收到数据: {}]", buffer);
  66 + try {
  67 + // 1. 处理认证
  68 + if (!authenticated) {
  69 + handleAuthentication(buffer);
  70 + return;
  71 + }
  72 + // 2. 处理消息
  73 + handleMessage(buffer);
  74 + } catch (Exception e) {
  75 + log.error("[handle][处理异常]", e);
  76 + socket.close();
  77 + }
  78 + }
  79 +
  80 + private void handleAuthentication(Buffer buffer) {
  81 + // 1. 解析认证信息
  82 + // TODO @芋艿:这里的认证协议,需要和设备端约定。默认为 productKey,deviceName,password
  83 + String[] parts = buffer.toString().split(",");
  84 + if (parts.length != 3) {
  85 + log.error("[handleAuthentication][认证信息({})格式不正确]", buffer);
  86 + socket.close();
  87 + return;
  88 + }
  89 + String productKey = parts[0];
  90 + String deviceName = parts[1];
  91 + String password = parts[2];
  92 +
  93 + // 2. 执行认证
  94 + CommonResult<Boolean> authResult = deviceApi.authDevice(new IotDeviceAuthReqDTO()
  95 + .setClientId(socket.remoteAddress().toString()).setUsername(productKey + "/" + deviceName)
  96 + .setPassword(password));
  97 + if (authResult.isError() || !BooleanUtil.isTrue(authResult.getData())) {
  98 + log.error("[handleAuthentication][认证失败,productKey({}) deviceName({}) password({})]", productKey, deviceName,
  99 + password);
  100 + socket.close();
  101 + return;
  102 + }
  103 +
  104 + // 3. 认证成功
  105 + this.authenticated = true;
  106 + this.device = deviceService.getDeviceFromCache(productKey, deviceName);
  107 + connectionManager.addConnection(String.valueOf(device.getId()), socket);
  108 +
  109 + // 4. 发送上线消息
  110 + IotDeviceMessage message = IotDeviceMessage.buildStateUpdateOnline();
  111 + messageService.sendDeviceMessage(message, productKey, deviceName, serverId);
  112 + log.info("[handleAuthentication][认证成功]");
  113 + }
  114 +
  115 + private void handleMessage(Buffer buffer) {
  116 + // 1. 解码消息
  117 + IotDeviceMessage message = messageService.decodeDeviceMessage(buffer.getBytes(),
  118 + device.getProductKey(), device.getDeviceName());
  119 + if (message == null) {
  120 + log.warn("[handleMessage][解码消息失败]");
  121 + return;
  122 + }
  123 + // 2. 发送消息到队列
  124 + messageService.sendDeviceMessage(message, device.getProductKey(), device.getDeviceName(), serverId);
  125 + }
  126 +
  127 + private void handleConnectionClose() {
  128 + // 1. 移除连接
  129 + connectionManager.removeConnection(socket);
  130 + // 2. 发送离线消息
  131 + if (device != null) {
  132 + IotDeviceMessage message = IotDeviceMessage.buildStateOffline();
  133 + messageService.sendDeviceMessage(message, device.getProductKey(), device.getDeviceName(), serverId);
  134 + }
  135 + }
  136 +
  137 + private void handleException(Throwable e) {
  138 + log.error("[handleException][连接({}) 发生异常]", socket.remoteAddress(), e);
  139 + socket.close();
  140 + }
  141 +
  142 +}
0 143 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/router/IotTcpDownstreamHandler.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.tcp.router;
  2 +
  3 +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
  4 +import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.IotTcpConnectionManager;
  5 +import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
  6 +import io.vertx.core.buffer.Buffer;
  7 +import io.vertx.core.net.NetSocket;
  8 +import lombok.RequiredArgsConstructor;
  9 +import lombok.extern.slf4j.Slf4j;
  10 +import org.springframework.stereotype.Component;
  11 +
  12 +/**
  13 + * IoT 网关 TCP 下行消息处理器
  14 + * <p>
  15 + * 从消息总线接收到下行消息,然后发布到 TCP 连接,从而被设备所接收
  16 + *
  17 + * @author 芋道源码
  18 + */
  19 +@Slf4j
  20 +@Component
  21 +@RequiredArgsConstructor
  22 +public class IotTcpDownstreamHandler {
  23 +
  24 + private final IotTcpConnectionManager connectionManager;
  25 + private final IotDeviceMessageService messageService;
  26 +
  27 + /**
  28 + * 处理下行消息
  29 + *
  30 + * @param message 设备消息
  31 + */
  32 + public void handle(IotDeviceMessage message) {
  33 + // 1. 获取设备对应的连接
  34 + NetSocket socket = connectionManager.getConnection(String.valueOf(message.getDeviceId()));
  35 + if (socket == null) {
  36 + log.error("[handle][设备({})的连接不存在]", message.getDeviceId());
  37 + return;
  38 + }
  39 +
  40 + // 2. 编码消息
  41 + byte[] bytes = messageService.encodeDeviceMessage(message, null, null);
  42 +
  43 + // 3. 发送消息
  44 + socket.write(Buffer.buffer(bytes));
  45 + // TODO @芋艿:这里的换行符,需要和设备端约定
  46 + socket.write("\n");
  47 + }
  48 +
  49 +}
0 50 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/util/IotMqttTopicUtils.java
... ... @@ -80,50 +80,53 @@ public final class IotMqttTopicUtils {
80 80 }
81 81  
82 82 /**
83   - * 构建设备服务调用主题
  83 + * 构建设备配置推送主题
84 84 *
85   - * @param productKey 产品 Key
86   - * @param deviceName 设备名称
87   - * @param serviceIdentifier 服务标识符
  85 + * @param productKey 产品 Key
  86 + * @param deviceName 设备名称
88 87 * @return 完整的主题路径
89 88 */
90   - public static String buildServiceTopic(String productKey, String deviceName, String serviceIdentifier) {
91   - return buildDeviceTopicPrefix(productKey, deviceName) + SERVICE_TOPIC_PREFIX + serviceIdentifier;
  89 + public static String buildConfigPushTopic(String productKey, String deviceName) {
  90 + return buildDeviceTopicPrefix(productKey, deviceName) + "/thing/config/push";
92 91 }
93 92  
94 93 /**
95   - * 构建设备服务调用回复主题
  94 + * 构建设备事件上报通用回复主题
  95 + * <p>
  96 + * 不包含具体的事件标识符,事件标识符通过消息 data 中的 identifier 字段传递
96 97 *
97   - * @param productKey 产品 Key
98   - * @param deviceName 设备名称
99   - * @param serviceIdentifier 服务标识符
  98 + * @param productKey 产品 Key
  99 + * @param deviceName 设备名称
100 100 * @return 完整的主题路径
101 101 */
102   - public static String buildServiceReplyTopic(String productKey, String deviceName, String serviceIdentifier) {
103   - return buildDeviceTopicPrefix(productKey, deviceName) + SERVICE_TOPIC_PREFIX + serviceIdentifier + "_reply";
  102 + public static String buildEventPostReplyTopicGeneric(String productKey, String deviceName) {
  103 + return buildDeviceTopicPrefix(productKey, deviceName) + "/thing/event/post_reply";
104 104 }
105 105  
106 106 /**
107   - * 构建设备事件上报回复主题
  107 + * 构建设备服务调用通用主题
  108 + * <p>
  109 + * 不包含具体的服务标识符,服务标识符通过消息 data 中的 identifier 字段传递
108 110 *
109   - * @param productKey 产品 Key
110   - * @param deviceName 设备名称
111   - * @param eventIdentifier 事件标识符
  111 + * @param productKey 产品 Key
  112 + * @param deviceName 设备名称
112 113 * @return 完整的主题路径
113 114 */
114   - public static String buildEventPostReplyTopic(String productKey, String deviceName, String eventIdentifier) {
115   - return buildDeviceTopicPrefix(productKey, deviceName) + "/thing/event/" + eventIdentifier + "_reply";
  115 + public static String buildServiceTopicGeneric(String productKey, String deviceName) {
  116 + return buildDeviceTopicPrefix(productKey, deviceName) + "/thing/service/invoke";
116 117 }
117 118  
118 119 /**
119   - * 构建设备配置推送主题
  120 + * 构建设备服务调用通用回复主题
  121 + * <p>
  122 + * 不包含具体的服务标识符,服务标识符通过消息 data 中的 identifier 字段传递
120 123 *
121 124 * @param productKey 产品 Key
122 125 * @param deviceName 设备名称
123 126 * @return 完整的主题路径
124 127 */
125   - public static String buildConfigPushTopic(String productKey, String deviceName) {
126   - return buildDeviceTopicPrefix(productKey, deviceName) + "/thing/config/push";
  128 + public static String buildServiceReplyTopicGeneric(String productKey, String deviceName) {
  129 + return buildDeviceTopicPrefix(productKey, deviceName) + "/thing/service/invoke_reply";
127 130 }
128 131  
129 132 }
130 133 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/resources/application-local.yaml
1 1 # ==================== IoT 网关本地开发环境配置 ====================
2   -
3 2 --- #################### 消息队列相关 ####################
4 3  
5 4 # rocketmq 配置项,对应 RocketMQProperties 配置类
... ... @@ -41,6 +40,13 @@ yudao:
41 40 mqtt-ssl: false # 是否开启 SSL
42 41 mqtt-topics:
43 42 - "/sys/#" # 系统主题
  43 + # ====================================
  44 + # 针对引入的 TCP 组件的配置
  45 + # ====================================
  46 + tcp:
  47 + enabled: true
  48 + server-port: 8093
  49 + server-host: 0.0.0.0
44 50  
45 51 # 消息总线配置
46 52 message-bus:
... ... @@ -52,7 +58,7 @@ yudao:
52 58 logging:
53 59 level:
54 60 # 开发环境详细日志
55   - cn.iocoder.yudao.module.iot.gateway.protocol.mqtt: DEBUG
  61 + cn.iocoder.yudao.module.iot.gateway.protocol.emqx: DEBUG
56 62 cn.iocoder.yudao.module.iot.gateway.protocol.http: DEBUG
57 63 # MQTT 客户端日志
58 64 # io.vertx.mqtt: DEBUG
59 65 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/resources/application.yaml
... ... @@ -41,6 +41,11 @@ yudao:
41 41 mqtt-ssl: false
42 42 mqtt-topics:
43 43 - "/sys/#" # 系统主题
  44 + # ====================================
  45 + # 针对引入的 TCP 组件的配置
  46 + # ====================================
  47 + tcp:
  48 + enabled: false
44 49  
45 50 # 消息总线配置
46 51 message-bus:
... ...