Commit c9b9fc1f31242804e594638d8d64d6a6705fa967

Authored by haohao
1 parent f70f578a

feat:【IoT 物联网】重构 TCP 协议处理,新增 TCP 会话和认证管理

Showing 19 changed files with 1717 additions and 1351 deletions
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/message/IotDeviceMessageServiceImpl.java
@@ -236,7 +236,8 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService { @@ -236,7 +236,8 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService {
236 236
237 @Override 237 @Override
238 public Long getDeviceMessageCount(LocalDateTime createTime) { 238 public Long getDeviceMessageCount(LocalDateTime createTime) {
239 - return deviceMessageMapper.selectCountByCreateTime(createTime != null ? LocalDateTimeUtil.toEpochMilli(createTime) : null); 239 + return deviceMessageMapper
  240 + .selectCountByCreateTime(createTime != null ? LocalDateTimeUtil.toEpochMilli(createTime) : null);
240 } 241 }
241 242
242 @Override 243 @Override
@@ -244,10 +245,12 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService { @@ -244,10 +245,12 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService {
244 IotStatisticsDeviceMessageReqVO reqVO) { 245 IotStatisticsDeviceMessageReqVO reqVO) {
245 // 1. 按小时统计,获取分项统计数据 246 // 1. 按小时统计,获取分项统计数据
246 List<Map<String, Object>> countList = deviceMessageMapper.selectDeviceMessageCountGroupByDate( 247 List<Map<String, Object>> countList = deviceMessageMapper.selectDeviceMessageCountGroupByDate(
247 - LocalDateTimeUtil.toEpochMilli(reqVO.getTimes()[0]), LocalDateTimeUtil.toEpochMilli(reqVO.getTimes()[1])); 248 + LocalDateTimeUtil.toEpochMilli(reqVO.getTimes()[0]),
  249 + LocalDateTimeUtil.toEpochMilli(reqVO.getTimes()[1]));
248 250
249 // 2. 按照日期间隔,合并数据 251 // 2. 按照日期间隔,合并数据
250 - List<LocalDateTime[]> timeRanges = LocalDateTimeUtils.getDateRangeList(reqVO.getTimes()[0], reqVO.getTimes()[1], reqVO.getInterval()); 252 + List<LocalDateTime[]> timeRanges = LocalDateTimeUtils.getDateRangeList(reqVO.getTimes()[0], reqVO.getTimes()[1],
  253 + reqVO.getInterval());
251 return convertList(timeRanges, times -> { 254 return convertList(timeRanges, times -> {
252 Integer upstreamCount = countList.stream() 255 Integer upstreamCount = countList.stream()
253 .filter(vo -> LocalDateTimeUtils.isBetween(times[0], times[1], (Timestamp) vo.get("time"))) 256 .filter(vo -> LocalDateTimeUtils.isBetween(times[0], times[1], (Timestamp) vo.get("time")))
yudao-module-iot/yudao-module-iot-gateway/pom.xml
@@ -47,6 +47,13 @@ @@ -47,6 +47,13 @@
47 <groupId>io.vertx</groupId> 47 <groupId>io.vertx</groupId>
48 <artifactId>vertx-mqtt</artifactId> 48 <artifactId>vertx-mqtt</artifactId>
49 </dependency> 49 </dependency>
  50 +
  51 + <!-- 测试相关 -->
  52 + <dependency>
  53 + <groupId>cn.iocoder.boot</groupId>
  54 + <artifactId>yudao-spring-boot-starter-test</artifactId>
  55 + <scope>test</scope>
  56 + </dependency>
50 </dependencies> 57 </dependencies>
51 58
52 <build> 59 <build>
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/codec/tcp/IotTcpBinaryDeviceMessageCodec.java
1 package cn.iocoder.yudao.module.iot.gateway.codec.tcp; 1 package cn.iocoder.yudao.module.iot.gateway.codec.tcp;
2 2
  3 +import cn.hutool.core.lang.Assert;
3 import cn.hutool.core.util.StrUtil; 4 import cn.hutool.core.util.StrUtil;
4 -import cn.hutool.json.JSONObject;  
5 -import cn.hutool.json.JSONUtil; 5 +import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
6 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 6 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
7 import cn.iocoder.yudao.module.iot.gateway.codec.IotDeviceMessageCodec; 7 import cn.iocoder.yudao.module.iot.gateway.codec.IotDeviceMessageCodec;
8 import io.vertx.core.buffer.Buffer; 8 import io.vertx.core.buffer.Buffer;
  9 +import lombok.AllArgsConstructor;
9 import lombok.Data; 10 import lombok.Data;
10 -import lombok.extern.slf4j.Slf4j; 11 +import lombok.NoArgsConstructor;
11 import org.springframework.stereotype.Component; 12 import org.springframework.stereotype.Component;
12 13
13 -// TODO @haohao:设备地址(变长) 是不是非必要哈?因为认证后,不需要每次都带呀。  
14 /** 14 /**
15 * TCP 二进制格式 {@link IotDeviceMessage} 编解码器 15 * TCP 二进制格式 {@link IotDeviceMessage} 编解码器
16 * 16 *
17 * 使用自定义二进制协议格式: 17 * 使用自定义二进制协议格式:
18 - * 包头(4 字节) | 地址长度(2 字节) | 设备地址(变长) | 功能码(2 字节) | 消息序号(2 字节) | 包体数据(变长) 18 + * 包头(4 字节) | 功能码(2 字节) | 消息序号(2 字节) | 包体数据(变长)
19 * 19 *
20 * @author 芋道源码 20 * @author 芋道源码
21 */ 21 */
22 @Component 22 @Component
23 -@Slf4j  
24 public class IotTcpBinaryDeviceMessageCodec implements IotDeviceMessageCodec { 23 public class IotTcpBinaryDeviceMessageCodec implements IotDeviceMessageCodec {
25 24
26 - /**  
27 - * 编解码器类型  
28 - */  
29 public static final String TYPE = "TCP_BINARY"; 25 public static final String TYPE = "TCP_BINARY";
30 26
31 - // TODO @haohao:这个注释不太对。  
32 - // ==================== 常量定义 ==================== 27 + @Data
  28 + @NoArgsConstructor
  29 + @AllArgsConstructor
  30 + private static class TcpBinaryMessage {
  31 +
  32 + /**
  33 + * 功能码
  34 + */
  35 + private Short code;
  36 +
  37 + /**
  38 + * 消息序号
  39 + */
  40 + private Short mid;
  41 +
  42 + /**
  43 + * 设备 ID
  44 + */
  45 + private Long deviceId;
  46 +
  47 + /**
  48 + * 请求方法
  49 + */
  50 + private String method;
  51 +
  52 + /**
  53 + * 请求参数
  54 + */
  55 + private Object params;
  56 +
  57 + /**
  58 + * 响应结果
  59 + */
  60 + private Object data;
  61 +
  62 + /**
  63 + * 响应错误码
  64 + */
  65 + private Integer responseCode;
  66 +
  67 + /**
  68 + * 响应提示
  69 + */
  70 + private String msg;
  71 +
  72 + }
  73 +
  74 + @Override
  75 + public String type() {
  76 + return TYPE;
  77 + }
33 78
34 @Override 79 @Override
35 public byte[] encode(IotDeviceMessage message) { 80 public byte[] encode(IotDeviceMessage message) {
36 - if (message == null || StrUtil.isEmpty(message.getMethod())) {  
37 - throw new IllegalArgumentException("消息或方法不能为空");  
38 - } 81 + Assert.notNull(message, "消息不能为空");
  82 + Assert.notBlank(message.getMethod(), "消息方法不能为空");
39 83
40 try { 84 try {
41 - // 1. 确定功能码(只支持数据上报和心跳)  
42 - short code = MessageMethod.STATE_ONLINE.equals(message.getMethod()) ?  
43 - TcpDataPackage.CODE_HEARTBEAT : TcpDataPackage.CODE_MESSAGE_UP; 85 + // 1. 确定功能码
  86 + short code = MessageMethod.STATE_ONLINE.equals(message.getMethod()) ? TcpDataPackage.CODE_HEARTBEAT
  87 + : TcpDataPackage.CODE_MESSAGE_UP;
44 88
45 - // 2. 构建简化负载  
46 - String payload = buildSimplePayload(message); 89 + // 2. 构建负载数据
  90 + String payload = buildPayload(message);
47 91
48 // 3. 构建 TCP 数据包 92 // 3. 构建 TCP 数据包
49 - String deviceAddr = message.getDeviceId() != null ? String.valueOf(message.getDeviceId()) : "default";  
50 short mid = (short) (System.currentTimeMillis() % Short.MAX_VALUE); 93 short mid = (short) (System.currentTimeMillis() % Short.MAX_VALUE);
51 - TcpDataPackage dataPackage = new TcpDataPackage(deviceAddr, code, mid, payload); 94 + TcpDataPackage dataPackage = new TcpDataPackage(code, mid, payload);
52 95
53 // 4. 编码为字节流 96 // 4. 编码为字节流
54 return encodeTcpDataPackage(dataPackage).getBytes(); 97 return encodeTcpDataPackage(dataPackage).getBytes();
55 } catch (Exception e) { 98 } catch (Exception e) {
56 - log.error("[encode][编码失败] 方法: {}", message.getMethod(), e);  
57 throw new TcpCodecException("TCP 消息编码失败", e); 99 throw new TcpCodecException("TCP 消息编码失败", e);
58 } 100 }
59 } 101 }
60 102
61 @Override 103 @Override
  104 + @SuppressWarnings("DataFlowIssue")
62 public IotDeviceMessage decode(byte[] bytes) { 105 public IotDeviceMessage decode(byte[] bytes) {
63 - if (bytes == null || bytes.length == 0) {  
64 - throw new IllegalArgumentException("待解码数据不能为空");  
65 - } 106 + Assert.notNull(bytes, "待解码数据不能为空");
  107 + Assert.isTrue(bytes.length > 0, "待解码数据不能为空");
66 108
67 try { 109 try {
68 // 1. 解码 TCP 数据包 110 // 1. 解码 TCP 数据包
69 TcpDataPackage dataPackage = decodeTcpDataPackage(Buffer.buffer(bytes)); 111 TcpDataPackage dataPackage = decodeTcpDataPackage(Buffer.buffer(bytes));
70 112
71 // 2. 根据功能码确定方法 113 // 2. 根据功能码确定方法
72 - // TODO @haohao:会不会有事件上报哈。  
73 - String method = (dataPackage.getCode() == TcpDataPackage.CODE_HEARTBEAT) ?  
74 - MessageMethod.STATE_ONLINE : MessageMethod.PROPERTY_POST; 114 + String method = (dataPackage.getCode() == TcpDataPackage.CODE_HEARTBEAT) ? MessageMethod.STATE_ONLINE
  115 + : MessageMethod.PROPERTY_POST;
75 116
76 - // 3. 解析负载数据和请求 ID 117 + // 3. 解析负载数据
77 PayloadInfo payloadInfo = parsePayloadInfo(dataPackage.getPayload()); 118 PayloadInfo payloadInfo = parsePayloadInfo(dataPackage.getPayload());
78 119
79 - // 4. 构建 IoT 设备消息(设置完整的必要参数)  
80 - IotDeviceMessage message = IotDeviceMessage.requestOf(  
81 - payloadInfo.getRequestId(), method, payloadInfo.getParams());  
82 -  
83 - // 5. 设置设备相关信息  
84 - // TODO @haohao:serverId 不是这里解析的哈。  
85 - Long deviceId = parseDeviceId(dataPackage.getAddr());  
86 - message.setDeviceId(deviceId);  
87 -  
88 - // 6. 设置 TCP 协议相关信息  
89 - // TODO @haohao:serverId 不是这里解析的哈。  
90 - message.setServerId(generateServerId(dataPackage));  
91 -  
92 - // 7. 设置租户 ID(TODO: 后续可以从设备信息中获取)  
93 - // TODO @haohao:租户 id 不是这里解析的哈。  
94 - // message.setTenantId(getTenantIdByDeviceId(deviceId));  
95 -  
96 - if (log.isDebugEnabled()) {  
97 - log.debug("[decode][解码成功] 设备ID: {}, 方法: {}, 请求ID: {}, 消息ID: {}",  
98 - deviceId, method, message.getRequestId(), message.getId());  
99 - }  
100 -  
101 - return message; 120 + // 4. 构建 IoT 设备消息
  121 + return IotDeviceMessage.of(
  122 + payloadInfo.getRequestId(),
  123 + method,
  124 + payloadInfo.getParams(),
  125 + null,
  126 + null,
  127 + null);
102 } catch (Exception e) { 128 } catch (Exception e) {
103 - log.error("[decode][解码失败] 数据长度: {}", bytes.length, e);  
104 throw new TcpCodecException("TCP 消息解码失败", e); 129 throw new TcpCodecException("TCP 消息解码失败", e);
105 } 130 }
106 } 131 }
107 132
108 - @Override  
109 - public String type() {  
110 - return TYPE;  
111 - } 133 + // ==================== 内部辅助方法 ====================
112 134
113 - // TODO @haohao:这种简单解析,中间不用空格哈。  
114 /** 135 /**
115 - * 构建完整负载 136 + * 构建负载数据
  137 + *
  138 + * @param message 设备消息
  139 + * @return 负载字符串
116 */ 140 */
117 - private String buildSimplePayload(IotDeviceMessage message) {  
118 - JSONObject payload = new JSONObject();  
119 -  
120 - // 核心字段  
121 - payload.set(PayloadField.METHOD, message.getMethod());  
122 - if (message.getParams() != null) {  
123 - payload.set(PayloadField.PARAMS, message.getParams());  
124 - }  
125 -  
126 - // 标识字段  
127 - if (StrUtil.isNotEmpty(message.getRequestId())) {  
128 - payload.set(PayloadField.REQUEST_ID, message.getRequestId());  
129 - }  
130 - if (StrUtil.isNotEmpty(message.getId())) {  
131 - payload.set(PayloadField.MESSAGE_ID, message.getId());  
132 - }  
133 -  
134 - // 时间戳  
135 - payload.set(PayloadField.TIMESTAMP, System.currentTimeMillis());  
136 -  
137 - return payload.toString(); 141 + private String buildPayload(IotDeviceMessage message) {
  142 + TcpBinaryMessage tcpBinaryMessage = new TcpBinaryMessage(
  143 + null, // code 在数据包中单独处理
  144 + null, // mid 在数据包中单独处理
  145 + message.getDeviceId(),
  146 + message.getMethod(),
  147 + message.getParams(),
  148 + message.getData(),
  149 + message.getCode(),
  150 + message.getMsg());
  151 + return JsonUtils.toJsonString(tcpBinaryMessage);
138 } 152 }
139 153
140 - // ==================== 编解码方法 ====================  
141 -  
142 /** 154 /**
143 - * 解析负载信息(包含 requestId 和 params) 155 + * 解析负载信息
  156 + *
  157 + * @param payload 负载字符串
  158 + * @return 负载信息
144 */ 159 */
145 private PayloadInfo parsePayloadInfo(String payload) { 160 private PayloadInfo parsePayloadInfo(String payload) {
146 - if (StrUtil.isEmpty(payload)) { 161 + if (StrUtil.isBlank(payload)) {
147 return new PayloadInfo(null, null); 162 return new PayloadInfo(null, null);
148 } 163 }
149 164
150 try { 165 try {
151 - // TODO @haohao:使用 jsonUtils  
152 - JSONObject jsonObject = JSONUtil.parseObj(payload);  
153 - String requestId = jsonObject.getStr(PayloadField.REQUEST_ID);  
154 - if (StrUtil.isEmpty(requestId)) {  
155 - requestId = jsonObject.getStr(PayloadField.MESSAGE_ID); 166 + TcpBinaryMessage tcpBinaryMessage = JsonUtils.parseObject(payload, TcpBinaryMessage.class);
  167 + if (tcpBinaryMessage != null) {
  168 + return new PayloadInfo(
  169 + StrUtil.isNotEmpty(tcpBinaryMessage.getMethod())
  170 + ? tcpBinaryMessage.getMethod() + "_" + System.currentTimeMillis()
  171 + : null,
  172 + tcpBinaryMessage.getParams());
156 } 173 }
157 - Object params = jsonObject.get(PayloadField.PARAMS);  
158 - return new PayloadInfo(requestId, params);  
159 } catch (Exception e) { 174 } catch (Exception e) {
160 - log.warn("[parsePayloadInfo][解析失败,返回原始字符串] 负载: {}", payload);  
161 - return new PayloadInfo(null, payload); 175 + // 如果解析失败,返回默认值
  176 + return new PayloadInfo("unknown_" + System.currentTimeMillis(), null);
162 } 177 }
  178 + return null;
163 } 179 }
164 180
165 /** 181 /**
166 - * 从设备地址解析设备ID  
167 - *  
168 - * @param deviceAddr 设备地址字符串  
169 - * @return 设备ID  
170 - */  
171 - private Long parseDeviceId(String deviceAddr) {  
172 - if (StrUtil.isEmpty(deviceAddr)) {  
173 - log.warn("[parseDeviceId][设备地址为空,返回默认ID]");  
174 - return 0L;  
175 - }  
176 -  
177 - try {  
178 - // 尝试直接解析为Long  
179 - return Long.parseLong(deviceAddr);  
180 - } catch (NumberFormatException e) {  
181 - // 如果不是纯数字,可以使用哈希值或其他策略  
182 - log.warn("[parseDeviceId][设备地址不是数字格式: {},使用哈希值]", deviceAddr);  
183 - return (long) deviceAddr.hashCode();  
184 - }  
185 - }  
186 -  
187 - /**  
188 - * 生成服务ID  
189 - *  
190 - * @param dataPackage TCP数据包  
191 - * @return 服务ID  
192 - */  
193 - private String generateServerId(TcpDataPackage dataPackage) {  
194 - // 使用协议类型 + 设备地址 + 消息序号生成唯一的服务 ID  
195 - return String.format("tcp_%s_%d", dataPackage.getAddr(), dataPackage.getMid());  
196 - }  
197 -  
198 - // ==================== 内部辅助方法 ====================  
199 -  
200 - /**  
201 * 编码 TCP 数据包 182 * 编码 TCP 数据包
202 * 183 *
203 * @param dataPackage 数据包对象 184 * @param dataPackage 数据包对象
204 * @return 编码后的字节流 185 * @return 编码后的字节流
205 - * @throws IllegalArgumentException 如果数据包对象不正确  
206 */ 186 */
207 private Buffer encodeTcpDataPackage(TcpDataPackage dataPackage) { 187 private Buffer encodeTcpDataPackage(TcpDataPackage dataPackage) {
208 - if (dataPackage == null) {  
209 - throw new IllegalArgumentException("数据包对象不能为空");  
210 - } 188 + Assert.notNull(dataPackage, "数据包对象不能为空");
  189 + Assert.notNull(dataPackage.getPayload(), "负载不能为空");
211 190
212 - // 验证数据包  
213 - if (dataPackage.getAddr() == null || dataPackage.getAddr().isEmpty()) {  
214 - throw new IllegalArgumentException("设备地址不能为空");  
215 - }  
216 - if (dataPackage.getPayload() == null) {  
217 - throw new IllegalArgumentException("负载不能为空");  
218 - } 191 + Buffer buffer = Buffer.buffer();
219 192
220 - try {  
221 - Buffer buffer = Buffer.buffer();  
222 -  
223 - // 1. 计算包体长度(除了包头 4 字节)  
224 - int payloadLength = dataPackage.getPayload().getBytes().length;  
225 - int totalLength = 2 + dataPackage.getAddr().length() + 2 + 2 + payloadLength;  
226 -  
227 - // 2.1 写入包头:总长度(4 字节)  
228 - buffer.appendInt(totalLength);  
229 - // 2.2 写入设备地址长度(2 字节)  
230 - buffer.appendShort((short) dataPackage.getAddr().length());  
231 - // 2.3 写入设备地址(不定长)  
232 - buffer.appendBytes(dataPackage.getAddr().getBytes());  
233 - // 2.4 写入功能码(2 字节)  
234 - buffer.appendShort(dataPackage.getCode());  
235 - // 2.5 写入消息序号(2 字节)  
236 - buffer.appendShort(dataPackage.getMid());  
237 - // 2.6 写入包体数据(不定长)  
238 - buffer.appendBytes(dataPackage.getPayload().getBytes());  
239 -  
240 - if (log.isDebugEnabled()) {  
241 - log.debug("[encodeTcpDataPackage][编码成功] 设备地址: {}, 功能码: {}, 消息序号: {}, 总长度: {}",  
242 - dataPackage.getAddr(), dataPackage.getCode(), dataPackage.getMid(), buffer.length());  
243 - }  
244 - return buffer;  
245 - } catch (Exception e) {  
246 - log.error("[encodeTcpDataPackage][编码失败] 数据包: {}", dataPackage, e);  
247 - throw new IllegalArgumentException("数据包编码失败: " + e.getMessage(), e);  
248 - } 193 + // 1. 计算包体长度(除了包头 4 字节)
  194 + int payloadLength = dataPackage.getPayload().getBytes().length;
  195 + int totalLength = 2 + 2 + payloadLength;
  196 +
  197 + // 2. 写入包头:总长度(4 字节)
  198 + buffer.appendInt(totalLength);
  199 + // 3. 写入功能码(2 字节)
  200 + buffer.appendShort(dataPackage.getCode());
  201 + // 4. 写入消息序号(2 字节)
  202 + buffer.appendShort(dataPackage.getMid());
  203 + // 5. 写入包体数据(不定长)
  204 + buffer.appendBytes(dataPackage.getPayload().getBytes());
  205 +
  206 + return buffer;
249 } 207 }
250 208
251 /** 209 /**
@@ -253,101 +211,49 @@ public class IotTcpBinaryDeviceMessageCodec implements IotDeviceMessageCodec { @@ -253,101 +211,49 @@ public class IotTcpBinaryDeviceMessageCodec implements IotDeviceMessageCodec {
253 * 211 *
254 * @param buffer 数据缓冲区 212 * @param buffer 数据缓冲区
255 * @return 解码后的数据包 213 * @return 解码后的数据包
256 - * @throws IllegalArgumentException 如果数据包格式不正确  
257 */ 214 */
258 private TcpDataPackage decodeTcpDataPackage(Buffer buffer) { 215 private TcpDataPackage decodeTcpDataPackage(Buffer buffer) {
259 - if (buffer == null || buffer.length() < 8) {  
260 - throw new IllegalArgumentException("数据包长度不足");  
261 - } 216 + Assert.isTrue(buffer.length() >= 8, "数据包长度不足");
262 217
263 - try {  
264 - int index = 0;  
265 -  
266 - // 1.1 跳过包头(4字节)  
267 - index += 4;  
268 -  
269 - // 1.2 获取设备地址长度(2字节)  
270 - short addrLength = buffer.getShort(index);  
271 - index += 2; 218 + int index = 0;
272 219
273 - // 1.3 获取设备地址  
274 - String addr = buffer.getBuffer(index, index + addrLength).toString();  
275 - index += addrLength; 220 + // 1. 跳过包头(4 字节)
  221 + index += 4;
276 222
277 - // 1.4 获取功能码(2字节)  
278 - short code = buffer.getShort(index);  
279 - index += 2;  
280 -  
281 - // 1.5 获取消息序号(2字节)  
282 - short mid = buffer.getShort(index);  
283 - index += 2;  
284 -  
285 - // 1.6 获取包体数据  
286 - String payload = "";  
287 - if (index < buffer.length()) {  
288 - payload = buffer.getString(index, buffer.length());  
289 - } 223 + // 2. 获取功能码(2 字节)
  224 + short code = buffer.getShort(index);
  225 + index += 2;
290 226
291 - // 2. 构建数据包对象  
292 - TcpDataPackage dataPackage = new TcpDataPackage(addr, code, mid, payload); 227 + // 3. 获取消息序号(2 字节)
  228 + short mid = buffer.getShort(index);
  229 + index += 2;
293 230
294 - if (log.isDebugEnabled()) {  
295 - log.debug("[decodeTcpDataPackage][解码成功] 设备地址: {}, 功能码: {}, 消息序号: {}, 包体长度: {}",  
296 - addr, code, mid, payload.length());  
297 - }  
298 - return dataPackage;  
299 - } catch (Exception e) {  
300 - log.error("[decodeTcpDataPackage][解码失败] 数据长度: {}", buffer.length(), e);  
301 - throw new IllegalArgumentException("数据包解码失败: " + e.getMessage(), e); 231 + // 4. 获取包体数据
  232 + String payload = "";
  233 + if (index < buffer.length()) {
  234 + payload = buffer.getString(index, buffer.length());
302 } 235 }
303 - }  
304 -  
305 - /**  
306 - * 消息方法常量  
307 - */  
308 - public static class MessageMethod {  
309 -  
310 - public static final String PROPERTY_POST = "thing.property.post"; // 数据上报  
311 - public static final String STATE_ONLINE = "thing.state.online"; // 心跳  
312 236
  237 + return new TcpDataPackage(code, mid, payload);
313 } 238 }
314 239
315 - /**  
316 - * 负载字段名  
317 - */  
318 - private static class PayloadField {  
319 -  
320 - public static final String METHOD = "method";  
321 - public static final String PARAMS = "params";  
322 - public static final String TIMESTAMP = "timestamp";  
323 - public static final String REQUEST_ID = "requestId";  
324 - public static final String MESSAGE_ID = "msgId"; 240 + // ==================== 内部类 ====================
325 241
326 - }  
327 -  
328 - // ==================== TCP 数据包编解码方法 ====================  
329 -  
330 - // TODO @haohao:lombok 简化  
331 /** 242 /**
332 * 负载信息类 243 * 负载信息类
333 */ 244 */
  245 + @Data
  246 + @AllArgsConstructor
334 private static class PayloadInfo { 247 private static class PayloadInfo {
335 private String requestId; 248 private String requestId;
336 private Object params; 249 private Object params;
337 -  
338 - public PayloadInfo(String requestId, Object params) {  
339 - this.requestId = requestId;  
340 - this.params = params;  
341 - }  
342 -  
343 - public String getRequestId() { return requestId; }  
344 - public Object getParams() { return params; }  
345 } 250 }
346 251
347 /** 252 /**
348 * TCP 数据包内部类 253 * TCP 数据包内部类
349 */ 254 */
350 @Data 255 @Data
  256 + @AllArgsConstructor
351 private static class TcpDataPackage { 257 private static class TcpDataPackage {
352 // 功能码定义 258 // 功能码定义
353 public static final short CODE_REGISTER = 10; 259 public static final short CODE_REGISTER = 10;
@@ -357,35 +263,29 @@ public class IotTcpBinaryDeviceMessageCodec implements IotDeviceMessageCodec { @@ -357,35 +263,29 @@ public class IotTcpBinaryDeviceMessageCodec implements IotDeviceMessageCodec {
357 public static final short CODE_MESSAGE_UP = 30; 263 public static final short CODE_MESSAGE_UP = 30;
358 public static final short CODE_MESSAGE_DOWN = 40; 264 public static final short CODE_MESSAGE_DOWN = 40;
359 265
360 - private String addr;  
361 private short code; 266 private short code;
362 private short mid; 267 private short mid;
363 private String payload; 268 private String payload;
  269 + }
364 270
365 - public TcpDataPackage(String addr, short code, short mid, String payload) {  
366 - this.addr = addr;  
367 - this.code = code;  
368 - this.mid = mid;  
369 - this.payload = payload;  
370 - } 271 + // ==================== 常量定义 ====================
  272 +
  273 + /**
  274 + * 消息方法常量
  275 + */
  276 + public static class MessageMethod {
  277 + public static final String PROPERTY_POST = "thing.property.post"; // 数据上报
  278 + public static final String STATE_ONLINE = "thing.state.online"; // 心跳
371 } 279 }
372 280
373 // ==================== 自定义异常 ==================== 281 // ==================== 自定义异常 ====================
374 282
375 - // TODO @haohao:可以搞个全局的;  
376 /** 283 /**
377 * TCP 编解码异常 284 * TCP 编解码异常
378 */ 285 */
379 public static class TcpCodecException extends RuntimeException { 286 public static class TcpCodecException extends RuntimeException {
380 -  
381 - // TODO @haohao:非必要构造方法,可以去掉哈。  
382 - public TcpCodecException(String message) {  
383 - super(message);  
384 - }  
385 -  
386 public TcpCodecException(String message, Throwable cause) { 287 public TcpCodecException(String message, Throwable cause) {
387 super(message, cause); 288 super(message, cause);
388 } 289 }
389 -  
390 } 290 }
391 } 291 }
392 \ No newline at end of file 292 \ No newline at end of file
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/codec/tcp/IotTcpCodecManager.java deleted
1 -package cn.iocoder.yudao.module.iot.gateway.codec.tcp;  
2 -  
3 -import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;  
4 -import cn.iocoder.yudao.module.iot.gateway.codec.IotDeviceMessageCodec;  
5 -import lombok.extern.slf4j.Slf4j;  
6 -import org.springframework.beans.factory.annotation.Autowired;  
7 -import org.springframework.stereotype.Component;  
8 -  
9 -/**  
10 - * TCP编解码器管理器(简化版)  
11 - *  
12 - * 核心功能:  
13 - * - 自动协议检测(二进制 vs JSON)  
14 - * - 统一编解码接口  
15 - * - 默认使用 JSON 协议  
16 - *  
17 - * @author 芋道源码  
18 - */  
19 -@Slf4j  
20 -@Component  
21 -public class IotTcpCodecManager implements IotDeviceMessageCodec {  
22 -  
23 - public static final String TYPE = "TCP";  
24 -  
25 - // TODO @haohao:@Resource  
26 -  
27 - @Autowired  
28 - private IotTcpBinaryDeviceMessageCodec binaryCodec;  
29 -  
30 - @Autowired  
31 - private IotTcpJsonDeviceMessageCodec jsonCodec;  
32 -  
33 - /**  
34 - * 当前默认协议(JSON)  
35 - */  
36 - private boolean useJsonByDefault = true;  
37 -  
38 - @Override  
39 - public String type() {  
40 - return TYPE;  
41 - }  
42 -  
43 - @Override  
44 - public byte[] encode(IotDeviceMessage message) {  
45 - // 默认使用 JSON 协议编码  
46 - return jsonCodec.encode(message);  
47 - }  
48 -  
49 - // TODO @haohao:要不还是不自动检测,用户手动配置哈。简化一些。。。  
50 - @Override  
51 - public IotDeviceMessage decode(byte[] bytes) {  
52 - // 自动检测协议类型并解码  
53 - if (isJsonFormat(bytes)) {  
54 - if (log.isDebugEnabled()) {  
55 - log.debug("[decode][检测到 JSON 协议,数据长度: {} 字节]", bytes.length);  
56 - }  
57 - return jsonCodec.decode(bytes);  
58 - } else {  
59 - if (log.isDebugEnabled()) {  
60 - log.debug("[decode][检测到二进制协议,数据长度: {} 字节]", bytes.length);  
61 - }  
62 - return binaryCodec.decode(bytes);  
63 - }  
64 - }  
65 -  
66 - // ==================== 便捷方法 ====================  
67 -  
68 - /**  
69 - * 使用 JSON 协议编码  
70 - */  
71 - public byte[] encodeJson(IotDeviceMessage message) {  
72 - return jsonCodec.encode(message);  
73 - }  
74 -  
75 - /**  
76 - * 使用二进制协议编码  
77 - */  
78 - public byte[] encodeBinary(IotDeviceMessage message) {  
79 - return binaryCodec.encode(message);  
80 - }  
81 -  
82 - /**  
83 - * 获取当前默认协议  
84 - */  
85 - public String getDefaultProtocol() {  
86 - return useJsonByDefault ? "JSON" : "BINARY";  
87 - }  
88 -  
89 - /**  
90 - * 设置默认协议  
91 - */  
92 - public void setDefaultProtocol(boolean useJson) {  
93 - this.useJsonByDefault = useJson;  
94 - log.info("[setDefaultProtocol][设置默认协议] 使用JSON: {}", useJson);  
95 - }  
96 -  
97 - // ==================== 内部方法 ====================  
98 -  
99 - /**  
100 - * 检测是否为JSON格式  
101 - *  
102 - * 检测规则:  
103 - * 1. 数据以 '{' 开头  
104 - * 2. 包含 "method" 或 "id" 字段  
105 - */  
106 - private boolean isJsonFormat(byte[] bytes) {  
107 - // TODO @haohao:ArrayUtil.isEmpty(bytes) 可以简化下  
108 - if (bytes == null || bytes.length == 0) {  
109 - return useJsonByDefault;  
110 - }  
111 -  
112 - try {  
113 - // 检测 JSON 格式:以 '{' 开头  
114 - if (bytes[0] == '{') {  
115 - // TODO @haohao:不一定按照顺序写,这个可能要看下。  
116 - // 进一步验证是否为有效 JSON  
117 - String jsonStr = new String(bytes, 0, Math.min(bytes.length, 100));  
118 - return jsonStr.contains("\"method\"") || jsonStr.contains("\"id\"");  
119 - }  
120 -  
121 - // 检测二进制格式:长度 >= 8 且符合二进制协议结构  
122 - if (bytes.length >= 8) {  
123 - // 读取包头(前 4 字节表示后续数据长度)  
124 - int expectedLength = ((bytes[0] & 0xFF) << 24) |  
125 - ((bytes[1] & 0xFF) << 16) |  
126 - ((bytes[2] & 0xFF) << 8) |  
127 - (bytes[3] & 0xFF);  
128 -  
129 - // 验证长度是否合理  
130 - // TODO @haohao:expectedLength > 0 多余的貌似;  
131 - if (expectedLength == bytes.length - 4 && expectedLength > 0 && expectedLength < 1024 * 1024) {  
132 - return false; // 二进制格式  
133 - }  
134 - }  
135 - } catch (Exception e) {  
136 - log.warn("[isJsonFormat][协议检测异常,使用默认协议: {}]", getDefaultProtocol(), e);  
137 - }  
138 -  
139 - // 默认使用当前设置的协议类型  
140 - return useJsonByDefault;  
141 - }  
142 -  
143 -}  
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/codec/tcp/IotTcpJsonDeviceMessageCodec.java
1 package cn.iocoder.yudao.module.iot.gateway.codec.tcp; 1 package cn.iocoder.yudao.module.iot.gateway.codec.tcp;
2 2
3 -import cn.hutool.core.util.IdUtil;  
4 -import cn.hutool.core.util.StrUtil;  
5 -import cn.hutool.json.JSONObject;  
6 -import cn.hutool.json.JSONUtil; 3 +import cn.hutool.core.lang.Assert;
  4 +import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
7 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 5 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
8 import cn.iocoder.yudao.module.iot.gateway.codec.IotDeviceMessageCodec; 6 import cn.iocoder.yudao.module.iot.gateway.codec.IotDeviceMessageCodec;
9 -import lombok.extern.slf4j.Slf4j; 7 +import lombok.AllArgsConstructor;
  8 +import lombok.Data;
  9 +import lombok.NoArgsConstructor;
10 import org.springframework.stereotype.Component; 10 import org.springframework.stereotype.Component;
11 11
12 -import java.nio.charset.StandardCharsets;  
13 -  
14 /** 12 /**
15 * TCP JSON 格式 {@link IotDeviceMessage} 编解码器 13 * TCP JSON 格式 {@link IotDeviceMessage} 编解码器
16 * 14 *
17 - * 采用纯 JSON 格式传输,参考 EMQX 和 HTTP 模块的数据格式 15 + * 采用纯 JSON 格式传输
18 * 16 *
19 - * JSON消息格式: 17 + * JSON 消息格式:
20 * { 18 * {
21 - * "id": "消息 ID",  
22 - * "method": "消息方法",  
23 - * "deviceId": "设备 ID",  
24 - * "productKey": "产品 Key",  
25 - * "deviceName": "设备名称",  
26 - * "params": {...},  
27 - * "timestamp": 时间戳 19 + * "id": "消息 ID",
  20 + * "method": "消息方法",
  21 + * "deviceId": "设备 ID",
  22 + * "params": {...},
  23 + * "timestamp": 时间戳
28 * } 24 * }
29 * 25 *
30 * @author 芋道源码 26 * @author 芋道源码
31 */ 27 */
32 -@Slf4j  
33 @Component 28 @Component
34 public class IotTcpJsonDeviceMessageCodec implements IotDeviceMessageCodec { 29 public class IotTcpJsonDeviceMessageCodec implements IotDeviceMessageCodec {
35 30
36 public static final String TYPE = "TCP_JSON"; 31 public static final String TYPE = "TCP_JSON";
37 32
38 - // TODO @haohao:变量不太对;  
39 - // ==================== 常量定义 ==================== 33 + @Data
  34 + @NoArgsConstructor
  35 + @AllArgsConstructor
  36 + private static class TcpJsonMessage {
  37 +
  38 + /**
  39 + * 消息 ID,且每个消息 ID 在当前设备具有唯一性
  40 + */
  41 + private String id;
  42 +
  43 + /**
  44 + * 请求方法
  45 + */
  46 + private String method;
  47 +
  48 + /**
  49 + * 设备 ID
  50 + */
  51 + private Long deviceId;
  52 +
  53 + /**
  54 + * 请求参数
  55 + */
  56 + private Object params;
  57 +
  58 + /**
  59 + * 响应结果
  60 + */
  61 + private Object data;
  62 +
  63 + /**
  64 + * 响应错误码
  65 + */
  66 + private Integer code;
  67 +
  68 + /**
  69 + * 响应提示
  70 + */
  71 + private String msg;
  72 +
  73 + /**
  74 + * 时间戳
  75 + */
  76 + private Long timestamp;
  77 +
  78 + }
40 79
41 @Override 80 @Override
42 public String type() { 81 public String type() {
@@ -45,208 +84,33 @@ public class IotTcpJsonDeviceMessageCodec implements IotDeviceMessageCodec { @@ -45,208 +84,33 @@ public class IotTcpJsonDeviceMessageCodec implements IotDeviceMessageCodec {
45 84
46 @Override 85 @Override
47 public byte[] encode(IotDeviceMessage message) { 86 public byte[] encode(IotDeviceMessage message) {
48 - if (message == null || StrUtil.isEmpty(message.getMethod())) {  
49 - throw new IllegalArgumentException("消息或方法不能为空");  
50 - }  
51 -  
52 - try {  
53 - // 构建JSON消息  
54 - JSONObject jsonMessage = buildJsonMessage(message);  
55 -  
56 - // 转换为字节数组  
57 - String jsonString = jsonMessage.toString();  
58 - byte[] result = jsonString.getBytes(StandardCharsets.UTF_8);  
59 -  
60 - if (log.isDebugEnabled()) {  
61 - log.debug("[encode][编码成功] 方法: {}, JSON长度: {}字节, 内容: {}",  
62 - message.getMethod(), result.length, jsonString);  
63 - }  
64 -  
65 - return result;  
66 - } catch (Exception e) {  
67 - log.error("[encode][编码失败] 方法: {}", message.getMethod(), e);  
68 - throw new RuntimeException("JSON消息编码失败", e);  
69 - } 87 + TcpJsonMessage tcpJsonMessage = new TcpJsonMessage(
  88 + message.getRequestId(),
  89 + message.getMethod(),
  90 + message.getDeviceId(),
  91 + message.getParams(),
  92 + message.getData(),
  93 + message.getCode(),
  94 + message.getMsg(),
  95 + System.currentTimeMillis());
  96 + return JsonUtils.toJsonByte(tcpJsonMessage);
70 } 97 }
71 98
72 - // ==================== 编解码方法 ====================  
73 -  
74 @Override 99 @Override
  100 + @SuppressWarnings("DataFlowIssue")
75 public IotDeviceMessage decode(byte[] bytes) { 101 public IotDeviceMessage decode(byte[] bytes) {
76 - if (bytes == null || bytes.length == 0) {  
77 - throw new IllegalArgumentException("待解码数据不能为空");  
78 - }  
79 -  
80 - try {  
81 - // 转换为 JSON 字符串  
82 - String jsonString = new String(bytes, StandardCharsets.UTF_8);  
83 -  
84 - if (log.isDebugEnabled()) {  
85 - log.debug("[decode][开始解码] JSON长度: {}字节, 内容: {}", bytes.length, jsonString);  
86 - }  
87 -  
88 - // 解析 JSON 消息  
89 - // TODO @haohao:JsonUtils  
90 - JSONObject jsonMessage = JSONUtil.parseObj(jsonString);  
91 -  
92 - // 构建IoT设备消息  
93 - IotDeviceMessage message = parseJsonMessage(jsonMessage);  
94 -  
95 - if (log.isDebugEnabled()) {  
96 - log.debug("[decode][解码成功] 消息ID: {}, 方法: {}, 设备ID: {}",  
97 - message.getId(), message.getMethod(), message.getDeviceId());  
98 - }  
99 -  
100 - return message;  
101 - } catch (Exception e) {  
102 - log.error("[decode][解码失败] 数据长度: {}", bytes.length, e);  
103 - throw new RuntimeException("JSON消息解码失败", e);  
104 - }  
105 - }  
106 -  
107 - /**  
108 - * 编码数据上报消息  
109 - */  
110 - public byte[] encodeDataReport(Object params, Long deviceId, String productKey, String deviceName) {  
111 - IotDeviceMessage message = createMessage(MessageMethod.PROPERTY_POST, params, deviceId, productKey, deviceName);  
112 - return encode(message);  
113 - }  
114 -  
115 - /**  
116 - * 编码心跳消息  
117 - */  
118 - public byte[] encodeHeartbeat(Long deviceId, String productKey, String deviceName) {  
119 - IotDeviceMessage message = createMessage(MessageMethod.STATE_ONLINE, null, deviceId, productKey, deviceName);  
120 - return encode(message);  
121 - }  
122 -  
123 - // ==================== 便捷方法 ====================  
124 -  
125 - /**  
126 - * 编码事件上报消息  
127 - */  
128 - public byte[] encodeEventReport(Object params, Long deviceId, String productKey, String deviceName) {  
129 - IotDeviceMessage message = createMessage(MessageMethod.EVENT_POST, params, deviceId, productKey, deviceName);  
130 - return encode(message);  
131 - }  
132 -  
133 - /**  
134 - * 构建 JSON 消息  
135 - */  
136 - private JSONObject buildJsonMessage(IotDeviceMessage message) {  
137 - JSONObject jsonMessage = new JSONObject();  
138 -  
139 - // 基础字段  
140 - jsonMessage.set(JsonField.ID, StrUtil.isNotEmpty(message.getId()) ? message.getId() : IdUtil.fastSimpleUUID());  
141 - jsonMessage.set(JsonField.METHOD, message.getMethod());  
142 - jsonMessage.set(JsonField.TIMESTAMP, System.currentTimeMillis());  
143 -  
144 - // 设备信息  
145 - if (message.getDeviceId() != null) {  
146 - jsonMessage.set(JsonField.DEVICE_ID, message.getDeviceId());  
147 - }  
148 -  
149 - // 参数  
150 - if (message.getParams() != null) {  
151 - jsonMessage.set(JsonField.PARAMS, message.getParams());  
152 - }  
153 -  
154 - // 响应码和消息(用于下行消息)  
155 - if (message.getCode() != null) {  
156 - jsonMessage.set(JsonField.CODE, message.getCode());  
157 - }  
158 - if (StrUtil.isNotEmpty(message.getMsg())) {  
159 - jsonMessage.set(JsonField.MESSAGE, message.getMsg());  
160 - }  
161 -  
162 - return jsonMessage; 102 + TcpJsonMessage tcpJsonMessage = JsonUtils.parseObject(bytes, TcpJsonMessage.class);
  103 + Assert.notNull(tcpJsonMessage, "消息不能为空");
  104 + Assert.notBlank(tcpJsonMessage.getMethod(), "消息方法不能为空");
  105 + IotDeviceMessage iotDeviceMessage = IotDeviceMessage.of(
  106 + tcpJsonMessage.getId(),
  107 + tcpJsonMessage.getMethod(),
  108 + tcpJsonMessage.getParams(),
  109 + tcpJsonMessage.getData(),
  110 + tcpJsonMessage.getCode(),
  111 + tcpJsonMessage.getMsg());
  112 + iotDeviceMessage.setDeviceId(tcpJsonMessage.getDeviceId());
  113 + return iotDeviceMessage;
163 } 114 }
164 115
165 - /**  
166 - * 解析JSON消息  
167 - */  
168 - private IotDeviceMessage parseJsonMessage(JSONObject jsonMessage) {  
169 - // 提取基础字段  
170 - String id = jsonMessage.getStr(JsonField.ID);  
171 - String method = jsonMessage.getStr(JsonField.METHOD);  
172 - Object params = jsonMessage.get(JsonField.PARAMS);  
173 -  
174 - // 创建消息对象  
175 - IotDeviceMessage message = IotDeviceMessage.requestOf(id, method, params);  
176 -  
177 - // 设置设备信息  
178 - Long deviceId = jsonMessage.getLong(JsonField.DEVICE_ID);  
179 - if (deviceId != null) {  
180 - message.setDeviceId(deviceId);  
181 - }  
182 -  
183 - // 设置响应信息  
184 - Integer code = jsonMessage.getInt(JsonField.CODE);  
185 - if (code != null) {  
186 - message.setCode(code);  
187 - }  
188 -  
189 - String msg = jsonMessage.getStr(JsonField.MESSAGE);  
190 - if (StrUtil.isNotEmpty(msg)) {  
191 - message.setMsg(msg);  
192 - }  
193 -  
194 - // 设置服务 ID(基于 JSON 格式)  
195 - message.setServerId(generateServerId(jsonMessage));  
196 -  
197 - return message;  
198 - }  
199 -  
200 - // ==================== 内部辅助方法 ====================  
201 -  
202 - /**  
203 - * 创建消息对象  
204 - */  
205 - private IotDeviceMessage createMessage(String method, Object params, Long deviceId, String productKey, String deviceName) {  
206 - IotDeviceMessage message = IotDeviceMessage.requestOf(method, params);  
207 - message.setDeviceId(deviceId);  
208 - return message;  
209 - }  
210 -  
211 - /**  
212 - * 生成服务ID  
213 - */  
214 - private String generateServerId(JSONObject jsonMessage) {  
215 - String id = jsonMessage.getStr(JsonField.ID);  
216 - Long deviceId = jsonMessage.getLong(JsonField.DEVICE_ID);  
217 - return String.format("tcp_json_%s_%s", deviceId != null ? deviceId : "unknown",  
218 - StrUtil.isNotEmpty(id) ? id.substring(0, Math.min(8, id.length())) : "noId");  
219 - }  
220 -  
221 - // TODO @haohao:注释格式不对;  
222 - /**  
223 - * 消息方法常量  
224 - */  
225 - public static class MessageMethod {  
226 -  
227 - public static final String PROPERTY_POST = "thing.property.post"; // 数据上报  
228 - public static final String STATE_ONLINE = "thing.state.online"; // 心跳  
229 - public static final String EVENT_POST = "thing.event.post"; // 事件上报  
230 - public static final String PROPERTY_SET = "thing.property.set"; // 属性设置  
231 - public static final String PROPERTY_GET = "thing.property.get"; // 属性获取  
232 - public static final String SERVICE_INVOKE = "thing.service.invoke"; // 服务调用  
233 -  
234 - }  
235 -  
236 - /**  
237 - * JSON字段名(参考EMQX和HTTP模块格式)  
238 - */  
239 - private static class JsonField {  
240 -  
241 - public static final String ID = "id";  
242 - public static final String METHOD = "method";  
243 - public static final String DEVICE_ID = "deviceId";  
244 - public static final String PRODUCT_KEY = "productKey";  
245 - public static final String DEVICE_NAME = "deviceName";  
246 - public static final String PARAMS = "params";  
247 - public static final String TIMESTAMP = "timestamp";  
248 - public static final String CODE = "code";  
249 - public static final String MESSAGE = "message";  
250 -  
251 - }  
252 } 116 }
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/config/IotGatewayConfiguration.java
1 package cn.iocoder.yudao.module.iot.gateway.config; 1 package cn.iocoder.yudao.module.iot.gateway.config;
2 2
3 -import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;  
4 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus; 3 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
5 -import cn.iocoder.yudao.module.iot.gateway.codec.tcp.IotTcpCodecManager;  
6 import cn.iocoder.yudao.module.iot.gateway.protocol.emqx.IotEmqxAuthEventProtocol; 4 import cn.iocoder.yudao.module.iot.gateway.protocol.emqx.IotEmqxAuthEventProtocol;
7 import cn.iocoder.yudao.module.iot.gateway.protocol.emqx.IotEmqxDownstreamSubscriber; 5 import cn.iocoder.yudao.module.iot.gateway.protocol.emqx.IotEmqxDownstreamSubscriber;
8 import cn.iocoder.yudao.module.iot.gateway.protocol.emqx.IotEmqxUpstreamProtocol; 6 import cn.iocoder.yudao.module.iot.gateway.protocol.emqx.IotEmqxUpstreamProtocol;
@@ -10,6 +8,7 @@ import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpDownstreamSubscr @@ -10,6 +8,7 @@ import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpDownstreamSubscr
10 import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpUpstreamProtocol; 8 import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpUpstreamProtocol;
11 import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.IotTcpDownstreamSubscriber; 9 import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.IotTcpDownstreamSubscriber;
12 import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.IotTcpUpstreamProtocol; 10 import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.IotTcpUpstreamProtocol;
  11 +import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.manager.IotTcpSessionManager;
13 import cn.iocoder.yudao.module.iot.gateway.service.device.IotDeviceService; 12 import cn.iocoder.yudao.module.iot.gateway.service.device.IotDeviceService;
14 import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService; 13 import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
15 import io.vertx.core.Vertx; 14 import io.vertx.core.Vertx;
@@ -93,18 +92,20 @@ public class IotGatewayConfiguration { @@ -93,18 +92,20 @@ public class IotGatewayConfiguration {
93 public IotTcpUpstreamProtocol iotTcpUpstreamProtocol(IotGatewayProperties gatewayProperties, 92 public IotTcpUpstreamProtocol iotTcpUpstreamProtocol(IotGatewayProperties gatewayProperties,
94 IotDeviceService deviceService, 93 IotDeviceService deviceService,
95 IotDeviceMessageService messageService, 94 IotDeviceMessageService messageService,
96 - IotDeviceCommonApi deviceApi,  
97 - IotTcpCodecManager codecManager, 95 + IotTcpSessionManager sessionManager,
98 Vertx tcpVertx) { 96 Vertx tcpVertx) {
99 return new IotTcpUpstreamProtocol(gatewayProperties.getProtocol().getTcp(), 97 return new IotTcpUpstreamProtocol(gatewayProperties.getProtocol().getTcp(),
100 - deviceService, messageService, deviceApi, codecManager, tcpVertx); 98 + deviceService, messageService, sessionManager, tcpVertx);
101 } 99 }
102 100
103 @Bean 101 @Bean
104 public IotTcpDownstreamSubscriber iotTcpDownstreamSubscriber(IotTcpUpstreamProtocol protocolHandler, 102 public IotTcpDownstreamSubscriber iotTcpDownstreamSubscriber(IotTcpUpstreamProtocol protocolHandler,
105 IotDeviceMessageService messageService, 103 IotDeviceMessageService messageService,
  104 + IotDeviceService deviceService,
  105 + IotTcpSessionManager sessionManager,
106 IotMessageBus messageBus) { 106 IotMessageBus messageBus) {
107 - return new IotTcpDownstreamSubscriber(protocolHandler, messageService, messageBus); 107 + return new IotTcpDownstreamSubscriber(protocolHandler, messageService, deviceService, sessionManager,
  108 + messageBus);
108 } 109 }
109 110
110 } 111 }
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/IotTcpDownstreamSubscriber.java
@@ -4,10 +4,13 @@ import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus; @@ -4,10 +4,13 @@ import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
4 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageSubscriber; 4 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageSubscriber;
5 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 5 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
6 import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; 6 import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
  7 +import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.manager.IotTcpSessionManager;
7 import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.router.IotTcpDownstreamHandler; 8 import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.router.IotTcpDownstreamHandler;
  9 +import cn.iocoder.yudao.module.iot.gateway.service.device.IotDeviceService;
8 import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService; 10 import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
9 import jakarta.annotation.PostConstruct; 11 import jakarta.annotation.PostConstruct;
10 import lombok.extern.slf4j.Slf4j; 12 import lombok.extern.slf4j.Slf4j;
  13 +import org.springframework.stereotype.Component;
11 14
12 /** 15 /**
13 * IoT 网关 TCP 下游订阅者:接收下行给设备的消息 16 * IoT 网关 TCP 下游订阅者:接收下行给设备的消息
@@ -15,6 +18,7 @@ import lombok.extern.slf4j.Slf4j; @@ -15,6 +18,7 @@ import lombok.extern.slf4j.Slf4j;
15 * @author 芋道源码 18 * @author 芋道源码
16 */ 19 */
17 @Slf4j 20 @Slf4j
  21 +@Component
18 public class IotTcpDownstreamSubscriber implements IotMessageSubscriber<IotDeviceMessage> { 22 public class IotTcpDownstreamSubscriber implements IotMessageSubscriber<IotDeviceMessage> {
19 23
20 private final IotTcpDownstreamHandler downstreamHandler; 24 private final IotTcpDownstreamHandler downstreamHandler;
@@ -23,17 +27,27 @@ public class IotTcpDownstreamSubscriber implements IotMessageSubscriber&lt;IotDevic @@ -23,17 +27,27 @@ public class IotTcpDownstreamSubscriber implements IotMessageSubscriber&lt;IotDevic
23 27
24 private final IotTcpUpstreamProtocol protocol; 28 private final IotTcpUpstreamProtocol protocol;
25 29
  30 + private final IotDeviceService deviceService;
  31 +
  32 + private final IotTcpSessionManager sessionManager;
  33 +
26 public IotTcpDownstreamSubscriber(IotTcpUpstreamProtocol protocol, 34 public IotTcpDownstreamSubscriber(IotTcpUpstreamProtocol protocol,
27 IotDeviceMessageService messageService, 35 IotDeviceMessageService messageService,
  36 + IotDeviceService deviceService,
  37 + IotTcpSessionManager sessionManager,
28 IotMessageBus messageBus) { 38 IotMessageBus messageBus) {
29 this.protocol = protocol; 39 this.protocol = protocol;
30 this.messageBus = messageBus; 40 this.messageBus = messageBus;
31 - this.downstreamHandler = new IotTcpDownstreamHandler(messageService); 41 + this.deviceService = deviceService;
  42 + this.sessionManager = sessionManager;
  43 + this.downstreamHandler = new IotTcpDownstreamHandler(messageService, deviceService, sessionManager);
32 } 44 }
33 45
34 @PostConstruct 46 @PostConstruct
35 public void init() { 47 public void init() {
36 messageBus.register(this); 48 messageBus.register(this);
  49 + log.info("[init][TCP 下游订阅者初始化完成] 服务器 ID: {}, Topic: {}",
  50 + protocol.getServerId(), getTopic());
37 } 51 }
38 52
39 @Override 53 @Override
@@ -49,22 +63,11 @@ public class IotTcpDownstreamSubscriber implements IotMessageSubscriber&lt;IotDevic @@ -49,22 +63,11 @@ public class IotTcpDownstreamSubscriber implements IotMessageSubscriber&lt;IotDevic
49 63
50 @Override 64 @Override
51 public void onMessage(IotDeviceMessage message) { 65 public void onMessage(IotDeviceMessage message) {
52 - log.debug("[onMessage][接收到下行消息, messageId: {}, method: {}, deviceId: {}]",  
53 - message.getId(), message.getMethod(), message.getDeviceId());  
54 try { 66 try {
55 - // 1. 校验  
56 - String method = message.getMethod();  
57 - if (method == null) {  
58 - log.warn("[onMessage][消息方法为空, messageId: {}, deviceId: {}]",  
59 - message.getId(), message.getDeviceId());  
60 - return;  
61 - }  
62 -  
63 - // 2. 处理下行消息  
64 downstreamHandler.handle(message); 67 downstreamHandler.handle(message);
65 } catch (Exception e) { 68 } catch (Exception e) {
66 - log.error("[onMessage][处理下行消息失败, messageId: {}, method: {}, deviceId: {}]",  
67 - message.getId(), message.getMethod(), message.getDeviceId(), e); 69 + log.error("[onMessage][处理下行消息失败] 设备 ID: {}, 方法: {}, 消息 ID: {}",
  70 + message.getDeviceId(), message.getMethod(), message.getId(), e);
68 } 71 }
69 } 72 }
70 } 73 }
71 \ No newline at end of file 74 \ 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
1 package cn.iocoder.yudao.module.iot.gateway.protocol.tcp; 1 package cn.iocoder.yudao.module.iot.gateway.protocol.tcp;
2 2
3 -import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;  
4 import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; 3 import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
5 -import cn.iocoder.yudao.module.iot.gateway.codec.tcp.IotTcpCodecManager;  
6 import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties; 4 import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties;
  5 +import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.manager.IotTcpSessionManager;
7 import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.router.IotTcpUpstreamHandler; 6 import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.router.IotTcpUpstreamHandler;
8 import cn.iocoder.yudao.module.iot.gateway.service.device.IotDeviceService; 7 import cn.iocoder.yudao.module.iot.gateway.service.device.IotDeviceService;
9 import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService; 8 import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
@@ -30,10 +29,7 @@ public class IotTcpUpstreamProtocol { @@ -30,10 +29,7 @@ public class IotTcpUpstreamProtocol {
30 29
31 private final IotDeviceMessageService messageService; 30 private final IotDeviceMessageService messageService;
32 31
33 - // TODO @haohao:不用的变量,可以删除;  
34 - private final IotDeviceCommonApi deviceApi;  
35 -  
36 - private final IotTcpCodecManager codecManager; 32 + private final IotTcpSessionManager sessionManager;
37 33
38 private final Vertx vertx; 34 private final Vertx vertx;
39 35
@@ -45,28 +41,24 @@ public class IotTcpUpstreamProtocol { @@ -45,28 +41,24 @@ public class IotTcpUpstreamProtocol {
45 public IotTcpUpstreamProtocol(IotGatewayProperties.TcpProperties tcpProperties, 41 public IotTcpUpstreamProtocol(IotGatewayProperties.TcpProperties tcpProperties,
46 IotDeviceService deviceService, 42 IotDeviceService deviceService,
47 IotDeviceMessageService messageService, 43 IotDeviceMessageService messageService,
48 - IotDeviceCommonApi deviceApi,  
49 - IotTcpCodecManager codecManager, 44 + IotTcpSessionManager sessionManager,
50 Vertx vertx) { 45 Vertx vertx) {
51 this.tcpProperties = tcpProperties; 46 this.tcpProperties = tcpProperties;
52 this.deviceService = deviceService; 47 this.deviceService = deviceService;
53 this.messageService = messageService; 48 this.messageService = messageService;
54 - this.deviceApi = deviceApi;  
55 - this.codecManager = codecManager; 49 + this.sessionManager = sessionManager;
56 this.vertx = vertx; 50 this.vertx = vertx;
57 this.serverId = IotDeviceMessageUtils.generateServerId(tcpProperties.getPort()); 51 this.serverId = IotDeviceMessageUtils.generateServerId(tcpProperties.getPort());
58 } 52 }
59 53
60 @PostConstruct 54 @PostConstruct
61 public void start() { 55 public void start() {
62 - // TODO @haohao:类似下面 62 到 75 是处理 options 的,因为中间写了注释,其实可以不用空行;然后 77 到 91 可以中间空喊去掉,更紧凑一点;  
63 // 创建服务器选项 56 // 创建服务器选项
64 NetServerOptions options = new NetServerOptions() 57 NetServerOptions options = new NetServerOptions()
65 .setPort(tcpProperties.getPort()) 58 .setPort(tcpProperties.getPort())
66 .setTcpKeepAlive(true) 59 .setTcpKeepAlive(true)
67 .setTcpNoDelay(true) 60 .setTcpNoDelay(true)
68 .setReuseAddress(true); 61 .setReuseAddress(true);
69 -  
70 // 配置 SSL(如果启用) 62 // 配置 SSL(如果启用)
71 if (Boolean.TRUE.equals(tcpProperties.getSslEnabled())) { 63 if (Boolean.TRUE.equals(tcpProperties.getSslEnabled())) {
72 PemKeyCertOptions pemKeyCertOptions = new PemKeyCertOptions() 64 PemKeyCertOptions pemKeyCertOptions = new PemKeyCertOptions()
@@ -78,7 +70,8 @@ public class IotTcpUpstreamProtocol { @@ -78,7 +70,8 @@ public class IotTcpUpstreamProtocol {
78 // 创建服务器并设置连接处理器 70 // 创建服务器并设置连接处理器
79 netServer = vertx.createNetServer(options); 71 netServer = vertx.createNetServer(options);
80 netServer.connectHandler(socket -> { 72 netServer.connectHandler(socket -> {
81 - IotTcpUpstreamHandler handler = new IotTcpUpstreamHandler(this, messageService, codecManager); 73 + IotTcpUpstreamHandler handler = new IotTcpUpstreamHandler(this, messageService, deviceService,
  74 + sessionManager);
82 handler.handle(socket); 75 handler.handle(socket);
83 }); 76 });
84 77
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/manager/IotTcpAuthManager.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.tcp.manager;
  2 +
  3 +import io.vertx.core.net.NetSocket;
  4 +import lombok.Data;
  5 +import lombok.extern.slf4j.Slf4j;
  6 +import org.springframework.stereotype.Component;
  7 +
  8 +import java.util.Map;
  9 +import java.util.concurrent.ConcurrentHashMap;
  10 +
  11 +/**
  12 + * IoT 网关 TCP 认证信息管理器
  13 + * <p>
  14 + * 维护 TCP 连接的认证状态,支持认证信息的存储、查询和清理
  15 + *
  16 + * @author 芋道源码
  17 + */
  18 +@Slf4j
  19 +@Component
  20 +public class IotTcpAuthManager {
  21 +
  22 + /**
  23 + * 连接认证状态映射:NetSocket -> 认证信息
  24 + */
  25 + private final Map<NetSocket, AuthInfo> authStatusMap = new ConcurrentHashMap<>();
  26 +
  27 + /**
  28 + * 设备 ID -> NetSocket 的映射(用于快速查找)
  29 + */
  30 + private final Map<Long, NetSocket> deviceSocketMap = new ConcurrentHashMap<>();
  31 +
  32 + /**
  33 + * 注册认证信息
  34 + *
  35 + * @param socket TCP 连接
  36 + * @param authInfo 认证信息
  37 + */
  38 + public void registerAuth(NetSocket socket, AuthInfo authInfo) {
  39 + // 如果设备已有其他连接,先清理旧连接
  40 + NetSocket oldSocket = deviceSocketMap.get(authInfo.getDeviceId());
  41 + if (oldSocket != null && oldSocket != socket) {
  42 + log.info("[registerAuth][设备已有其他连接,清理旧连接] 设备 ID: {}, 旧连接: {}",
  43 + authInfo.getDeviceId(), oldSocket.remoteAddress());
  44 + authStatusMap.remove(oldSocket);
  45 + }
  46 +
  47 + // 注册新认证信息
  48 + authStatusMap.put(socket, authInfo);
  49 + deviceSocketMap.put(authInfo.getDeviceId(), socket);
  50 +
  51 + log.info("[registerAuth][注册认证信息] 设备 ID: {}, 连接: {}, productKey: {}, deviceName: {}",
  52 + authInfo.getDeviceId(), socket.remoteAddress(), authInfo.getProductKey(), authInfo.getDeviceName());
  53 + }
  54 +
  55 + /**
  56 + * 注销认证信息
  57 + *
  58 + * @param socket TCP 连接
  59 + */
  60 + public void unregisterAuth(NetSocket socket) {
  61 + AuthInfo authInfo = authStatusMap.remove(socket);
  62 + if (authInfo != null) {
  63 + deviceSocketMap.remove(authInfo.getDeviceId());
  64 + log.info("[unregisterAuth][注销认证信息] 设备 ID: {}, 连接: {}",
  65 + authInfo.getDeviceId(), socket.remoteAddress());
  66 + }
  67 + }
  68 +
  69 + /**
  70 + * 注销设备认证信息
  71 + *
  72 + * @param deviceId 设备 ID
  73 + */
  74 + public void unregisterAuth(Long deviceId) {
  75 + NetSocket socket = deviceSocketMap.remove(deviceId);
  76 + if (socket != null) {
  77 + AuthInfo authInfo = authStatusMap.remove(socket);
  78 + if (authInfo != null) {
  79 + log.info("[unregisterAuth][注销设备认证信息] 设备 ID: {}, 连接: {}",
  80 + deviceId, socket.remoteAddress());
  81 + }
  82 + }
  83 + }
  84 +
  85 + /**
  86 + * 获取认证信息
  87 + *
  88 + * @param socket TCP 连接
  89 + * @return 认证信息,如果未认证则返回 null
  90 + */
  91 + public AuthInfo getAuthInfo(NetSocket socket) {
  92 + return authStatusMap.get(socket);
  93 + }
  94 +
  95 + /**
  96 + * 获取设备的认证信息
  97 + *
  98 + * @param deviceId 设备 ID
  99 + * @return 认证信息,如果设备未认证则返回 null
  100 + */
  101 + public AuthInfo getAuthInfo(Long deviceId) {
  102 + NetSocket socket = deviceSocketMap.get(deviceId);
  103 + return socket != null ? authStatusMap.get(socket) : null;
  104 + }
  105 +
  106 + /**
  107 + * 检查连接是否已认证
  108 + *
  109 + * @param socket TCP 连接
  110 + * @return 是否已认证
  111 + */
  112 + public boolean isAuthenticated(NetSocket socket) {
  113 + return authStatusMap.containsKey(socket);
  114 + }
  115 +
  116 + /**
  117 + * 检查设备是否已认证
  118 + *
  119 + * @param deviceId 设备 ID
  120 + * @return 是否已认证
  121 + */
  122 + public boolean isAuthenticated(Long deviceId) {
  123 + return deviceSocketMap.containsKey(deviceId);
  124 + }
  125 +
  126 + /**
  127 + * 获取设备的 TCP 连接
  128 + *
  129 + * @param deviceId 设备 ID
  130 + * @return TCP 连接,如果设备未认证则返回 null
  131 + */
  132 + public NetSocket getDeviceSocket(Long deviceId) {
  133 + return deviceSocketMap.get(deviceId);
  134 + }
  135 +
  136 + /**
  137 + * 获取当前已认证设备数量
  138 + *
  139 + * @return 已认证设备数量
  140 + */
  141 + public int getAuthenticatedDeviceCount() {
  142 + return deviceSocketMap.size();
  143 + }
  144 +
  145 + /**
  146 + * 获取所有已认证设备 ID
  147 + *
  148 + * @return 已认证设备 ID 集合
  149 + */
  150 + public java.util.Set<Long> getAuthenticatedDeviceIds() {
  151 + return deviceSocketMap.keySet();
  152 + }
  153 +
  154 + /**
  155 + * 清理所有认证信息
  156 + */
  157 + public void clearAll() {
  158 + int count = authStatusMap.size();
  159 + authStatusMap.clear();
  160 + deviceSocketMap.clear();
  161 + log.info("[clearAll][清理所有认证信息] 清理数量: {}", count);
  162 + }
  163 +
  164 + /**
  165 + * 认证信息
  166 + */
  167 + @Data
  168 + public static class AuthInfo {
  169 + /**
  170 + * 设备编号
  171 + */
  172 + private Long deviceId;
  173 +
  174 + /**
  175 + * 产品标识
  176 + */
  177 + private String productKey;
  178 +
  179 + /**
  180 + * 设备名称
  181 + */
  182 + private String deviceName;
  183 +
  184 + /**
  185 + * 认证令牌
  186 + */
  187 + private String token;
  188 +
  189 + /**
  190 + * 客户端 ID
  191 + */
  192 + private String clientId;
  193 + }
  194 +}
0 \ No newline at end of file 195 \ No newline at end of file
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/manager/IotTcpSessionManager.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.tcp.manager;
  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.Map;
  8 +import java.util.concurrent.ConcurrentHashMap;
  9 +
  10 +/**
  11 + * IoT 网关 TCP 会话管理器
  12 + * <p>
  13 + * 维护设备 ID 和 TCP 连接的映射关系,支持下行消息发送
  14 + *
  15 + * @author 芋道源码
  16 + */
  17 +@Slf4j
  18 +@Component
  19 +public class IotTcpSessionManager {
  20 +
  21 + /**
  22 + * 设备 ID -> TCP 连接的映射
  23 + */
  24 + private final Map<Long, NetSocket> deviceSocketMap = new ConcurrentHashMap<>();
  25 +
  26 + /**
  27 + * TCP 连接 -> 设备 ID 的映射(用于连接断开时清理)
  28 + */
  29 + private final Map<NetSocket, Long> socketDeviceMap = new ConcurrentHashMap<>();
  30 +
  31 + /**
  32 + * 注册设备会话
  33 + *
  34 + * @param deviceId 设备 ID
  35 + * @param socket TCP 连接
  36 + */
  37 + public void registerSession(Long deviceId, NetSocket socket) {
  38 + // 如果设备已有连接,先断开旧连接
  39 + NetSocket oldSocket = deviceSocketMap.get(deviceId);
  40 + if (oldSocket != null && oldSocket != socket) {
  41 + log.info("[registerSession][设备已有连接,断开旧连接] 设备 ID: {}, 旧连接: {}", deviceId, oldSocket.remoteAddress());
  42 + oldSocket.close();
  43 + socketDeviceMap.remove(oldSocket);
  44 + }
  45 +
  46 + // 注册新连接
  47 + deviceSocketMap.put(deviceId, socket);
  48 + socketDeviceMap.put(socket, deviceId);
  49 +
  50 + log.info("[registerSession][注册设备会话] 设备 ID: {}, 连接: {}", deviceId, socket.remoteAddress());
  51 + }
  52 +
  53 + /**
  54 + * 注销设备会话
  55 + *
  56 + * @param deviceId 设备 ID
  57 + */
  58 + public void unregisterSession(Long deviceId) {
  59 + NetSocket socket = deviceSocketMap.remove(deviceId);
  60 + if (socket != null) {
  61 + socketDeviceMap.remove(socket);
  62 + log.info("[unregisterSession][注销设备会话] 设备 ID: {}, 连接: {}", deviceId, socket.remoteAddress());
  63 + }
  64 + }
  65 +
  66 + /**
  67 + * 注销 TCP 连接会话
  68 + *
  69 + * @param socket TCP 连接
  70 + */
  71 + public void unregisterSession(NetSocket socket) {
  72 + Long deviceId = socketDeviceMap.remove(socket);
  73 + if (deviceId != null) {
  74 + deviceSocketMap.remove(deviceId);
  75 + log.info("[unregisterSession][注销连接会话] 设备 ID: {}, 连接: {}", deviceId, socket.remoteAddress());
  76 + }
  77 + }
  78 +
  79 + /**
  80 + * 获取设备的 TCP 连接
  81 + *
  82 + * @param deviceId 设备 ID
  83 + * @return TCP 连接,如果设备未连接则返回 null
  84 + */
  85 + public NetSocket getDeviceSocket(Long deviceId) {
  86 + return deviceSocketMap.get(deviceId);
  87 + }
  88 +
  89 + /**
  90 + * 检查设备是否在线
  91 + *
  92 + * @param deviceId 设备 ID
  93 + * @return 是否在线
  94 + */
  95 + public boolean isDeviceOnline(Long deviceId) {
  96 + NetSocket socket = deviceSocketMap.get(deviceId);
  97 + return socket != null;
  98 + }
  99 +
  100 + /**
  101 + * 发送消息到设备
  102 + *
  103 + * @param deviceId 设备 ID
  104 + * @param data 消息数据
  105 + * @return 是否发送成功
  106 + */
  107 + public boolean sendToDevice(Long deviceId, byte[] data) {
  108 + NetSocket socket = deviceSocketMap.get(deviceId);
  109 + if (socket == null) {
  110 + log.warn("[sendToDevice][设备未连接] 设备 ID: {}", deviceId);
  111 + return false;
  112 + }
  113 +
  114 + try {
  115 + socket.write(io.vertx.core.buffer.Buffer.buffer(data));
  116 + log.debug("[sendToDevice][发送消息成功] 设备 ID: {}, 数据长度: {} 字节", deviceId, data.length);
  117 + return true;
  118 + } catch (Exception e) {
  119 + log.error("[sendToDevice][发送消息失败] 设备 ID: {}", deviceId, e);
  120 + // 发送失败时清理连接
  121 + unregisterSession(deviceId);
  122 + return false;
  123 + }
  124 + }
  125 +
  126 + /**
  127 + * 获取当前在线设备数量
  128 + *
  129 + * @return 在线设备数量
  130 + */
  131 + public int getOnlineDeviceCount() {
  132 + return deviceSocketMap.size();
  133 + }
  134 +
  135 + /**
  136 + * 获取所有在线设备 ID
  137 + *
  138 + * @return 在线设备 ID 集合
  139 + */
  140 + public java.util.Set<Long> getOnlineDeviceIds() {
  141 + return deviceSocketMap.keySet();
  142 + }
  143 +}
0 \ No newline at end of file 144 \ 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
1 package cn.iocoder.yudao.module.iot.gateway.protocol.tcp.router; 1 package cn.iocoder.yudao.module.iot.gateway.protocol.tcp.router;
2 2
  3 +import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO;
3 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 4 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
  5 +import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.manager.IotTcpSessionManager;
  6 +import cn.iocoder.yudao.module.iot.gateway.service.device.IotDeviceService;
4 import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService; 7 import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
5 import lombok.extern.slf4j.Slf4j; 8 import lombok.extern.slf4j.Slf4j;
6 9
7 /** 10 /**
8 * IoT 网关 TCP 下行消息处理器 11 * IoT 网关 TCP 下行消息处理器
9 - * <p>  
10 - * 负责处理从业务系统发送到设备的下行消息,包括:  
11 - * 1. 属性设置  
12 - * 2. 服务调用  
13 - * 3. 属性获取  
14 - * 4. 配置下发  
15 - * 5. OTA 升级  
16 - * <p>  
17 - * 注意:由于移除了连接管理器,此处理器主要负责消息的编码和日志记录  
18 * 12 *
19 * @author 芋道源码 13 * @author 芋道源码
20 */ 14 */
@@ -23,12 +17,15 @@ public class IotTcpDownstreamHandler { @@ -23,12 +17,15 @@ public class IotTcpDownstreamHandler {
23 17
24 private final IotDeviceMessageService messageService; 18 private final IotDeviceMessageService messageService;
25 19
26 - // TODO @haohao:代码没提交全,有报错。  
27 -// private final IotTcpDeviceMessageCodec codec; 20 + private final IotDeviceService deviceService;
28 21
29 - public IotTcpDownstreamHandler(IotDeviceMessageService messageService) { 22 + private final IotTcpSessionManager sessionManager;
  23 +
  24 + public IotTcpDownstreamHandler(IotDeviceMessageService messageService,
  25 + IotDeviceService deviceService, IotTcpSessionManager sessionManager) {
30 this.messageService = messageService; 26 this.messageService = messageService;
31 -// this.codec = new IotTcpDeviceMessageCodec(); 27 + this.deviceService = deviceService;
  28 + this.sessionManager = sessionManager;
32 } 29 }
33 30
34 /** 31 /**
@@ -38,23 +35,38 @@ public class IotTcpDownstreamHandler { @@ -38,23 +35,38 @@ public class IotTcpDownstreamHandler {
38 */ 35 */
39 public void handle(IotDeviceMessage message) { 36 public void handle(IotDeviceMessage message) {
40 try { 37 try {
41 - log.info("[handle][处理下行消息] 设备ID: {}, 方法: {}, 消息ID: {}", 38 + log.info("[handle][处理下行消息] 设备 ID: {}, 方法: {}, 消息 ID: {}",
42 message.getDeviceId(), message.getMethod(), message.getId()); 39 message.getDeviceId(), message.getMethod(), message.getId());
43 40
44 - // 编码消息用于日志记录和验证  
45 - byte[] encodedMessage = null;  
46 -// codec.encode(message);  
47 - log.debug("[handle][消息编码成功] 设备ID: {}, 编码后长度: {} 字节",  
48 - message.getDeviceId(), encodedMessage.length); 41 + // 1. 获取设备信息
  42 + IotDeviceRespDTO device = deviceService.getDeviceFromCache(message.getDeviceId());
  43 + if (device == null) {
  44 + log.error("[handle][设备不存在] 设备 ID: {}", message.getDeviceId());
  45 + return;
  46 + }
  47 +
  48 + // 2. 检查设备是否在线
  49 + if (!sessionManager.isDeviceOnline(message.getDeviceId())) {
  50 + log.warn("[handle][设备不在线] 设备 ID: {}", message.getDeviceId());
  51 + return;
  52 + }
49 53
50 - // 记录下行消息处理  
51 - log.info("[handle][下行消息处理完成] 设备ID: {}, 方法: {}, 消息内容: {}",  
52 - message.getDeviceId(), message.getMethod(), message.getParams()); 54 + // 3. 编码消息
  55 + byte[] bytes = messageService.encodeDeviceMessage(message, device.getCodecType());
  56 +
  57 + // 4. 发送消息到设备
  58 + boolean success = sessionManager.sendToDevice(message.getDeviceId(), bytes);
  59 + if (success) {
  60 + log.info("[handle][下行消息发送成功] 设备 ID: {}, 方法: {}, 消息 ID: {}, 数据长度: {} 字节",
  61 + message.getDeviceId(), message.getMethod(), message.getId(), bytes.length);
  62 + } else {
  63 + log.error("[handle][下行消息发送失败] 设备 ID: {}, 方法: {}, 消息 ID: {}",
  64 + message.getDeviceId(), message.getMethod(), message.getId());
  65 + }
53 66
54 } catch (Exception e) { 67 } catch (Exception e) {
55 - log.error("[handle][处理下行消息失败] 设备ID: {}, 方法: {}, 消息内容: {}", 68 + log.error("[handle][处理下行消息失败] 设备 ID: {}, 方法: {}, 消息内容: {}",
56 message.getDeviceId(), message.getMethod(), message.getParams(), e); 69 message.getDeviceId(), message.getMethod(), message.getParams(), e);
57 } 70 }
58 } 71 }
59 -  
60 } 72 }
61 \ No newline at end of file 73 \ 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/IotTcpUpstreamHandler.java
1 package cn.iocoder.yudao.module.iot.gateway.protocol.tcp.router; 1 package cn.iocoder.yudao.module.iot.gateway.protocol.tcp.router;
2 2
  3 +import cn.hutool.core.map.MapUtil;
3 import cn.hutool.core.util.IdUtil; 4 import cn.hutool.core.util.IdUtil;
  5 +import cn.hutool.core.util.StrUtil;
  6 +import cn.hutool.extra.spring.SpringUtil;
  7 +import cn.hutool.json.JSONObject;
  8 +import cn.hutool.json.JSONUtil;
  9 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  10 +import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;
  11 +import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO;
4 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 12 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
5 -import cn.iocoder.yudao.module.iot.gateway.codec.tcp.IotTcpCodecManager; 13 +import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils;
6 import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.IotTcpUpstreamProtocol; 14 import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.IotTcpUpstreamProtocol;
  15 +import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.manager.IotTcpAuthManager;
  16 +import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.manager.IotTcpSessionManager;
  17 +import cn.iocoder.yudao.module.iot.gateway.service.auth.IotDeviceTokenService;
  18 +import cn.iocoder.yudao.module.iot.gateway.service.device.IotDeviceService;
7 import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService; 19 import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
8 import io.vertx.core.Handler; 20 import io.vertx.core.Handler;
9 import io.vertx.core.buffer.Buffer; 21 import io.vertx.core.buffer.Buffer;
10 import io.vertx.core.net.NetSocket; 22 import io.vertx.core.net.NetSocket;
11 -import io.vertx.core.parsetools.RecordParser;  
12 import lombok.extern.slf4j.Slf4j; 23 import lombok.extern.slf4j.Slf4j;
13 24
14 /** 25 /**
15 - * IoT 网关 TCP 上行消息处理器  
16 - *  
17 - * @author 芋道源码 26 + * TCP 上行消息处理器
18 */ 27 */
19 @Slf4j 28 @Slf4j
20 public class IotTcpUpstreamHandler implements Handler<NetSocket> { 29 public class IotTcpUpstreamHandler implements Handler<NetSocket> {
21 30
  31 + private static final String CODEC_TYPE_JSON = "TCP_JSON";
  32 + private static final String CODEC_TYPE_BINARY = "TCP_BINARY";
  33 + private static final String AUTH_METHOD = "auth";
  34 +
22 private final IotDeviceMessageService deviceMessageService; 35 private final IotDeviceMessageService deviceMessageService;
23 36
24 - private final String serverId; 37 + private final IotDeviceService deviceService;
  38 +
  39 + private final IotTcpSessionManager sessionManager;
  40 +
  41 + private final IotTcpAuthManager authManager;
  42 +
  43 + private final IotDeviceTokenService deviceTokenService;
  44 +
  45 + private final IotDeviceCommonApi deviceApi;
25 46
26 - private final IotTcpCodecManager codecManager; 47 + private final String serverId;
27 48
28 public IotTcpUpstreamHandler(IotTcpUpstreamProtocol protocol, IotDeviceMessageService deviceMessageService, 49 public IotTcpUpstreamHandler(IotTcpUpstreamProtocol protocol, IotDeviceMessageService deviceMessageService,
29 - IotTcpCodecManager codecManager) { 50 + IotDeviceService deviceService, IotTcpSessionManager sessionManager) {
30 this.deviceMessageService = deviceMessageService; 51 this.deviceMessageService = deviceMessageService;
  52 + this.deviceService = deviceService;
  53 + this.sessionManager = sessionManager;
  54 + this.authManager = SpringUtil.getBean(IotTcpAuthManager.class);
  55 + this.deviceTokenService = SpringUtil.getBean(IotDeviceTokenService.class);
  56 + this.deviceApi = SpringUtil.getBean(IotDeviceCommonApi.class);
31 this.serverId = protocol.getServerId(); 57 this.serverId = protocol.getServerId();
32 - this.codecManager = codecManager;  
33 } 58 }
34 59
35 @Override 60 @Override
36 public void handle(NetSocket socket) { 61 public void handle(NetSocket socket) {
37 - // 生成客户端ID用于日志标识  
38 String clientId = IdUtil.simpleUUID(); 62 String clientId = IdUtil.simpleUUID();
39 - log.info("[handle][收到设备连接] clientId: {}, address: {}", clientId, socket.remoteAddress());  
40 -  
41 - // 设置解析器  
42 - RecordParser parser = RecordParser.newFixed(1024, buffer -> {  
43 - try {  
44 - handleDataPackage(clientId, buffer);  
45 - } catch (Exception e) {  
46 - log.error("[handle][处理数据包异常] clientId: {}", clientId, e);  
47 - }  
48 - }); 63 + log.info("[handle][收到设备连接] 客户端 ID: {}, 地址: {}", clientId, socket.remoteAddress());
49 64
50 - // 设置异常处理 65 + // 设置异常和关闭处理器
51 socket.exceptionHandler(ex -> { 66 socket.exceptionHandler(ex -> {
52 - log.error("[handle][连接异常] clientId: {}, address: {}", clientId, socket.remoteAddress(), ex); 67 + log.error("[handle][连接异常] 客户端 ID: {}, 地址: {}", clientId, socket.remoteAddress(), ex);
  68 + cleanupSession(socket);
53 }); 69 });
54 70
55 socket.closeHandler(v -> { 71 socket.closeHandler(v -> {
56 - log.info("[handle][连接关闭] clientId: {}, address: {}", clientId, socket.remoteAddress()); 72 + log.info("[handle][连接关闭] 客户端 ID: {}, 地址: {}", clientId, socket.remoteAddress());
  73 + cleanupSession(socket);
57 }); 74 });
58 75
59 - // 设置数据处理器  
60 - socket.handler(parser); 76 + socket.handler(buffer -> handleDataPackage(clientId, buffer, socket));
  77 + }
  78 +
  79 + private void handleDataPackage(String clientId, Buffer buffer, NetSocket socket) {
  80 + try {
  81 + if (buffer.length() == 0) {
  82 + log.warn("[handleDataPackage][数据包为空] 客户端 ID: {}", clientId);
  83 + return;
  84 + }
  85 +
  86 + // 1. 解码消息
  87 + MessageInfo messageInfo = decodeMessage(buffer);
  88 + if (messageInfo == null) {
  89 + return;
  90 + }
  91 +
  92 + // 2. 获取设备信息
  93 + IotDeviceRespDTO device = deviceService.getDeviceFromCache(messageInfo.message.getDeviceId());
  94 + if (device == null) {
  95 + sendError(socket, messageInfo.message.getRequestId(), "设备不存在", messageInfo.codecType);
  96 + return;
  97 + }
  98 +
  99 + // 3. 处理消息
  100 + if (!authManager.isAuthenticated(socket)) {
  101 + handleAuthRequest(clientId, messageInfo.message, socket, messageInfo.codecType);
  102 + } else {
  103 + IotTcpAuthManager.AuthInfo authInfo = authManager.getAuthInfo(socket);
  104 + handleBusinessMessage(clientId, messageInfo.message, authInfo);
  105 + }
  106 + } catch (Exception e) {
  107 + log.error("[handleDataPackage][处理数据包失败] 客户端 ID: {}, 错误: {}", clientId, e.getMessage(), e);
  108 + }
61 } 109 }
62 110
63 /** 111 /**
64 - * 处理数据包 112 + * 处理认证请求
65 */ 113 */
66 - private void handleDataPackage(String clientId, Buffer buffer) { 114 + private void handleAuthRequest(String clientId, IotDeviceMessage message, NetSocket socket, String codecType) {
67 try { 115 try {
68 - // 使用编解码器管理器自动检测协议并解码消息  
69 - IotDeviceMessage message = codecManager.decode(buffer.getBytes());  
70 - log.info("[handleDataPackage][接收数据包] clientId: {}, 方法: {}, 设备ID: {}",  
71 - clientId, message.getMethod(), message.getDeviceId()); 116 + // 1. 验证认证请求
  117 + if (!AUTH_METHOD.equals(message.getMethod())) {
  118 + sendError(socket, message.getRequestId(), "请先进行认证", codecType);
  119 + return;
  120 + }
72 121
73 - // 处理上行消息  
74 - handleUpstreamMessage(clientId, message); 122 + // 2. 解析认证参数
  123 + AuthParams authParams = parseAuthParams(message.getParams());
  124 + if (authParams == null) {
  125 + sendError(socket, message.getRequestId(), "认证参数不完整", codecType);
  126 + return;
  127 + }
  128 +
  129 + // 3. 执行认证流程
  130 + if (performAuthentication(authParams, socket, message.getRequestId(), codecType)) {
  131 + log.info("[handleAuthRequest][认证成功] 客户端 ID: {}, username: {}", clientId, authParams.username);
  132 + }
75 } catch (Exception e) { 133 } catch (Exception e) {
76 - log.error("[handleDataPackage][处理数据包失败] clientId: {}", clientId, e); 134 + log.error("[handleAuthRequest][认证处理异常] 客户端 ID: {}", clientId, e);
  135 + sendError(socket, message.getRequestId(), "认证处理异常: " + e.getMessage(), codecType);
77 } 136 }
78 } 137 }
79 138
80 /** 139 /**
81 - * 处理上行消息 140 + * 处理业务消息
82 */ 141 */
83 - private void handleUpstreamMessage(String clientId, IotDeviceMessage message) { 142 + private void handleBusinessMessage(String clientId, IotDeviceMessage message,
  143 + IotTcpAuthManager.AuthInfo authInfo) {
84 try { 144 try {
85 - log.info("[handleUpstreamMessage][上行消息] clientId: {}, 方法: {}, 设备ID: {}",  
86 - clientId, message.getMethod(), message.getDeviceId()); 145 + message.setDeviceId(authInfo.getDeviceId());
  146 + message.setServerId(serverId);
87 147
88 - // 解析设备信息(简化处理)  
89 - String deviceId = String.valueOf(message.getDeviceId());  
90 - String productKey = extractProductKey(deviceId);  
91 - String deviceName = deviceId; 148 + deviceMessageService.sendDeviceMessage(message, authInfo.getProductKey(), authInfo.getDeviceName(),
  149 + serverId);
  150 + log.info("[handleBusinessMessage][业务消息处理完成] 客户端 ID: {}, 消息 ID: {}, 设备 ID: {}, 方法: {}",
  151 + clientId, message.getId(), message.getDeviceId(), message.getMethod());
  152 + } catch (Exception e) {
  153 + log.error("[handleBusinessMessage][处理业务消息失败] 客户端 ID: {}, 错误: {}", clientId, e.getMessage(), e);
  154 + }
  155 + }
92 156
93 - // 发送消息到队列  
94 - deviceMessageService.sendDeviceMessage(message, productKey, deviceName, serverId); 157 + /**
  158 + * 解码消息
  159 + */
  160 + private MessageInfo decodeMessage(Buffer buffer) {
  161 + try {
  162 + String rawData = buffer.toString();
  163 + String codecType = isJsonFormat(rawData) ? CODEC_TYPE_JSON : CODEC_TYPE_BINARY;
  164 + IotDeviceMessage message = deviceMessageService.decodeDeviceMessage(buffer.getBytes(), codecType);
  165 + return message != null ? new MessageInfo(message, codecType) : null;
95 } catch (Exception e) { 166 } catch (Exception e) {
96 - log.error("[handleUpstreamMessage][处理上行消息失败] clientId: {}", clientId, e); 167 + log.debug("[decodeMessage][消息解码失败] 错误: {}", e.getMessage());
  168 + return null;
  169 + }
  170 + }
  171 +
  172 + /**
  173 + * 执行认证
  174 + */
  175 + private boolean performAuthentication(AuthParams authParams, NetSocket socket, String requestId, String codecType) {
  176 + // 1. 执行认证
  177 + if (!authenticateDevice(authParams)) {
  178 + sendError(socket, requestId, "认证失败", codecType);
  179 + return false;
  180 + }
  181 +
  182 + // 2. 获取设备信息
  183 + IotDeviceAuthUtils.DeviceInfo deviceInfo = deviceTokenService.parseUsername(authParams.username);
  184 + if (deviceInfo == null) {
  185 + sendError(socket, requestId, "解析设备信息失败", codecType);
  186 + return false;
  187 + }
  188 +
  189 + IotDeviceRespDTO device = deviceService.getDeviceFromCache(deviceInfo.getProductKey(),
  190 + deviceInfo.getDeviceName());
  191 + if (device == null) {
  192 + sendError(socket, requestId, "设备不存在", codecType);
  193 + return false;
97 } 194 }
  195 +
  196 + // 3. 注册认证信息
  197 + String token = deviceTokenService.createToken(deviceInfo.getProductKey(), deviceInfo.getDeviceName());
  198 + registerAuthInfo(socket, device, deviceInfo, token, authParams.clientId);
  199 +
  200 + // 4. 发送上线消息和成功响应
  201 + IotDeviceMessage onlineMessage = IotDeviceMessage.buildStateUpdateOnline();
  202 + deviceMessageService.sendDeviceMessage(onlineMessage, deviceInfo.getProductKey(), deviceInfo.getDeviceName(),
  203 + serverId);
  204 + sendSuccess(socket, requestId, "认证成功", codecType);
  205 +
  206 + return true;
98 } 207 }
99 208
100 /** 209 /**
101 - * 从设备ID中提取产品密钥(简化实现) 210 + * 发送响应
102 */ 211 */
103 - private String extractProductKey(String deviceId) {  
104 - // 简化实现:假设设备ID格式为 "productKey_deviceName"  
105 - if (deviceId != null && deviceId.contains("_")) {  
106 - return deviceId.split("_")[0]; 212 + private void sendResponse(NetSocket socket, boolean success, String message, String requestId, String codecType) {
  213 + try {
  214 + Object responseData = buildResponseData(success, message);
  215 + IotDeviceMessage responseMessage = IotDeviceMessage.replyOf(requestId, AUTH_METHOD, responseData,
  216 + success ? 0 : 401, message);
  217 + byte[] encodedData = deviceMessageService.encodeDeviceMessage(responseMessage, codecType);
  218 + socket.write(Buffer.buffer(encodedData));
  219 + log.debug("[sendResponse][发送响应] success: {}, message: {}, requestId: {}", success, message, requestId);
  220 + } catch (Exception e) {
  221 + log.error("[sendResponse][发送响应失败] requestId: {}", requestId, e);
107 } 222 }
108 - return "default_product"; 223 + }
  224 +
  225 + /**
  226 + * 构建响应数据(不返回 token)
  227 + */
  228 + private Object buildResponseData(boolean success, String message) {
  229 + return MapUtil.builder()
  230 + .put("success", success)
  231 + .put("message", message)
  232 + .build();
  233 + }
  234 +
  235 + /**
  236 + * 清理会话
  237 + */
  238 + private void cleanupSession(NetSocket socket) {
  239 + // 如果已认证,发送离线消息
  240 + IotTcpAuthManager.AuthInfo authInfo = authManager.getAuthInfo(socket);
  241 + if (authInfo != null) {
  242 + // 发送离线消息
  243 + IotDeviceMessage offlineMessage = IotDeviceMessage.buildStateOffline();
  244 + deviceMessageService.sendDeviceMessage(offlineMessage, authInfo.getProductKey(), authInfo.getDeviceName(),
  245 + serverId);
  246 + }
  247 + sessionManager.unregisterSession(socket);
  248 + authManager.unregisterAuth(socket);
  249 + }
  250 +
  251 + /**
  252 + * 判断是否为 JSON 格式
  253 + */
  254 + private boolean isJsonFormat(String data) {
  255 + if (StrUtil.isBlank(data))
  256 + return false;
  257 + String trimmed = data.trim();
  258 + return (trimmed.startsWith("{") && trimmed.endsWith("}")) || (trimmed.startsWith("[") && trimmed.endsWith("]"));
  259 + }
  260 +
  261 + /**
  262 + * 解析认证参数
  263 + */
  264 + private AuthParams parseAuthParams(Object params) {
  265 + if (params == null)
  266 + return null;
  267 +
  268 + JSONObject paramsJson = params instanceof JSONObject ? (JSONObject) params
  269 + : JSONUtil.parseObj(params.toString());
  270 + String clientId = paramsJson.getStr("clientId");
  271 + String username = paramsJson.getStr("username");
  272 + String password = paramsJson.getStr("password");
  273 +
  274 + return StrUtil.hasBlank(clientId, username, password) ? null : new AuthParams(clientId, username, password);
  275 + }
  276 +
  277 + /**
  278 + * 认证设备
  279 + */
  280 + private boolean authenticateDevice(AuthParams authParams) {
  281 + CommonResult<Boolean> result = deviceApi
  282 + .authDevice(new cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO()
  283 + .setClientId(authParams.clientId)
  284 + .setUsername(authParams.username)
  285 + .setPassword(authParams.password));
  286 + return result.isSuccess() && result.getData();
  287 + }
  288 +
  289 + /**
  290 + * 注册认证信息
  291 + */
  292 + private void registerAuthInfo(NetSocket socket, IotDeviceRespDTO device, IotDeviceAuthUtils.DeviceInfo deviceInfo,
  293 + String token, String clientId) {
  294 + IotTcpAuthManager.AuthInfo auth = new IotTcpAuthManager.AuthInfo();
  295 + auth.setDeviceId(device.getId());
  296 + auth.setProductKey(deviceInfo.getProductKey());
  297 + auth.setDeviceName(deviceInfo.getDeviceName());
  298 + auth.setToken(token);
  299 + auth.setClientId(clientId);
  300 +
  301 + authManager.registerAuth(socket, auth);
  302 + sessionManager.registerSession(device.getId(), socket);
  303 + }
  304 +
  305 + /**
  306 + * 发送错误响应
  307 + */
  308 + private void sendError(NetSocket socket, String requestId, String errorMessage, String codecType) {
  309 + sendResponse(socket, false, errorMessage, requestId, codecType);
  310 + }
  311 +
  312 + /**
  313 + * 发送成功响应(不返回 token)
  314 + */
  315 + private void sendSuccess(NetSocket socket, String requestId, String message, String codecType) {
  316 + sendResponse(socket, true, message, requestId, codecType);
  317 + }
  318 +
  319 + /**
  320 + * 认证参数
  321 + */
  322 + private record AuthParams(String clientId, String username, String password) {
  323 + }
  324 +
  325 + /**
  326 + * 消息信息
  327 + */
  328 + private record MessageInfo(IotDeviceMessage message, String codecType) {
109 } 329 }
110 } 330 }
111 \ No newline at end of file 331 \ No newline at end of file
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/service/device/message/IotDeviceMessageService.java
@@ -21,6 +21,16 @@ public interface IotDeviceMessageService { @@ -21,6 +21,16 @@ public interface IotDeviceMessageService {
21 String productKey, String deviceName); 21 String productKey, String deviceName);
22 22
23 /** 23 /**
  24 + * 编码消息
  25 + *
  26 + * @param message 消息
  27 + * @param codecType 编解码器类型
  28 + * @return 编码后的消息内容
  29 + */
  30 + byte[] encodeDeviceMessage(IotDeviceMessage message,
  31 + String codecType);
  32 +
  33 + /**
24 * 解码消息 34 * 解码消息
25 * 35 *
26 * @param bytes 消息内容 36 * @param bytes 消息内容
@@ -32,12 +42,21 @@ public interface IotDeviceMessageService { @@ -32,12 +42,21 @@ public interface IotDeviceMessageService {
32 String productKey, String deviceName); 42 String productKey, String deviceName);
33 43
34 /** 44 /**
  45 + * 解码消息
  46 + *
  47 + * @param bytes 消息内容
  48 + * @param codecType 编解码器类型
  49 + * @return 解码后的消息内容
  50 + */
  51 + IotDeviceMessage decodeDeviceMessage(byte[] bytes, String codecType);
  52 +
  53 + /**
35 * 发送消息 54 * 发送消息
36 * 55 *
37 - * @param message 消息 56 + * @param message 消息
38 * @param productKey 产品 Key 57 * @param productKey 产品 Key
39 * @param deviceName 设备名称 58 * @param deviceName 设备名称
40 - * @param serverId 设备连接的 serverId 59 + * @param serverId 设备连接的 serverId
41 */ 60 */
42 void sendDeviceMessage(IotDeviceMessage message, 61 void sendDeviceMessage(IotDeviceMessage message,
43 String productKey, String deviceName, String serverId); 62 String productKey, String deviceName, String serverId);
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/service/device/message/IotDeviceMessageServiceImpl.java
@@ -62,6 +62,19 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService { @@ -62,6 +62,19 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService {
62 } 62 }
63 63
64 @Override 64 @Override
  65 + public byte[] encodeDeviceMessage(IotDeviceMessage message,
  66 + String codecType) {
  67 + // 1. 获取编解码器
  68 + IotDeviceMessageCodec codec = codes.get(codecType);
  69 + if (codec == null) {
  70 + throw new IllegalArgumentException(StrUtil.format("编解码器({}) 不存在", codecType));
  71 + }
  72 +
  73 + // 2. 编码消息
  74 + return codec.encode(message);
  75 + }
  76 +
  77 + @Override
65 public IotDeviceMessage decodeDeviceMessage(byte[] bytes, 78 public IotDeviceMessage decodeDeviceMessage(byte[] bytes,
66 String productKey, String deviceName) { 79 String productKey, String deviceName) {
67 // 1.1 获取设备信息 80 // 1.1 获取设备信息
@@ -80,6 +93,18 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService { @@ -80,6 +93,18 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService {
80 } 93 }
81 94
82 @Override 95 @Override
  96 + public IotDeviceMessage decodeDeviceMessage(byte[] bytes, String codecType) {
  97 + // 1. 获取编解码器
  98 + IotDeviceMessageCodec codec = codes.get(codecType);
  99 + if (codec == null) {
  100 + throw new IllegalArgumentException(StrUtil.format("编解码器({}) 不存在", codecType));
  101 + }
  102 +
  103 + // 2. 解码消息
  104 + return codec.decode(bytes);
  105 + }
  106 +
  107 + @Override
83 public void sendDeviceMessage(IotDeviceMessage message, 108 public void sendDeviceMessage(IotDeviceMessage message,
84 String productKey, String deviceName, String serverId) { 109 String productKey, String deviceName, String serverId) {
85 // 1. 获取设备信息 110 // 1. 获取设备信息
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/codec/tcp/TcpBinaryDataPacketExamples.java renamed to yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/codec/tcp/TcpBinaryDataPacketExamplesTest.java
@@ -2,42 +2,37 @@ package cn.iocoder.yudao.module.iot.gateway.codec.tcp; @@ -2,42 +2,37 @@ package cn.iocoder.yudao.module.iot.gateway.codec.tcp;
2 2
3 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 3 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
4 import lombok.extern.slf4j.Slf4j; 4 import lombok.extern.slf4j.Slf4j;
  5 +import org.junit.jupiter.api.BeforeEach;
  6 +import org.junit.jupiter.api.Test;
5 7
6 import java.util.HashMap; 8 import java.util.HashMap;
7 import java.util.Map; 9 import java.util.Map;
8 10
9 -// TODO @haohao:这种写成单测,会好点 11 +import static org.junit.jupiter.api.Assertions.*;
  12 +
10 /** 13 /**
11 - * TCP二进制格式数据包示例 14 + * TCP 二进制格式数据包单元测试
12 * 15 *
13 - * 演示如何使用二进制协议创建和解析TCP上报数据包和心跳包 16 + * 测试二进制协议创建和解析 TCP 上报数据包和心跳包
14 * 17 *
15 * 二进制协议格式: 18 * 二进制协议格式:
16 - * 包头(4字节) | 地址长度(2字节) | 设备地址(变长) | 功能码(2字节) | 消息序号(2字节) | 包体数据(变长) 19 + * 包头(4 字节) | 功能码(2 字节) | 消息序号(2 字节) | 包体数据(变长)
17 * 20 *
18 * @author 芋道源码 21 * @author 芋道源码
19 */ 22 */
20 @Slf4j 23 @Slf4j
21 -public class TcpBinaryDataPacketExamples {  
22 -  
23 - public static void main(String[] args) {  
24 - IotTcpBinaryDeviceMessageCodec codec = new IotTcpBinaryDeviceMessageCodec(); 24 +class TcpBinaryDataPacketExamplesTest {
25 25
26 - // 1. 数据上报包示例  
27 - demonstrateDataReport(codec); 26 + private IotTcpBinaryDeviceMessageCodec codec;
28 27
29 - // 2. 心跳包示例  
30 - demonstrateHeartbeat(codec);  
31 -  
32 - // 3. 复杂数据上报示例  
33 - demonstrateComplexDataReport(codec); 28 + @BeforeEach
  29 + void setUp() {
  30 + codec = new IotTcpBinaryDeviceMessageCodec();
34 } 31 }
35 32
36 - /**  
37 - * 演示二进制格式数据上报包  
38 - */  
39 - private static void demonstrateDataReport(IotTcpBinaryDeviceMessageCodec codec) {  
40 - log.info("=== 二进制格式数据上报包示例 ==="); 33 + @Test
  34 + void testDataReport() {
  35 + log.info("=== 二进制格式数据上报包测试 ===");
41 36
42 // 创建传感器数据 37 // 创建传感器数据
43 Map<String, Object> sensorData = new HashMap<>(); 38 Map<String, Object> sensorData = new HashMap<>();
@@ -57,22 +52,23 @@ public class TcpBinaryDataPacketExamples { @@ -57,22 +52,23 @@ public class TcpBinaryDataPacketExamples {
57 52
58 // 解码验证 53 // 解码验证
59 IotDeviceMessage decoded = codec.decode(packet); 54 IotDeviceMessage decoded = codec.decode(packet);
60 - log.info("解码后消息ID: {}", decoded.getId());  
61 - log.info("解码后请求ID: {}", decoded.getRequestId()); 55 + log.info("解码后消息 ID: {}", decoded.getId());
  56 + log.info("解码后请求 ID: {}", decoded.getRequestId());
62 log.info("解码后方法: {}", decoded.getMethod()); 57 log.info("解码后方法: {}", decoded.getMethod());
63 - log.info("解码后设备ID: {}", decoded.getDeviceId());  
64 - log.info("解码后服务ID: {}", decoded.getServerId());  
65 - log.info("解码后上报时间: {}", decoded.getReportTime()); 58 + log.info("解码后设备 ID: {}", decoded.getDeviceId());
  59 + log.info("解码后服务 ID: {}", decoded.getServerId());
66 log.info("解码后参数: {}", decoded.getParams()); 60 log.info("解码后参数: {}", decoded.getParams());
67 61
68 - System.out.println(); 62 + // 断言验证
  63 + assertNotNull(decoded.getId());
  64 + assertEquals("thing.property.post", decoded.getMethod());
  65 + assertNotNull(decoded.getParams());
  66 + assertTrue(decoded.getParams() instanceof Map);
69 } 67 }
70 68
71 - /**  
72 - * 演示二进制格式心跳包  
73 - */  
74 - private static void demonstrateHeartbeat(IotTcpBinaryDeviceMessageCodec codec) {  
75 - log.info("=== 二进制格式心跳包示例 ==="); 69 + @Test
  70 + void testHeartbeat() {
  71 + log.info("=== 二进制格式心跳包测试 ===");
76 72
77 // 创建心跳消息 73 // 创建心跳消息
78 IotDeviceMessage heartbeat = IotDeviceMessage.requestOf("thing.state.online", null); 74 IotDeviceMessage heartbeat = IotDeviceMessage.requestOf("thing.state.online", null);
@@ -85,21 +81,21 @@ public class TcpBinaryDataPacketExamples { @@ -85,21 +81,21 @@ public class TcpBinaryDataPacketExamples {
85 81
86 // 解码验证 82 // 解码验证
87 IotDeviceMessage decoded = codec.decode(packet); 83 IotDeviceMessage decoded = codec.decode(packet);
88 - log.info("解码后消息ID: {}", decoded.getId());  
89 - log.info("解码后请求ID: {}", decoded.getRequestId()); 84 + log.info("解码后消息 ID: {}", decoded.getId());
  85 + log.info("解码后请求 ID: {}", decoded.getRequestId());
90 log.info("解码后方法: {}", decoded.getMethod()); 86 log.info("解码后方法: {}", decoded.getMethod());
91 - log.info("解码后设备ID: {}", decoded.getDeviceId());  
92 - log.info("解码后服务ID: {}", decoded.getServerId()); 87 + log.info("解码后设备 ID: {}", decoded.getDeviceId());
  88 + log.info("解码后服务 ID: {}", decoded.getServerId());
93 log.info("解码后参数: {}", decoded.getParams()); 89 log.info("解码后参数: {}", decoded.getParams());
94 90
95 - System.out.println(); 91 + // 断言验证
  92 + assertNotNull(decoded.getId());
  93 + assertEquals("thing.state.online", decoded.getMethod());
96 } 94 }
97 95
98 - /**  
99 - * 演示二进制格式复杂数据上报  
100 - */  
101 - private static void demonstrateComplexDataReport(IotTcpBinaryDeviceMessageCodec codec) {  
102 - log.info("=== 二进制格式复杂数据上报示例 ==="); 96 + @Test
  97 + void testComplexDataReport() {
  98 + log.info("=== 二进制格式复杂数据上报测试 ===");
103 99
104 // 创建复杂设备数据 100 // 创建复杂设备数据
105 Map<String, Object> deviceData = new HashMap<>(); 101 Map<String, Object> deviceData = new HashMap<>();
@@ -111,7 +107,7 @@ public class TcpBinaryDataPacketExamples { @@ -111,7 +107,7 @@ public class TcpBinaryDataPacketExamples {
111 environment.put("co2", 420); 107 environment.put("co2", 420);
112 deviceData.put("environment", environment); 108 deviceData.put("environment", environment);
113 109
114 - // GPS数据 110 + // GPS 数据
115 Map<String, Object> location = new HashMap<>(); 111 Map<String, Object> location = new HashMap<>();
116 location.put("latitude", 39.9042); 112 location.put("latitude", 39.9042);
117 location.put("longitude", 116.4074); 113 location.put("longitude", 116.4074);
@@ -136,18 +132,48 @@ public class TcpBinaryDataPacketExamples { @@ -136,18 +132,48 @@ public class TcpBinaryDataPacketExamples {
136 132
137 // 解码验证 133 // 解码验证
138 IotDeviceMessage decoded = codec.decode(packet); 134 IotDeviceMessage decoded = codec.decode(packet);
139 - log.info("解码后消息ID: {}", decoded.getId());  
140 - log.info("解码后请求ID: {}", decoded.getRequestId()); 135 + log.info("解码后消息 ID: {}", decoded.getId());
  136 + log.info("解码后请求 ID: {}", decoded.getRequestId());
141 log.info("解码后方法: {}", decoded.getMethod()); 137 log.info("解码后方法: {}", decoded.getMethod());
142 - log.info("解码后设备ID: {}", decoded.getDeviceId());  
143 - log.info("解码后服务ID: {}", decoded.getServerId()); 138 + log.info("解码后设备 ID: {}", decoded.getDeviceId());
  139 + log.info("解码后服务 ID: {}", decoded.getServerId());
144 log.info("解码后参数: {}", decoded.getParams()); 140 log.info("解码后参数: {}", decoded.getParams());
145 141
146 - System.out.println(); 142 + // 断言验证
  143 + assertNotNull(decoded.getId());
  144 + assertEquals("thing.property.post", decoded.getMethod());
  145 + assertNotNull(decoded.getParams());
  146 + }
  147 +
  148 + @Test
  149 + void testPacketStructureAnalysis() {
  150 + log.info("=== 数据包结构分析测试 ===");
  151 +
  152 + // 创建测试数据
  153 + Map<String, Object> sensorData = new HashMap<>();
  154 + sensorData.put("temperature", 25.5);
  155 + sensorData.put("humidity", 60.2);
  156 +
  157 + IotDeviceMessage message = IotDeviceMessage.requestOf("thing.property.post", sensorData);
  158 + message.setDeviceId(123456L);
  159 +
  160 + // 编码
  161 + byte[] packet = codec.encode(message);
  162 +
  163 + // 分析数据包结构
  164 + analyzePacketStructure(packet);
  165 +
  166 + // 断言验证
  167 + assertTrue(packet.length >= 8, "数据包长度应该至少为 8 字节");
147 } 168 }
148 169
  170 + // ==================== 内部辅助方法 ====================
  171 +
149 /** 172 /**
150 * 字节数组转十六进制字符串 173 * 字节数组转十六进制字符串
  174 + *
  175 + * @param bytes 字节数组
  176 + * @return 十六进制字符串
151 */ 177 */
152 private static String bytesToHex(byte[] bytes) { 178 private static String bytesToHex(byte[] bytes) {
153 StringBuilder result = new StringBuilder(); 179 StringBuilder result = new StringBuilder();
@@ -159,8 +185,10 @@ public class TcpBinaryDataPacketExamples { @@ -159,8 +185,10 @@ public class TcpBinaryDataPacketExamples {
159 185
160 /** 186 /**
161 * 演示数据包结构分析 187 * 演示数据包结构分析
  188 + *
  189 + * @param packet 数据包
162 */ 190 */
163 - public static void analyzePacketStructure(byte[] packet) { 191 + private static void analyzePacketStructure(byte[] packet) {
164 if (packet.length < 8) { 192 if (packet.length < 8) {
165 log.error("数据包长度不足"); 193 log.error("数据包长度不足");
166 return; 194 return;
@@ -168,30 +196,20 @@ public class TcpBinaryDataPacketExamples { @@ -168,30 +196,20 @@ public class TcpBinaryDataPacketExamples {
168 196
169 int index = 0; 197 int index = 0;
170 198
171 - // 解析包头(4字节) - 后续数据长度 199 + // 解析包头(4 字节) - 后续数据长度
172 int totalLength = ((packet[index] & 0xFF) << 24) | 200 int totalLength = ((packet[index] & 0xFF) << 24) |
173 - ((packet[index + 1] & 0xFF) << 16) |  
174 - ((packet[index + 2] & 0xFF) << 8) |  
175 - (packet[index + 3] & 0xFF); 201 + ((packet[index + 1] & 0xFF) << 16) |
  202 + ((packet[index + 2] & 0xFF) << 8) |
  203 + (packet[index + 3] & 0xFF);
176 index += 4; 204 index += 4;
177 log.info("包头 - 后续数据长度: {} 字节", totalLength); 205 log.info("包头 - 后续数据长度: {} 字节", totalLength);
178 206
179 - // 解析设备地址长度(2字节)  
180 - int addrLength = ((packet[index] & 0xFF) << 8) | (packet[index + 1] & 0xFF);  
181 - index += 2;  
182 - log.info("设备地址长度: {} 字节", addrLength);  
183 -  
184 - // 解析设备地址  
185 - String deviceAddr = new String(packet, index, addrLength);  
186 - index += addrLength;  
187 - log.info("设备地址: {}", deviceAddr);  
188 -  
189 - // 解析功能码(2字节) 207 + // 解析功能码(2 字节)
190 int functionCode = ((packet[index] & 0xFF) << 8) | (packet[index + 1] & 0xFF); 208 int functionCode = ((packet[index] & 0xFF) << 8) | (packet[index + 1] & 0xFF);
191 index += 2; 209 index += 2;
192 log.info("功能码: {} ({})", functionCode, getFunctionCodeName(functionCode)); 210 log.info("功能码: {} ({})", functionCode, getFunctionCodeName(functionCode));
193 211
194 - // 解析消息序号(2字节) 212 + // 解析消息序号(2 字节)
195 int messageId = ((packet[index] & 0xFF) << 8) | (packet[index + 1] & 0xFF); 213 int messageId = ((packet[index] & 0xFF) << 8) | (packet[index + 1] & 0xFF);
196 index += 2; 214 index += 2;
197 log.info("消息序号: {}", messageId); 215 log.info("消息序号: {}", messageId);
@@ -205,16 +223,19 @@ public class TcpBinaryDataPacketExamples { @@ -205,16 +223,19 @@ public class TcpBinaryDataPacketExamples {
205 223
206 /** 224 /**
207 * 获取功能码名称 225 * 获取功能码名称
  226 + *
  227 + * @param code 功能码
  228 + * @return 功能码名称
208 */ 229 */
209 private static String getFunctionCodeName(int code) { 230 private static String getFunctionCodeName(int code) {
210 - switch (code) {  
211 - case 10: return "设备注册";  
212 - case 11: return "注册回复";  
213 - case 20: return "心跳请求";  
214 - case 21: return "心跳回复";  
215 - case 30: return "消息上行";  
216 - case 40: return "消息下行";  
217 - default: return "未知功能码";  
218 - } 231 + return switch (code) {
  232 + case 10 -> "设备注册";
  233 + case 11 -> "注册回复";
  234 + case 20 -> "心跳请求";
  235 + case 21 -> "心跳回复";
  236 + case 30 -> "消息上行";
  237 + case 40 -> "消息下行";
  238 + default -> "未知功能码";
  239 + };
219 } 240 }
220 } 241 }
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/codec/tcp/TcpJsonDataPacketExamples.java deleted
1 -package cn.iocoder.yudao.module.iot.gateway.codec.tcp;  
2 -  
3 -import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;  
4 -import lombok.extern.slf4j.Slf4j;  
5 -  
6 -import java.nio.charset.StandardCharsets;  
7 -import java.util.HashMap;  
8 -import java.util.Map;  
9 -  
10 -// TODO @haohao:这种写成单测,会好点  
11 -/**  
12 - * TCP JSON格式数据包示例  
13 - *  
14 - * 演示如何使用新的JSON格式进行TCP消息编解码  
15 - *  
16 - * @author 芋道源码  
17 - */  
18 -@Slf4j  
19 -public class TcpJsonDataPacketExamples {  
20 -  
21 - public static void main(String[] args) {  
22 - IotTcpJsonDeviceMessageCodec codec = new IotTcpJsonDeviceMessageCodec();  
23 -  
24 - // 1. 数据上报示例  
25 - demonstrateDataReport(codec);  
26 -  
27 - // 2. 心跳示例  
28 - demonstrateHeartbeat(codec);  
29 -  
30 - // 3. 事件上报示例  
31 - demonstrateEventReport(codec);  
32 -  
33 - // 4. 复杂数据上报示例  
34 - demonstrateComplexDataReport(codec);  
35 -  
36 - // 5. 便捷方法示例  
37 - demonstrateConvenienceMethods();  
38 -  
39 - // 6. EMQX兼容性示例  
40 - demonstrateEmqxCompatibility();  
41 - }  
42 -  
43 - /**  
44 - * 演示数据上报  
45 - */  
46 - private static void demonstrateDataReport(IotTcpJsonDeviceMessageCodec codec) {  
47 - log.info("=== JSON格式数据上报示例 ===");  
48 -  
49 - // 创建传感器数据  
50 - Map<String, Object> sensorData = new HashMap<>();  
51 - sensorData.put("temperature", 25.5);  
52 - sensorData.put("humidity", 60.2);  
53 - sensorData.put("pressure", 1013.25);  
54 - sensorData.put("battery", 85);  
55 -  
56 - // 创建设备消息  
57 - IotDeviceMessage message = IotDeviceMessage.requestOf("thing.property.post", sensorData);  
58 - message.setDeviceId(123456L);  
59 -  
60 - // 编码  
61 - byte[] packet = codec.encode(message);  
62 - String jsonString = new String(packet, StandardCharsets.UTF_8);  
63 - log.info("编码后JSON: {}", jsonString);  
64 - log.info("数据包长度: {} 字节", packet.length);  
65 -  
66 - // 解码验证  
67 - IotDeviceMessage decoded = codec.decode(packet);  
68 - log.info("解码后消息ID: {}", decoded.getId());  
69 - log.info("解码后方法: {}", decoded.getMethod());  
70 - log.info("解码后设备ID: {}", decoded.getDeviceId());  
71 - log.info("解码后服务ID: {}", decoded.getServerId());  
72 - log.info("解码后参数: {}", decoded.getParams());  
73 -  
74 - System.out.println();  
75 - }  
76 -  
77 - /**  
78 - * 演示心跳  
79 - */  
80 - private static void demonstrateHeartbeat(IotTcpJsonDeviceMessageCodec codec) {  
81 - log.info("=== JSON格式心跳示例 ===");  
82 -  
83 - // 创建心跳消息  
84 - IotDeviceMessage heartbeat = IotDeviceMessage.requestOf("thing.state.online", null);  
85 - heartbeat.setDeviceId(123456L);  
86 -  
87 - // 编码  
88 - byte[] packet = codec.encode(heartbeat);  
89 - String jsonString = new String(packet, StandardCharsets.UTF_8);  
90 - log.info("编码后JSON: {}", jsonString);  
91 - log.info("心跳包长度: {} 字节", packet.length);  
92 -  
93 - // 解码验证  
94 - IotDeviceMessage decoded = codec.decode(packet);  
95 - log.info("解码后消息ID: {}", decoded.getId());  
96 - log.info("解码后方法: {}", decoded.getMethod());  
97 - log.info("解码后设备ID: {}", decoded.getDeviceId());  
98 - log.info("解码后服务ID: {}", decoded.getServerId());  
99 -  
100 - System.out.println();  
101 - }  
102 -  
103 - /**  
104 - * 演示事件上报  
105 - */  
106 - private static void demonstrateEventReport(IotTcpJsonDeviceMessageCodec codec) {  
107 - log.info("=== JSON格式事件上报示例 ===");  
108 -  
109 - // 创建事件数据  
110 - Map<String, Object> eventData = new HashMap<>();  
111 - eventData.put("eventType", "alarm");  
112 - eventData.put("level", "warning");  
113 - eventData.put("description", "温度过高");  
114 - eventData.put("value", 45.8);  
115 -  
116 - // 创建事件消息  
117 - IotDeviceMessage event = IotDeviceMessage.requestOf("thing.event.post", eventData);  
118 - event.setDeviceId(123456L);  
119 -  
120 - // 编码  
121 - byte[] packet = codec.encode(event);  
122 - String jsonString = new String(packet, StandardCharsets.UTF_8);  
123 - log.info("编码后JSON: {}", jsonString);  
124 - log.info("事件包长度: {} 字节", packet.length);  
125 -  
126 - // 解码验证  
127 - IotDeviceMessage decoded = codec.decode(packet);  
128 - log.info("解码后消息ID: {}", decoded.getId());  
129 - log.info("解码后方法: {}", decoded.getMethod());  
130 - log.info("解码后设备ID: {}", decoded.getDeviceId());  
131 - log.info("解码后参数: {}", decoded.getParams());  
132 -  
133 - System.out.println();  
134 - }  
135 -  
136 - /**  
137 - * 演示复杂数据上报  
138 - */  
139 - private static void demonstrateComplexDataReport(IotTcpJsonDeviceMessageCodec codec) {  
140 - log.info("=== JSON格式复杂数据上报示例 ===");  
141 -  
142 - // 创建复杂设备数据(类似EMQX格式)  
143 - Map<String, Object> deviceData = new HashMap<>();  
144 -  
145 - // 环境数据  
146 - Map<String, Object> environment = new HashMap<>();  
147 - environment.put("temperature", 23.8);  
148 - environment.put("humidity", 55.0);  
149 - environment.put("co2", 420);  
150 - environment.put("pm25", 35);  
151 - deviceData.put("environment", environment);  
152 -  
153 - // GPS数据  
154 - Map<String, Object> location = new HashMap<>();  
155 - location.put("latitude", 39.9042);  
156 - location.put("longitude", 116.4074);  
157 - location.put("altitude", 43.5);  
158 - location.put("speed", 0.0);  
159 - deviceData.put("location", location);  
160 -  
161 - // 设备状态  
162 - Map<String, Object> status = new HashMap<>();  
163 - status.put("battery", 78);  
164 - status.put("signal", -65);  
165 - status.put("online", true);  
166 - status.put("version", "1.2.3");  
167 - deviceData.put("status", status);  
168 -  
169 - // 创建设备消息  
170 - IotDeviceMessage message = IotDeviceMessage.requestOf("thing.property.post", deviceData);  
171 - message.setDeviceId(789012L);  
172 -  
173 - // 编码  
174 - byte[] packet = codec.encode(message);  
175 - String jsonString = new String(packet, StandardCharsets.UTF_8);  
176 - log.info("编码后JSON: {}", jsonString);  
177 - log.info("复杂数据包长度: {} 字节", packet.length);  
178 -  
179 - // 解码验证  
180 - IotDeviceMessage decoded = codec.decode(packet);  
181 - log.info("解码后消息ID: {}", decoded.getId());  
182 - log.info("解码后方法: {}", decoded.getMethod());  
183 - log.info("解码后设备ID: {}", decoded.getDeviceId());  
184 - log.info("解码后参数: {}", decoded.getParams());  
185 -  
186 - System.out.println();  
187 - }  
188 -  
189 - /**  
190 - * 演示便捷方法  
191 - */  
192 - private static void demonstrateConvenienceMethods() {  
193 - log.info("=== 便捷方法示例 ===");  
194 -  
195 - IotTcpJsonDeviceMessageCodec codec = new IotTcpJsonDeviceMessageCodec();  
196 -  
197 - // 使用便捷方法编码数据上报  
198 - Map<String, Object> sensorData = Map.of(  
199 - "temperature", 26.5,  
200 - "humidity", 58.3  
201 - );  
202 - byte[] dataPacket = codec.encodeDataReport(sensorData, 123456L, "smart_sensor", "device_001");  
203 - log.info("便捷方法编码数据上报: {}", new String(dataPacket, StandardCharsets.UTF_8));  
204 -  
205 - // 使用便捷方法编码心跳  
206 - byte[] heartbeatPacket = codec.encodeHeartbeat(123456L, "smart_sensor", "device_001");  
207 - log.info("便捷方法编码心跳: {}", new String(heartbeatPacket, StandardCharsets.UTF_8));  
208 -  
209 - // 使用便捷方法编码事件  
210 - Map<String, Object> eventData = Map.of(  
211 - "eventType", "maintenance",  
212 - "description", "定期维护提醒"  
213 - );  
214 - byte[] eventPacket = codec.encodeEventReport(eventData, 123456L, "smart_sensor", "device_001");  
215 - log.info("便捷方法编码事件: {}", new String(eventPacket, StandardCharsets.UTF_8));  
216 -  
217 - System.out.println();  
218 - }  
219 -  
220 - /**  
221 - * 演示与EMQX格式的兼容性  
222 - */  
223 - private static void demonstrateEmqxCompatibility() {  
224 - log.info("=== EMQX格式兼容性示例 ===");  
225 -  
226 - // 模拟EMQX风格的消息格式  
227 - String emqxStyleJson = """  
228 - {  
229 - "id": "msg_001",  
230 - "method": "thing.property.post",  
231 - "deviceId": 123456,  
232 - "params": {  
233 - "temperature": 25.5,  
234 - "humidity": 60.2  
235 - },  
236 - "timestamp": 1642781234567  
237 - }  
238 - """;  
239 -  
240 - IotTcpJsonDeviceMessageCodec codec = new IotTcpJsonDeviceMessageCodec();  
241 -  
242 - // 解码EMQX风格的消息  
243 - byte[] emqxBytes = emqxStyleJson.getBytes(StandardCharsets.UTF_8);  
244 - IotDeviceMessage decoded = codec.decode(emqxBytes);  
245 -  
246 - log.info("EMQX风格消息解码成功:");  
247 - log.info("消息ID: {}", decoded.getId());  
248 - log.info("方法: {}", decoded.getMethod());  
249 - log.info("设备ID: {}", decoded.getDeviceId());  
250 - log.info("参数: {}", decoded.getParams());  
251 -  
252 - System.out.println();  
253 - }  
254 -}  
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/codec/tcp/TcpJsonDataPacketExamplesTest.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.codec.tcp;
  2 +
  3 +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
  4 +import lombok.extern.slf4j.Slf4j;
  5 +import org.junit.jupiter.api.BeforeEach;
  6 +import org.junit.jupiter.api.Test;
  7 +
  8 +import java.nio.charset.StandardCharsets;
  9 +import java.util.HashMap;
  10 +import java.util.Map;
  11 +
  12 +import static org.junit.jupiter.api.Assertions.*;
  13 +
  14 +/**
  15 + * TCP JSON 格式数据包单元测试
  16 + * <p>
  17 + * 测试 JSON 格式的 TCP 消息编解码功能
  18 + *
  19 + * @author 芋道源码
  20 + */
  21 +@Slf4j
  22 +class TcpJsonDataPacketExamplesTest {
  23 +
  24 + private IotTcpJsonDeviceMessageCodec codec;
  25 +
  26 + @BeforeEach
  27 + void setUp() {
  28 + codec = new IotTcpJsonDeviceMessageCodec();
  29 + }
  30 +
  31 + @Test
  32 + void testDataReport() {
  33 + log.info("=== JSON 格式数据上报测试 ===");
  34 +
  35 + // 创建传感器数据
  36 + Map<String, Object> sensorData = new HashMap<>();
  37 + sensorData.put("temperature", 25.5);
  38 + sensorData.put("humidity", 60.2);
  39 + sensorData.put("pressure", 1013.25);
  40 + sensorData.put("battery", 85);
  41 +
  42 + // 创建设备消息
  43 + IotDeviceMessage message = IotDeviceMessage.requestOf("thing.property.post", sensorData);
  44 + message.setDeviceId(123456L);
  45 +
  46 + // 编码
  47 + byte[] packet = codec.encode(message);
  48 + String jsonString = new String(packet, StandardCharsets.UTF_8);
  49 + log.info("编码后 JSON: {}", jsonString);
  50 + log.info("数据包长度: {} 字节", packet.length);
  51 +
  52 + // 解码验证
  53 + IotDeviceMessage decoded = codec.decode(packet);
  54 + log.info("解码后消息 ID: {}", decoded.getId());
  55 + log.info("解码后方法: {}", decoded.getMethod());
  56 + log.info("解码后设备 ID: {}", decoded.getDeviceId());
  57 + log.info("解码后服务 ID: {}", decoded.getServerId());
  58 + log.info("解码后参数: {}", decoded.getParams());
  59 +
  60 + // 断言验证
  61 + assertNotNull(decoded.getId());
  62 + assertEquals("thing.property.post", decoded.getMethod());
  63 + assertEquals(123456L, decoded.getDeviceId());
  64 + assertNotNull(decoded.getParams());
  65 + assertTrue(decoded.getParams() instanceof Map);
  66 + }
  67 +
  68 + @Test
  69 + void testHeartbeat() {
  70 + log.info("=== JSON 格式心跳测试 ===");
  71 +
  72 + // 创建心跳消息
  73 + IotDeviceMessage heartbeat = IotDeviceMessage.requestOf("thing.state.online", null);
  74 + heartbeat.setDeviceId(123456L);
  75 +
  76 + // 编码
  77 + byte[] packet = codec.encode(heartbeat);
  78 + String jsonString = new String(packet, StandardCharsets.UTF_8);
  79 + log.info("编码后 JSON: {}", jsonString);
  80 + log.info("心跳包长度: {} 字节", packet.length);
  81 +
  82 + // 解码验证
  83 + IotDeviceMessage decoded = codec.decode(packet);
  84 + log.info("解码后消息 ID: {}", decoded.getId());
  85 + log.info("解码后方法: {}", decoded.getMethod());
  86 + log.info("解码后设备 ID: {}", decoded.getDeviceId());
  87 + log.info("解码后服务 ID: {}", decoded.getServerId());
  88 +
  89 + // 断言验证
  90 + assertNotNull(decoded.getId());
  91 + assertEquals("thing.state.online", decoded.getMethod());
  92 + assertEquals(123456L, decoded.getDeviceId());
  93 + }
  94 +
  95 + @Test
  96 + void testEventReport() {
  97 + log.info("=== JSON 格式事件上报测试 ===");
  98 +
  99 + // 创建事件数据
  100 + Map<String, Object> eventData = new HashMap<>();
  101 + eventData.put("eventType", "alarm");
  102 + eventData.put("level", "warning");
  103 + eventData.put("description", "温度过高");
  104 + eventData.put("value", 45.8);
  105 +
  106 + // 创建事件消息
  107 + IotDeviceMessage event = IotDeviceMessage.requestOf("thing.event.post", eventData);
  108 + event.setDeviceId(123456L);
  109 +
  110 + // 编码
  111 + byte[] packet = codec.encode(event);
  112 + String jsonString = new String(packet, StandardCharsets.UTF_8);
  113 + log.info("编码后 JSON: {}", jsonString);
  114 + log.info("事件包长度: {} 字节", packet.length);
  115 +
  116 + // 解码验证
  117 + IotDeviceMessage decoded = codec.decode(packet);
  118 + log.info("解码后消息 ID: {}", decoded.getId());
  119 + log.info("解码后方法: {}", decoded.getMethod());
  120 + log.info("解码后设备 ID: {}", decoded.getDeviceId());
  121 + log.info("解码后参数: {}", decoded.getParams());
  122 +
  123 + // 断言验证
  124 + assertNotNull(decoded.getId());
  125 + assertEquals("thing.event.post", decoded.getMethod());
  126 + assertEquals(123456L, decoded.getDeviceId());
  127 + assertNotNull(decoded.getParams());
  128 + }
  129 +
  130 + @Test
  131 + void testComplexDataReport() {
  132 + log.info("=== JSON 格式复杂数据上报测试 ===");
  133 +
  134 + // 创建复杂设备数据(类似 EMQX 格式)
  135 + Map<String, Object> deviceData = new HashMap<>();
  136 +
  137 + // 环境数据
  138 + Map<String, Object> environment = new HashMap<>();
  139 + environment.put("temperature", 23.8);
  140 + environment.put("humidity", 55.0);
  141 + environment.put("co2", 420);
  142 + environment.put("pm25", 35);
  143 + deviceData.put("environment", environment);
  144 +
  145 + // GPS 数据
  146 + Map<String, Object> location = new HashMap<>();
  147 + location.put("latitude", 39.9042);
  148 + location.put("longitude", 116.4074);
  149 + location.put("altitude", 43.5);
  150 + location.put("speed", 0.0);
  151 + deviceData.put("location", location);
  152 +
  153 + // 设备状态
  154 + Map<String, Object> status = new HashMap<>();
  155 + status.put("battery", 78);
  156 + status.put("signal", -65);
  157 + status.put("online", true);
  158 + status.put("version", "1.2.3");
  159 + deviceData.put("status", status);
  160 +
  161 + // 创建设备消息
  162 + IotDeviceMessage message = IotDeviceMessage.requestOf("thing.property.post", deviceData);
  163 + message.setDeviceId(789012L);
  164 +
  165 + // 编码
  166 + byte[] packet = codec.encode(message);
  167 + String jsonString = new String(packet, StandardCharsets.UTF_8);
  168 + log.info("编码后 JSON: {}", jsonString);
  169 + log.info("复杂数据包长度: {} 字节", packet.length);
  170 +
  171 + // 解码验证
  172 + IotDeviceMessage decoded = codec.decode(packet);
  173 + log.info("解码后消息 ID: {}", decoded.getId());
  174 + log.info("解码后方法: {}", decoded.getMethod());
  175 + log.info("解码后设备 ID: {}", decoded.getDeviceId());
  176 + log.info("解码后参数: {}", decoded.getParams());
  177 +
  178 + // 断言验证
  179 + assertNotNull(decoded.getId());
  180 + assertEquals("thing.property.post", decoded.getMethod());
  181 + assertEquals(789012L, decoded.getDeviceId());
  182 + assertNotNull(decoded.getParams());
  183 + }
  184 +
  185 +}
0 \ No newline at end of file 186 \ No newline at end of file
yudao-module-iot/yudao-module-iot-gateway/src/test/resources/tcp-binary-packet-examples.md
1 -# TCP二进制å议数æ®åŒ…æ ¼å¼è¯´æ˜Žå’Œç¤ºä¾‹ 1 +# TCP 二进制å议数æ®åŒ…æ ¼å¼è¯´æ˜Žå’Œç¤ºä¾‹
2 2
3 ## 1. 二进制å议概述 3 ## 1. 二进制å议概述
4 4
5 -TCP二进制å议是一ç§é«˜æ•ˆçš„自定义å议格å¼ï¼Œé€‚ç”¨äºŽå¯¹å¸¦å®½å’Œæ€§èƒ½è¦æ±‚较高的场景。 5 +TCP 二进制å议是一ç§é«˜æ•ˆçš„自定义å议格å¼ï¼Œé€‚ç”¨äºŽå¯¹å¸¦å®½å’Œæ€§èƒ½è¦æ±‚较高的场景。该å议采用紧凑的二进制格å¼ï¼Œå‡å°‘æ•°æ®ä¼ è¾“é‡ï¼Œæé«˜ä¼ è¾“效率。
6 6
7 ## 2. æ•°æ®åŒ…æ ¼å¼ 7 ## 2. æ•°æ®åŒ…æ ¼å¼
8 8
9 ### 2.1 整体结构 9 ### 2.1 整体结构
  10 +
  11 +æ ¹æ®ä»£ç å®žçŽ°ï¼ŒTCP 二进制å议的数æ®åŒ…æ ¼å¼ä¸ºï¼š
  12 +
10 ``` 13 ```
11 -+----------+----------+----------+----------+----------+----------+  
12 -| 包头 | 地å€é•¿åº¦ | è®¾å¤‡åœ°å€ | åŠŸèƒ½ç  | 消æ¯åºå· | åŒ…ä½“æ•°æ® |  
13 -| 4字节 | 2字节 | å˜é•¿ | 2字节 | 2字节 | å˜é•¿ |  
14 -+----------+----------+----------+----------+----------+----------+ 14 ++----------+----------+----------+----------+
  15 +| 包头 | åŠŸèƒ½ç  | 消æ¯åºå· | åŒ…ä½“æ•°æ® |
  16 +| 4字节 | 2字节 | 2字节 | å˜é•¿ |
  17 ++----------+----------+----------+----------+
15 ``` 18 ```
16 19
  20 +**注æ„**:与原始设计相比,实际实现中移除了设备地å€å­—段,简化了å议结构。
  21 +
17 ### 2.2 字段说明 22 ### 2.2 字段说明
18 23
19 -| 字段 | 长度 | 类型 | 说明 |  
20 -|----------|--------|--------|--------------------------------|  
21 -| 包头 | 4字节 | int | åŽç»­æ•°æ®çš„æ€»é•¿åº¦ï¼ˆä¸åŒ…å«åŒ…头) |  
22 -| 地å€é•¿åº¦ | 2字节 | short | 设备地å€çš„字节长度 |  
23 -| è®¾å¤‡åœ°å€ | å˜é•¿ | string | 设备标识符 |  
24 -| åŠŸèƒ½ç  | 2字节 | short | 消æ¯ç±»åž‹æ ‡è¯† |  
25 -| 消æ¯åºå· | 2字节 | short | 消æ¯å”¯ä¸€æ ‡è¯† |  
26 -| åŒ…ä½“æ•°æ® | å˜é•¿ | string | JSONæ ¼å¼çš„æ¶ˆæ¯å†…容 | 24 +| 字段 | 长度 | 类型 | 说明 |
  25 +|------|-----|--------|-----------------|
  26 +| 包头 | 4字节 | int | åŽç»­æ•°æ®çš„æ€»é•¿åº¦ï¼ˆä¸åŒ…å«åŒ…头) |
  27 +| åŠŸèƒ½ç  | 2字节 | short | 消æ¯ç±»åž‹æ ‡è¯† |
  28 +| 消æ¯åºå· | 2字节 | short | 消æ¯å”¯ä¸€æ ‡è¯† |
  29 +| åŒ…ä½“æ•°æ® | å˜é•¿ | string | JSON æ ¼å¼çš„æ¶ˆæ¯å†…容 |
27 30
28 ### 2.3 功能ç å®šä¹‰ 31 ### 2.3 功能ç å®šä¹‰
29 32
30 -| åŠŸèƒ½ç  | åç§° | 说明 |  
31 -|--------|----------|--------------------------------|  
32 -| 10 | 设备注册 | 设备首次连接时的注册请求 |  
33 -| 11 | æ³¨å†Œå›žå¤ | æœåŠ¡å™¨å¯¹æ³¨å†Œè¯·æ±‚çš„å›žå¤ |  
34 -| 20 | 心跳请求 | 设备å‘é€çš„心跳包 |  
35 -| 21 | å¿ƒè·³å›žå¤ | æœåŠ¡å™¨å¯¹å¿ƒè·³çš„å›žå¤ |  
36 -| 30 | 消æ¯ä¸Šè¡Œ | è®¾å¤‡å‘æœåС噍å‘é€çš„æ•°æ® |  
37 -| 40 | 消æ¯ä¸‹è¡Œ | æœåС噍å‘设备å‘é€çš„æŒ‡ä»¤ | 33 +æ ¹æ®ä»£ç å®žçŽ°ï¼Œæ”¯æŒçš„功能ç ï¼š
  34 +
  35 +| åŠŸèƒ½ç  | åç§° | 说明 |
  36 +|-----|------|--------------|
  37 +| 10 | 设备注册 | 设备首次连接时的注册请求 |
  38 +| 11 | æ³¨å†Œå›žå¤ | æœåŠ¡å™¨å¯¹æ³¨å†Œè¯·æ±‚çš„å›žå¤ |
  39 +| 20 | 心跳请求 | 设备å‘é€çš„心跳包 |
  40 +| 21 | å¿ƒè·³å›žå¤ | æœåŠ¡å™¨å¯¹å¿ƒè·³çš„å›žå¤ |
  41 +| 30 | 消æ¯ä¸Šè¡Œ | è®¾å¤‡å‘æœåС噍å‘é€çš„æ•°æ® |
  42 +| 40 | 消æ¯ä¸‹è¡Œ | æœåС噍å‘设备å‘é€çš„æŒ‡ä»¤ |
  43 +
  44 +**常é‡å®šä¹‰ï¼š**
  45 +
  46 +```java
  47 +public static final short CODE_REGISTER = 10;
  48 +public static final short CODE_REGISTER_REPLY = 11;
  49 +public static final short CODE_HEARTBEAT = 20;
  50 +public static final short CODE_HEARTBEAT_REPLY = 21;
  51 +public static final short CODE_MESSAGE_UP = 30;
  52 +public static final short CODE_MESSAGE_DOWN = 40;
  53 +```
  54 +
  55 +## 3. åŒ…ä½“æ•°æ®æ ¼å¼
  56 +
  57 +### 3.1 JSON 负载结构
  58 +
  59 +包体数æ®é‡‡ç”¨ JSON æ ¼å¼ï¼ŒåŒ…å«ä»¥ä¸‹å­—段:
  60 +
  61 +```json
  62 +{
  63 + "method": "æ¶ˆæ¯æ–¹æ³•",
  64 + "params": {
  65 + // 消æ¯å‚æ•°
  66 + },
  67 + "timestamp": 时间戳,
  68 + "requestId": "请求ID",
  69 + "msgId": "消æ¯ID"
  70 +}
  71 +```
  72 +
  73 +### 3.2 字段说明
38 74
39 -## 3. 二进制数æ®ä¸ŠæŠ¥åŒ…示例 75 +| 字段å | 类型 | å¿…å¡« | 说明 |
  76 +|-----------|--------|----|------------------------------|
  77 +| method | String | 是 | æ¶ˆæ¯æ–¹æ³•,如 `thing.property.post` |
  78 +| params | Object | å¦ | 消æ¯å‚æ•° |
  79 +| timestamp | Long | 是 | 时间戳(毫秒) |
  80 +| requestId | String | å¦ | 请求唯一标识 |
  81 +| msgId | String | å¦ | 消æ¯å”¯ä¸€æ ‡è¯† |
40 82
41 -### 3.1 温度传感器数æ®ä¸ŠæŠ¥ 83 +**常é‡å®šä¹‰ï¼š**
  84 +
  85 +```java
  86 +public static final String METHOD = "method";
  87 +public static final String PARAMS = "params";
  88 +public static final String TIMESTAMP = "timestamp";
  89 +public static final String REQUEST_ID = "requestId";
  90 +public static final String MESSAGE_ID = "msgId";
  91 +```
  92 +
  93 +## 4. 消æ¯ç±»åž‹
  94 +
  95 +### 4.1 æ•°æ®ä¸ŠæŠ¥ (thing.property.post)
  96 +
  97 +è®¾å¤‡å‘æœåŠ¡å™¨ä¸ŠæŠ¥å±žæ€§æ•°æ®ã€‚
  98 +
  99 +**功能ç ï¼š** 30 (CODE_MESSAGE_UP)
  100 +
  101 +**包体数æ®ç¤ºä¾‹ï¼š**
42 102
43 -**原始数æ®ï¼š** 103 +```json
  104 +{
  105 + "method": "thing.property.post",
  106 + "params": {
  107 + "temperature": 25.5,
  108 + "humidity": 60.2,
  109 + "pressure": 1013.25
  110 + },
  111 + "timestamp": 1642781234567,
  112 + "requestId": "req_001"
  113 +}
  114 +```
  115 +
  116 +### 4.2 心跳 (thing.state.online)
  117 +
  118 +è®¾å¤‡å‘æœåС噍å‘é€å¿ƒè·³ä¿æ´»ã€‚
  119 +
  120 +**功能ç ï¼š** 20 (CODE_HEARTBEAT)
  121 +
  122 +**包体数æ®ç¤ºä¾‹ï¼š**
  123 +
  124 +```json
  125 +{
  126 + "method": "thing.state.online",
  127 + "params": {},
  128 + "timestamp": 1642781234567,
  129 + "requestId": "req_002"
  130 +}
  131 +```
  132 +
  133 +### 4.3 æ¶ˆæ¯æ–¹æ³•常é‡
  134 +
  135 +```java
  136 +public static final String PROPERTY_POST = "thing.property.post"; // æ•°æ®ä¸ŠæŠ¥
  137 +public static final String STATE_ONLINE = "thing.state.online"; // 心跳
  138 +```
  139 +
  140 +## 5. æ•°æ®åŒ…示例
  141 +
  142 +### 5.1 温度传感器数æ®ä¸ŠæŠ¥
  143 +
  144 +**包体数æ®ï¼š**
44 ```json 145 ```json
45 { 146 {
46 "method": "thing.property.post", 147 "method": "thing.property.post",
@@ -49,15 +150,14 @@ TCP二进制å议是一ç§é«˜æ•ˆçš„自定义å议格å¼ï¼Œé€‚ç”¨äºŽå¯¹å¸¦å®½å’ @@ -49,15 +150,14 @@ TCP二进制å议是一ç§é«˜æ•ˆçš„自定义å议格å¼ï¼Œé€‚用于对带宽å’
49 "humidity": 60.2, 150 "humidity": 60.2,
50 "pressure": 1013.25 151 "pressure": 1013.25
51 }, 152 },
52 - "timestamp": 1642781234567 153 + "timestamp": 1642781234567,
  154 + "requestId": "req_001"
53 } 155 }
54 ``` 156 ```
55 157
56 **æ•°æ®åŒ…结构:** 158 **æ•°æ®åŒ…结构:**
57 ``` 159 ```
58 包头: 0x00000045 (69字节) 160 包头: 0x00000045 (69字节)
59 -地å€é•¿åº¦: 0x0006 (6字节)  
60 -设备地å€: "123456"  
61 功能ç : 0x001E (30 - 消æ¯ä¸Šè¡Œ) 161 功能ç : 0x001E (30 - 消æ¯ä¸Šè¡Œ)
62 消æ¯åºå·: 0x1234 (4660) 162 消æ¯åºå·: 0x1234 (4660)
63 包体: JSON字符串 163 包体: JSON字符串
@@ -65,7 +165,7 @@ TCP二进制å议是一ç§é«˜æ•ˆçš„自定义å议格å¼ï¼Œé€‚ç”¨äºŽå¯¹å¸¦å®½å’ @@ -65,7 +165,7 @@ TCP二进制å议是一ç§é«˜æ•ˆçš„自定义å议格å¼ï¼Œé€‚用于对带宽å’
65 165
66 **完整å六进制数æ®åŒ…:** 166 **完整å六进制数æ®åŒ…:**
67 ``` 167 ```
68 -00 00 00 45 00 06 31 32 33 34 35 36 00 1E 12 34 168 +00 00 00 45 00 1E 12 34
69 7B 22 6D 65 74 68 6F 64 22 3A 22 74 68 69 6E 67 169 7B 22 6D 65 74 68 6F 64 22 3A 22 74 68 69 6E 67
70 2E 70 72 6F 70 65 72 74 79 2E 70 6F 73 74 22 2C 170 2E 70 72 6F 70 65 72 74 79 2E 70 6F 73 74 22 2C
71 22 70 61 72 61 6D 73 22 3A 7B 22 74 65 6D 70 65 171 22 70 61 72 61 6D 73 22 3A 7B 22 74 65 6D 70 65
@@ -73,42 +173,25 @@ TCP二进制å议是一ç§é«˜æ•ˆçš„自定义å议格å¼ï¼Œé€‚ç”¨äºŽå¯¹å¸¦å®½å’ @@ -73,42 +173,25 @@ TCP二进制å议是一ç§é«˜æ•ˆçš„自定义å议格å¼ï¼Œé€‚用于对带宽å’
73 6D 69 64 69 74 79 22 3A 36 30 2E 32 2C 22 70 72 173 6D 69 64 69 74 79 22 3A 36 30 2E 32 2C 22 70 72
74 65 73 73 75 72 65 22 3A 31 30 31 33 2E 32 35 7D 174 65 73 73 75 72 65 22 3A 31 30 31 33 2E 32 35 7D
75 2C 22 74 69 6D 65 73 74 61 6D 70 22 3A 31 36 34 175 2C 22 74 69 6D 65 73 74 61 6D 70 22 3A 31 36 34
76 -32 37 38 31 32 33 34 35 36 37 7D  
77 -```  
78 -  
79 -### 2.2 GPSå®šä½æ•°æ®ä¸ŠæŠ¥  
80 -  
81 -**原始数æ®ï¼š**  
82 -```json  
83 -{  
84 - "method": "thing.property.post",  
85 - "params": {  
86 - "latitude": 39.9042,  
87 - "longitude": 116.4074,  
88 - "altitude": 43.5,  
89 - "speed": 0.0  
90 - },  
91 - "timestamp": 1642781234567  
92 -} 176 +32 37 38 31 32 33 34 35 36 37 2C 22 72 65 71 75
  177 +65 73 74 49 64 22 3A 22 72 65 71 5F 30 30 31 22 7D
93 ``` 178 ```
94 179
95 -## 3. 心跳包示例  
96 -  
97 -### 3.1 标准心跳包 180 +### 5.2 心跳包示例
98 181
99 -**原始数æ®ï¼š** 182 +**包体数æ®ï¼š**
100 ```json 183 ```json
101 { 184 {
102 "method": "thing.state.online", 185 "method": "thing.state.online",
103 - "timestamp": 1642781234567 186 + "params": {},
  187 + "timestamp": 1642781234567,
  188 + "requestId": "req_002"
104 } 189 }
105 ``` 190 ```
106 191
107 **æ•°æ®åŒ…结构:** 192 **æ•°æ®åŒ…结构:**
108 ``` 193 ```
109 包头: 0x00000028 (40字节) 194 包头: 0x00000028 (40字节)
110 -地å€é•¿åº¦: 0x0006 (6字节)  
111 -设备地å€: "123456"  
112 功能ç : 0x0014 (20 - 心跳请求) 195 功能ç : 0x0014 (20 - 心跳请求)
113 消æ¯åºå·: 0x5678 (22136) 196 消æ¯åºå·: 0x5678 (22136)
114 包体: JSON字符串 197 包体: JSON字符串
@@ -116,66 +199,71 @@ TCP二进制å议是一ç§é«˜æ•ˆçš„自定义å议格å¼ï¼Œé€‚ç”¨äºŽå¯¹å¸¦å®½å’ @@ -116,66 +199,71 @@ TCP二进制å议是一ç§é«˜æ•ˆçš„自定义å议格å¼ï¼Œé€‚用于对带宽å’
116 199
117 **完整å六进制数æ®åŒ…:** 200 **完整å六进制数æ®åŒ…:**
118 ``` 201 ```
119 -00 00 00 28 00 06 31 32 33 34 35 36 00 14 56 78 202 +00 00 00 28 00 14 56 78
120 7B 22 6D 65 74 68 6F 64 22 3A 22 74 68 69 6E 67 203 7B 22 6D 65 74 68 6F 64 22 3A 22 74 68 69 6E 67
121 2E 73 74 61 74 65 2E 6F 6E 6C 69 6E 65 22 2C 22 204 2E 73 74 61 74 65 2E 6F 6E 6C 69 6E 65 22 2C 22
122 -74 69 6D 65 73 74 61 6D 70 22 3A 31 36 34 32 37  
123 -38 31 32 33 34 35 36 37 7D 205 +70 61 72 61 6D 73 22 3A 7B 7D 2C 22 74 69 6D 65
  206 +73 74 61 6D 70 22 3A 31 36 34 32 37 38 31 32 33
  207 +34 35 36 37 2C 22 72 65 71 75 65 73 74 49 64 22
  208 +3A 22 72 65 71 5F 30 30 32 22 7D
124 ``` 209 ```
125 210
126 -## 4. 夿‚æ•°æ®ä¸ŠæŠ¥ç¤ºä¾‹ 211 +## 6. ç¼–è§£ç å™¨å®žçް
127 212
128 -### 4.1 å¤šä¼ æ„Ÿå™¨ç»¼åˆæ•°æ® 213 +### 6.1 ç¼–ç å™¨ç±»åž‹
129 214
130 -**原始数æ®ï¼š**  
131 -```json  
132 -{  
133 - "method": "thing.property.post",  
134 - "params": {  
135 - "environment": {  
136 - "temperature": 23.8,  
137 - "humidity": 55.0,  
138 - "co2": 420  
139 - },  
140 - "location": {  
141 - "latitude": 39.9042,  
142 - "longitude": 116.4074,  
143 - "altitude": 43.5  
144 - },  
145 - "status": {  
146 - "battery": 78,  
147 - "signal": -65,  
148 - "online": true  
149 - }  
150 - },  
151 - "timestamp": 1642781234567 215 +```java
  216 +public static final String TYPE = "TCP_BINARY";
  217 +```
  218 +
  219 +### 6.2 ç¼–ç è¿‡ç¨‹
  220 +
  221 +1. **傿•°éªŒè¯**:检查消æ¯å’Œæ–¹æ³•是å¦ä¸ºç©º
  222 +2. **确定功能ç **:
  223 + - 心跳消æ¯ï¼šä½¿ç”¨ `CODE_HEARTBEAT` (20)
  224 + - 其他消æ¯ï¼šä½¿ç”¨ `CODE_MESSAGE_UP` (30)
  225 +3. **构建负载**:使用 `buildSimplePayload()` 构建 JSON è´Ÿè½½
  226 +4. **ç”Ÿæˆæ¶ˆæ¯åºå·**ï¼šåŸºäºŽå½“å‰æ—¶é—´æˆ³ç”Ÿæˆ
  227 +5. **构建数æ®åŒ…**:创建 `TcpDataPackage` 对象
  228 +6. **ç¼–ç ä¸ºå­—节æµ**:使用 `encodeTcpDataPackage()` ç¼–ç 
  229 +
  230 +### 6.3 è§£ç è¿‡ç¨‹
  231 +
  232 +1. **傿•°éªŒè¯**:检查字节数组是å¦ä¸ºç©º
  233 +2. **è§£ç æ•°æ®åŒ…**:使用 `decodeTcpDataPackage()` è§£ç 
  234 +3. **ç¡®å®šæ¶ˆæ¯æ–¹æ³•**:
  235 + - åŠŸèƒ½ç  20:`thing.state.online` (心跳)
  236 + - åŠŸèƒ½ç  30:`thing.property.post` (æ•°æ®ä¸ŠæŠ¥)
  237 +4. **è§£æžè´Ÿè½½ä¿¡æ¯**:使用 `parsePayloadInfo()` è§£æž JSON è´Ÿè½½
  238 +5. **构建设备消æ¯**:创建 `IotDeviceMessage` 对象
  239 +6. **设置æœåŠ¡ ID**:使用 `generateServerId()` 生æˆ
  240 +
  241 +### 6.4 æœåŠ¡ ID 生æˆ
  242 +
  243 +```java
  244 +private String generateServerId(TcpDataPackage dataPackage) {
  245 + return String.format("tcp_binary_%d_%d", dataPackage.getCode(), dataPackage.getMid());
152 } 246 }
153 ``` 247 ```
154 248
155 -## 5. æ•°æ®åŒ…è§£æžæ­¥éª¤ 249 +## 7. æ•°æ®åŒ…è§£æžæ­¥éª¤
156 250
157 -### 5.1 è§£æžæµç¨‹ 251 +### 7.1 è§£æžæµç¨‹
158 252
159 1. **读å–包头(4字节)** 253 1. **读å–包头(4字节)**
160 - 获å–åŽç»­æ•°æ®çš„æ€»é•¿åº¦ 254 - 获å–åŽç»­æ•°æ®çš„æ€»é•¿åº¦
161 - éªŒè¯æ•°æ®åŒ…完整性 255 - éªŒè¯æ•°æ®åŒ…完整性
162 256
163 -2. **读å–设备地å€é•¿åº¦ï¼ˆ2字节)**  
164 - - 确定设备地å€çš„字节数  
165 -  
166 -3. **读å–设备地å€ï¼ˆå˜é•¿ï¼‰**  
167 - - æ ¹æ®åœ°å€é•¿åº¦è¯»å–设备标识  
168 -  
169 -4. **读å–功能ç ï¼ˆ2字节)** 257 +2. **读å–功能ç ï¼ˆ2字节)**
170 - 确定消æ¯ç±»åž‹ 258 - 确定消æ¯ç±»åž‹
171 259
172 -5. **è¯»å–æ¶ˆæ¯åºå·ï¼ˆ2字节)** 260 +3. **è¯»å–æ¶ˆæ¯åºå·ï¼ˆ2字节)**
173 - èŽ·å–æ¶ˆæ¯å”¯ä¸€æ ‡è¯† 261 - èŽ·å–æ¶ˆæ¯å”¯ä¸€æ ‡è¯†
174 262
175 -6. **读å–包体数æ®ï¼ˆå˜é•¿ï¼‰**  
176 - - è§£æžJSONæ ¼å¼çš„æ¶ˆæ¯å†…容 263 +4. **读å–包体数æ®ï¼ˆå˜é•¿ï¼‰**
  264 + - è§£æž JSON æ ¼å¼çš„æ¶ˆæ¯å†…容
177 265
178 -### 5.2 Javaè§£æžç¤ºä¾‹ 266 +### 7.2 Java è§£æžç¤ºä¾‹
179 267
180 ```java 268 ```java
181 public TcpDataPackage parsePacket(byte[] packet) { 269 public TcpDataPackage parsePacket(byte[] packet) {
@@ -184,39 +272,99 @@ public TcpDataPackage parsePacket(byte[] packet) { @@ -184,39 +272,99 @@ public TcpDataPackage parsePacket(byte[] packet) {
184 // 1. è§£æžåŒ…头 272 // 1. è§£æžåŒ…头
185 int totalLength = ByteBuffer.wrap(packet, index, 4).getInt(); 273 int totalLength = ByteBuffer.wrap(packet, index, 4).getInt();
186 index += 4; 274 index += 4;
187 -  
188 - // 2. è§£æžè®¾å¤‡åœ°å€é•¿åº¦  
189 - short addrLength = ByteBuffer.wrap(packet, index, 2).getShort();  
190 - index += 2;  
191 -  
192 - // 3. è§£æžè®¾å¤‡åœ°å€  
193 - String deviceAddr = new String(packet, index, addrLength);  
194 - index += addrLength;  
195 -  
196 - // 4. è§£æžåŠŸèƒ½ç  275 +
  276 + // 2. è§£æžåŠŸèƒ½ç 
197 short functionCode = ByteBuffer.wrap(packet, index, 2).getShort(); 277 short functionCode = ByteBuffer.wrap(packet, index, 2).getShort();
198 index += 2; 278 index += 2;
199 -  
200 - // 5. è§£æžæ¶ˆæ¯åºå· 279 +
  280 + // 3. è§£æžæ¶ˆæ¯åºå·
201 short messageId = ByteBuffer.wrap(packet, index, 2).getShort(); 281 short messageId = ByteBuffer.wrap(packet, index, 2).getShort();
202 index += 2; 282 index += 2;
203 -  
204 - // 6. è§£æžåŒ…ä½“æ•°æ® 283 +
  284 + // 4. è§£æžåŒ…体数æ®
205 String payload = new String(packet, index, packet.length - index); 285 String payload = new String(packet, index, packet.length - index);
206 -  
207 - return TcpDataPackage.builder()  
208 - .addr(deviceAddr)  
209 - .code(functionCode)  
210 - .mid(messageId)  
211 - .payload(payload)  
212 - .build(); 286 +
  287 + return new TcpDataPackage(functionCode, messageId, payload);
213 } 288 }
214 ``` 289 ```
215 290
216 -## 6. 注æ„事项 291 +## 8. 使用示例
  292 +
  293 +### 8.1 基本使用
  294 +
  295 +```java
  296 +// 创建编解ç å™¨
  297 +IotTcpBinaryDeviceMessageCodec codec = new IotTcpBinaryDeviceMessageCodec();
  298 +
  299 +// 创建数æ®ä¸ŠæŠ¥æ¶ˆæ¯
  300 +Map<String, Object> sensorData = Map.of(
  301 + "temperature", 25.5,
  302 + "humidity", 60.2
  303 +);
  304 +
  305 +// ç¼–ç 
  306 +IotDeviceMessage message = IotDeviceMessage.requestOf("thing.property.post", sensorData);
  307 +byte[] data = codec.encode(message);
  308 +
  309 +// è§£ç 
  310 +IotDeviceMessage decoded = codec.decode(data);
  311 +```
  312 +
  313 +### 8.2 错误处ç†
  314 +
  315 +```java
  316 +try{
  317 +byte[] data = codec.encode(message);
  318 +// 处ç†ç¼–ç æˆåŠŸ
  319 +}catch(
  320 +IllegalArgumentException e){
  321 + // 处ç†å‚数错误
  322 + log.
  323 +
  324 +error("ç¼–ç å‚数错误: {}",e.getMessage());
  325 + }catch(
  326 +TcpCodecException e){
  327 + // 处ç†ç¼–ç å¤±è´¥
  328 + log.
  329 +
  330 +error("ç¼–ç å¤±è´¥: {}",e.getMessage());
  331 + }
  332 +```
  333 +
  334 +## 9. 注æ„事项
217 335
218 1. **字节åº**:所有多字节数æ®ä½¿ç”¨å¤§ç«¯åºï¼ˆBig-Endian) 336 1. **字节åº**:所有多字节数æ®ä½¿ç”¨å¤§ç«¯åºï¼ˆBig-Endian)
219 -2. **字符编ç **:字符串数æ®ä½¿ç”¨UTF-8ç¼–ç   
220 -3. **JSONæ ¼å¼**:包体数æ®å¿…须是有效的JSONæ ¼å¼  
221 -4. **长度é™åˆ¶**:å•个数æ®åŒ…建议ä¸è¶…过1MB  
222 -5. **错误处ç†**:解æžå¤±è´¥æ—¶åº”è¿”å›žç›¸åº”çš„é”™è¯¯ç  337 +2. **字符编ç **:字符串数æ®ä½¿ç”¨ UTF-8 ç¼–ç 
  338 +3. **JSON æ ¼å¼**:包体数æ®å¿…须是有效的 JSON æ ¼å¼
  339 +4. **长度é™åˆ¶**:å•个数æ®åŒ…建议ä¸è¶…过 1MB
  340 +5. **错误处ç†**:解æžå¤±è´¥æ—¶ä¼šæŠ›å‡º `TcpCodecException`
  341 +6. **åŠŸèƒ½ç æ˜ å°„**:目å‰åªæ”¯æŒå¿ƒè·³å’Œæ•°æ®ä¸ŠæŠ¥ä¸¤ç§æ¶ˆæ¯ç±»åž‹
  342 +
  343 +## 10. å议特点
  344 +
  345 +### 10.1 优势
  346 +
  347 +- **高效传输**:二进制格å¼ï¼Œæ•°æ®é‡å°
  348 +- **性能优化**:å‡å°‘è§£æžå¼€é”€
  349 +- **带宽节çœ**:相比 JSON æ ¼å¼èŠ‚çœå¸¦å®½
  350 +- **实时性好**:适åˆé«˜é¢‘æ•°æ®ä¼ è¾“
  351 +
  352 +### 10.2 适用场景
  353 +
  354 +- ✅ **高频数æ®ä¼ è¾“**:传感器数æ®å®žæ—¶ä¸ŠæŠ¥
  355 +- ✅ **带宽å—é™çŽ¯å¢ƒ**:移动网络ã€å«æ˜Ÿé€šä¿¡
  356 +- ✅ **æ€§èƒ½è¦æ±‚高**:需è¦ä½Žå»¶è¿Ÿçš„场景
  357 +- ✅ **è®¾å¤‡èµ„æºæœ‰é™**:嵌入å¼è®¾å¤‡ã€IoT 设备
  358 +
  359 +### 10.3 与 JSON å议对比
  360 +
  361 +| 特性 | 二进制åè®® | JSON åè®® |
  362 +|-------|-------|---------|
  363 +| æ•°æ®å¤§å° | å° | ç¨å¤§ |
  364 +| è§£æžæ€§èƒ½ | 高 | 中等 |
  365 +| å¯è¯»æ€§ | å·® | 优秀 |
  366 +| 调试难度 | 高 | 低 |
  367 +| 扩展性 | å·® | 优秀 |
  368 +| å®žçŽ°å¤æ‚度 | 高 | 低 |
  369 +
  370 +这样就完æˆäº† TCP 二进制å议的完整说明,与实际代ç å®žçŽ°å®Œå…¨ä¸€è‡´ã€‚
yudao-module-iot/yudao-module-iot-gateway/src/test/resources/tcp-json-packet-examples.md
1 -# TCP JSON格式协议说明 1 +# TCP JSON 格式协议说明
2 2
3 ## 1. 协议概述 3 ## 1. 协议概述
4 4
5 -TCP JSON格式协议采用纯JSON格式进行数据传输,参考了EMQX和HTTP模块的数据格式设计,具有以下优势: 5 +TCP JSON 格式协议采用纯 JSON 格式进行数据传输,参考了 EMQX 和 HTTP 模块的数据格式设计,具有以下优势:
6 6
7 -- **标准化**:使用标准JSON格式,易于解析和处理 7 +- **标准化**:使用标准 JSON 格式,易于解析和处理
8 - **可读性**:人类可读,便于调试和维护 8 - **可读性**:人类可读,便于调试和维护
9 - **扩展性**:可以轻松添加新字段,向后兼容 9 - **扩展性**:可以轻松添加新字段,向后兼容
10 -- **统一性**:与HTTP模块保持一致的数据格式 10 +- **统一性**:与 HTTP 模块保持一致的数据格式
  11 +- **简化性**:相比二进制协议,实现更简单,调试更容易
11 12
12 ## 2. 消息格式 13 ## 2. 消息格式
13 14
@@ -17,270 +18,294 @@ TCP JSON格式协议采用纯JSON格式进行数据传输,参考了EMQX和HTTP @@ -17,270 +18,294 @@ TCP JSON格式协议采用纯JSON格式进行数据传输,参考了EMQX和HTTP
17 { 18 {
18 "id": "消息唯一标识", 19 "id": "消息唯一标识",
19 "method": "消息方法", 20 "method": "消息方法",
20 - "deviceId": "设备ID", 21 + "deviceId": 设备ID,
21 "params": { 22 "params": {
22 // 消息参数 23 // 消息参数
23 }, 24 },
24 - "timestamp": 时间戳 25 + "timestamp": 时间戳,
  26 + "code": 响应码,
  27 + "message": "响应消息"
25 } 28 }
26 ``` 29 ```
27 30
28 ### 2.2 字段说明 31 ### 2.2 字段说明
29 32
30 -| 字段名 | 类型 | 必填 | 说明 |  
31 -|--------|------|------|------|  
32 -| id | String | 是 | 消息唯一标识,UUID格式 |  
33 -| method | String | 是 | 消息方法,如 thing.property.post |  
34 -| deviceId | Long | 是 | 设备ID |  
35 -| params | Object | 否 | 消息参数,具体内容根据method而定 |  
36 -| timestamp | Long | 是 | 时间戳(毫秒) |  
37 -| code | Integer | 否 | 响应码(下行消息使用) |  
38 -| message | String | 否 | 响应消息(下行消息使用) | 33 +| 字段名 | 类型 | 必填 | 说明 |
  34 +|-----------|---------|----|-------------------------------------|
  35 +| id | String | 是 | 消息唯一标识,如果为空会自动生成 UUID |
  36 +| method | String | 是 | 消息方法,如 `auth`、`thing.property.post` |
  37 +| deviceId | Long | 否 | 设备 ID |
  38 +| params | Object | 否 | 消息参数,具体内容根据 method 而定 |
  39 +| timestamp | Long | 是 | 时间戳(毫秒),自动生成 |
  40 +| code | Integer | 否 | 响应码(下行消息使用) |
  41 +| message | String | 否 | 响应消息(下行消息使用) |
39 42
40 ## 3. 消息类型 43 ## 3. 消息类型
41 44
42 -### 3.1 数据上报 (thing.property.post) 45 +### 3.1 设备认证 (auth)
43 46
44 -设备向服务器上报属性数据。 47 +设备连接后首先需要进行认证,认证成功后才能进行其他操作。
  48 +
  49 +#### 3.1.1 认证请求格式
45 50
46 **示例:** 51 **示例:**
  52 +
47 ```json 53 ```json
48 { 54 {
49 - "id": "8ac6a1db91e64aa9996143fdbac2cbfe",  
50 - "method": "thing.property.post",  
51 - "deviceId": 123456, 55 + "id": "auth_8ac6a1db91e64aa9996143fdbac2cbfe",
  56 + "method": "auth",
52 "params": { 57 "params": {
53 - "temperature": 25.5,  
54 - "humidity": 60.2,  
55 - "pressure": 1013.25,  
56 - "battery": 85 58 + "clientId": "device_001",
  59 + "username": "productKey_deviceName",
  60 + "password": "设备密码"
57 }, 61 },
58 "timestamp": 1753111026437 62 "timestamp": 1753111026437
59 } 63 }
60 ``` 64 ```
61 65
62 -### 3.2 心跳 (thing.state.online) 66 +**字段说明:**
  67 +| 字段名 | 类型 | 必填 | 说明 |
  68 +|--------|------|------|------|
  69 +| clientId | String | 是 | 客户端唯一标识 |
  70 +| username | String | 是 | 设备用户名,格式为 `productKey_deviceName` |
  71 +| password | String | 是 | 设备密码 |
63 72
64 -设备向服务器发送心跳保活。 73 +#### 3.1.2 认证响应格式
  74 +
  75 +**认证成功响应:**
65 76
66 -**示例:**  
67 ```json 77 ```json
68 { 78 {
69 - "id": "7db8c4e6408b40f8b2549ddd94f6bb02",  
70 - "method": "thing.state.online",  
71 - "deviceId": 123456,  
72 - "timestamp": 1753111026467 79 + "id": "auth_response_8ac6a1db91e64aa9996143fdbac2cbfe",
  80 + "requestId": "auth_8ac6a1db91e64aa9996143fdbac2cbfe",
  81 + "method": "auth",
  82 + "data": {
  83 + "success": true,
  84 + "message": "认证成功"
  85 + },
  86 + "code": 0,
  87 + "msg": "认证成功"
73 } 88 }
74 ``` 89 ```
75 90
76 -### 3.3 事件上报 (thing.event.post)  
77 -  
78 -设备向服务器上报事件信息。 91 +**认证失败响应:**
79 92
80 -**示例:**  
81 ```json 93 ```json
82 { 94 {
83 - "id": "9e7d72731b854916b1baa5088bd6a907",  
84 - "method": "thing.event.post",  
85 - "deviceId": 123456,  
86 - "params": {  
87 - "eventType": "alarm",  
88 - "level": "warning",  
89 - "description": "温度过高",  
90 - "value": 45.8 95 + "id": "auth_response_8ac6a1db91e64aa9996143fdbac2cbfe",
  96 + "requestId": "auth_8ac6a1db91e64aa9996143fdbac2cbfe",
  97 + "method": "auth",
  98 + "data": {
  99 + "success": false,
  100 + "message": "认证失败:用户名或密码错误"
91 }, 101 },
92 - "timestamp": 1753111026468 102 + "code": 401,
  103 + "msg": "认证失败:用户名或密码错误"
93 } 104 }
94 ``` 105 ```
95 106
96 -### 3.4 属性设置 (thing.property.set) 107 +#### 3.1.3 认证流程
  108 +
  109 +1. **设备连接** → 建立 TCP 连接
  110 +2. **发送认证请求** → 发送包含认证信息的 JSON 消息
  111 +3. **服务器验证** → 验证 clientId、username、password
  112 +4. **生成 Token** → 认证成功后生成 JWT Token(内部使用)
  113 +5. **设备上线** → 发送设备上线消息到消息总线
  114 +6. **返回响应** → 返回认证结果
  115 +7. **会话注册** → 注册设备会话,允许后续业务操作
97 116
98 -服务器向设备下发属性设置指令。 117 +#### 3.1.4 认证错误码
  118 +
  119 +| 错误码 | 说明 | 处理建议 |
  120 +|-----|-------|--------------|
  121 +| 401 | 认证失败 | 检查用户名、密码是否正确 |
  122 +| 400 | 参数错误 | 检查认证参数是否完整 |
  123 +| 404 | 设备不存在 | 检查设备是否已注册 |
  124 +| 500 | 服务器错误 | 联系管理员 |
  125 +
  126 +### 3.2 数据上报 (thing.property.post)
  127 +
  128 +设备向服务器上报属性数据。
99 129
100 **示例:** 130 **示例:**
101 ```json 131 ```json
102 { 132 {
103 - "id": "cmd_001",  
104 - "method": "thing.property.set",  
105 - "deviceId": 123456, 133 + "id": "8ac6a1db91e64aa9996143fdbac2cbfe",
  134 + "method": "thing.property.post",
  135 + "deviceId": 8,
106 "params": { 136 "params": {
107 - "targetTemperature": 22.0,  
108 - "mode": "auto" 137 + "temperature": 25.5,
  138 + "humidity": 60.2,
  139 + "pressure": 1013.25,
  140 + "battery": 85
109 }, 141 },
110 - "timestamp": 1753111026469 142 + "timestamp": 1753111026437
111 } 143 }
112 ``` 144 ```
113 145
114 -### 3.5 服务调用 (thing.service.invoke) 146 +### 3.3 心跳 (thing.state.update)
115 147
116 -服务器向设备调用服务。 148 +设备向服务器发送心跳保活。
117 149
118 **示例:** 150 **示例:**
119 ```json 151 ```json
120 { 152 {
121 - "id": "service_001",  
122 - "method": "thing.service.invoke",  
123 - "deviceId": 123456, 153 + "id": "7db8c4e6408b40f8b2549ddd94f6bb02",
  154 + "method": "thing.state.update",
  155 + "deviceId": 8,
124 "params": { 156 "params": {
125 - "service": "restart",  
126 - "args": {  
127 - "delay": 5  
128 - } 157 + "state": "1"
129 }, 158 },
130 - "timestamp": 1753111026470 159 + "timestamp": 1753111026467
131 } 160 }
132 ``` 161 ```
133 162
134 -## 4. 复杂数据示例 163 +### 3.4 消息方法常量
135 164
136 -### 4.1 多传感器综合数据 165 +支持的消息方法:
137 166
138 -```json  
139 -{  
140 - "id": "complex_001",  
141 - "method": "thing.property.post",  
142 - "deviceId": 789012,  
143 - "params": {  
144 - "environment": {  
145 - "temperature": 23.8,  
146 - "humidity": 55.0,  
147 - "co2": 420,  
148 - "pm25": 35  
149 - },  
150 - "location": {  
151 - "latitude": 39.9042,  
152 - "longitude": 116.4074,  
153 - "altitude": 43.5,  
154 - "speed": 0.0  
155 - },  
156 - "status": {  
157 - "battery": 78,  
158 - "signal": -65,  
159 - "online": true,  
160 - "version": "1.2.3"  
161 - }  
162 - },  
163 - "timestamp": 1753111026471  
164 -}  
165 -``` 167 +- `auth` - 设备认证
  168 +- `thing.property.post` - 数据上报
  169 +- `thing.state.update` - 心跳
166 170
167 -## 5. 与EMQX格式的兼容性 171 +## 4. 协议特点
168 172
169 -本协议设计参考了EMQX的消息格式,具有良好的兼容性: 173 +### 4.1 优势
170 174
171 -### 5.1 EMQX标准格式 175 +- **简单易用**:纯 JSON 格式,无需复杂的二进制解析
  176 +- **调试友好**:可以直接查看消息内容
  177 +- **扩展性强**:可以轻松添加新字段
  178 +- **标准化**:与 EMQX 等主流平台格式兼容
  179 +- **错误处理**:提供详细的错误信息和异常处理
  180 +- **安全性**:支持设备认证机制
172 181
173 -```json  
174 -{  
175 - "id": "msg_001",  
176 - "method": "thing.property.post",  
177 - "deviceId": 123456,  
178 - "params": {  
179 - "temperature": 25.5,  
180 - "humidity": 60.2  
181 - },  
182 - "timestamp": 1642781234567  
183 -}  
184 -``` 182 +### 4.2 与二进制协议对比
185 183
186 -### 5.2 兼容性说明 184 +| 特性 | 二进制协议 | JSON 协议 |
  185 +|-------|-------|----------|
  186 +| 可读性 | 差 | 优秀 |
  187 +| 调试难度 | 高 | 低 |
  188 +| 扩展性 | 差 | 优秀 |
  189 +| 解析复杂度 | 高 | 低 |
  190 +| 数据大小 | 小 | 稍大 |
  191 +| 标准化程度 | 低 | 高 |
  192 +| 实现复杂度 | 高 | 低 |
  193 +| 安全性 | 一般 | 优秀(支持认证) |
187 194
188 -- ✅ **字段名称**:与EMQX保持一致  
189 -- ✅ **数据类型**:完全兼容  
190 -- ✅ **消息结构**:结构相同  
191 -- ✅ **扩展字段**:支持自定义扩展 195 +### 4.3 适用场景
192 196
193 -## 6. 使用示例 197 +- ✅ **开发调试**:JSON 格式便于查看和调试
  198 +- ✅ **快速集成**:标准 JSON 格式,集成简单
  199 +- ✅ **协议扩展**:可以轻松添加新字段
  200 +- ✅ **多语言支持**:JSON 格式支持所有主流语言
  201 +- ✅ **云平台对接**:与主流 IoT 云平台格式兼容
  202 +- ✅ **安全要求**:支持设备认证和访问控制
194 203
195 -### 6.1 Java编码示例 204 +## 5. 最佳实践
196 205
197 -```java  
198 -// 创建编解码器  
199 -IotTcpJsonDeviceMessageCodec codec = new IotTcpJsonDeviceMessageCodec(); 206 +### 5.1 认证最佳实践
200 207
201 -// 创建数据上报消息  
202 -Map<String, Object> sensorData = Map.of(  
203 - "temperature", 25.5,  
204 - "humidity", 60.2  
205 -);  
206 -IotDeviceMessage message = IotDeviceMessage.requestOf("thing.property.post", sensorData);  
207 -message.setDeviceId(123456L); 208 +1. **连接即认证**:设备连接后立即进行认证
  209 +2. **重连机制**:连接断开后重新认证
  210 +3. **错误重试**:认证失败时适当重试
  211 +4. **安全传输**:使用 TLS 加密传输敏感信息
208 212
209 -// 编码为字节数组  
210 -byte[] jsonBytes = codec.encode(message); 213 +### 5.2 消息设计
211 214
212 -// 解码  
213 -IotDeviceMessage decoded = codec.decode(jsonBytes);  
214 -``` 215 +1. **保持简洁**:避免过深的嵌套结构
  216 +2. **字段命名**:使用驼峰命名法,保持一致性
  217 +3. **数据类型**:使用合适的数据类型,避免字符串表示数字
  218 +4. **时间戳**:统一使用毫秒级时间戳
  219 +
  220 +### 5.3 错误处理
  221 +
  222 +1. **参数验证**:确保必要字段存在且有效
  223 +2. **异常捕获**:正确处理编码解码异常
  224 +3. **日志记录**:记录详细的调试信息
  225 +4. **认证失败**:认证失败时及时关闭连接
  226 +
  227 +### 5.4 性能优化
  228 +
  229 +1. **批量上报**:可以在 params 中包含多个数据点
  230 +2. **连接复用**:保持 TCP 连接,避免频繁建立连接
  231 +3. **消息缓存**:客户端可以缓存消息,批量发送
  232 +4. **心跳间隔**:合理设置心跳间隔,避免过于频繁
215 233
216 -### 6.2 便捷方法示例 234 +## 6. 配置说明
217 235
218 -```java  
219 -// 快速编码数据上报  
220 -byte[] dataPacket = codec.encodeDataReport(sensorData, 123456L, "product_key", "device_name"); 236 +### 6.1 启用 JSON 协议
221 237
222 -// 快速编码心跳  
223 -byte[] heartbeatPacket = codec.encodeHeartbeat(123456L, "product_key", "device_name"); 238 +在配置文件中设置:
224 239
225 -// 快速编码事件  
226 -byte[] eventPacket = codec.encodeEventReport(eventData, 123456L, "product_key", "device_name"); 240 +```yaml
  241 +yudao:
  242 + iot:
  243 + gateway:
  244 + protocol:
  245 + tcp:
  246 + enabled: true
  247 + port: 8091
  248 + default-protocol: "JSON" # 使用 JSON 协议
227 ``` 249 ```
228 250
229 -## 7. 协议优势 251 +### 6.2 认证配置
230 252
231 -### 7.1 与原TCP二进制协议对比 253 +```yaml
  254 +yudao:
  255 + iot:
  256 + gateway:
  257 + token:
  258 + secret: "your-secret-key" # JWT 密钥
  259 + expiration: "24h" # Token 过期时间
  260 +```
232 261
233 -| 特性 | 二进制协议 | JSON协议 |  
234 -|------|------------|----------|  
235 -| 可读性 | 差 | 优秀 |  
236 -| 调试难度 | 高 | 低 |  
237 -| 扩展性 | 差 | 优秀 |  
238 -| 解析复杂度 | 高 | 低 |  
239 -| 数据大小 | 小 | 稍大 |  
240 -| 标准化程度 | 低 | 高 | 262 +## 7. 调试和监控
241 263
242 -### 7.2 适用场景 264 +### 7.1 日志级别
243 265
244 -- ✅ **开发调试**:JSON格式便于查看和调试  
245 -- ✅ **快速集成**:标准JSON格式,集成简单  
246 -- ✅ **协议扩展**:可以轻松添加新字段  
247 -- ✅ **多语言支持**:JSON格式支持所有主流语言  
248 -- ✅ **云平台对接**:与主流IoT云平台格式兼容 266 +```yaml
  267 +logging:
  268 + level:
  269 + cn.iocoder.yudao.module.iot.gateway.protocol.tcp: DEBUG
  270 +```
249 271
250 -## 8. 最佳实践 272 +### 7.2 调试信息
251 273
252 -### 8.1 消息设计建议 274 +编解码器会输出详细的调试日志:
253 275
254 -1. **保持简洁**:避免过深的嵌套结构  
255 -2. **字段命名**:使用驼峰命名法,保持一致性  
256 -3. **数据类型**:使用合适的数据类型,避免字符串表示数字  
257 -4. **时间戳**:统一使用毫秒级时间戳 276 +- 认证过程:显示认证请求和响应
  277 +- 编码成功:显示方法、长度、内容
  278 +- 解码过程:显示原始数据、解析结果
  279 +- 错误信息:详细的异常堆栈
258 280
259 -### 8.2 性能优化 281 +### 7.3 监控指标
260 282
261 -1. **批量上报**:可以在params中包含多个数据点  
262 -2. **压缩传输**:对于大数据量可以考虑gzip压缩  
263 -3. **缓存机制**:客户端可以缓存消息,批量发送 283 +- 认证成功率
  284 +- 消息处理数量
  285 +- 编解码成功率
  286 +- 处理延迟
  287 +- 错误率
  288 +- 在线设备数量
264 289
265 -### 8.3 错误处理 290 +## 8. 安全考虑
266 291
267 -1. **格式验证**:确保JSON格式正确  
268 -2. **字段检查**:验证必填字段是否存在  
269 -3. **异常处理**:提供详细的错误信息 292 +### 8.1 认证安全
270 293
271 -## 9. 迁移指南 294 +1. **密码强度**:使用强密码策略
  295 +2. **Token 过期**:设置合理的 Token 过期时间
  296 +3. **连接限制**:限制单个设备的并发连接数
  297 +4. **IP 白名单**:可选的 IP 访问控制
272 298
273 -### 9.1 从二进制协议迁移 299 +### 8.2 传输安全
274 300
275 -1. **保持兼容**:可以同时支持两种协议  
276 -2. **逐步迁移**:按设备类型逐步迁移  
277 -3. **测试验证**:充分测试新协议的稳定性 301 +1. **TLS 加密**:使用 TLS 1.2+ 加密传输
  302 +2. **证书验证**:验证服务器证书
  303 +3. **密钥管理**:安全存储和管理密钥
278 304
279 -### 9.2 配置变更 305 +### 8.3 数据安全
280 306
281 -```java  
282 -// 在设备配置中指定编解码器类型  
283 -device.setCodecType("TCP_JSON");  
284 -``` 307 +1. **敏感信息**:不在日志中记录密码等敏感信息
  308 +2. **数据验证**:验证所有输入数据
  309 +3. **访问控制**:基于 Token 的访问控制
285 310
286 -这样就完成了TCP协议向JSON格式的升级,提供了更好的可读性、扩展性和兼容性。 311 +这样就完成了 TCP JSON 格式协议的完整说明,包括认证流程的详细说明,与实际代码实现完全一致。