Commit a77e1780ccfa9c42d325fe5da7d8e8a1fde7dfcf
1 parent
cc0d786d
feat(iot):【协议改造】移除 codecType,使用 protocolType 替代
Showing
25 changed files
with
159 additions
and
662 deletions
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/device/IoTDeviceApiImpl.java
| ... | ... | @@ -58,7 +58,7 @@ public class IoTDeviceApiImpl implements IotDeviceCommonApi { |
| 58 | 58 | return success(BeanUtils.toBean(device, IotDeviceRespDTO.class, deviceDTO -> { |
| 59 | 59 | IotProductDO product = productService.getProductFromCache(deviceDTO.getProductId()); |
| 60 | 60 | if (product != null) { |
| 61 | - deviceDTO.setCodecType(product.getCodecType()); | |
| 61 | + deviceDTO.setProtocolType(product.getProtocolType()).setSerializeType(product.getSerializeType()); | |
| 62 | 62 | } |
| 63 | 63 | })); |
| 64 | 64 | } | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/product/IotProductRespVO.java
| ... | ... | @@ -67,10 +67,15 @@ public class IotProductRespVO { |
| 67 | 67 | @DictFormat(DictTypeConstants.NET_TYPE) |
| 68 | 68 | private Integer netType; |
| 69 | 69 | |
| 70 | - @Schema(description = "数据格式", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") | |
| 71 | - @ExcelProperty(value = "数据格式", converter = DictConvert.class) | |
| 72 | - @DictFormat(DictTypeConstants.CODEC_TYPE) | |
| 73 | - private String codecType; | |
| 70 | + @Schema(description = "协议类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "mqtt") | |
| 71 | + @ExcelProperty(value = "协议类型", converter = DictConvert.class) | |
| 72 | + @DictFormat(DictTypeConstants.PROTOCOL_TYPE) | |
| 73 | + private String protocolType; | |
| 74 | + | |
| 75 | + @Schema(description = "序列化类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "json") | |
| 76 | + @ExcelProperty(value = "序列化类型", converter = DictConvert.class) | |
| 77 | + @DictFormat(DictTypeConstants.SERIALIZE_TYPE) | |
| 78 | + private String serializeType; | |
| 74 | 79 | |
| 75 | 80 | @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) |
| 76 | 81 | @ExcelProperty("创建时间") | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/product/IotProductSaveReqVO.java
| 1 | 1 | package cn.iocoder.yudao.module.iot.controller.admin.product.vo.product; |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.common.validation.InEnum; |
| 4 | +import cn.iocoder.yudao.module.iot.core.enums.IotProtocolTypeEnum; | |
| 5 | +import cn.iocoder.yudao.module.iot.core.enums.IotSerializeTypeEnum; | |
| 4 | 6 | import cn.iocoder.yudao.module.iot.enums.product.IotNetTypeEnum; |
| 5 | 7 | import cn.iocoder.yudao.module.iot.enums.product.IotProductDeviceTypeEnum; |
| 6 | 8 | import io.swagger.v3.oas.annotations.media.Schema; |
| ... | ... | @@ -44,9 +46,15 @@ public class IotProductSaveReqVO { |
| 44 | 46 | @InEnum(value = IotNetTypeEnum.class, message = "联网方式必须是 {value}") |
| 45 | 47 | private Integer netType; |
| 46 | 48 | |
| 47 | - @Schema(description = "数据格式", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") | |
| 48 | - @NotEmpty(message = "数据格式不能为空") | |
| 49 | - private String codecType; | |
| 49 | + @Schema(description = "协议类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "mqtt") | |
| 50 | + @InEnum(value = IotProtocolTypeEnum.class, message = "协议类型必须是 {value}") | |
| 51 | + @NotEmpty(message = "协议类型不能为空") | |
| 52 | + private String protocolType; | |
| 53 | + | |
| 54 | + @Schema(description = "序列化类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "json") | |
| 55 | + @InEnum(value = IotSerializeTypeEnum.class, message = "序列化类型必须是 {value}") | |
| 56 | + @NotEmpty(message = "序列化类型不能为空") | |
| 57 | + private String serializeType; | |
| 50 | 58 | |
| 51 | 59 | @Schema(description = "是否开启动态注册", example = "false") |
| 52 | 60 | @NotNull(message = "是否开启动态注册不能为空") | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/product/IotProductDO.java
| ... | ... | @@ -78,12 +78,16 @@ public class IotProductDO extends TenantBaseDO { |
| 78 | 78 | */ |
| 79 | 79 | private Integer netType; |
| 80 | 80 | /** |
| 81 | - * 数据格式(编解码器类型) | |
| 81 | + * 协议类型 | |
| 82 | 82 | * <p> |
| 83 | - * 字典 {@link cn.iocoder.yudao.module.iot.enums.DictTypeConstants#CODEC_TYPE} | |
| 84 | - * | |
| 85 | - * 目的:用于 gateway-server 解析消息格式 | |
| 83 | + * 枚举 {@link cn.iocoder.yudao.module.iot.core.enums.IotProtocolTypeEnum} | |
| 86 | 84 | */ |
| 87 | - private String codecType; | |
| 85 | + private String protocolType; | |
| 86 | + /** | |
| 87 | + * 序列化类型 | |
| 88 | + * <p> | |
| 89 | + * 枚举 {@link cn.iocoder.yudao.module.iot.core.enums.IotSerializeTypeEnum} | |
| 90 | + */ | |
| 91 | + private String serializeType; | |
| 88 | 92 | |
| 89 | 93 | } |
| 90 | 94 | \ No newline at end of file | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/DictTypeConstants.java
| ... | ... | @@ -8,8 +8,8 @@ package cn.iocoder.yudao.module.iot.enums; |
| 8 | 8 | public class DictTypeConstants { |
| 9 | 9 | |
| 10 | 10 | public static final String NET_TYPE = "iot_net_type"; |
| 11 | - public static final String LOCATION_TYPE = "iot_location_type"; | |
| 12 | - public static final String CODEC_TYPE = "iot_codec_type"; | |
| 11 | + public static final String PROTOCOL_TYPE = "iot_protocol_type"; | |
| 12 | + public static final String SERIALIZE_TYPE = "iot_serialize_type"; | |
| 13 | 13 | |
| 14 | 14 | public static final String PRODUCT_STATUS = "iot_product_status"; |
| 15 | 15 | public static final String PRODUCT_DEVICE_TYPE = "iot_product_device_type"; | ... | ... |
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/biz/dto/IotDeviceRespDTO.java
| ... | ... | @@ -34,8 +34,12 @@ public class IotDeviceRespDTO { |
| 34 | 34 | */ |
| 35 | 35 | private Long productId; |
| 36 | 36 | /** |
| 37 | - * 编解码器类型 | |
| 37 | + * 协议类型 | |
| 38 | 38 | */ |
| 39 | - private String codecType; | |
| 39 | + private String protocolType; | |
| 40 | + /** | |
| 41 | + * 序列化类型 | |
| 42 | + */ | |
| 43 | + private String serializeType; | |
| 40 | 44 | |
| 41 | 45 | } |
| 42 | 46 | \ No newline at end of file | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/codec/IotDeviceMessageCodec.java deleted
| 1 | -package cn.iocoder.yudao.module.iot.gateway.codec; | |
| 2 | - | |
| 3 | -import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | |
| 4 | - | |
| 5 | -/** | |
| 6 | - * {@link cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage} 的编解码器 | |
| 7 | - * | |
| 8 | - * @author 芋道源码 | |
| 9 | - */ | |
| 10 | -public interface IotDeviceMessageCodec { | |
| 11 | - | |
| 12 | - /** | |
| 13 | - * 编码消息 | |
| 14 | - * | |
| 15 | - * @param message 消息 | |
| 16 | - * @return 编码后的消息内容 | |
| 17 | - */ | |
| 18 | - byte[] encode(IotDeviceMessage message); | |
| 19 | - | |
| 20 | - /** | |
| 21 | - * 解码消息 | |
| 22 | - * | |
| 23 | - * @param bytes 消息内容 | |
| 24 | - * @return 解码后的消息内容 | |
| 25 | - */ | |
| 26 | - IotDeviceMessage decode(byte[] bytes); | |
| 27 | - | |
| 28 | - /** | |
| 29 | - * @return 数据格式(编码器类型) | |
| 30 | - */ | |
| 31 | - String type(); | |
| 32 | - | |
| 33 | -} |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/codec/alink/IotAlinkDeviceMessageCodec.java deleted
| 1 | -package cn.iocoder.yudao.module.iot.gateway.codec.alink; | |
| 2 | - | |
| 3 | -import cn.hutool.core.lang.Assert; | |
| 4 | -import cn.iocoder.yudao.framework.common.pojo.CommonResult; | |
| 5 | -import cn.iocoder.yudao.framework.common.util.json.JsonUtils; | |
| 6 | -import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | |
| 7 | -import cn.iocoder.yudao.module.iot.gateway.codec.IotDeviceMessageCodec; | |
| 8 | -import lombok.AllArgsConstructor; | |
| 9 | -import lombok.Data; | |
| 10 | -import lombok.NoArgsConstructor; | |
| 11 | -import org.springframework.stereotype.Component; | |
| 12 | - | |
| 13 | -/** | |
| 14 | - * 阿里云 Alink {@link IotDeviceMessage} 的编解码器 | |
| 15 | - * | |
| 16 | - * @author 芋道源码 | |
| 17 | - */ | |
| 18 | -@Component | |
| 19 | -public class IotAlinkDeviceMessageCodec implements IotDeviceMessageCodec { | |
| 20 | - | |
| 21 | - public static final String TYPE = "Alink"; | |
| 22 | - | |
| 23 | - @Data | |
| 24 | - @NoArgsConstructor | |
| 25 | - @AllArgsConstructor | |
| 26 | - private static class AlinkMessage { | |
| 27 | - | |
| 28 | - public static final String VERSION_1 = "1.0"; | |
| 29 | - | |
| 30 | - /** | |
| 31 | - * 消息 ID,且每个消息 ID 在当前设备具有唯一性 | |
| 32 | - */ | |
| 33 | - private String id; | |
| 34 | - | |
| 35 | - /** | |
| 36 | - * 版本号 | |
| 37 | - */ | |
| 38 | - private String version; | |
| 39 | - | |
| 40 | - /** | |
| 41 | - * 请求方法 | |
| 42 | - */ | |
| 43 | - private String method; | |
| 44 | - | |
| 45 | - /** | |
| 46 | - * 请求参数 | |
| 47 | - */ | |
| 48 | - private Object params; | |
| 49 | - | |
| 50 | - /** | |
| 51 | - * 响应结果 | |
| 52 | - */ | |
| 53 | - private Object data; | |
| 54 | - /** | |
| 55 | - * 响应错误码 | |
| 56 | - */ | |
| 57 | - private Integer code; | |
| 58 | - /** | |
| 59 | - * 响应提示 | |
| 60 | - * | |
| 61 | - * 特殊:这里阿里云是 message,为了保持和项目的 {@link CommonResult#getMsg()} 一致。 | |
| 62 | - */ | |
| 63 | - private String msg; | |
| 64 | - | |
| 65 | - } | |
| 66 | - | |
| 67 | - @Override | |
| 68 | - public String type() { | |
| 69 | - return TYPE; | |
| 70 | - } | |
| 71 | - | |
| 72 | - @Override | |
| 73 | - public byte[] encode(IotDeviceMessage message) { | |
| 74 | - AlinkMessage alinkMessage = new AlinkMessage(message.getRequestId(), AlinkMessage.VERSION_1, | |
| 75 | - message.getMethod(), message.getParams(), message.getData(), message.getCode(), message.getMsg()); | |
| 76 | - return JsonUtils.toJsonByte(alinkMessage); | |
| 77 | - } | |
| 78 | - | |
| 79 | - @Override | |
| 80 | - @SuppressWarnings("DataFlowIssue") | |
| 81 | - public IotDeviceMessage decode(byte[] bytes) { | |
| 82 | - AlinkMessage alinkMessage = JsonUtils.parseObject(bytes, AlinkMessage.class); | |
| 83 | - Assert.notNull(alinkMessage, "消息不能为空"); | |
| 84 | - Assert.equals(alinkMessage.getVersion(), AlinkMessage.VERSION_1, "消息版本号必须是 1.0"); | |
| 85 | - return IotDeviceMessage.of(alinkMessage.getId(), alinkMessage.getMethod(), alinkMessage.getParams(), | |
| 86 | - alinkMessage.getData(), alinkMessage.getCode(), alinkMessage.getMsg()); | |
| 87 | - } | |
| 88 | - | |
| 89 | -} |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/codec/package-info.java deleted
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/codec/tcp/IotTcpBinaryDeviceMessageCodec.java deleted
| 1 | -package cn.iocoder.yudao.module.iot.gateway.codec.tcp; | |
| 2 | - | |
| 3 | -import cn.hutool.core.lang.Assert; | |
| 4 | -import cn.hutool.core.util.StrUtil; | |
| 5 | -import cn.iocoder.yudao.framework.common.util.json.JsonUtils; | |
| 6 | -import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | |
| 7 | -import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; | |
| 8 | -import cn.iocoder.yudao.module.iot.gateway.codec.IotDeviceMessageCodec; | |
| 9 | -import io.vertx.core.buffer.Buffer; | |
| 10 | -import lombok.extern.slf4j.Slf4j; | |
| 11 | -import org.springframework.stereotype.Component; | |
| 12 | - | |
| 13 | -import java.nio.charset.StandardCharsets; | |
| 14 | - | |
| 15 | -/** | |
| 16 | - * TCP/UDP 二进制格式 {@link IotDeviceMessage} 编解码器 | |
| 17 | - * <p> | |
| 18 | - * 二进制协议格式(所有数值使用大端序): | |
| 19 | - * | |
| 20 | - * <pre> | |
| 21 | - * +--------+--------+--------+---------------------------+--------+--------+ | |
| 22 | - * | 魔术字 | 版本号 | 消息类型| 消息长度(4 字节) | | |
| 23 | - * +--------+--------+--------+---------------------------+--------+--------+ | |
| 24 | - * | 消息 ID 长度(2 字节) | 消息 ID (变长字符串) | | |
| 25 | - * +--------+--------+--------+--------+--------+--------+--------+--------+ | |
| 26 | - * | 方法名长度(2 字节) | 方法名(变长字符串) | | |
| 27 | - * +--------+--------+--------+--------+--------+--------+--------+--------+ | |
| 28 | - * | 消息体数据(变长) | | |
| 29 | - * +--------+--------+--------+--------+--------+--------+--------+--------+ | |
| 30 | - * </pre> | |
| 31 | - * <p> | |
| 32 | - * 消息体格式: | |
| 33 | - * - 请求消息:params 数据(JSON) | |
| 34 | - * - 响应消息:code (4字节) + msg 长度(2字节) + msg 字符串 + data 数据(JSON) | |
| 35 | - * <p> | |
| 36 | - * 注意:deviceId 不包含在协议中,由服务器根据连接上下文自动设置 | |
| 37 | - * | |
| 38 | - * @author 芋道源码 | |
| 39 | - */ | |
| 40 | -@Slf4j | |
| 41 | -@Component | |
| 42 | -public class IotTcpBinaryDeviceMessageCodec implements IotDeviceMessageCodec { | |
| 43 | - | |
| 44 | - public static final String TYPE = "TCP_BINARY"; | |
| 45 | - | |
| 46 | - /** | |
| 47 | - * 协议魔术字,用于协议识别 | |
| 48 | - */ | |
| 49 | - private static final byte MAGIC_NUMBER = (byte) 0x7E; | |
| 50 | - | |
| 51 | - /** | |
| 52 | - * 协议版本号 | |
| 53 | - */ | |
| 54 | - private static final byte PROTOCOL_VERSION = (byte) 0x01; | |
| 55 | - | |
| 56 | - /** | |
| 57 | - * 请求消息类型 | |
| 58 | - */ | |
| 59 | - private static final byte REQUEST = (byte) 0x01; | |
| 60 | - | |
| 61 | - /** | |
| 62 | - * 响应消息类型 | |
| 63 | - */ | |
| 64 | - private static final byte RESPONSE = (byte) 0x02; | |
| 65 | - | |
| 66 | - /** | |
| 67 | - * 协议头部固定长度(魔术字 + 版本号 + 消息类型 + 消息长度) | |
| 68 | - */ | |
| 69 | - private static final int HEADER_FIXED_LENGTH = 7; | |
| 70 | - | |
| 71 | - /** | |
| 72 | - * 最小消息长度(头部 + 消息ID长度 + 方法名长度) | |
| 73 | - */ | |
| 74 | - private static final int MIN_MESSAGE_LENGTH = HEADER_FIXED_LENGTH + 4; | |
| 75 | - | |
| 76 | - @Override | |
| 77 | - public String type() { | |
| 78 | - return TYPE; | |
| 79 | - } | |
| 80 | - | |
| 81 | - @Override | |
| 82 | - public byte[] encode(IotDeviceMessage message) { | |
| 83 | - Assert.notNull(message, "消息不能为空"); | |
| 84 | - Assert.notBlank(message.getMethod(), "消息方法不能为空"); | |
| 85 | - try { | |
| 86 | - // 1. 确定消息类型 | |
| 87 | - byte messageType = determineMessageType(message); | |
| 88 | - // 2. 构建消息体 | |
| 89 | - byte[] bodyData = buildMessageBody(message, messageType); | |
| 90 | - // 3. 构建完整消息 | |
| 91 | - return buildCompleteMessage(message, messageType, bodyData); | |
| 92 | - } catch (Exception e) { | |
| 93 | - log.error("[encode][TCP 二进制消息编码失败,消息: {}]", message, e); | |
| 94 | - throw new RuntimeException("TCP 二进制消息编码失败: " + e.getMessage(), e); | |
| 95 | - } | |
| 96 | - } | |
| 97 | - | |
| 98 | - @Override | |
| 99 | - public IotDeviceMessage decode(byte[] bytes) { | |
| 100 | - Assert.notNull(bytes, "待解码数据不能为空"); | |
| 101 | - Assert.isTrue(bytes.length >= MIN_MESSAGE_LENGTH, "数据包长度不足"); | |
| 102 | - try { | |
| 103 | - Buffer buffer = Buffer.buffer(bytes); | |
| 104 | - // 解析协议头部和消息内容 | |
| 105 | - int index = 0; | |
| 106 | - // 1. 验证魔术字 | |
| 107 | - byte magic = buffer.getByte(index++); | |
| 108 | - Assert.isTrue(magic == MAGIC_NUMBER, "无效的协议魔术字: " + magic); | |
| 109 | - | |
| 110 | - // 2. 验证版本号 | |
| 111 | - byte version = buffer.getByte(index++); | |
| 112 | - Assert.isTrue(version == PROTOCOL_VERSION, "不支持的协议版本: " + version); | |
| 113 | - | |
| 114 | - // 3. 读取消息类型 | |
| 115 | - byte messageType = buffer.getByte(index++); | |
| 116 | - // 直接验证消息类型,无需抽取方法 | |
| 117 | - Assert.isTrue(messageType == REQUEST || messageType == RESPONSE, | |
| 118 | - "无效的消息类型: " + messageType); | |
| 119 | - | |
| 120 | - // 4. 读取消息长度 | |
| 121 | - int messageLength = buffer.getInt(index); | |
| 122 | - index += 4; | |
| 123 | - Assert.isTrue(messageLength == buffer.length(), | |
| 124 | - "消息长度不匹配,期望: " + messageLength + ", 实际: " + buffer.length()); | |
| 125 | - | |
| 126 | - // 5. 读取消息 ID | |
| 127 | - short messageIdLength = buffer.getShort(index); | |
| 128 | - index += 2; | |
| 129 | - String messageId = buffer.getString(index, index + messageIdLength, StandardCharsets.UTF_8.name()); | |
| 130 | - index += messageIdLength; | |
| 131 | - | |
| 132 | - // 6. 读取方法名 | |
| 133 | - short methodLength = buffer.getShort(index); | |
| 134 | - index += 2; | |
| 135 | - String method = buffer.getString(index, index + methodLength, StandardCharsets.UTF_8.name()); | |
| 136 | - index += methodLength; | |
| 137 | - | |
| 138 | - // 7. 解析消息体 | |
| 139 | - return parseMessageBody(buffer, index, messageType, messageId, method); | |
| 140 | - } catch (Exception e) { | |
| 141 | - log.error("[decode][TCP 二进制消息解码失败,数据长度: {}]", bytes.length, e); | |
| 142 | - throw new RuntimeException("TCP 二进制消息解码失败: " + e.getMessage(), e); | |
| 143 | - } | |
| 144 | - } | |
| 145 | - | |
| 146 | - /** | |
| 147 | - * 确定消息类型 | |
| 148 | - * 优化后的判断逻辑:有响应字段就是响应消息,否则就是请求消息 | |
| 149 | - */ | |
| 150 | - private byte determineMessageType(IotDeviceMessage message) { | |
| 151 | - // 判断是否为响应消息:有响应码或响应消息时为响应 | |
| 152 | - if (message.getCode() != null) { | |
| 153 | - return RESPONSE; | |
| 154 | - } | |
| 155 | - // 默认为请求消息 | |
| 156 | - return REQUEST; | |
| 157 | - } | |
| 158 | - | |
| 159 | - /** | |
| 160 | - * 构建消息体 | |
| 161 | - */ | |
| 162 | - private byte[] buildMessageBody(IotDeviceMessage message, byte messageType) { | |
| 163 | - Buffer bodyBuffer = Buffer.buffer(); | |
| 164 | - if (messageType == RESPONSE) { | |
| 165 | - // code | |
| 166 | - bodyBuffer.appendInt(message.getCode() != null ? message.getCode() : 0); | |
| 167 | - // msg | |
| 168 | - String msg = message.getMsg() != null ? message.getMsg() : ""; | |
| 169 | - byte[] msgBytes = StrUtil.utf8Bytes(msg); | |
| 170 | - bodyBuffer.appendShort((short) msgBytes.length); | |
| 171 | - bodyBuffer.appendBytes(msgBytes); | |
| 172 | - // data | |
| 173 | - if (message.getData() != null) { | |
| 174 | - bodyBuffer.appendBytes(JsonUtils.toJsonByte(message.getData())); | |
| 175 | - } | |
| 176 | - } else { | |
| 177 | - // 请求消息只处理 params 参数 | |
| 178 | - // TODO @haohao:如果为空,是不是得写个长度 0 哈? | |
| 179 | - if (message.getParams() != null) { | |
| 180 | - bodyBuffer.appendBytes(JsonUtils.toJsonByte(message.getParams())); | |
| 181 | - } | |
| 182 | - } | |
| 183 | - return bodyBuffer.getBytes(); | |
| 184 | - } | |
| 185 | - | |
| 186 | - /** | |
| 187 | - * 构建完整消息 | |
| 188 | - */ | |
| 189 | - private byte[] buildCompleteMessage(IotDeviceMessage message, byte messageType, byte[] bodyData) { | |
| 190 | - Buffer buffer = Buffer.buffer(); | |
| 191 | - // 1. 写入协议头部 | |
| 192 | - buffer.appendByte(MAGIC_NUMBER); | |
| 193 | - buffer.appendByte(PROTOCOL_VERSION); | |
| 194 | - buffer.appendByte(messageType); | |
| 195 | - // 2. 预留消息长度位置(在 5. 更新消息长度) | |
| 196 | - int lengthPosition = buffer.length(); | |
| 197 | - buffer.appendInt(0); | |
| 198 | - // 3. 写入消息 ID | |
| 199 | - String messageId = StrUtil.isNotBlank(message.getRequestId()) ? message.getRequestId() | |
| 200 | - : IotDeviceMessageUtils.generateMessageId(); | |
| 201 | - byte[] messageIdBytes = StrUtil.utf8Bytes(messageId); | |
| 202 | - buffer.appendShort((short) messageIdBytes.length); | |
| 203 | - buffer.appendBytes(messageIdBytes); | |
| 204 | - // 4. 写入方法名 | |
| 205 | - byte[] methodBytes = StrUtil.utf8Bytes(message.getMethod()); | |
| 206 | - buffer.appendShort((short) methodBytes.length); | |
| 207 | - buffer.appendBytes(methodBytes); | |
| 208 | - // 5. 写入消息体 | |
| 209 | - buffer.appendBytes(bodyData); | |
| 210 | - // 6. 更新消息长度 | |
| 211 | - buffer.setInt(lengthPosition, buffer.length()); | |
| 212 | - return buffer.getBytes(); | |
| 213 | - } | |
| 214 | - | |
| 215 | - /** | |
| 216 | - * 解析消息体 | |
| 217 | - */ | |
| 218 | - private IotDeviceMessage parseMessageBody(Buffer buffer, int startIndex, byte messageType, | |
| 219 | - String messageId, String method) { | |
| 220 | - if (startIndex >= buffer.length()) { | |
| 221 | - // 空消息体 | |
| 222 | - return IotDeviceMessage.of(messageId, method, null, null, null, null); | |
| 223 | - } | |
| 224 | - | |
| 225 | - if (messageType == RESPONSE) { | |
| 226 | - // 响应消息:解析 code + msg + data | |
| 227 | - return parseResponseMessage(buffer, startIndex, messageId, method); | |
| 228 | - } else { | |
| 229 | - // 请求消息:解析 payload | |
| 230 | - Object payload = parseJsonData(buffer, startIndex, buffer.length()); | |
| 231 | - return IotDeviceMessage.of(messageId, method, payload, null, null, null); | |
| 232 | - } | |
| 233 | - } | |
| 234 | - | |
| 235 | - /** | |
| 236 | - * 解析响应消息 | |
| 237 | - */ | |
| 238 | - private IotDeviceMessage parseResponseMessage(Buffer buffer, int startIndex, String messageId, String method) { | |
| 239 | - int index = startIndex; | |
| 240 | - | |
| 241 | - // 1. 读取响应码 | |
| 242 | - Integer code = buffer.getInt(index); | |
| 243 | - index += 4; | |
| 244 | - | |
| 245 | - // 2. 读取响应消息 | |
| 246 | - short msgLength = buffer.getShort(index); | |
| 247 | - index += 2; | |
| 248 | - String msg = msgLength > 0 ? buffer.getString(index, index + msgLength, StandardCharsets.UTF_8.name()) : null; | |
| 249 | - index += msgLength; | |
| 250 | - | |
| 251 | - // 3. 读取响应数据 | |
| 252 | - Object data = null; | |
| 253 | - if (index < buffer.length()) { | |
| 254 | - data = parseJsonData(buffer, index, buffer.length()); | |
| 255 | - } | |
| 256 | - | |
| 257 | - return IotDeviceMessage.of(messageId, method, null, data, code, msg); | |
| 258 | - } | |
| 259 | - | |
| 260 | - /** | |
| 261 | - * 解析 JSON 数据 | |
| 262 | - */ | |
| 263 | - private Object parseJsonData(Buffer buffer, int startIndex, int endIndex) { | |
| 264 | - if (startIndex >= endIndex) { | |
| 265 | - return null; | |
| 266 | - } | |
| 267 | - try { | |
| 268 | - String jsonStr = buffer.getString(startIndex, endIndex, StandardCharsets.UTF_8.name()); | |
| 269 | - return JsonUtils.parseObject(jsonStr, Object.class); | |
| 270 | - } catch (Exception e) { | |
| 271 | - log.warn("[parseJsonData][JSON 解析失败,返回原始字符串]", e); | |
| 272 | - return buffer.getString(startIndex, endIndex, StandardCharsets.UTF_8.name()); | |
| 273 | - } | |
| 274 | - } | |
| 275 | - | |
| 276 | - /** | |
| 277 | - * 快速检测是否为二进制格式 | |
| 278 | - * | |
| 279 | - * @param data 数据 | |
| 280 | - * @return 是否为二进制格式 | |
| 281 | - */ | |
| 282 | - public static boolean isBinaryFormatQuick(byte[] data) { | |
| 283 | - return data != null && data.length >= 1 && data[0] == MAGIC_NUMBER; | |
| 284 | - } | |
| 285 | - | |
| 286 | -} | |
| 287 | 0 | \ No newline at end of file |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/codec/tcp/IotTcpJsonDeviceMessageCodec.java deleted
| 1 | -package cn.iocoder.yudao.module.iot.gateway.codec.tcp; | |
| 2 | - | |
| 3 | -import cn.hutool.core.lang.Assert; | |
| 4 | -import cn.hutool.core.util.StrUtil; | |
| 5 | -import cn.iocoder.yudao.framework.common.util.json.JsonUtils; | |
| 6 | -import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | |
| 7 | -import cn.iocoder.yudao.module.iot.gateway.codec.IotDeviceMessageCodec; | |
| 8 | -import lombok.AllArgsConstructor; | |
| 9 | -import lombok.Data; | |
| 10 | -import lombok.NoArgsConstructor; | |
| 11 | -import org.springframework.stereotype.Component; | |
| 12 | - | |
| 13 | -/** | |
| 14 | - * TCP/UDP JSON 格式 {@link IotDeviceMessage} 编解码器 | |
| 15 | - * | |
| 16 | - * 采用纯 JSON 格式传输,格式如下: | |
| 17 | - * { | |
| 18 | - * "id": "消息 ID", | |
| 19 | - * "method": "消息方法", | |
| 20 | - * "params": {...}, // 请求参数 | |
| 21 | - * "data": {...}, // 响应结果 | |
| 22 | - * "code": 200, // 响应错误码 | |
| 23 | - * "msg": "success", // 响应提示 | |
| 24 | - * "timestamp": 时间戳 | |
| 25 | - * } | |
| 26 | - * | |
| 27 | - * @author 芋道源码 | |
| 28 | - */ | |
| 29 | -@Component | |
| 30 | -public class IotTcpJsonDeviceMessageCodec implements IotDeviceMessageCodec { | |
| 31 | - | |
| 32 | - public static final String TYPE = "TCP_JSON"; | |
| 33 | - | |
| 34 | - @Data | |
| 35 | - @NoArgsConstructor | |
| 36 | - @AllArgsConstructor | |
| 37 | - private static class TcpJsonMessage { | |
| 38 | - | |
| 39 | - /** | |
| 40 | - * 消息 ID,且每个消息 ID 在当前设备具有唯一性 | |
| 41 | - */ | |
| 42 | - private String id; | |
| 43 | - | |
| 44 | - /** | |
| 45 | - * 请求方法 | |
| 46 | - */ | |
| 47 | - private String method; | |
| 48 | - | |
| 49 | - /** | |
| 50 | - * 请求参数 | |
| 51 | - */ | |
| 52 | - private Object params; | |
| 53 | - | |
| 54 | - /** | |
| 55 | - * 响应结果 | |
| 56 | - */ | |
| 57 | - private Object data; | |
| 58 | - | |
| 59 | - /** | |
| 60 | - * 响应错误码 | |
| 61 | - */ | |
| 62 | - private Integer code; | |
| 63 | - | |
| 64 | - /** | |
| 65 | - * 响应提示 | |
| 66 | - */ | |
| 67 | - private String msg; | |
| 68 | - | |
| 69 | - /** | |
| 70 | - * 时间戳 | |
| 71 | - */ | |
| 72 | - private Long timestamp; | |
| 73 | - | |
| 74 | - } | |
| 75 | - | |
| 76 | - @Override | |
| 77 | - public String type() { | |
| 78 | - return TYPE; | |
| 79 | - } | |
| 80 | - | |
| 81 | - @Override | |
| 82 | - public byte[] encode(IotDeviceMessage message) { | |
| 83 | - TcpJsonMessage tcpJsonMessage = new TcpJsonMessage( | |
| 84 | - message.getRequestId(), | |
| 85 | - message.getMethod(), | |
| 86 | - message.getParams(), | |
| 87 | - message.getData(), | |
| 88 | - message.getCode(), | |
| 89 | - message.getMsg(), | |
| 90 | - System.currentTimeMillis()); | |
| 91 | - return JsonUtils.toJsonByte(tcpJsonMessage); | |
| 92 | - } | |
| 93 | - | |
| 94 | - @Override | |
| 95 | - @SuppressWarnings("DataFlowIssue") | |
| 96 | - public IotDeviceMessage decode(byte[] bytes) { | |
| 97 | - String jsonStr = StrUtil.utf8Str(bytes).trim(); | |
| 98 | - TcpJsonMessage tcpJsonMessage = JsonUtils.parseObject(jsonStr, TcpJsonMessage.class); | |
| 99 | - Assert.notNull(tcpJsonMessage, "消息不能为空"); | |
| 100 | - Assert.notBlank(tcpJsonMessage.getMethod(), "消息方法不能为空"); | |
| 101 | - return IotDeviceMessage.of( | |
| 102 | - tcpJsonMessage.getId(), | |
| 103 | - tcpJsonMessage.getMethod(), | |
| 104 | - tcpJsonMessage.getParams(), | |
| 105 | - tcpJsonMessage.getData(), | |
| 106 | - tcpJsonMessage.getCode(), | |
| 107 | - tcpJsonMessage.getMsg()); | |
| 108 | - } | |
| 109 | - | |
| 110 | -} |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/emqx/handler/downstream/IotEmqxDownstreamHandler.java
| ... | ... | @@ -53,9 +53,10 @@ public class IotEmqxDownstreamHandler { |
| 53 | 53 | return; |
| 54 | 54 | } |
| 55 | 55 | // 2.2 构建载荷 |
| 56 | - byte[] payload = deviceMessageService.encodeDeviceMessage(message, deviceInfo.getProductKey(), | |
| 56 | + byte[] payload = deviceMessageService.serializeDeviceMessage(message, deviceInfo.getProductKey(), | |
| 57 | 57 | deviceInfo.getDeviceName()); |
| 58 | - // 2.3 发布消息 | |
| 58 | + | |
| 59 | + // 3. 发布消息 | |
| 59 | 60 | protocol.publishMessage(topic, payload); |
| 60 | 61 | } |
| 61 | 62 | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/emqx/handler/upstream/IotEmqxAuthEventHandler.java
| ... | ... | @@ -452,16 +452,17 @@ public class IotEmqxAuthEventHandler { |
| 452 | 452 | IotDeviceIdentity deviceInfo = IotDeviceAuthUtils.parseUsername(username); |
| 453 | 453 | Assert.notNull(deviceInfo, "设备信息不能为空"); |
| 454 | 454 | try { |
| 455 | - // 1. 构建响应消息 | |
| 455 | + // 1.1 构建响应消息 | |
| 456 | 456 | String method = IotDeviceMessageMethodEnum.DEVICE_REGISTER.getMethod(); |
| 457 | 457 | IotDeviceMessage responseMessage = IotDeviceMessage.replyOf(null, method, result, 0, null); |
| 458 | - | |
| 459 | - // 2. 编码消息 | |
| 460 | - byte[] encodedData = deviceMessageService.encodeDeviceMessage(responseMessage, "Alink"); | |
| 461 | - | |
| 462 | - // 3. 构建响应主题并延迟发布(等待设备连接成功并完成订阅) | |
| 458 | + // 1.2 序列化消息 | |
| 459 | + byte[] encodedData = deviceMessageService.serializeDeviceMessage(responseMessage, | |
| 460 | + cn.iocoder.yudao.module.iot.core.enums.IotSerializeTypeEnum.JSON); | |
| 461 | + // 1.3 构建响应主题 | |
| 463 | 462 | String replyTopic = IotMqttTopicUtils.buildTopicByMethod(method, |
| 464 | 463 | deviceInfo.getProductKey(), deviceInfo.getDeviceName(), true); |
| 464 | + | |
| 465 | + // 2. 构建响应主题,并延迟发布(等待设备连接成功并完成订阅) | |
| 465 | 466 | protocol.publishDelayMessage(replyTopic, encodedData, 5000); |
| 466 | 467 | log.info("[sendRegisterResultMessage][发送注册结果: topic={}]", replyTopic); |
| 467 | 468 | } catch (Exception e) { | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/emqx/handler/upstream/IotEmqxUpstreamHandler.java
| 1 | 1 | package cn.iocoder.yudao.module.iot.gateway.protocol.emqx.handler.upstream; |
| 2 | 2 | |
| 3 | +import cn.hutool.core.util.ArrayUtil; | |
| 3 | 4 | import cn.hutool.core.util.StrUtil; |
| 4 | 5 | import cn.hutool.extra.spring.SpringUtil; |
| 5 | 6 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; |
| ... | ... | @@ -34,22 +35,21 @@ public class IotEmqxUpstreamHandler { |
| 34 | 35 | try { |
| 35 | 36 | // 1. 解析主题,一次性获取所有信息 |
| 36 | 37 | String[] topicParts = topic.split("/"); |
| 37 | - if (topicParts.length < 4 || StrUtil.hasBlank(topicParts[2], topicParts[3])) { | |
| 38 | + String productKey = ArrayUtil.get(topicParts, 2); | |
| 39 | + String deviceName = ArrayUtil.get(topicParts, 3); | |
| 40 | + if (topicParts.length < 4 || StrUtil.hasBlank(productKey, deviceName)) { | |
| 38 | 41 | log.warn("[handle][topic({}) 格式不正确,无法解析有效的 productKey 和 deviceName]", topic); |
| 39 | 42 | return; |
| 40 | 43 | } |
| 41 | 44 | |
| 42 | - String productKey = topicParts[2]; | |
| 43 | - String deviceName = topicParts[3]; | |
| 44 | - | |
| 45 | - // 3. 解码消息 | |
| 46 | - IotDeviceMessage message = deviceMessageService.decodeDeviceMessage(payload, productKey, deviceName); | |
| 45 | + // 2. 反序列化消息 | |
| 46 | + IotDeviceMessage message = deviceMessageService.deserializeDeviceMessage(payload, productKey, deviceName); | |
| 47 | 47 | if (message == null) { |
| 48 | 48 | log.warn("[handle][topic({}) payload({}) 消息解码失败]", topic, new String(payload)); |
| 49 | 49 | return; |
| 50 | 50 | } |
| 51 | 51 | |
| 52 | - // 4. 发送消息到队列 | |
| 52 | + // 3. 发送消息到队列 | |
| 53 | 53 | deviceMessageService.sendDeviceMessage(message, productKey, deviceName, serverId); |
| 54 | 54 | } catch (Exception e) { |
| 55 | 55 | log.error("[handle][topic({}) payload({}) 处理异常]", topic, new String(payload), e); | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/handler/downstream/IotMqttDownstreamHandler.java
| ... | ... | @@ -43,7 +43,7 @@ public class IotMqttDownstreamHandler { |
| 43 | 43 | } |
| 44 | 44 | |
| 45 | 45 | // 2.1 序列化消息 |
| 46 | - byte[] payload = deviceMessageService.encodeDeviceMessage(message, connectionInfo.getProductKey(), | |
| 46 | + byte[] payload = deviceMessageService.serializeDeviceMessage(message, connectionInfo.getProductKey(), | |
| 47 | 47 | connectionInfo.getDeviceName()); |
| 48 | 48 | Assert.isTrue(payload != null && payload.length > 0, "消息编码结果不能为空"); |
| 49 | 49 | // 2.2 构建主题 | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/handler/upstream/IotMqttAbstractHandler.java
| ... | ... | @@ -21,12 +21,6 @@ import lombok.extern.slf4j.Slf4j; |
| 21 | 21 | @RequiredArgsConstructor |
| 22 | 22 | public abstract class IotMqttAbstractHandler { |
| 23 | 23 | |
| 24 | - // TODO @AI:当前使用 Alink 序列化类型,后续可考虑支持更多序列化方式 | |
| 25 | - /** | |
| 26 | - * 默认编解码类型(MQTT 使用 Alink 协议) | |
| 27 | - */ | |
| 28 | - protected static final String DEFAULT_CODEC_TYPE = "Alink"; | |
| 29 | - | |
| 30 | 24 | protected final IotMqttConnectionManager connectionManager; |
| 31 | 25 | protected final IotDeviceMessageService deviceMessageService; |
| 32 | 26 | |
| ... | ... | @@ -40,8 +34,9 @@ public abstract class IotMqttAbstractHandler { |
| 40 | 34 | * @param method 方法名 |
| 41 | 35 | * @param data 响应数据 |
| 42 | 36 | */ |
| 37 | + @SuppressWarnings("SameParameterValue") | |
| 43 | 38 | protected void sendSuccessResponse(MqttEndpoint endpoint, String productKey, String deviceName, |
| 44 | - String requestId, String method, Object data) { | |
| 39 | + String requestId, String method, Object data) { | |
| 45 | 40 | IotDeviceMessage responseMessage = IotDeviceMessage.replyOf(requestId, method, data, 0, null); |
| 46 | 41 | writeResponse(endpoint, productKey, deviceName, method, responseMessage); |
| 47 | 42 | } |
| ... | ... | @@ -75,11 +70,12 @@ public abstract class IotMqttAbstractHandler { |
| 75 | 70 | private void writeResponse(MqttEndpoint endpoint, String productKey, String deviceName, |
| 76 | 71 | String method, IotDeviceMessage responseMessage) { |
| 77 | 72 | try { |
| 78 | - // 1. 编码消息(使用默认编解码器) | |
| 79 | - byte[] encodedData = deviceMessageService.encodeDeviceMessage(responseMessage, DEFAULT_CODEC_TYPE); | |
| 80 | - | |
| 81 | - // 2. 构建响应主题,并发送 | |
| 73 | + // 1.1 序列化消息(根据设备配置的序列化类型) | |
| 74 | + byte[] encodedData = deviceMessageService.serializeDeviceMessage(responseMessage, productKey, deviceName); | |
| 75 | + // 1.2 构建响应主题 | |
| 82 | 76 | String replyTopic = IotMqttTopicUtils.buildTopicByMethod(method, productKey, deviceName, true); |
| 77 | + | |
| 78 | + // 2. 发送响应消息 | |
| 83 | 79 | endpoint.publish(replyTopic, Buffer.buffer(encodedData), MqttQoS.AT_LEAST_ONCE, false, false); |
| 84 | 80 | log.debug("[writeResponse][发送响应,主题: {},code: {}]", replyTopic, responseMessage.getCode()); |
| 85 | 81 | } catch (Exception e) { | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/handler/upstream/IotMqttRegisterHandler.java
| ... | ... | @@ -14,6 +14,8 @@ import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessa |
| 14 | 14 | import io.vertx.mqtt.MqttEndpoint; |
| 15 | 15 | import lombok.extern.slf4j.Slf4j; |
| 16 | 16 | |
| 17 | +import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR; | |
| 18 | + | |
| 17 | 19 | /** |
| 18 | 20 | * IoT 网关 MQTT 设备注册处理器:处理设备动态注册消息(一型一密) |
| 19 | 21 | * |
| ... | ... | @@ -76,7 +78,8 @@ public class IotMqttRegisterHandler extends IotMqttAbstractHandler { |
| 76 | 78 | log.warn("[handleRegister][注册失败,客户端 ID: {},错误: {}]", clientId, e.getMessage()); |
| 77 | 79 | // 接受连接,并发送错误响应 |
| 78 | 80 | endpoint.accept(false); |
| 79 | - sendErrorResponse(endpoint, productKey, deviceName, null, method, 500, e.getMessage()); | |
| 81 | + sendErrorResponse(endpoint, productKey, deviceName, null, method, | |
| 82 | + INTERNAL_SERVER_ERROR.getCode(), e.getMessage()); | |
| 80 | 83 | } finally { |
| 81 | 84 | // 注册完成后关闭连接(一型一密只用于获取 deviceSecret,不保持连接) |
| 82 | 85 | endpoint.close(); | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/handler/upstream/IotMqttUpstreamHandler.java
| ... | ... | @@ -60,7 +60,7 @@ public class IotMqttUpstreamHandler extends IotMqttAbstractHandler { |
| 60 | 60 | Assert.equals(deviceName, connectionInfo.getDeviceName(), "设备名称不匹配"); |
| 61 | 61 | |
| 62 | 62 | // 2. 反序列化消息 |
| 63 | - message = deviceMessageService.decodeDeviceMessage(payload, productKey, deviceName); | |
| 63 | + message = deviceMessageService.deserializeDeviceMessage(payload, productKey, deviceName); | |
| 64 | 64 | if (message == null) { |
| 65 | 65 | log.warn("[handleBusinessRequest][消息解码失败,客户端 ID: {},主题: {}]", clientId, topic); |
| 66 | 66 | sendErrorResponse(endpoint, productKey, deviceName, null, null, | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/package-info.java
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/serialize/package-info.java
0 → 100644
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/service/device/message/IotDeviceMessageService.java
| 1 | 1 | package cn.iocoder.yudao.module.iot.gateway.service.device.message; |
| 2 | 2 | |
| 3 | +import cn.iocoder.yudao.module.iot.core.enums.IotSerializeTypeEnum; | |
| 3 | 4 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; |
| 4 | 5 | |
| 5 | 6 | /** |
| ... | ... | @@ -10,45 +11,45 @@ import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; |
| 10 | 11 | public interface IotDeviceMessageService { |
| 11 | 12 | |
| 12 | 13 | /** |
| 13 | - * 编码消息 | |
| 14 | + * 序列化消息 | |
| 14 | 15 | * |
| 15 | 16 | * @param message 消息 |
| 16 | 17 | * @param productKey 产品 Key |
| 17 | 18 | * @param deviceName 设备名称 |
| 18 | - * @return 编码后的消息内容 | |
| 19 | + * @return 序列化后的消息内容 | |
| 19 | 20 | */ |
| 20 | - byte[] encodeDeviceMessage(IotDeviceMessage message, | |
| 21 | - String productKey, String deviceName); | |
| 21 | + byte[] serializeDeviceMessage(IotDeviceMessage message, | |
| 22 | + String productKey, String deviceName); | |
| 22 | 23 | |
| 23 | 24 | /** |
| 24 | - * 编码消息 | |
| 25 | + * 序列化消息 | |
| 25 | 26 | * |
| 26 | - * @param message 消息 | |
| 27 | - * @param codecType 编解码器类型 | |
| 28 | - * @return 编码后的消息内容 | |
| 27 | + * @param message 消息 | |
| 28 | + * @param serializeType 序列化类型 | |
| 29 | + * @return 序列化后的消息内容 | |
| 29 | 30 | */ |
| 30 | - byte[] encodeDeviceMessage(IotDeviceMessage message, | |
| 31 | - String codecType); | |
| 31 | + byte[] serializeDeviceMessage(IotDeviceMessage message, | |
| 32 | + IotSerializeTypeEnum serializeType); | |
| 32 | 33 | |
| 33 | 34 | /** |
| 34 | - * 解码消息 | |
| 35 | + * 反序列化消息 | |
| 35 | 36 | * |
| 36 | 37 | * @param bytes 消息内容 |
| 37 | 38 | * @param productKey 产品 Key |
| 38 | 39 | * @param deviceName 设备名称 |
| 39 | - * @return 解码后的消息内容 | |
| 40 | + * @return 反序列化后的消息内容 | |
| 40 | 41 | */ |
| 41 | - IotDeviceMessage decodeDeviceMessage(byte[] bytes, | |
| 42 | - String productKey, String deviceName); | |
| 42 | + IotDeviceMessage deserializeDeviceMessage(byte[] bytes, | |
| 43 | + String productKey, String deviceName); | |
| 43 | 44 | |
| 44 | 45 | /** |
| 45 | - * 解码消息 | |
| 46 | + * 反序列化消息 | |
| 46 | 47 | * |
| 47 | - * @param bytes 消息内容 | |
| 48 | - * @param codecType 编解码器类型 | |
| 49 | - * @return 解码后的消息内容 | |
| 48 | + * @param bytes 消息内容 | |
| 49 | + * @param serializeType 序列化类型 | |
| 50 | + * @return 反序列化后的消息内容 | |
| 50 | 51 | */ |
| 51 | - IotDeviceMessage decodeDeviceMessage(byte[] bytes, String codecType); | |
| 52 | + IotDeviceMessage deserializeDeviceMessage(byte[] bytes, IotSerializeTypeEnum serializeType); | |
| 52 | 53 | |
| 53 | 54 | /** |
| 54 | 55 | * 发送消息 | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/service/device/message/IotDeviceMessageServiceImpl.java
| 1 | 1 | package cn.iocoder.yudao.module.iot.gateway.service.device.message; |
| 2 | 2 | |
| 3 | +import cn.hutool.core.lang.Assert; | |
| 3 | 4 | import cn.hutool.core.util.StrUtil; |
| 4 | -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; | |
| 5 | 5 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO; |
| 6 | +import cn.iocoder.yudao.module.iot.core.enums.IotSerializeTypeEnum; | |
| 6 | 7 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; |
| 7 | 8 | import cn.iocoder.yudao.module.iot.core.mq.producer.IotDeviceMessageProducer; |
| 8 | 9 | import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; |
| 9 | -import cn.iocoder.yudao.module.iot.gateway.codec.IotDeviceMessageCodec; | |
| 10 | +import cn.iocoder.yudao.module.iot.gateway.serialize.IotMessageSerializer; | |
| 11 | +import cn.iocoder.yudao.module.iot.gateway.serialize.IotMessageSerializerManager; | |
| 10 | 12 | import cn.iocoder.yudao.module.iot.gateway.service.device.IotDeviceService; |
| 11 | 13 | import jakarta.annotation.Resource; |
| 12 | 14 | import lombok.extern.slf4j.Slf4j; |
| 13 | 15 | import org.springframework.stereotype.Service; |
| 14 | 16 | |
| 15 | 17 | import java.time.LocalDateTime; |
| 16 | -import java.util.List; | |
| 17 | -import java.util.Map; | |
| 18 | 18 | |
| 19 | 19 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| 20 | 20 | import static cn.iocoder.yudao.module.iot.gateway.enums.ErrorCodeConstants.DEVICE_NOT_EXISTS; |
| ... | ... | @@ -28,80 +28,70 @@ import static cn.iocoder.yudao.module.iot.gateway.enums.ErrorCodeConstants.DEVIC |
| 28 | 28 | @Slf4j |
| 29 | 29 | public class IotDeviceMessageServiceImpl implements IotDeviceMessageService { |
| 30 | 30 | |
| 31 | - /** | |
| 32 | - * 编解码器 | |
| 33 | - */ | |
| 34 | - private final Map<String, IotDeviceMessageCodec> codes; | |
| 35 | - | |
| 36 | 31 | @Resource |
| 37 | 32 | private IotDeviceService deviceService; |
| 38 | 33 | |
| 39 | 34 | @Resource |
| 40 | 35 | private IotDeviceMessageProducer deviceMessageProducer; |
| 41 | 36 | |
| 42 | - public IotDeviceMessageServiceImpl(List<IotDeviceMessageCodec> codes) { | |
| 43 | - this.codes = CollectionUtils.convertMap(codes, IotDeviceMessageCodec::type); | |
| 44 | - } | |
| 37 | + @Resource | |
| 38 | + private IotMessageSerializerManager messageSerializerManager; | |
| 45 | 39 | |
| 46 | 40 | @Override |
| 47 | - public byte[] encodeDeviceMessage(IotDeviceMessage message, | |
| 48 | - String productKey, String deviceName) { | |
| 41 | + public byte[] serializeDeviceMessage(IotDeviceMessage message, | |
| 42 | + String productKey, String deviceName) { | |
| 49 | 43 | // 1.1 获取设备信息 |
| 50 | 44 | IotDeviceRespDTO device = deviceService.getDeviceFromCache(productKey, deviceName); |
| 51 | 45 | if (device == null) { |
| 52 | 46 | throw exception(DEVICE_NOT_EXISTS, productKey, deviceName); |
| 53 | 47 | } |
| 54 | - // 1.2 获取编解码器 | |
| 55 | - IotDeviceMessageCodec codec = codes.get(device.getCodecType()); | |
| 56 | - if (codec == null) { | |
| 57 | - throw new IllegalArgumentException(StrUtil.format("编解码器({}) 不存在", device.getCodecType())); | |
| 58 | - } | |
| 48 | + // 1.2 获取序列化器 | |
| 49 | + IotSerializeTypeEnum serializeType = IotSerializeTypeEnum.of(device.getSerializeType()); | |
| 50 | + Assert.notNull(serializeType, "设备序列化类型不能为空"); | |
| 59 | 51 | |
| 60 | - // 2. 编码消息 | |
| 61 | - return codec.encode(message); | |
| 52 | + // 2. 序列化消息 | |
| 53 | + return serializeDeviceMessage(message, serializeType); | |
| 62 | 54 | } |
| 63 | 55 | |
| 64 | 56 | @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)); | |
| 57 | + public byte[] serializeDeviceMessage(IotDeviceMessage message, | |
| 58 | + IotSerializeTypeEnum serializeType) { | |
| 59 | + // 1. 获取序列化器 | |
| 60 | + IotMessageSerializer serializer = messageSerializerManager.get(serializeType); | |
| 61 | + if (serializer == null) { | |
| 62 | + throw new IllegalArgumentException(StrUtil.format("序列化器({}) 不存在", serializeType)); | |
| 71 | 63 | } |
| 72 | 64 | |
| 73 | - // 2. 编码消息 | |
| 74 | - return codec.encode(message); | |
| 65 | + // 2. 序列化消息 | |
| 66 | + return serializer.serialize(message); | |
| 75 | 67 | } |
| 76 | 68 | |
| 77 | 69 | @Override |
| 78 | - public IotDeviceMessage decodeDeviceMessage(byte[] bytes, | |
| 79 | - String productKey, String deviceName) { | |
| 70 | + public IotDeviceMessage deserializeDeviceMessage(byte[] bytes, | |
| 71 | + String productKey, String deviceName) { | |
| 80 | 72 | // 1.1 获取设备信息 |
| 81 | 73 | IotDeviceRespDTO device = deviceService.getDeviceFromCache(productKey, deviceName); |
| 82 | 74 | if (device == null) { |
| 83 | 75 | throw exception(DEVICE_NOT_EXISTS, productKey, deviceName); |
| 84 | 76 | } |
| 85 | - // 1.2 获取编解码器 | |
| 86 | - IotDeviceMessageCodec codec = codes.get(device.getCodecType()); | |
| 87 | - if (codec == null) { | |
| 88 | - throw new IllegalArgumentException(StrUtil.format("编解码器({}) 不存在", device.getCodecType())); | |
| 89 | - } | |
| 77 | + // 1.2 获取序列化器 | |
| 78 | + IotSerializeTypeEnum serializeType = IotSerializeTypeEnum.of(device.getSerializeType()); | |
| 79 | + Assert.notNull(serializeType, "设备序列化类型不能为空"); | |
| 90 | 80 | |
| 91 | - // 2. 解码消息 | |
| 92 | - return codec.decode(bytes); | |
| 81 | + // 2. 反序列化消息 | |
| 82 | + return deserializeDeviceMessage(bytes, serializeType); | |
| 93 | 83 | } |
| 94 | 84 | |
| 95 | 85 | @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)); | |
| 86 | + public IotDeviceMessage deserializeDeviceMessage(byte[] bytes, IotSerializeTypeEnum serializeType) { | |
| 87 | + // 1. 获取序列化器 | |
| 88 | + IotMessageSerializer serializer = messageSerializerManager.get(serializeType); | |
| 89 | + if (serializer == null) { | |
| 90 | + throw new IllegalArgumentException(StrUtil.format("序列化器({}) 不存在", serializeType)); | |
| 101 | 91 | } |
| 102 | 92 | |
| 103 | - // 2. 解码消息 | |
| 104 | - return codec.decode(bytes); | |
| 93 | + // 2. 反序列化消息 | |
| 94 | + return serializer.deserialize(bytes); | |
| 105 | 95 | } |
| 106 | 96 | |
| 107 | 97 | @Override | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/IotDirectDeviceMqttProtocolIntegrationTest.java
| ... | ... | @@ -8,8 +8,8 @@ import cn.iocoder.yudao.module.iot.core.topic.event.IotDeviceEventPostReqDTO; |
| 8 | 8 | import cn.iocoder.yudao.module.iot.core.topic.property.IotDevicePropertyPostReqDTO; |
| 9 | 9 | import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; |
| 10 | 10 | import cn.iocoder.yudao.module.iot.core.util.IotProductAuthUtils; |
| 11 | -import cn.iocoder.yudao.module.iot.gateway.codec.IotDeviceMessageCodec; | |
| 12 | -import cn.iocoder.yudao.module.iot.gateway.codec.alink.IotAlinkDeviceMessageCodec; | |
| 11 | +import cn.iocoder.yudao.module.iot.gateway.serialize.IotMessageSerializer; | |
| 12 | +import cn.iocoder.yudao.module.iot.gateway.serialize.json.IotJsonSerializer; | |
| 13 | 13 | import io.netty.handler.codec.mqtt.MqttQoS; |
| 14 | 14 | import io.vertx.core.Vertx; |
| 15 | 15 | import io.vertx.core.buffer.Buffer; |
| ... | ... | @@ -57,9 +57,9 @@ public class IotDirectDeviceMqttProtocolIntegrationTest { |
| 57 | 57 | |
| 58 | 58 | private static Vertx vertx; |
| 59 | 59 | |
| 60 | - // ===================== 编解码器(MQTT 使用 Alink 协议) ===================== | |
| 60 | + // ===================== 序列化器 ===================== | |
| 61 | 61 | |
| 62 | - private static final IotDeviceMessageCodec CODEC = new IotAlinkDeviceMessageCodec(); | |
| 62 | + private static final IotMessageSerializer SERIALIZER = new IotJsonSerializer(); | |
| 63 | 63 | |
| 64 | 64 | // ===================== 直连设备信息(根据实际情况修改,从 iot_device 表查询) ===================== |
| 65 | 65 | |
| ... | ... | @@ -211,7 +211,7 @@ public class IotDirectDeviceMqttProtocolIntegrationTest { |
| 211 | 211 | client.publishHandler(message -> { |
| 212 | 212 | log.info("[testDeviceRegister][收到响应: topic={}, payload={}]", |
| 213 | 213 | message.topicName(), message.payload().toString()); |
| 214 | - IotDeviceMessage response = CODEC.decode(message.payload().getBytes()); | |
| 214 | + IotDeviceMessage response = SERIALIZER.deserialize(message.payload().getBytes()); | |
| 215 | 215 | responseFuture.complete(response); |
| 216 | 216 | }); |
| 217 | 217 | // 3.2 订阅 _reply 主题 |
| ... | ... | @@ -314,14 +314,14 @@ public class IotDirectDeviceMqttProtocolIntegrationTest { |
| 314 | 314 | client.publishHandler(message -> { |
| 315 | 315 | log.info("[publishAndWaitReply][收到响应: topic={}, payload={}]", |
| 316 | 316 | message.topicName(), message.payload().toString()); |
| 317 | - IotDeviceMessage response = CODEC.decode(message.payload().getBytes()); | |
| 317 | + IotDeviceMessage response = SERIALIZER.deserialize(message.payload().getBytes()); | |
| 318 | 318 | responseFuture.complete(response); |
| 319 | 319 | }); |
| 320 | 320 | |
| 321 | - // 2. 编码并发布消息 | |
| 322 | - byte[] payload = CODEC.encode(request); | |
| 323 | - log.info("[publishAndWaitReply][Codec: {}, 发送消息: topic={}, payload={}]", | |
| 324 | - CODEC.type(), topic, new String(payload)); | |
| 321 | + // 2. 序列化并发布消息 | |
| 322 | + byte[] payload = SERIALIZER.serialize(request); | |
| 323 | + log.info("[publishAndWaitReply][Serializer: {}, 发送消息: topic={}, payload={}]", | |
| 324 | + SERIALIZER.getType(), topic, new String(payload)); | |
| 325 | 325 | client.publish(topic, Buffer.buffer(payload), MqttQoS.AT_LEAST_ONCE, false, false) |
| 326 | 326 | .toCompletionStage().toCompletableFuture().get(TIMEOUT_SECONDS, TimeUnit.SECONDS); |
| 327 | 327 | log.info("[publishAndWaitReply][消息发布成功]"); | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/IotGatewayDeviceMqttProtocolIntegrationTest.java
| ... | ... | @@ -12,8 +12,8 @@ import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoAddReqDTO; |
| 12 | 12 | import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoDeleteReqDTO; |
| 13 | 13 | import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoGetReqDTO; |
| 14 | 14 | import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; |
| 15 | -import cn.iocoder.yudao.module.iot.gateway.codec.IotDeviceMessageCodec; | |
| 16 | -import cn.iocoder.yudao.module.iot.gateway.codec.alink.IotAlinkDeviceMessageCodec; | |
| 15 | +import cn.iocoder.yudao.module.iot.gateway.serialize.IotMessageSerializer; | |
| 16 | +import cn.iocoder.yudao.module.iot.gateway.serialize.json.IotJsonSerializer; | |
| 17 | 17 | import io.netty.handler.codec.mqtt.MqttQoS; |
| 18 | 18 | import io.vertx.core.Vertx; |
| 19 | 19 | import io.vertx.core.buffer.Buffer; |
| ... | ... | @@ -65,9 +65,9 @@ public class IotGatewayDeviceMqttProtocolIntegrationTest { |
| 65 | 65 | |
| 66 | 66 | private static Vertx vertx; |
| 67 | 67 | |
| 68 | - // ===================== 编解码器(MQTT 使用 Alink 协议) ===================== | |
| 68 | + // ===================== 序列化器 ===================== | |
| 69 | 69 | |
| 70 | - private static final IotDeviceMessageCodec CODEC = new IotAlinkDeviceMessageCodec(); | |
| 70 | + private static final IotMessageSerializer SERIALIZER = new IotJsonSerializer(); | |
| 71 | 71 | |
| 72 | 72 | // ===================== 网关设备信息(根据实际情况修改,从 iot_device 表查询网关设备) ===================== |
| 73 | 73 | |
| ... | ... | @@ -399,14 +399,14 @@ public class IotGatewayDeviceMqttProtocolIntegrationTest { |
| 399 | 399 | client.publishHandler(message -> { |
| 400 | 400 | log.info("[publishAndWaitReply][收到响应: topic={}, payload={}]", |
| 401 | 401 | message.topicName(), message.payload().toString()); |
| 402 | - IotDeviceMessage response = CODEC.decode(message.payload().getBytes()); | |
| 402 | + IotDeviceMessage response = SERIALIZER.deserialize(message.payload().getBytes()); | |
| 403 | 403 | responseFuture.complete(response); |
| 404 | 404 | }); |
| 405 | 405 | |
| 406 | - // 2. 编码并发布消息 | |
| 407 | - byte[] payload = CODEC.encode(request); | |
| 408 | - log.info("[publishAndWaitReply][Codec: {}, 发送消息: topic={}, payload={}]", | |
| 409 | - CODEC.type(), topic, new String(payload)); | |
| 406 | + // 2. 序列化并发布消息 | |
| 407 | + byte[] payload = SERIALIZER.serialize(request); | |
| 408 | + log.info("[publishAndWaitReply][Serializer: {}, 发送消息: topic={}, payload={}]", | |
| 409 | + SERIALIZER.getType(), topic, new String(payload)); | |
| 410 | 410 | client.publish(topic, Buffer.buffer(payload), MqttQoS.AT_LEAST_ONCE, false, false) |
| 411 | 411 | .toCompletionStage().toCompletableFuture().get(TIMEOUT_SECONDS, TimeUnit.SECONDS); |
| 412 | 412 | log.info("[publishAndWaitReply][消息发布成功]"); | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/IotGatewaySubDeviceMqttProtocolIntegrationTest.java
| ... | ... | @@ -7,8 +7,8 @@ import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; |
| 7 | 7 | import cn.iocoder.yudao.module.iot.core.topic.event.IotDeviceEventPostReqDTO; |
| 8 | 8 | import cn.iocoder.yudao.module.iot.core.topic.property.IotDevicePropertyPostReqDTO; |
| 9 | 9 | import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; |
| 10 | -import cn.iocoder.yudao.module.iot.gateway.codec.IotDeviceMessageCodec; | |
| 11 | -import cn.iocoder.yudao.module.iot.gateway.codec.alink.IotAlinkDeviceMessageCodec; | |
| 10 | +import cn.iocoder.yudao.module.iot.gateway.serialize.IotMessageSerializer; | |
| 11 | +import cn.iocoder.yudao.module.iot.gateway.serialize.json.IotJsonSerializer; | |
| 12 | 12 | import io.netty.handler.codec.mqtt.MqttQoS; |
| 13 | 13 | import io.vertx.core.Vertx; |
| 14 | 14 | import io.vertx.core.buffer.Buffer; |
| ... | ... | @@ -59,9 +59,9 @@ public class IotGatewaySubDeviceMqttProtocolIntegrationTest { |
| 59 | 59 | |
| 60 | 60 | private static Vertx vertx; |
| 61 | 61 | |
| 62 | - // ===================== 编解码器(MQTT 使用 Alink 协议) ===================== | |
| 62 | + // ===================== 序列化器 ===================== | |
| 63 | 63 | |
| 64 | - private static final IotDeviceMessageCodec CODEC = new IotAlinkDeviceMessageCodec(); | |
| 64 | + private static final IotMessageSerializer SERIALIZER = new IotJsonSerializer(); | |
| 65 | 65 | |
| 66 | 66 | // ===================== 网关子设备信息(根据实际情况修改,从 iot_device 表查询子设备) ===================== |
| 67 | 67 | |
| ... | ... | @@ -236,14 +236,14 @@ public class IotGatewaySubDeviceMqttProtocolIntegrationTest { |
| 236 | 236 | client.publishHandler(message -> { |
| 237 | 237 | log.info("[publishAndWaitReply][收到响应: topic={}, payload={}]", |
| 238 | 238 | message.topicName(), message.payload().toString()); |
| 239 | - IotDeviceMessage response = CODEC.decode(message.payload().getBytes()); | |
| 239 | + IotDeviceMessage response = SERIALIZER.deserialize(message.payload().getBytes()); | |
| 240 | 240 | responseFuture.complete(response); |
| 241 | 241 | }); |
| 242 | 242 | |
| 243 | - // 2. 编码并发布消息 | |
| 244 | - byte[] payload = CODEC.encode(request); | |
| 245 | - log.info("[publishAndWaitReply][Codec: {}, 发送消息: topic={}, payload={}]", | |
| 246 | - CODEC.type(), topic, new String(payload)); | |
| 243 | + // 2. 序列化并发布消息 | |
| 244 | + byte[] payload = SERIALIZER.serialize(request); | |
| 245 | + log.info("[publishAndWaitReply][Serializer: {}, 发送消息: topic={}, payload={}]", | |
| 246 | + SERIALIZER.getType(), topic, new String(payload)); | |
| 247 | 247 | client.publish(topic, Buffer.buffer(payload), MqttQoS.AT_LEAST_ONCE, false, false) |
| 248 | 248 | .toCompletionStage().toCompletableFuture().get(TIMEOUT_SECONDS, TimeUnit.SECONDS); |
| 249 | 249 | log.info("[publishAndWaitReply][消息发布成功]"); | ... | ... |