Commit 824a801b39225d356bf5a7d6745e62d084b93a83

Authored by 安浩浩
1 parent 71b45a29

【功能完善】IoT: 添加 Webhook 处理器以处理设备连接和断开事件,更新设备状态管理逻辑

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