【代码评审】IoT:整体实现

This commit is contained in:
YunaiV 2025-03-16 23:11:04 +08:00
parent b6c7937aeb
commit a9733b4d2a
18 changed files with 83 additions and 84 deletions

View File

@ -43,8 +43,9 @@ public class IotPluginCommonAutoConfiguration {
} }
@Bean(initMethod = "init", destroyMethod = "stop") @Bean(initMethod = "init", destroyMethod = "stop")
public IotPluginInstanceHeartbeatJob pluginInstanceHeartbeatJob( public IotPluginInstanceHeartbeatJob pluginInstanceHeartbeatJob(IotDeviceUpstreamApi deviceDataApi,
IotDeviceUpstreamApi deviceDataApi, IotDeviceDownstreamServer deviceDownstreamServer, IotPluginCommonProperties commonProperties) { IotDeviceDownstreamServer deviceDownstreamServer,
IotPluginCommonProperties commonProperties) {
return new IotPluginInstanceHeartbeatJob(deviceDataApi, deviceDownstreamServer, commonProperties); return new IotPluginInstanceHeartbeatJob(deviceDataApi, deviceDownstreamServer, commonProperties);
} }

View File

@ -25,6 +25,7 @@ import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeC
@RequiredArgsConstructor @RequiredArgsConstructor
public class IotDeviceConfigSetVertxHandler implements Handler<RoutingContext> { public class IotDeviceConfigSetVertxHandler implements Handler<RoutingContext> {
// TODO @haohao是不是可以把 PATHMethod 所有的抽到一个枚举类里因为 topicpathmethod 相当于不同的几个表达
public static final String PATH = "/sys/:productKey/:deviceName/thing/service/config/set"; public static final String PATH = "/sys/:productKey/:deviceName/thing/service/config/set";
public static final String METHOD = "thing.service.config.set"; public static final String METHOD = "thing.service.config.set";
@ -57,12 +58,9 @@ public class IotDeviceConfigSetVertxHandler implements Handler<RoutingContext> {
CommonResult<Boolean> result = deviceDownstreamHandler.setDeviceConfig(reqDTO); CommonResult<Boolean> result = deviceDownstreamHandler.setDeviceConfig(reqDTO);
// 3. 响应结果 // 3. 响应结果
IotStandardResponse response; IotStandardResponse response = result.isSuccess() ?
if (result.isSuccess()) { IotStandardResponse.success(reqDTO.getRequestId(), METHOD, result.getData())
response = IotStandardResponse.success(reqDTO.getRequestId(), METHOD, result.getData()); : IotStandardResponse.error(reqDTO.getRequestId(), METHOD, result.getCode(), result.getMsg());
} else {
response = IotStandardResponse.error(reqDTO.getRequestId(), METHOD, result.getCode(), result.getMsg());
}
IotPluginCommonUtils.writeJsonResponse(routingContext, response); IotPluginCommonUtils.writeJsonResponse(routingContext, response);
} catch (Exception e) { } catch (Exception e) {
log.error("[handle][请求参数({}) 配置设置异常]", reqDTO, e); log.error("[handle][请求参数({}) 配置设置异常]", reqDTO, e);

View File

@ -62,15 +62,14 @@ public class IotDeviceOtaUpgradeVertxHandler implements Handler<RoutingContext>
CommonResult<Boolean> result = deviceDownstreamHandler.upgradeDeviceOta(reqDTO); CommonResult<Boolean> result = deviceDownstreamHandler.upgradeDeviceOta(reqDTO);
// 3. 响应结果 // 3. 响应结果
IotStandardResponse response; // TODO @haohao可以考虑 IotStandardResponse.of(requestId, method, CommonResult)
if (result.isSuccess()) { IotStandardResponse response = result.isSuccess() ?
response = IotStandardResponse.success(reqDTO.getRequestId(), METHOD, result.getData()); IotStandardResponse.success(reqDTO.getRequestId(), METHOD, result.getData())
} else { :IotStandardResponse.error(reqDTO.getRequestId(), METHOD, result.getCode(), result.getMsg());
response = IotStandardResponse.error(reqDTO.getRequestId(), METHOD, result.getCode(), result.getMsg());
}
IotPluginCommonUtils.writeJsonResponse(routingContext, response); IotPluginCommonUtils.writeJsonResponse(routingContext, response);
} catch (Exception e) { } catch (Exception e) {
log.error("[handle][请求参数({}) OTA 升级异常]", reqDTO, e); log.error("[handle][请求参数({}) OTA 升级异常]", reqDTO, e);
// TODO @haohao可以考虑 IotStandardResponse.of(requestId, method, ErrorCode)
IotStandardResponse errorResponse = IotStandardResponse.error( IotStandardResponse errorResponse = IotStandardResponse.error(
reqDTO.getRequestId(), METHOD, INTERNAL_SERVER_ERROR.getCode(), INTERNAL_SERVER_ERROR.getMsg()); reqDTO.getRequestId(), METHOD, INTERNAL_SERVER_ERROR.getCode(), INTERNAL_SERVER_ERROR.getMsg());
IotPluginCommonUtils.writeJsonResponse(routingContext, errorResponse); IotPluginCommonUtils.writeJsonResponse(routingContext, errorResponse);

View File

@ -1,7 +1,6 @@
package cn.iocoder.yudao.module.iot.plugin.common.pojo; package cn.iocoder.yudao.module.iot.plugin.common.pojo;
import lombok.Data; import lombok.Data;
import lombok.experimental.Accessors;
// TODO @芋艿1后续考虑要不要叫 IoT 网关之类的 Response2包名 pojo // TODO @芋艿1后续考虑要不要叫 IoT 网关之类的 Response2包名 pojo
/** /**
@ -12,7 +11,6 @@ import lombok.experimental.Accessors;
* @author haohao * @author haohao
*/ */
@Data @Data
@Accessors(chain = true)
public class IotStandardResponse { public class IotStandardResponse {
/** /**
@ -92,4 +90,5 @@ public class IotStandardResponse {
.setMethod(method) .setMethod(method)
.setVersion("1.0"); .setVersion("1.0");
} }
} }

View File

@ -72,4 +72,5 @@ public class IotPluginCommonUtils {
.putHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE) .putHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE)
.end(JsonUtils.toJsonString(response)); .end(JsonUtils.toJsonString(response));
} }
} }

View File

@ -15,6 +15,7 @@
<version>1.0.0</version> <version>1.0.0</version>
<name>${project.artifactId}</name> <name>${project.artifactId}</name>
<!-- TODO @芋艿:待整理 -->
<description> <description>
物联网 插件模块 - emqx 插件 物联网 插件模块 - emqx 插件
</description> </description>

View File

@ -14,7 +14,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
/** /**
* IoT 插件 Emqx 的专用自动配置类 * IoT 插件 EMQX 的专用自动配置类
* *
* @author haohao * @author haohao
*/ */
@ -34,7 +34,7 @@ public class IotPluginEmqxAutoConfiguration {
.setClientId("yudao-iot-downstream-" + IdUtil.fastSimpleUUID()) .setClientId("yudao-iot-downstream-" + IdUtil.fastSimpleUUID())
.setUsername(emqxProperties.getMqttUsername()) .setUsername(emqxProperties.getMqttUsername())
.setPassword(emqxProperties.getMqttPassword()) .setPassword(emqxProperties.getMqttPassword())
.setSsl(emqxProperties.isMqttSsl()); .setSsl(emqxProperties.getMqttSsl());
return MqttClient.create(vertx, options); return MqttClient.create(vertx, options);
} }

View File

@ -14,6 +14,8 @@ import org.springframework.validation.annotation.Validated;
@Data @Data
public class IotPluginEmqxProperties { public class IotPluginEmqxProperties {
// TODO @haohao参数校验加下啊哈
/** /**
* 服务主机 * 服务主机
*/ */
@ -21,12 +23,11 @@ public class IotPluginEmqxProperties {
/** /**
* 服务端口 * 服务端口
*/ */
private int mqttPort; private Integer mqttPort;
/** /**
* 服务用户名 * 服务用户名
*/ */
private String mqttUsername; private String mqttUsername;
/** /**
* 服务密码 * 服务密码
*/ */
@ -34,7 +35,7 @@ public class IotPluginEmqxProperties {
/** /**
* 是否启用 SSL * 是否启用 SSL
*/ */
private boolean mqttSsl; private Boolean mqttSsl;
/** /**
* 订阅的主题列表 * 订阅的主题列表
@ -44,6 +45,6 @@ public class IotPluginEmqxProperties {
/** /**
* 认证端口 * 认证端口
*/ */
private int authPort; private Integer authPort;
} }

View File

@ -25,6 +25,7 @@ public class IotDeviceDownstreamHandlerImpl implements IotDeviceDownstreamHandle
private static final String SYS_TOPIC_PREFIX = "/sys/"; private static final String SYS_TOPIC_PREFIX = "/sys/";
// TODO @haohao是不是可以类似 IotDeviceConfigSetVertxHandler 的建议抽到统一的枚举类
// TODO @haohao讨论感觉 mqtt http可以做个相对统一的格式哈回复 都使用 Alink 格式方便后续扩展 // TODO @haohao讨论感觉 mqtt http可以做个相对统一的格式哈回复 都使用 Alink 格式方便后续扩展
// 设备服务调用 标准 JSON // 设备服务调用 标准 JSON
// 请求Topic/sys/${productKey}/${deviceName}/thing/service/${tsl.service.identifier} // 请求Topic/sys/${productKey}/${deviceName}/thing/service/${tsl.service.identifier}
@ -63,7 +64,6 @@ public class IotDeviceDownstreamHandlerImpl implements IotDeviceDownstreamHandle
// 构建请求消息 // 构建请求消息
String requestId = reqDTO.getRequestId() != null ? reqDTO.getRequestId() : generateRequestId(); String requestId = reqDTO.getRequestId() != null ? reqDTO.getRequestId() : generateRequestId();
JSONObject request = buildServiceRequest(requestId, reqDTO.getIdentifier(), reqDTO.getParams()); JSONObject request = buildServiceRequest(requestId, reqDTO.getIdentifier(), reqDTO.getParams());
// 发送消息 // 发送消息
publishMessage(topic, request); publishMessage(topic, request);
@ -82,9 +82,8 @@ public class IotDeviceDownstreamHandlerImpl implements IotDeviceDownstreamHandle
@Override @Override
public CommonResult<Boolean> setDeviceProperty(IotDevicePropertySetReqDTO reqDTO) { public CommonResult<Boolean> setDeviceProperty(IotDevicePropertySetReqDTO reqDTO) {
log.info("[setProperty][开始设置设备属性][reqDTO: {}]", JSONUtil.toJsonStr(reqDTO));
// 验证参数 // 验证参数
log.info("[setProperty][开始设置设备属性][reqDTO: {}]", JSONUtil.toJsonStr(reqDTO));
if (reqDTO.getProductKey() == null || reqDTO.getDeviceName() == null) { if (reqDTO.getProductKey() == null || reqDTO.getDeviceName() == null) {
log.error("[setProperty][参数不完整][reqDTO: {}]", JSONUtil.toJsonStr(reqDTO)); log.error("[setProperty][参数不完整][reqDTO: {}]", JSONUtil.toJsonStr(reqDTO));
return CommonResult.error(MQTT_TOPIC_ILLEGAL.getCode(), MQTT_TOPIC_ILLEGAL.getMsg()); return CommonResult.error(MQTT_TOPIC_ILLEGAL.getCode(), MQTT_TOPIC_ILLEGAL.getMsg());
@ -96,7 +95,6 @@ public class IotDeviceDownstreamHandlerImpl implements IotDeviceDownstreamHandle
// 构建请求消息 // 构建请求消息
String requestId = reqDTO.getRequestId() != null ? reqDTO.getRequestId() : generateRequestId(); String requestId = reqDTO.getRequestId() != null ? reqDTO.getRequestId() : generateRequestId();
JSONObject request = buildPropertySetRequest(requestId, reqDTO.getProperties()); JSONObject request = buildPropertySetRequest(requestId, reqDTO.getProperties());
// 发送消息 // 发送消息
publishMessage(topic, request); publishMessage(topic, request);
@ -132,6 +130,7 @@ public class IotDeviceDownstreamHandlerImpl implements IotDeviceDownstreamHandle
return SYS_TOPIC_PREFIX + productKey + "/" + deviceName + PROPERTY_SET_TOPIC; return SYS_TOPIC_PREFIX + productKey + "/" + deviceName + PROPERTY_SET_TOPIC;
} }
// TODO @haohao这个后面搞个对象会不会好点哈
/** /**
* 构建服务调用请求 * 构建服务调用请求
*/ */
@ -168,7 +167,7 @@ public class IotDeviceDownstreamHandlerImpl implements IotDeviceDownstreamHandle
} }
/** /**
* 生成请求ID * 生成请求 ID
*/ */
private String generateRequestId() { private String generateRequestId() {
return IdUtil.fastSimpleUUID(); return IdUtil.fastSimpleUUID();

View File

@ -36,10 +36,6 @@ public class IotDeviceUpstreamServer {
* 连接超时时间(毫秒) * 连接超时时间(毫秒)
*/ */
private static final int CONNECTION_TIMEOUT_MS = 10000; private static final int CONNECTION_TIMEOUT_MS = 10000;
/**
* 主题分隔符
*/
private static final String TOPIC_SEPARATOR = ",";
/** /**
* 默认 QoS 级别 * 默认 QoS 级别
*/ */
@ -84,42 +80,40 @@ public class IotDeviceUpstreamServer {
*/ */
public void start() { public void start() {
if (isRunning) { if (isRunning) {
log.warn("服务已经在运行中,请勿重复启动"); log.warn("[start][服务已经在运行中,请勿重复启动]");
return; return;
} }
log.info("[start][开始启动服务]");
log.info("[start] 开始启动服务");
// 1. 启动 HTTP 服务器 // 1. 启动 HTTP 服务器
CompletableFuture<Void> httpFuture = server.listen(emqxProperties.getAuthPort()) CompletableFuture<Void> httpFuture = server.listen(emqxProperties.getAuthPort())
.toCompletionStage() .toCompletionStage()
.toCompletableFuture() .toCompletableFuture()
.thenAccept(v -> log.info("[start] HTTP服务器启动完成端口: {}", server.actualPort())); .thenAccept(v -> log.info("[start][HTTP服务器启动完成端口: {}]", server.actualPort()));
// 2. 连接 MQTT Broker // 2. 连接 MQTT Broker
CompletableFuture<Void> mqttFuture = connectMqtt() CompletableFuture<Void> mqttFuture = connectMqtt()
.toCompletionStage() .toCompletionStage()
.toCompletableFuture() .toCompletableFuture()
.thenAccept(v -> { .thenAccept(v -> {
// 3. 添加 MQTT 断开重连监听器 // 2.1 添加 MQTT 断开重连监听器
client.closeHandler(closeEvent -> { client.closeHandler(closeEvent -> {
log.warn("[closeHandler] MQTT连接已断开准备重连"); log.warn("[closeHandler][MQTT连接已断开准备重连]");
reconnectWithDelay(); reconnectWithDelay();
}); });
// 2. 设置 MQTT 消息处理器
// 4. 设置 MQTT 消息处理器
setupMessageHandler(); setupMessageHandler();
}); });
// 等待所有服务启动完成 // 3. 等待所有服务启动完成
CompletableFuture.allOf(httpFuture, mqttFuture) CompletableFuture.allOf(httpFuture, mqttFuture)
.orTimeout(CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS) .orTimeout(CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)
.whenComplete((result, error) -> { .whenComplete((result, error) -> {
if (error != null) { if (error != null) {
log.error("[start] 服务启动失败", error); log.error("[start][服务启动失败]", error);
} else { } else {
isRunning = true; isRunning = true;
log.info("[start] 所有服务启动完成"); log.info("[start][所有服务启动完成]");
} }
}); });
} }
@ -129,7 +123,7 @@ public class IotDeviceUpstreamServer {
*/ */
private void setupMessageHandler() { private void setupMessageHandler() {
client.publishHandler(mqttMessageHandler::handle); client.publishHandler(mqttMessageHandler::handle);
log.debug("[setupMessageHandler] MQTT消息处理器设置完成"); log.debug("[setupMessageHandler][MQTT消息处理器设置完成]");
} }
/** /**
@ -137,12 +131,12 @@ public class IotDeviceUpstreamServer {
*/ */
private void reconnectWithDelay() { private void reconnectWithDelay() {
if (!isRunning) { if (!isRunning) {
log.info("[reconnectWithDelay] 服务已停止,不再尝试重连"); log.info("[reconnectWithDelay][服务已停止,不再尝试重连]");
return; return;
} }
vertx.setTimer(RECONNECT_DELAY_MS, id -> { vertx.setTimer(RECONNECT_DELAY_MS, id -> {
log.info("[reconnectWithDelay] 开始重新连接MQTT"); log.info("[reconnectWithDelay][开始重新连接 MQTT]");
connectMqtt(); connectMqtt();
}); });
} }
@ -155,28 +149,28 @@ public class IotDeviceUpstreamServer {
private Future<Void> connectMqtt() { private Future<Void> connectMqtt() {
return client.connect(emqxProperties.getMqttPort(), emqxProperties.getMqttHost()) return client.connect(emqxProperties.getMqttPort(), emqxProperties.getMqttHost())
.compose(connAck -> { .compose(connAck -> {
log.info("[connectMqtt] MQTT客户端连接成功"); log.info("[connectMqtt][MQTT客户端连接成功]");
return subscribeToTopics(); return subscribeToTopics();
}) })
.recover(err -> { .recover(error -> {
log.error("[connectMqtt] 连接MQTT Broker失败: {}", err.getMessage()); log.error("[connectMqtt][连接MQTT Broker失败:]", error);
reconnectWithDelay(); reconnectWithDelay();
return Future.failedFuture(err); return Future.failedFuture(error);
}); });
} }
/** /**
* 订阅设备上行消息主题 * 订阅设备上行消息主题
* *
* @return 订阅结果的Future * @return 订阅结果的 Future
*/ */
private Future<Void> subscribeToTopics() { private Future<Void> subscribeToTopics() {
String[] topics = emqxProperties.getMqttTopics(); String[] topics = emqxProperties.getMqttTopics();
if (ArrayUtil.isEmpty(topics)) { if (ArrayUtil.isEmpty(topics)) {
log.warn("[subscribeToTopics] 未配置MQTT主题跳过订阅"); log.warn("[subscribeToTopics][未配置MQTT主题跳过订阅]");
return Future.succeededFuture(); return Future.succeededFuture();
} }
log.info("[subscribeToTopics] 开始订阅设备上行消息主题"); log.info("[subscribeToTopics][开始订阅设备上行消息主题]");
Future<Void> compositeFuture = Future.succeededFuture(); Future<Void> compositeFuture = Future.succeededFuture();
for (String topic : topics) { for (String topic : topics) {
@ -186,11 +180,11 @@ public class IotDeviceUpstreamServer {
} }
compositeFuture = compositeFuture.compose(v -> client.subscribe(trimmedTopic, DEFAULT_QOS.value()) compositeFuture = compositeFuture.compose(v -> client.subscribe(trimmedTopic, DEFAULT_QOS.value())
.<Void>map(ack -> { .<Void>map(ack -> {
log.info("[subscribeToTopics] 成功订阅主题: {}", trimmedTopic); log.info("[subscribeToTopics][成功订阅主题: {}]", trimmedTopic);
return null; return null;
}) })
.recover(err -> { .recover(error -> {
log.error("[subscribeToTopics] 订阅主题失败: {}, 原因: {}", trimmedTopic, err.getMessage()); log.error("[subscribeToTopics][订阅主题失败: {}]", trimmedTopic, error);
return Future.<Void>succeededFuture(); // 继续订阅其他主题 return Future.<Void>succeededFuture(); // 继续订阅其他主题
})); }));
} }
@ -202,10 +196,10 @@ public class IotDeviceUpstreamServer {
*/ */
public void stop() { public void stop() {
if (!isRunning) { if (!isRunning) {
log.warn("[stop] 服务未运行,无需停止"); log.warn("[stop][服务未运行,无需停止]");
return; return;
} }
log.info("[stop] 开始关闭服务"); log.info("[stop][开始关闭服务]");
isRunning = false; isRunning = false;
try { try {
@ -224,14 +218,14 @@ public class IotDeviceUpstreamServer {
.orTimeout(CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS) .orTimeout(CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)
.whenComplete((result, error) -> { .whenComplete((result, error) -> {
if (error != null) { if (error != null) {
log.error("[stop] 服务关闭过程中发生异常", error); log.error("[stop][服务关闭过程中发生异常]", error);
} else { } else {
log.info("[stop] 所有服务关闭完成"); log.info("[stop][所有服务关闭完成]");
} }
}); });
} catch (Exception e) { } catch (Exception e) {
log.error("[stop] 关闭服务异常", e); log.error("[stop][关闭服务异常]", e);
throw new RuntimeException("关闭IoT设备上行服务失败", e); throw new RuntimeException("关闭 IoT 设备上行服务失败", e);
} }
} }
} }

View File

@ -15,7 +15,7 @@ import java.util.Collections;
/** /**
* IoT EMQX 连接认证的 Vert.x Handler * IoT EMQX 连接认证的 Vert.x Handler
* *
* <a href="https://docs.emqx.com/zh/emqx/latest/access-control/authn/http.html">EMQX HTTP</a> * 参考<a href="https://docs.emqx.com/zh/emqx/latest/access-control/authn/http.html">EMQX HTTP</a>
* *
* 注意该处理器需要返回特定格式{"result": "allow"} {"result": "deny"} * 注意该处理器需要返回特定格式{"result": "allow"} {"result": "deny"}
* 以符合 EMQX 认证插件的要求因此不使用 IotStandardResponse 实体类 * 以符合 EMQX 认证插件的要求因此不使用 IotStandardResponse 实体类
@ -31,7 +31,6 @@ public class IotDeviceAuthVertxHandler implements Handler<RoutingContext> {
private final IotDeviceUpstreamApi deviceUpstreamApi; private final IotDeviceUpstreamApi deviceUpstreamApi;
@Override @Override
@SuppressWarnings("unchecked")
public void handle(RoutingContext routingContext) { public void handle(RoutingContext routingContext) {
try { try {
// 构建认证请求 DTO // 构建认证请求 DTO

View File

@ -23,7 +23,7 @@ import java.util.Map;
/** /**
* IoT 设备 MQTT 消息处理器 * IoT 设备 MQTT 消息处理器
* *
* 参考"<a href="https://help.aliyun.com/zh/iot/user-guide/device-properties-events-and-services">设备属性、事件、服务</a>"> * 参考<a href="https://help.aliyun.com/zh/iot/user-guide/device-properties-events-and-services">设备属性事件服务</a>
*/ */
@Slf4j @Slf4j
public class IotDeviceMqttMessageHandler { public class IotDeviceMqttMessageHandler {

View File

@ -18,7 +18,7 @@ import java.util.Collections;
/** /**
* IoT EMQX Webhook 事件处理的 Vert.x Handler * IoT EMQX Webhook 事件处理的 Vert.x Handler
* *
* <a href="https://docs.emqx.com/zh/emqx/latest/data-integration/webhook.html">EMQX Webhook</a> * 参考<a href="https://docs.emqx.com/zh/emqx/latest/data-integration/webhook.html">EMQX Webhook</a>
* *
* 注意该处理器需要返回特定格式{"result": "success"} {"result": "error"} * 注意该处理器需要返回特定格式{"result": "success"} {"result": "error"}
* 以符合 EMQX Webhook 插件的要求因此不使用 IotStandardResponse 实体类 * 以符合 EMQX Webhook 插件的要求因此不使用 IotStandardResponse 实体类
@ -51,8 +51,7 @@ public class IotDeviceWebhookVertxHandler implements Handler<RoutingContext> {
handleClientDisconnected(clientId, username); handleClientDisconnected(clientId, username);
break; break;
default: default:
log.info("[handle][未处理的 Webhook 事件] event={}, clientId={}, username={}", event, clientId, log.info("[handle][未处理的 Webhook 事件] event={}, clientId={}, username={}", event, clientId, username);
username);
break; break;
} }

View File

@ -23,10 +23,8 @@ public class IotHttpVertxPlugin extends SpringPlugin {
public void start() { public void start() {
log.info("[HttpVertxPlugin][HttpVertxPlugin 插件启动开始...]"); log.info("[HttpVertxPlugin][HttpVertxPlugin 插件启动开始...]");
try { try {
// 1. 获取插件上下文
ApplicationContext pluginContext = getApplicationContext(); ApplicationContext pluginContext = getApplicationContext();
Assert.notNull(pluginContext, "pluginContext 不能为空"); Assert.notNull(pluginContext, "pluginContext 不能为空");
log.info("[HttpVertxPlugin][HttpVertxPlugin 插件启动成功...]"); log.info("[HttpVertxPlugin][HttpVertxPlugin 插件启动成功...]");
} catch (Exception e) { } catch (Exception e) {
log.error("[HttpVertxPlugin][HttpVertxPlugin 插件开启动异常...]", e); log.error("[HttpVertxPlugin][HttpVertxPlugin 插件开启动异常...]", e);
@ -43,6 +41,7 @@ public class IotHttpVertxPlugin extends SpringPlugin {
} }
} }
// TODO @芋艿思考下未来要不要
@Override @Override
protected ApplicationContext createApplicationContext() { protected ApplicationContext createApplicationContext() {
// 创建插件自己的 ApplicationContext // 创建插件自己的 ApplicationContext
@ -52,6 +51,7 @@ public class IotHttpVertxPlugin extends SpringPlugin {
// 继续使用插件自己的 ClassLoader 以加载插件内部的类 // 继续使用插件自己的 ClassLoader 以加载插件内部的类
pluginContext.setClassLoader(getWrapper().getPluginClassLoader()); pluginContext.setClassLoader(getWrapper().getPluginClassLoader());
// 扫描当前插件的自动配置包 // 扫描当前插件的自动配置包
// TODO @芋艿后续看看怎么配置类包
pluginContext.scan("cn.iocoder.yudao.module.iot.plugin.http.config"); pluginContext.scan("cn.iocoder.yudao.module.iot.plugin.http.config");
pluginContext.refresh(); pluginContext.refresh();
return pluginContext; return pluginContext;

View File

@ -34,6 +34,7 @@ import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeC
@Slf4j @Slf4j
public class IotDeviceUpstreamVertxHandler implements Handler<RoutingContext> { public class IotDeviceUpstreamVertxHandler implements Handler<RoutingContext> {
// TODO @haohao要不要类似 IotDeviceConfigSetVertxHandler 写的把这些 PATHMETHOD 之类的抽走
/** /**
* 属性上报路径 * 属性上报路径
*/ */
@ -49,6 +50,7 @@ public class IotDeviceUpstreamVertxHandler implements Handler<RoutingContext> {
private final IotDeviceUpstreamApi deviceUpstreamApi; private final IotDeviceUpstreamApi deviceUpstreamApi;
// TODO @haohao要不要分成多个 Handler每个只解决一个问题哈
@Override @Override
public void handle(RoutingContext routingContext) { public void handle(RoutingContext routingContext) {
String path = routingContext.request().path(); String path = routingContext.request().path();
@ -102,7 +104,6 @@ public class IotDeviceUpstreamVertxHandler implements Handler<RoutingContext> {
IotPluginCommonUtils.writeJsonResponse(routingContext, response); IotPluginCommonUtils.writeJsonResponse(routingContext, response);
} catch (Exception e) { } catch (Exception e) {
log.error("[handle][处理上行请求异常] path={}", path, e); log.error("[handle][处理上行请求异常] path={}", path, e);
// 构建错误响应
String method = path.contains("/property/") ? PROPERTY_METHOD String method = path.contains("/property/") ? PROPERTY_METHOD
: EVENT_METHOD_PREFIX + (routingContext.pathParams().containsKey("identifier") : EVENT_METHOD_PREFIX + (routingContext.pathParams().containsKey("identifier")
? routingContext.pathParam("identifier") ? routingContext.pathParam("identifier")
@ -115,27 +116,28 @@ public class IotDeviceUpstreamVertxHandler implements Handler<RoutingContext> {
/** /**
* 更新设备状态 * 更新设备状态
* *
* @param productKey 产品Key * @param productKey 产品 Key
* @param deviceName 设备名称 * @param deviceName 设备名称
*/ */
private void updateDeviceState(String productKey, String deviceName) { private void updateDeviceState(String productKey, String deviceName) {
deviceUpstreamApi.updateDeviceState(((IotDeviceStateUpdateReqDTO) new IotDeviceStateUpdateReqDTO().setRequestId(IdUtil.fastSimpleUUID()).setProcessId(IotPluginCommonUtils.getProcessId()).setReportTime(LocalDateTime.now()).setProductKey(productKey).setDeviceName(deviceName)).setState(IotDeviceStateEnum.ONLINE.getState())); deviceUpstreamApi.updateDeviceState(((IotDeviceStateUpdateReqDTO) new IotDeviceStateUpdateReqDTO()
.setRequestId(IdUtil.fastSimpleUUID()).setProcessId(IotPluginCommonUtils.getProcessId()).setReportTime(LocalDateTime.now())
.setProductKey(productKey).setDeviceName(deviceName)).setState(IotDeviceStateEnum.ONLINE.getState()));
} }
/** /**
* 解析属性上报请求 * 解析属性上报请求
* *
* @param productKey 产品Key * @param productKey 产品 Key
* @param deviceName 设备名称 * @param deviceName 设备名称
* @param requestId 请求ID * @param requestId 请求 ID
* @param body 请求体 * @param body 请求体
* @return 属性上报请求DTO * @return 属性上报请求 DTO
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private IotDevicePropertyReportReqDTO parsePropertyReportRequest(String productKey, String deviceName, String requestId, JsonObject body) { private IotDevicePropertyReportReqDTO parsePropertyReportRequest(String productKey, String deviceName, String requestId, JsonObject body) {
// 按照标准 JSON 格式处理属性数据 // 按照标准 JSON 格式处理属性数据
Map<String, Object> properties = new HashMap<>(); Map<String, Object> properties = new HashMap<>();
// 优先使用 params 字段符合标准
Map<String, Object> params = body.getJsonObject("params") != null ? body.getJsonObject("params").getMap() : null; Map<String, Object> params = body.getJsonObject("params") != null ? body.getJsonObject("params").getMap() : null;
if (params != null) { if (params != null) {
// 将标准格式的 params 转换为平台需要的 properties 格式 // 将标准格式的 params 转换为平台需要的 properties 格式
@ -153,24 +155,25 @@ public class IotDeviceUpstreamVertxHandler implements Handler<RoutingContext> {
} }
// 构建属性上报请求 DTO // 构建属性上报请求 DTO
return ((IotDevicePropertyReportReqDTO) new IotDevicePropertyReportReqDTO().setRequestId(requestId).setProcessId(IotPluginCommonUtils.getProcessId()).setReportTime(LocalDateTime.now()).setProductKey(productKey).setDeviceName(deviceName)).setProperties(properties); return ((IotDevicePropertyReportReqDTO) new IotDevicePropertyReportReqDTO().setRequestId(requestId)
.setProcessId(IotPluginCommonUtils.getProcessId()).setReportTime(LocalDateTime.now())
.setProductKey(productKey).setDeviceName(deviceName)).setProperties(properties);
} }
/** /**
* 解析事件上报请求 * 解析事件上报请求
* *
* @param productKey 产品Key * @param productKey 产品K ey
* @param deviceName 设备名称 * @param deviceName 设备名称
* @param identifier 事件标识符 * @param identifier 事件标识符
* @param requestId 请求ID * @param requestId 请求 ID
* @param body 请求体 * @param body 请求体
* @return 事件上报请求DTO * @return 事件上报请求 DTO
*/ */
private IotDeviceEventReportReqDTO parseEventReportRequest(String productKey, String deviceName, String identifier, String requestId, JsonObject body) { private IotDeviceEventReportReqDTO parseEventReportRequest(String productKey, String deviceName, String identifier, String requestId, JsonObject body) {
// 按照标准JSON格式处理事件参数 // 按照标准 JSON 格式处理事件参数
Map<String, Object> params; Map<String, Object> params;
// 优先使用params字段符合标准 if (body.containsKey("params")) {
if (body.getJsonObject("params") != null) {
params = body.getJsonObject("params").getMap(); params = body.getJsonObject("params").getMap();
} else { } else {
// 兼容旧格式 // 兼容旧格式
@ -178,6 +181,8 @@ public class IotDeviceUpstreamVertxHandler implements Handler<RoutingContext> {
} }
// 构建事件上报请求 DTO // 构建事件上报请求 DTO
return ((IotDeviceEventReportReqDTO) new IotDeviceEventReportReqDTO().setRequestId(requestId).setProcessId(IotPluginCommonUtils.getProcessId()).setReportTime(LocalDateTime.now()).setProductKey(productKey).setDeviceName(deviceName)).setIdentifier(identifier).setParams(params); return ((IotDeviceEventReportReqDTO) new IotDeviceEventReportReqDTO().setRequestId(requestId)
.setProcessId(IotPluginCommonUtils.getProcessId()).setReportTime(LocalDateTime.now())
.setProductKey(productKey).setDeviceName(deviceName)).setIdentifier(identifier).setParams(params);
} }
} }

View File

@ -14,6 +14,7 @@
<artifactId>yudao-module-iot-plugin-mqtt</artifactId> <artifactId>yudao-module-iot-plugin-mqtt</artifactId>
<name>${project.artifactId}</name> <name>${project.artifactId}</name>
<!-- TODO @芋艿:待整理 -->
<description> <description>
物联网 插件模块 - mqtt 插件 物联网 插件模块 - mqtt 插件
</description> </description>

View File

@ -4,6 +4,7 @@ import lombok.extern.slf4j.Slf4j;
import org.pf4j.Plugin; import org.pf4j.Plugin;
import org.pf4j.PluginWrapper; import org.pf4j.PluginWrapper;
// TODO @芋艿暂未实现
@Slf4j @Slf4j
public class MqttPlugin extends Plugin { public class MqttPlugin extends Plugin {

View File

@ -21,6 +21,7 @@ import java.nio.charset.Charset;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
// TODO @芋艿暂未实现
/** /**
* 根据官方示例整合常见 MQTT 功能到 PF4J Extension 类中 * 根据官方示例整合常见 MQTT 功能到 PF4J Extension 类中
*/ */