2026-06-24 f4bd1f3c89d906131495a0aca5aaf82966378510
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
package cn.iocoder.yudao.module.iot.gateway.serialize.binary;
 
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.module.iot.core.enums.IotSerializeTypeEnum;
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
import cn.iocoder.yudao.module.iot.gateway.serialize.IotMessageSerializer;
import io.vertx.core.buffer.Buffer;
import lombok.extern.slf4j.Slf4j;
 
import java.nio.charset.StandardCharsets;
 
/**
 * 二进制格式的消息序列化器
 *
 * 二进制协议格式(所有数值使用大端序):
 *
 * <pre>
 * +--------+--------+--------+---------------------------+--------+--------+
 * | 魔术字 | 版本号 | 消息类型|         消息长度(4 字节)          |
 * +--------+--------+--------+---------------------------+--------+--------+
 * |           消息 ID 长度(2 字节)        |      消息 ID (变长字符串)         |
 * +--------+--------+--------+--------+--------+--------+--------+--------+
 * |           方法名长度(2 字节)        |      方法名(变长字符串)         |
 * +--------+--------+--------+--------+--------+--------+--------+--------+
 * |                        消息体数据(变长)                              |
 * +--------+--------+--------+--------+--------+--------+--------+--------+
 * </pre>
 *
 * 消息体格式:
 * - 请求消息:params 数据(JSON)
 * - 响应消息:code (4字节) + msg 长度(2字节) + msg 字符串 + data 数据(JSON)
 *
 * @author 芋道源码
 */
@Slf4j
public class IotBinarySerializer implements IotMessageSerializer {
 
    /**
     * 协议魔术字,用于协议识别
     */
    private static final byte MAGIC_NUMBER = (byte) 0x7E;
 
    /**
     * 协议版本号
     */
    private static final byte PROTOCOL_VERSION = (byte) 0x01;
 
    /**
     * 请求消息类型
     */
    private static final byte REQUEST = (byte) 0x01;
 
    /**
     * 响应消息类型
     */
    private static final byte RESPONSE = (byte) 0x02;
 
    /**
     * 协议头部固定长度(魔术字 + 版本号 + 消息类型 + 消息长度)
     */
    private static final int HEADER_FIXED_LENGTH = 7;
 
    /**
     * 最小消息长度(头部 + 消息ID长度 + 方法名长度)
     */
    private static final int MIN_MESSAGE_LENGTH = HEADER_FIXED_LENGTH + 4;
 
    @Override
    public IotSerializeTypeEnum getType() {
        return IotSerializeTypeEnum.BINARY;
    }
 
    @Override
    public byte[] serialize(IotDeviceMessage message) {
        Assert.notNull(message, "消息不能为空");
        Assert.notBlank(message.getMethod(), "消息方法不能为空");
        try {
            // 1. 确定消息类型
            byte messageType = determineMessageType(message);
            // 2. 构建消息体
            byte[] bodyData = buildMessageBody(message, messageType);
            // 3. 构建完整消息
            return buildCompleteMessage(message, messageType, bodyData);
        } catch (Exception e) {
            log.error("[encode][二进制消息编码失败,消息: {}]", message, e);
            throw new RuntimeException("二进制消息编码失败: " + e.getMessage(), e);
        }
    }
 
    @Override
    public IotDeviceMessage deserialize(byte[] bytes) {
        Assert.notNull(bytes, "待解码数据不能为空");
        Assert.isTrue(bytes.length >= MIN_MESSAGE_LENGTH, "数据包长度不足");
        try {
            Buffer buffer = Buffer.buffer(bytes);
            int index = 0;
 
            // 1. 验证魔术字
            byte magic = buffer.getByte(index++);
            Assert.isTrue(magic == MAGIC_NUMBER, "无效的协议魔术字: " + magic);
 
            // 2. 验证版本号
            byte version = buffer.getByte(index++);
            Assert.isTrue(version == PROTOCOL_VERSION, "不支持的协议版本: " + version);
 
            // 3. 读取消息类型
            byte messageType = buffer.getByte(index++);
            Assert.isTrue(messageType == REQUEST || messageType == RESPONSE, "无效的消息类型: " + messageType);
 
            // 4. 读取消息长度
            int messageLength = buffer.getInt(index);
            index += 4;
            Assert.isTrue(messageLength == buffer.length(),
                    "消息长度不匹配,期望: " + messageLength + ", 实际: " + buffer.length());
 
            // 5. 读取消息 ID
            short messageIdLength = buffer.getShort(index);
            index += 2;
            String messageId = buffer.getString(index, index + messageIdLength, StandardCharsets.UTF_8.name());
            index += messageIdLength;
 
            // 6. 读取方法名
            short methodLength = buffer.getShort(index);
            index += 2;
            String method = buffer.getString(index, index + methodLength, StandardCharsets.UTF_8.name());
            index += methodLength;
 
            // 7. 解析消息体
            return parseMessageBody(buffer, index, messageType, messageId, method);
        } catch (Exception e) {
            log.error("[decode][二进制消息解码失败,数据长度: {}]", bytes.length, e);
            throw new RuntimeException("二进制消息解码失败: " + e.getMessage(), e);
        }
    }
 
    /**
     * 快速检测是否为二进制格式
     *
     * @param data 数据
     * @return 是否为二进制格式
     */
    public static boolean isBinaryFormat(byte[] data) {
        return data != null && data.length >= 1 && data[0] == MAGIC_NUMBER;
    }
 
    private byte determineMessageType(IotDeviceMessage message) {
        if (message.getCode() != null) {
            return RESPONSE;
        }
        return REQUEST;
    }
 
    private byte[] buildMessageBody(IotDeviceMessage message, byte messageType) {
        Buffer bodyBuffer = Buffer.buffer();
        if (messageType == RESPONSE) {
            // code
            bodyBuffer.appendInt(message.getCode() != null ? message.getCode() : 0);
            // msg
            String msg = message.getMsg() != null ? message.getMsg() : "";
            byte[] msgBytes = StrUtil.utf8Bytes(msg);
            bodyBuffer.appendShort((short) msgBytes.length);
            bodyBuffer.appendBytes(msgBytes);
            // data
            if (message.getData() != null) {
                bodyBuffer.appendBytes(JsonUtils.toJsonByte(message.getData()));
            }
        } else {
            // 请求消息只处理 params 参数
            if (message.getParams() != null) {
                bodyBuffer.appendBytes(JsonUtils.toJsonByte(message.getParams()));
            }
        }
        return bodyBuffer.getBytes();
    }
 
    private byte[] buildCompleteMessage(IotDeviceMessage message, byte messageType, byte[] bodyData) {
        Buffer buffer = Buffer.buffer();
        // 1. 写入协议头部
        buffer.appendByte(MAGIC_NUMBER);
        buffer.appendByte(PROTOCOL_VERSION);
        buffer.appendByte(messageType);
        // 2. 预留消息长度位置
        int lengthPosition = buffer.length();
        buffer.appendInt(0);
        // 3. 写入消息 ID
        String messageId = StrUtil.isNotBlank(message.getRequestId()) ? message.getRequestId()
                : IotDeviceMessageUtils.generateMessageId();
        byte[] messageIdBytes = StrUtil.utf8Bytes(messageId);
        buffer.appendShort((short) messageIdBytes.length);
        buffer.appendBytes(messageIdBytes);
        // 4. 写入方法名
        byte[] methodBytes = StrUtil.utf8Bytes(message.getMethod());
        buffer.appendShort((short) methodBytes.length);
        buffer.appendBytes(methodBytes);
        // 5. 写入消息体
        buffer.appendBytes(bodyData);
        // 6. 更新消息长度
        buffer.setInt(lengthPosition, buffer.length());
        return buffer.getBytes();
    }
 
    private IotDeviceMessage parseMessageBody(Buffer buffer, int startIndex, byte messageType,
                                              String messageId, String method) {
        if (startIndex >= buffer.length()) {
            return IotDeviceMessage.of(messageId, method, null, null, null, null);
        }
 
        if (messageType == RESPONSE) {
            return parseResponseMessage(buffer, startIndex, messageId, method);
        } else {
            Object payload = parseJsonData(buffer, startIndex, buffer.length());
            return IotDeviceMessage.of(messageId, method, payload, null, null, null);
        }
    }
 
    private IotDeviceMessage parseResponseMessage(Buffer buffer, int startIndex, String messageId, String method) {
        int index = startIndex;
 
        // 1. 读取响应码
        Integer code = buffer.getInt(index);
        index += 4;
 
        // 2. 读取响应消息
        short msgLength = buffer.getShort(index);
        index += 2;
        String msg = msgLength > 0 ? buffer.getString(index, index + msgLength, StandardCharsets.UTF_8.name()) : null;
        index += msgLength;
 
        // 3. 读取响应数据
        Object data = null;
        if (index < buffer.length()) {
            data = parseJsonData(buffer, index, buffer.length());
        }
 
        return IotDeviceMessage.of(messageId, method, null, data, code, msg);
    }
 
    private Object parseJsonData(Buffer buffer, int startIndex, int endIndex) {
        if (startIndex >= endIndex) {
            return null;
        }
        try {
            String jsonStr = buffer.getString(startIndex, endIndex, StandardCharsets.UTF_8.name());
            return JsonUtils.parseObject(jsonStr, Object.class);
        } catch (Exception e) {
            log.warn("[parseJsonData][JSON 解析失败,返回原始字符串]", e);
            return buffer.getString(startIndex, endIndex, StandardCharsets.UTF_8.name());
        }
    }
 
}