Commit 120029bb1793737d6e0c8d429d7c2d43eb925fe6
1 parent
f58cf282
reactor:【IoT 物联网】实现 alink 的 IotAlinkDeviceMessageCodec,并尝试接入 http 协议
Showing
14 changed files
with
388 additions
and
133 deletions
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/IotDeviceMessageMethodEnum.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.core.enums; | |
| 2 | + | |
| 3 | +import lombok.AllArgsConstructor; | |
| 4 | +import lombok.Getter; | |
| 5 | + | |
| 6 | +/** | |
| 7 | + * IoT 设备消息的方法枚举 | |
| 8 | + * | |
| 9 | + * @author haohao | |
| 10 | + */ | |
| 11 | +@Getter | |
| 12 | +@AllArgsConstructor | |
| 13 | +public enum IotDeviceMessageMethodEnum { | |
| 14 | + | |
| 15 | + // ========== 设备状态 ========== | |
| 16 | + | |
| 17 | + STATE_ONLINE("thing.state.online"), | |
| 18 | + STATE_OFFLINE("thing.state.offline"), | |
| 19 | + | |
| 20 | + ; | |
| 21 | + | |
| 22 | + private final String method; | |
| 23 | + | |
| 24 | + | |
| 25 | +} | ... | ... |
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/mq/message/IotDeviceMessage.java
| 1 | 1 | package cn.iocoder.yudao.module.iot.core.mq.message; |
| 2 | 2 | |
| 3 | -import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageIdentifierEnum; | |
| 4 | -import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageTypeEnum; | |
| 3 | +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; | |
| 5 | 4 | import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; |
| 6 | 5 | import lombok.AllArgsConstructor; |
| 7 | 6 | import lombok.Builder; |
| ... | ... | @@ -9,7 +8,6 @@ import lombok.Data; |
| 9 | 8 | import lombok.NoArgsConstructor; |
| 10 | 9 | |
| 11 | 10 | import java.time.LocalDateTime; |
| 12 | -import java.util.Map; | |
| 13 | 11 | |
| 14 | 12 | /** |
| 15 | 13 | * IoT 设备消息 |
| ... | ... | @@ -32,109 +30,126 @@ public class IotDeviceMessage { |
| 32 | 30 | */ |
| 33 | 31 | public static final String MESSAGE_BUS_GATEWAY_DEVICE_MESSAGE_TOPIC = MESSAGE_BUS_DEVICE_MESSAGE_TOPIC + "_%s"; |
| 34 | 32 | |
| 35 | - // TODO @芋艿:thingsboard 对应 id,全部由后端生成,由于追溯;是不是调整下? | |
| 36 | 33 | /** |
| 37 | 34 | * 消息编号 |
| 35 | + * | |
| 36 | + * 由后端生成,通过 {@link IotDeviceMessageUtils#generateMessageId()} | |
| 38 | 37 | */ |
| 39 | - private String messageId; | |
| 40 | - | |
| 41 | - // TODO @芋艿:thingsboard 是使用 deviceId | |
| 42 | - /** | |
| 43 | - * 设备信息 | |
| 44 | - */ | |
| 45 | - private String productKey; | |
| 38 | + private String id; | |
| 46 | 39 | /** |
| 47 | - * 设备名称 | |
| 40 | + * 上报时间 | |
| 41 | + * | |
| 42 | + * 由后端生成,当前时间 | |
| 48 | 43 | */ |
| 49 | - private String deviceName; | |
| 44 | + private LocalDateTime reportTime; | |
| 45 | + | |
| 46 | + // ========== codec(编解码)字段 ========== | |
| 50 | 47 | |
| 51 | - // TODO @芋艿:thingsboard 只定义了 type;相当于 type + identifier 结合!TbMsgType | |
| 52 | 48 | /** |
| 53 | - * 消息类型 | |
| 49 | + * 请求编号 | |
| 54 | 50 | * |
| 55 | - * 枚举 {@link IotDeviceMessageTypeEnum} | |
| 51 | + * 由设备生成,对应阿里云 IoT 的 Alink 协议中的 id、华为云 IoTDA 协议的 request_id | |
| 56 | 52 | */ |
| 57 | - private String type; | |
| 53 | + private String requestId; | |
| 58 | 54 | /** |
| 59 | - * 标识符 | |
| 55 | + * 请求方法 | |
| 60 | 56 | * |
| 61 | - * 枚举 {@link IotDeviceMessageIdentifierEnum} | |
| 57 | + * 枚举 {@link IotDeviceMessageMethodEnum} | |
| 58 | + * 例如说:thing.property.report 属性上报 | |
| 62 | 59 | */ |
| 63 | - private String identifier; | |
| 64 | - | |
| 65 | - // TODO @芋艿:thingsboard 只有 data 字段,没有 code 字段; | |
| 66 | - // TODO @芋艿:要不提前序列化成字符串???类似 thingsboard 的 data 字段 | |
| 60 | + private String method; | |
| 67 | 61 | /** |
| 68 | 62 | * 请求参数 |
| 69 | 63 | * |
| 70 | 64 | * 例如说:属性上报的 properties、事件上报的 params |
| 71 | 65 | */ |
| 66 | + private Object params; | |
| 67 | + /** | |
| 68 | + * 响应结果 | |
| 69 | + */ | |
| 72 | 70 | private Object data; |
| 73 | - // TODO @芋艿:可能会去掉 | |
| 74 | 71 | /** |
| 75 | - * 响应码 | |
| 76 | - * | |
| 77 | - * 目前只有 server 下行消息给 device 设备时,才会有响应码 | |
| 72 | + * 响应错误码 | |
| 78 | 73 | */ |
| 79 | 74 | private Integer code; |
| 80 | 75 | |
| 76 | + // ========== 后端字段 ========== | |
| 77 | + | |
| 81 | 78 | /** |
| 82 | - * 上报时间 | |
| 79 | + * 设备编号 | |
| 83 | 80 | */ |
| 84 | - private LocalDateTime reportTime; | |
| 81 | + private Long deviceId; | |
| 82 | + /** | |
| 83 | + * 租户编号 | |
| 84 | + */ | |
| 85 | + private Long tenantId; | |
| 85 | 86 | |
| 86 | 87 | /** |
| 87 | 88 | * 服务编号,该消息由哪个 server 服务进行消费 |
| 88 | 89 | */ |
| 89 | 90 | private String serverId; |
| 90 | 91 | |
| 91 | - public IotDeviceMessage ofPropertyReport(Map<String, Object> properties) { | |
| 92 | - this.setType(IotDeviceMessageTypeEnum.PROPERTY.getType()); | |
| 93 | - this.setIdentifier(IotDeviceMessageIdentifierEnum.PROPERTY_REPORT.getIdentifier()); | |
| 94 | - this.setData(properties); | |
| 95 | - return this; | |
| 96 | - } | |
| 97 | - | |
| 98 | - public IotDeviceMessage ofPropertySet(Map<String, Object> properties) { | |
| 99 | - this.setType(IotDeviceMessageTypeEnum.PROPERTY.getType()); | |
| 100 | - this.setIdentifier(IotDeviceMessageIdentifierEnum.PROPERTY_SET.getIdentifier()); | |
| 101 | - this.setData(properties); | |
| 102 | - return this; | |
| 103 | - } | |
| 104 | - | |
| 105 | - public IotDeviceMessage ofStateOnline() { | |
| 106 | - this.setType(IotDeviceMessageTypeEnum.STATE.getType()); | |
| 107 | - this.setIdentifier(IotDeviceMessageIdentifierEnum.STATE_ONLINE.getIdentifier()); | |
| 108 | - return this; | |
| 109 | - } | |
| 110 | - | |
| 111 | - public IotDeviceMessage ofStateOffline() { | |
| 112 | - this.setType(IotDeviceMessageTypeEnum.STATE.getType()); | |
| 113 | - this.setIdentifier(IotDeviceMessageIdentifierEnum.STATE_OFFLINE.getIdentifier()); | |
| 114 | - return this; | |
| 115 | - } | |
| 116 | - | |
| 117 | - public static IotDeviceMessage of(String productKey, String deviceName) { | |
| 118 | - return of(productKey, deviceName, | |
| 119 | - null, null); | |
| 120 | - } | |
| 121 | - | |
| 122 | - public static IotDeviceMessage of(String productKey, String deviceName, | |
| 123 | - String serverId) { | |
| 124 | - return of(productKey, deviceName, | |
| 125 | - null, serverId); | |
| 92 | +// public IotDeviceMessage ofPropertyReport(Map<String, Object> properties) { | |
| 93 | +// this.setType(IotDeviceMessageTypeEnum.PROPERTY.getType()); | |
| 94 | +// this.setIdentifier(IotDeviceMessageIdentifierEnum.PROPERTY_REPORT.getIdentifier()); | |
| 95 | +// this.setData(properties); | |
| 96 | +// return this; | |
| 97 | +// } | |
| 98 | +// | |
| 99 | +// public IotDeviceMessage ofPropertySet(Map<String, Object> properties) { | |
| 100 | +// this.setType(IotDeviceMessageTypeEnum.PROPERTY.getType()); | |
| 101 | +// this.setIdentifier(IotDeviceMessageIdentifierEnum.PROPERTY_SET.getIdentifier()); | |
| 102 | +// this.setData(properties); | |
| 103 | +// return this; | |
| 104 | +// } | |
| 105 | +// | |
| 106 | +// public IotDeviceMessage ofStateOnline() { | |
| 107 | +// this.setType(IotDeviceMessageTypeEnum.STATE.getType()); | |
| 108 | +// this.setIdentifier(IotDeviceMessageIdentifierEnum.STATE_ONLINE.getIdentifier()); | |
| 109 | +// return this; | |
| 110 | +// } | |
| 111 | +// | |
| 112 | +// public IotDeviceMessage ofStateOffline() { | |
| 113 | +// this.setType(IotDeviceMessageTypeEnum.STATE.getType()); | |
| 114 | +// this.setIdentifier(IotDeviceMessageIdentifierEnum.STATE_OFFLINE.getIdentifier()); | |
| 115 | +// return this; | |
| 116 | +// } | |
| 117 | +// | |
| 118 | +// public static IotDeviceMessage of(String productKey, String deviceName) { | |
| 119 | +// return of(productKey, deviceName, | |
| 120 | +// null, null); | |
| 121 | +// } | |
| 122 | +// | |
| 123 | +// public static IotDeviceMessage of(String productKey, String deviceName, | |
| 124 | +// String serverId) { | |
| 125 | +// return of(productKey, deviceName, | |
| 126 | +// null, serverId); | |
| 127 | +// } | |
| 128 | +// | |
| 129 | +// public static IotDeviceMessage of(String productKey, String deviceName, | |
| 130 | +// LocalDateTime reportTime, String serverId) { | |
| 131 | +// if (reportTime == null) { | |
| 132 | +// reportTime = LocalDateTime.now(); | |
| 133 | +// } | |
| 134 | +// String messageId = IotDeviceMessageUtils.generateMessageId(); | |
| 135 | +// return IotDeviceMessage.builder() | |
| 136 | +// .messageId(messageId).reportTime(reportTime) | |
| 137 | +// .productKey(productKey).deviceName(deviceName) | |
| 138 | +// .serverId(serverId).build(); | |
| 139 | +// } | |
| 140 | + | |
| 141 | + public static IotDeviceMessage of(String requestId, String method, Object params) { | |
| 142 | + return of(requestId, method, params, null, null); | |
| 126 | 143 | } |
| 127 | 144 | |
| 128 | - public static IotDeviceMessage of(String productKey, String deviceName, | |
| 129 | - LocalDateTime reportTime, String serverId) { | |
| 130 | - if (reportTime == null) { | |
| 131 | - reportTime = LocalDateTime.now(); | |
| 132 | - } | |
| 133 | - String messageId = IotDeviceMessageUtils.generateMessageId(); | |
| 134 | - return IotDeviceMessage.builder() | |
| 135 | - .messageId(messageId).reportTime(reportTime) | |
| 136 | - .productKey(productKey).deviceName(deviceName) | |
| 137 | - .serverId(serverId).build(); | |
| 145 | + public static IotDeviceMessage of(String requestId, String method, | |
| 146 | + Object params, Object data, Integer code) { | |
| 147 | + // 通用参数 | |
| 148 | + IotDeviceMessage message = new IotDeviceMessage() | |
| 149 | + .setId(IotDeviceMessageUtils.generateMessageId()).setReportTime(LocalDateTime.now()); | |
| 150 | + // 当前参数 | |
| 151 | + message.setRequestId(requestId).setMethod(method).setParams(params).setData(data).setCode(code); | |
| 152 | + return message; | |
| 138 | 153 | } |
| 139 | 154 | |
| 140 | 155 | } | ... | ... |
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/mq/producer/IotDeviceMessageProducer.java
| ... | ... | @@ -30,7 +30,7 @@ public class IotDeviceMessageProducer { |
| 30 | 30 | * @param serverId 网关的 serverId 标识 |
| 31 | 31 | * @param message 设备消息 |
| 32 | 32 | */ |
| 33 | - public void sendGatewayDeviceMessage(String serverId, Object message) { | |
| 33 | + public void sendDeviceMessageToGateway(String serverId, IotDeviceMessage message) { | |
| 34 | 34 | messageBus.post(IotDeviceMessageUtils.buildMessageBusGatewayDeviceMessageTopic(serverId), message); |
| 35 | 35 | } |
| 36 | 36 | ... | ... |
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/util/IotDeviceMessageUtils.java
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/codec/IotDeviceMessageCodec.java
0 → 100644
| 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
0 → 100644
| 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.util.json.JsonUtils; | |
| 5 | +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | |
| 6 | +import cn.iocoder.yudao.module.iot.gateway.codec.IotDeviceMessageCodec; | |
| 7 | +import lombok.AllArgsConstructor; | |
| 8 | +import lombok.Data; | |
| 9 | +import lombok.NoArgsConstructor; | |
| 10 | +import org.springframework.stereotype.Component; | |
| 11 | + | |
| 12 | +/** | |
| 13 | + * 阿里云 Alink {@link IotDeviceMessage} 的编解码器 | |
| 14 | + * | |
| 15 | + * @author 芋道源码 | |
| 16 | + */ | |
| 17 | +@Component | |
| 18 | +public class IotAlinkDeviceMessageCodec implements IotDeviceMessageCodec { | |
| 19 | + | |
| 20 | + @Data | |
| 21 | + @NoArgsConstructor | |
| 22 | + @AllArgsConstructor | |
| 23 | + private static class AlinkMessage { | |
| 24 | + | |
| 25 | + public static final String VERSION_1 = "1.0"; | |
| 26 | + | |
| 27 | + /** | |
| 28 | + * 消息 ID,且每个消息 ID 在当前设备具有唯一性 | |
| 29 | + */ | |
| 30 | + private String id; | |
| 31 | + | |
| 32 | + /** | |
| 33 | + * 版本号 | |
| 34 | + */ | |
| 35 | + private String version; | |
| 36 | + | |
| 37 | + /** | |
| 38 | + * 请求方法 | |
| 39 | + */ | |
| 40 | + private String method; | |
| 41 | + | |
| 42 | + /** | |
| 43 | + * 请求参数 | |
| 44 | + */ | |
| 45 | + private Object params; | |
| 46 | + | |
| 47 | + /** | |
| 48 | + * 响应结果 | |
| 49 | + */ | |
| 50 | + private Object data; | |
| 51 | + | |
| 52 | + /** | |
| 53 | + * 响应错误码 | |
| 54 | + */ | |
| 55 | + private Integer code; | |
| 56 | + | |
| 57 | + } | |
| 58 | + | |
| 59 | + @Override | |
| 60 | + public byte[] encode(IotDeviceMessage message) { | |
| 61 | + AlinkMessage alinkMessage = new AlinkMessage(message.getRequestId(), AlinkMessage.VERSION_1, | |
| 62 | + message.getMethod(), message.getParams(), message.getData(), message.getCode()); | |
| 63 | + return JsonUtils.toJsonByte(alinkMessage); | |
| 64 | + } | |
| 65 | + | |
| 66 | + @Override | |
| 67 | + @SuppressWarnings("DataFlowIssue") | |
| 68 | + public IotDeviceMessage decode(byte[] bytes) { | |
| 69 | + AlinkMessage alinkMessage = JsonUtils.parseObject(bytes, AlinkMessage.class); | |
| 70 | + Assert.notNull(alinkMessage, "消息不能为空"); | |
| 71 | + Assert.equals(alinkMessage.getVersion(), AlinkMessage.VERSION_1, "消息版本号必须是 1.0"); | |
| 72 | + return IotDeviceMessage.of(alinkMessage.getId(), | |
| 73 | + alinkMessage.getMethod(), alinkMessage.getParams(), alinkMessage.getData(), alinkMessage.getCode()); | |
| 74 | + } | |
| 75 | + | |
| 76 | + @Override | |
| 77 | + public String type() { | |
| 78 | + return "alink"; | |
| 79 | + } | |
| 80 | + | |
| 81 | +} | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/codec/alink/package-info.java deleted
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/router/IotHttpAbstractHandler.java
| ... | ... | @@ -33,7 +33,7 @@ public abstract class IotHttpAbstractHandler implements Handler<RoutingContext> |
| 33 | 33 | private final IotDeviceTokenService deviceTokenService = SpringUtil.getBean(IotDeviceTokenService.class); |
| 34 | 34 | |
| 35 | 35 | @Override |
| 36 | - public void handle(RoutingContext context) { | |
| 36 | + public final void handle(RoutingContext context) { | |
| 37 | 37 | try { |
| 38 | 38 | // 1. 前置处理 |
| 39 | 39 | CommonResult<Object> result = beforeHandle(context); | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/router/IotHttpAuthHandler.java
| ... | ... | @@ -13,6 +13,7 @@ import cn.iocoder.yudao.module.iot.core.mq.producer.IotDeviceMessageProducer; |
| 13 | 13 | import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; |
| 14 | 14 | import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpUpstreamProtocol; |
| 15 | 15 | import cn.iocoder.yudao.module.iot.gateway.service.auth.IotDeviceTokenService; |
| 16 | +import cn.iocoder.yudao.module.iot.gateway.service.message.IotDeviceMessageService; | |
| 16 | 17 | import io.vertx.core.json.JsonObject; |
| 17 | 18 | import io.vertx.ext.web.RoutingContext; |
| 18 | 19 | |
| ... | ... | @@ -40,11 +41,14 @@ public class IotHttpAuthHandler extends IotHttpAbstractHandler { |
| 40 | 41 | |
| 41 | 42 | private final IotDeviceCommonApi deviceClientService; |
| 42 | 43 | |
| 44 | + private final IotDeviceMessageService deviceMessageService; | |
| 45 | + | |
| 43 | 46 | public IotHttpAuthHandler(IotHttpUpstreamProtocol protocol) { |
| 44 | 47 | this.protocol = protocol; |
| 45 | 48 | this.deviceMessageProducer = SpringUtil.getBean(IotDeviceMessageProducer.class); |
| 46 | 49 | this.deviceTokenService = SpringUtil.getBean(IotDeviceTokenService.class); |
| 47 | 50 | this.deviceClientService = SpringUtil.getBean(IotDeviceCommonApi.class); |
| 51 | + this.deviceMessageService = SpringUtil.getBean(IotDeviceMessageService.class); | |
| 48 | 52 | } |
| 49 | 53 | |
| 50 | 54 | @Override |
| ... | ... | @@ -78,9 +82,9 @@ public class IotHttpAuthHandler extends IotHttpAbstractHandler { |
| 78 | 82 | Assert.notBlank(token, "生成 token 不能为空位"); |
| 79 | 83 | |
| 80 | 84 | // 3. 执行上线 |
| 81 | - deviceMessageProducer.sendDeviceMessage(IotDeviceMessage.of(deviceInfo.getProductKey(), deviceInfo.getDeviceName(), | |
| 82 | - protocol.getServerId()) | |
| 83 | - .ofStateOnline()); | |
| 85 | + IotDeviceMessage message = deviceMessageService.buildDeviceMessageOfStateOnline( | |
| 86 | + deviceInfo.getProductKey(), deviceInfo.getDeviceName(), protocol.getServerId()); | |
| 87 | + deviceMessageProducer.sendDeviceMessage(message); | |
| 84 | 88 | |
| 85 | 89 | // 构建响应数据 |
| 86 | 90 | return success(MapUtil.of("token", token)); | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/router/IotHttpUpstreamHandler.java
| 1 | 1 | package cn.iocoder.yudao.module.iot.gateway.protocol.http.router; |
| 2 | 2 | |
| 3 | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | +import cn.hutool.core.lang.Assert; | |
| 4 | 5 | import cn.hutool.core.map.MapUtil; |
| 6 | +import cn.hutool.core.text.StrPool; | |
| 5 | 7 | import cn.hutool.core.util.StrUtil; |
| 6 | 8 | import cn.hutool.extra.spring.SpringUtil; |
| 9 | +import cn.iocoder.yudao.framework.common.pojo.CommonResult; | |
| 7 | 10 | import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; |
| 8 | 11 | import cn.iocoder.yudao.module.iot.core.mq.producer.IotDeviceMessageProducer; |
| 9 | 12 | import cn.iocoder.yudao.module.iot.gateway.enums.IotDeviceTopicEnum; |
| 10 | 13 | import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpUpstreamProtocol; |
| 11 | -import io.vertx.core.Handler; | |
| 14 | +import cn.iocoder.yudao.module.iot.gateway.service.message.IotDeviceMessageService; | |
| 12 | 15 | import io.vertx.core.json.JsonObject; |
| 13 | 16 | import io.vertx.ext.web.RoutingContext; |
| 14 | 17 | import lombok.RequiredArgsConstructor; |
| ... | ... | @@ -23,49 +26,39 @@ import java.util.Map; |
| 23 | 26 | */ |
| 24 | 27 | @RequiredArgsConstructor |
| 25 | 28 | @Slf4j |
| 26 | -public class IotHttpUpstreamHandler implements Handler<RoutingContext> { | |
| 29 | +public class IotHttpUpstreamHandler extends IotHttpAbstractHandler { | |
| 27 | 30 | |
| 28 | - // TODO @haohao:你说,咱要不要把 "/sys/:productKey/:deviceName" | |
| 29 | - // + IotDeviceTopicEnum.PROPERTY_POST_TOPIC.getTopic(),也抽到 IotDeviceTopicEnum 的 build 这种?尽量都收敛掉? | |
| 30 | - /** | |
| 31 | - * 属性上报路径 | |
| 32 | - */ | |
| 33 | - public static final String PROPERTY_PATH = "/sys/:productKey/:deviceName" | |
| 34 | - + IotDeviceTopicEnum.PROPERTY_POST_TOPIC.getTopic(); | |
| 35 | - | |
| 36 | - /** | |
| 37 | - * 事件上报路径 | |
| 38 | - */ | |
| 39 | - public static final String EVENT_PATH = "/sys/:productKey/:deviceName" | |
| 40 | - + IotDeviceTopicEnum.EVENT_POST_TOPIC_PREFIX.getTopic() + ":identifier" | |
| 41 | - + IotDeviceTopicEnum.EVENT_POST_TOPIC_SUFFIX.getTopic(); | |
| 31 | + public static final String PATH = "/topic/sys/:productKey/:deviceName/*"; | |
| 42 | 32 | |
| 43 | 33 | private final IotHttpUpstreamProtocol protocol; |
| 44 | 34 | |
| 45 | 35 | private final IotDeviceMessageProducer deviceMessageProducer; |
| 46 | 36 | |
| 37 | + private final IotDeviceMessageService deviceMessageService; | |
| 38 | + | |
| 47 | 39 | public IotHttpUpstreamHandler(IotHttpUpstreamProtocol protocol) { |
| 48 | 40 | this.protocol = protocol; |
| 49 | 41 | this.deviceMessageProducer = SpringUtil.getBean(IotDeviceMessageProducer.class); |
| 42 | + this.deviceMessageService = SpringUtil.getBean(IotDeviceMessageService.class); | |
| 50 | 43 | } |
| 51 | 44 | |
| 52 | 45 | @Override |
| 53 | - public void handle(RoutingContext context) { | |
| 54 | - String path = context.request().path(); | |
| 46 | + protected CommonResult<Object> handle0(RoutingContext context) { | |
| 55 | 47 | // 1. 解析通用参数 |
| 56 | 48 | String productKey = context.pathParam("productKey"); |
| 57 | 49 | String deviceName = context.pathParam("deviceName"); |
| 58 | - JsonObject body = context.body().asJsonObject(); | |
| 59 | - | |
| 60 | - // 2. 根据路径模式处理不同类型的请求 | |
| 61 | - if (isPropertyPostPath(path)) { | |
| 62 | - // 处理属性上报 | |
| 63 | - handlePropertyPost(context, productKey, deviceName, body); | |
| 64 | - } else if (isEventPostPath(path)) { | |
| 65 | - // 处理事件上报 | |
| 66 | - String identifier = context.pathParam("identifier"); | |
| 67 | - handleEventPost(context, productKey, deviceName, identifier, body); | |
| 68 | - } | |
| 50 | + String method = context.pathParam("*").replaceAll(StrPool.SLASH, StrPool.DOT); | |
| 51 | + | |
| 52 | + // 2.1 解析消息 | |
| 53 | + byte[] bytes = context.body().buffer().getBytes(); | |
| 54 | + IotDeviceMessage message = deviceMessageService.decodeDeviceMessage(bytes, | |
| 55 | + productKey, deviceName, protocol.getServerId()); | |
| 56 | + Assert.equals(method, message.getMethod(), "method 不匹配"); | |
| 57 | + // 2.2 发送消息 | |
| 58 | + deviceMessageProducer.sendDeviceMessage(message); | |
| 59 | + | |
| 60 | + // 3. 返回结果 | |
| 61 | + return CommonResult.success(MapUtil.of("messageId", message.getId())); | |
| 69 | 62 | } |
| 70 | 63 | |
| 71 | 64 | /** |
| ... | ... | @@ -101,7 +94,8 @@ public class IotHttpUpstreamHandler implements Handler<RoutingContext> { |
| 101 | 94 | JsonObject body) { |
| 102 | 95 | // 1.1 构建设备消息 |
| 103 | 96 | IotDeviceMessage message = IotDeviceMessage.of(productKey, deviceName, protocol.getServerId()) |
| 104 | - .ofPropertyReport(parsePropertiesFromBody(body)); | |
| 97 | +// .ofPropertyReport(parsePropertiesFromBody(body)) | |
| 98 | + ; | |
| 105 | 99 | // 1.2 发送消息 |
| 106 | 100 | deviceMessageProducer.sendDeviceMessage(message); |
| 107 | 101 | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/router/package-info.java deleted
| 1 | -/** | |
| 2 | - * MQTT 协议路由器包 | |
| 3 | - * <p> | |
| 4 | - * 包含 MQTT 协议的所有路由处理器和抽象基类: | |
| 5 | - * <ul> | |
| 6 | - * <li>{@link cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.router.IotMqttAbstractHandler} | |
| 7 | - * - 抽象路由处理器基类</li> | |
| 8 | - * <li>{@link cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.router.IotMqttUpstreamRouter} | |
| 9 | - * - 上行消息路由器</li> | |
| 10 | - * <li>{@link cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.router.IotMqttAuthRouter} | |
| 11 | - * - 认证路由器</li> | |
| 12 | - * <li>{@link cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.router.IotMqttPropertyHandler} | |
| 13 | - * - 属性处理器</li> | |
| 14 | - * <li>{@link cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.router.IotMqttEventHandler} | |
| 15 | - * - 事件处理器</li> | |
| 16 | - * <li>{@link cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.router.IotMqttServiceHandler} | |
| 17 | - * - 服务处理器</li> | |
| 18 | - * </ul> | |
| 19 | - * | |
| 20 | - * @author 芋道源码 | |
| 21 | - */ | |
| 22 | -package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.router; | |
| 23 | 0 | \ No newline at end of file |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/service/auth/IotDeviceTokenService.java
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/service/message/IotDeviceMessageService.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.gateway.service.message; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | |
| 4 | + | |
| 5 | +/** | |
| 6 | + * IoT 设备消息 Service 接口 | |
| 7 | + * | |
| 8 | + * @author 芋道源码 | |
| 9 | + */ | |
| 10 | +public interface IotDeviceMessageService { | |
| 11 | + | |
| 12 | + /** | |
| 13 | + * 编码消息 | |
| 14 | + * | |
| 15 | + * @param message 消息 | |
| 16 | + * @param productKey 产品 Key | |
| 17 | + * @param deviceName 设备名称 | |
| 18 | + * @return 编码后的消息内容 | |
| 19 | + */ | |
| 20 | + byte[] encodeDeviceMessage(IotDeviceMessage message, | |
| 21 | + String productKey, String deviceName); | |
| 22 | + | |
| 23 | + /** | |
| 24 | + * 解码消息 | |
| 25 | + * | |
| 26 | + * @param bytes 消息内容 | |
| 27 | + * @param productKey 产品 Key | |
| 28 | + * @param deviceName 设备名称 | |
| 29 | + * @param serverId 设备连接的 serverId | |
| 30 | + * @return 解码后的消息内容 | |
| 31 | + */ | |
| 32 | + IotDeviceMessage decodeDeviceMessage(byte[] bytes, | |
| 33 | + String productKey, String deviceName, String serverId); | |
| 34 | + | |
| 35 | + /** | |
| 36 | + * 构建【设备上线】消息 | |
| 37 | + * | |
| 38 | + * @return 消息 | |
| 39 | + */ | |
| 40 | + IotDeviceMessage buildDeviceMessageOfStateOnline(String productKey, String deviceName, String serverId); | |
| 41 | + | |
| 42 | +} | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/service/message/IotDeviceMessageServiceImpl.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.gateway.service.message; | |
| 2 | + | |
| 3 | +import cn.hutool.core.util.StrUtil; | |
| 4 | +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; | |
| 5 | +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; | |
| 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.alink.IotAlinkDeviceMessageCodec; | |
| 9 | +import org.springframework.stereotype.Service; | |
| 10 | + | |
| 11 | +import java.time.LocalDateTime; | |
| 12 | +import java.util.List; | |
| 13 | +import java.util.Map; | |
| 14 | + | |
| 15 | +/** | |
| 16 | + * IoT 设备消息 Service 实现类 | |
| 17 | + * | |
| 18 | + * @author 芋道源码 | |
| 19 | + */ | |
| 20 | +@Service | |
| 21 | +public class IotDeviceMessageServiceImpl implements IotDeviceMessageService { | |
| 22 | + | |
| 23 | + /** | |
| 24 | + * 编解码器 | |
| 25 | + */ | |
| 26 | + private final Map<String, IotAlinkDeviceMessageCodec> codes; | |
| 27 | + | |
| 28 | + public IotDeviceMessageServiceImpl(List<IotAlinkDeviceMessageCodec> codes) { | |
| 29 | + this.codes = CollectionUtils.convertMap(codes, IotAlinkDeviceMessageCodec::type); | |
| 30 | + } | |
| 31 | + | |
| 32 | + @Override | |
| 33 | + public byte[] encodeDeviceMessage(IotDeviceMessage message, | |
| 34 | + String productKey, String deviceName) { | |
| 35 | + // TODO @芋艿:获取设备信息 | |
| 36 | + String codecType = "alink"; | |
| 37 | + return codes.get(codecType).encode(message); | |
| 38 | + } | |
| 39 | + | |
| 40 | + @Override | |
| 41 | + public IotDeviceMessage decodeDeviceMessage(byte[] bytes, | |
| 42 | + String productKey, String deviceName, String serverId) { | |
| 43 | + // TODO @芋艿:获取设备信息 | |
| 44 | + String codecType = "alink"; | |
| 45 | + IotDeviceMessage message = codes.get(codecType).decode(bytes); | |
| 46 | + // 补充后端字段 | |
| 47 | + Long deviceId = 25L; | |
| 48 | + Long tenantId = 1L; | |
| 49 | + appendDeviceMessage(message, deviceId, tenantId, serverId); | |
| 50 | + return message; | |
| 51 | + } | |
| 52 | + | |
| 53 | + @Override | |
| 54 | + public IotDeviceMessage buildDeviceMessageOfStateOnline(String productKey, String deviceName, String serverId) { | |
| 55 | + IotDeviceMessage message = IotDeviceMessage.of(null, | |
| 56 | + IotDeviceMessageMethodEnum.STATE_ONLINE.getMethod(), null); | |
| 57 | + // 补充后端字段 | |
| 58 | + Long deviceId = 25L; | |
| 59 | + Long tenantId = 1L; | |
| 60 | + return appendDeviceMessage(message, deviceId, tenantId, serverId); | |
| 61 | + } | |
| 62 | + | |
| 63 | + /** | |
| 64 | + * 补充消息的后端字段 | |
| 65 | + * | |
| 66 | + * @param message 消息 | |
| 67 | + * @param deviceId 设备编号 | |
| 68 | + * @param tenantId 租户编号 | |
| 69 | + * @param serverId 设备连接的 serverId | |
| 70 | + * @return 消息 | |
| 71 | + */ | |
| 72 | + private IotDeviceMessage appendDeviceMessage(IotDeviceMessage message, | |
| 73 | + Long deviceId, Long tenantId, String serverId) { | |
| 74 | + message.setId(IotDeviceMessageUtils.generateMessageId()).setReportTime(LocalDateTime.now()) | |
| 75 | + .setDeviceId(deviceId).setTenantId(tenantId).setServerId(serverId); | |
| 76 | + // 特殊:如果设备没有指定 requestId,则使用 messageId | |
| 77 | + if (StrUtil.isEmpty(message.getRequestId())) { | |
| 78 | + message.setRequestId(message.getId()); | |
| 79 | + } | |
| 80 | + return message; | |
| 81 | + } | |
| 82 | + | |
| 83 | +} | ... | ... |