Commit 824a801b39225d356bf5a7d6745e62d084b93a83
1 parent
71b45a29
【功能完善】IoT: 添加 Webhook 处理器以处理设备连接和断开事件,更新设备状态管理逻辑
Showing
2 changed files
with
158 additions
and
0 deletions
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-emqx/src/main/java/cn/iocoder/yudao/module/iot/plugin/emqx/upstream/IotDeviceUpstreamServer.java
| @@ -5,6 +5,7 @@ import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi; | @@ -5,6 +5,7 @@ import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi; | ||
| 5 | import cn.iocoder.yudao.module.iot.plugin.emqx.config.IotPluginEmqxProperties; | 5 | import cn.iocoder.yudao.module.iot.plugin.emqx.config.IotPluginEmqxProperties; |
| 6 | import cn.iocoder.yudao.module.iot.plugin.emqx.upstream.router.IotDeviceAuthVertxHandler; | 6 | import cn.iocoder.yudao.module.iot.plugin.emqx.upstream.router.IotDeviceAuthVertxHandler; |
| 7 | import cn.iocoder.yudao.module.iot.plugin.emqx.upstream.router.IotDeviceMqttMessageHandler; | 7 | import cn.iocoder.yudao.module.iot.plugin.emqx.upstream.router.IotDeviceMqttMessageHandler; |
| 8 | +import cn.iocoder.yudao.module.iot.plugin.emqx.upstream.router.IotDeviceWebhookVertxHandler; | ||
| 8 | import io.netty.handler.codec.mqtt.MqttQoS; | 9 | import io.netty.handler.codec.mqtt.MqttQoS; |
| 9 | import io.vertx.core.Future; | 10 | import io.vertx.core.Future; |
| 10 | import io.vertx.core.Vertx; | 11 | import io.vertx.core.Vertx; |
| @@ -70,6 +71,9 @@ public class IotDeviceUpstreamServer { | @@ -70,6 +71,9 @@ public class IotDeviceUpstreamServer { | ||
| 70 | // TODO @haohao:疑问,mqtt 的认证,需要通过 http 呀? | 71 | // TODO @haohao:疑问,mqtt 的认证,需要通过 http 呀? |
| 71 | // 回复:MQTT 认证不必须通过 HTTP 进行,但 HTTP 认证是 EMQX 等 MQTT 服务器支持的一种灵活的认证方式 | 72 | // 回复:MQTT 认证不必须通过 HTTP 进行,但 HTTP 认证是 EMQX 等 MQTT 服务器支持的一种灵活的认证方式 |
| 72 | .handler(new IotDeviceAuthVertxHandler(deviceUpstreamApi)); | 73 | .handler(new IotDeviceAuthVertxHandler(deviceUpstreamApi)); |
| 74 | + // 添加 Webhook 处理器,用于处理设备连接和断开连接事件 | ||
| 75 | + router.post(IotDeviceWebhookVertxHandler.PATH) | ||
| 76 | + .handler(new IotDeviceWebhookVertxHandler(deviceUpstreamApi)); | ||
| 73 | // 创建 HttpServer 实例 | 77 | // 创建 HttpServer 实例 |
| 74 | this.server = vertx.createHttpServer().requestHandler(router); | 78 | this.server = vertx.createHttpServer().requestHandler(router); |
| 75 | this.mqttMessageHandler = new IotDeviceMqttMessageHandler(deviceUpstreamApi, client); | 79 | this.mqttMessageHandler = new IotDeviceMqttMessageHandler(deviceUpstreamApi, client); |
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-emqx/src/main/java/cn/iocoder/yudao/module/iot/plugin/emqx/upstream/router/IotDeviceWebhookVertxHandler.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.plugin.emqx.upstream.router; | ||
| 2 | + | ||
| 3 | +import cn.hutool.core.util.StrUtil; | ||
| 4 | +import cn.iocoder.yudao.framework.common.pojo.CommonResult; | ||
| 5 | +import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi; | ||
| 6 | +import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDeviceStateUpdateReqDTO; | ||
| 7 | +import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStateEnum; | ||
| 8 | +import cn.iocoder.yudao.module.iot.plugin.common.util.IotPluginCommonUtils; | ||
| 9 | +import io.vertx.core.Handler; | ||
| 10 | +import io.vertx.core.json.JsonObject; | ||
| 11 | +import io.vertx.ext.web.RoutingContext; | ||
| 12 | +import lombok.RequiredArgsConstructor; | ||
| 13 | +import lombok.extern.slf4j.Slf4j; | ||
| 14 | + | ||
| 15 | +import java.time.LocalDateTime; | ||
| 16 | +import java.util.Collections; | ||
| 17 | + | ||
| 18 | +/** | ||
| 19 | + * IoT Emqx Webhook 事件处理的 Vert.x Handler | ||
| 20 | + * <a href= | ||
| 21 | + * "https://docs.emqx.com/zh/emqx/latest/data-integration/webhook.html">EMQX | ||
| 22 | + * Webhook</a> | ||
| 23 | + * | ||
| 24 | + * @author haohao | ||
| 25 | + */ | ||
| 26 | +@RequiredArgsConstructor | ||
| 27 | +@Slf4j | ||
| 28 | +public class IotDeviceWebhookVertxHandler implements Handler<RoutingContext> { | ||
| 29 | + | ||
| 30 | + public static final String PATH = "/mqtt/webhook"; | ||
| 31 | + | ||
| 32 | + private final IotDeviceUpstreamApi deviceUpstreamApi; | ||
| 33 | + | ||
| 34 | + @Override | ||
| 35 | + public void handle(RoutingContext routingContext) { | ||
| 36 | + try { | ||
| 37 | + // 解析请求体 | ||
| 38 | + JsonObject json = routingContext.body().asJsonObject(); | ||
| 39 | + String event = json.getString("event"); | ||
| 40 | + String clientId = json.getString("clientid"); | ||
| 41 | + String username = json.getString("username"); | ||
| 42 | + | ||
| 43 | + // 处理不同的事件类型 | ||
| 44 | + switch (event) { | ||
| 45 | + case "client.connected": | ||
| 46 | + handleClientConnected(clientId, username); | ||
| 47 | + break; | ||
| 48 | + case "client.disconnected": | ||
| 49 | + handleClientDisconnected(clientId, username); | ||
| 50 | + break; | ||
| 51 | + default: | ||
| 52 | + log.info("[handle][未处理的 Webhook 事件] event={}, clientId={}, username={}", event, clientId, | ||
| 53 | + username); | ||
| 54 | + break; | ||
| 55 | + } | ||
| 56 | + | ||
| 57 | + // 返回成功响应 | ||
| 58 | + IotPluginCommonUtils.writeJson(routingContext, Collections.singletonMap("result", "success")); | ||
| 59 | + } catch (Exception e) { | ||
| 60 | + log.error("[handle][处理 Webhook 事件异常]", e); | ||
| 61 | + IotPluginCommonUtils.writeJson(routingContext, Collections.singletonMap("result", "error")); | ||
| 62 | + } | ||
| 63 | + } | ||
| 64 | + | ||
| 65 | + /** | ||
| 66 | + * 处理客户端连接事件 | ||
| 67 | + * | ||
| 68 | + * @param clientId 客户端ID | ||
| 69 | + * @param username 用户名 | ||
| 70 | + */ | ||
| 71 | + private void handleClientConnected(String clientId, String username) { | ||
| 72 | + if (StrUtil.isEmpty(username) || "undefined".equals(username)) { | ||
| 73 | + log.warn("[handleClientConnected][客户端连接事件,但用户名为空] clientId={}", clientId); | ||
| 74 | + return; | ||
| 75 | + } | ||
| 76 | + | ||
| 77 | + // 解析产品标识和设备名称 | ||
| 78 | + String[] parts = parseUsername(username); | ||
| 79 | + if (parts == null) { | ||
| 80 | + return; | ||
| 81 | + } | ||
| 82 | + | ||
| 83 | + // 更新设备状态为在线 | ||
| 84 | + IotDeviceStateUpdateReqDTO updateReqDTO = new IotDeviceStateUpdateReqDTO(); | ||
| 85 | + updateReqDTO.setProductKey(parts[1]); | ||
| 86 | + updateReqDTO.setDeviceName(parts[0]); | ||
| 87 | + updateReqDTO.setState(IotDeviceStateEnum.ONLINE.getState()); | ||
| 88 | + updateReqDTO.setProcessId(IotPluginCommonUtils.getProcessId()); | ||
| 89 | + updateReqDTO.setReportTime(LocalDateTime.now()); | ||
| 90 | + | ||
| 91 | + CommonResult<Boolean> result = deviceUpstreamApi.updateDeviceState(updateReqDTO); | ||
| 92 | + if (result.getCode() != 0 || !result.getData()) { | ||
| 93 | + log.error("[handleClientConnected][更新设备状态为在线失败] clientId={}, username={}, code={}, msg={}", | ||
| 94 | + clientId, username, result.getCode(), result.getMsg()); | ||
| 95 | + } else { | ||
| 96 | + log.info("[handleClientConnected][更新设备状态为在线成功] clientId={}, username={}", clientId, username); | ||
| 97 | + } | ||
| 98 | + } | ||
| 99 | + | ||
| 100 | + /** | ||
| 101 | + * 处理客户端断开连接事件 | ||
| 102 | + * | ||
| 103 | + * @param clientId 客户端ID | ||
| 104 | + * @param username 用户名 | ||
| 105 | + */ | ||
| 106 | + private void handleClientDisconnected(String clientId, String username) { | ||
| 107 | + if (StrUtil.isEmpty(username) || "undefined".equals(username)) { | ||
| 108 | + log.warn("[handleClientDisconnected][客户端断开连接事件,但用户名为空] clientId={}", clientId); | ||
| 109 | + return; | ||
| 110 | + } | ||
| 111 | + | ||
| 112 | + // 解析产品标识和设备名称 | ||
| 113 | + String[] parts = parseUsername(username); | ||
| 114 | + if (parts == null) { | ||
| 115 | + return; | ||
| 116 | + } | ||
| 117 | + | ||
| 118 | + // 更新设备状态为离线 | ||
| 119 | + IotDeviceStateUpdateReqDTO offlineReqDTO = new IotDeviceStateUpdateReqDTO(); | ||
| 120 | + offlineReqDTO.setProductKey(parts[1]); | ||
| 121 | + offlineReqDTO.setDeviceName(parts[0]); | ||
| 122 | + offlineReqDTO.setState(IotDeviceStateEnum.OFFLINE.getState()); | ||
| 123 | + offlineReqDTO.setProcessId(IotPluginCommonUtils.getProcessId()); | ||
| 124 | + offlineReqDTO.setReportTime(LocalDateTime.now()); | ||
| 125 | + | ||
| 126 | + CommonResult<Boolean> offlineResult = deviceUpstreamApi.updateDeviceState(offlineReqDTO); | ||
| 127 | + if (offlineResult.getCode() != 0 || !offlineResult.getData()) { | ||
| 128 | + log.error("[handleClientDisconnected][更新设备状态为离线失败] clientId={}, username={}, code={}, msg={}", | ||
| 129 | + clientId, username, offlineResult.getCode(), offlineResult.getMsg()); | ||
| 130 | + } else { | ||
| 131 | + log.info("[handleClientDisconnected][更新设备状态为离线成功] clientId={}, username={}", clientId, username); | ||
| 132 | + } | ||
| 133 | + } | ||
| 134 | + | ||
| 135 | + /** | ||
| 136 | + * 解析用户名,格式为 deviceName&productKey | ||
| 137 | + * | ||
| 138 | + * @param username 用户名 | ||
| 139 | + * @return 解析结果,[0]为deviceName,[1]为productKey,解析失败返回null | ||
| 140 | + */ | ||
| 141 | + private String[] parseUsername(String username) { | ||
| 142 | + if (StrUtil.isEmpty(username)) { | ||
| 143 | + return null; | ||
| 144 | + } | ||
| 145 | + | ||
| 146 | + String[] parts = username.split("&"); | ||
| 147 | + if (parts.length != 2) { | ||
| 148 | + log.warn("[parseUsername][用户名格式不正确,无法解析产品标识和设备名称] username={}", username); | ||
| 149 | + return null; | ||
| 150 | + } | ||
| 151 | + | ||
| 152 | + return parts; | ||
| 153 | + } | ||
| 154 | +} | ||
| 0 | \ No newline at end of file | 155 | \ No newline at end of file |