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());
|
}
|
}
|
|
}
|