【代码评审】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")
public IotPluginInstanceHeartbeatJob pluginInstanceHeartbeatJob(
IotDeviceUpstreamApi deviceDataApi, IotDeviceDownstreamServer deviceDownstreamServer, IotPluginCommonProperties commonProperties) {
public IotPluginInstanceHeartbeatJob pluginInstanceHeartbeatJob(IotDeviceUpstreamApi deviceDataApi,
IotDeviceDownstreamServer deviceDownstreamServer,
IotPluginCommonProperties commonProperties) {
return new IotPluginInstanceHeartbeatJob(deviceDataApi, deviceDownstreamServer, commonProperties);
}

View File

@ -25,6 +25,7 @@ import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeC
@RequiredArgsConstructor
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 METHOD = "thing.service.config.set";
@ -57,12 +58,9 @@ public class IotDeviceConfigSetVertxHandler implements Handler<RoutingContext> {
CommonResult<Boolean> result = deviceDownstreamHandler.setDeviceConfig(reqDTO);
// 3. 响应结果
IotStandardResponse response;
if (result.isSuccess()) {
response = IotStandardResponse.success(reqDTO.getRequestId(), METHOD, result.getData());
} else {
response = IotStandardResponse.error(reqDTO.getRequestId(), METHOD, result.getCode(), result.getMsg());
}
IotStandardResponse response = result.isSuccess() ?
IotStandardResponse.success(reqDTO.getRequestId(), METHOD, result.getData())
: IotStandardResponse.error(reqDTO.getRequestId(), METHOD, result.getCode(), result.getMsg());
IotPluginCommonUtils.writeJsonResponse(routingContext, response);
} catch (Exception e) {
log.error("[handle][请求参数({}) 配置设置异常]", reqDTO, e);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -14,6 +14,8 @@ import org.springframework.validation.annotation.Validated;
@Data
public class IotPluginEmqxProperties {
// TODO @haohao参数校验加下啊哈
/**
* 服务主机
*/
@ -21,12 +23,11 @@ public class IotPluginEmqxProperties {
/**
* 服务端口
*/
private int mqttPort;
private Integer mqttPort;
/**
* 服务用户名
*/
private String mqttUsername;
/**
* 服务密码
*/
@ -34,7 +35,7 @@ public class IotPluginEmqxProperties {
/**
* 是否启用 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/";
// TODO @haohao是不是可以类似 IotDeviceConfigSetVertxHandler 的建议抽到统一的枚举类
// TODO @haohao讨论感觉 mqtt http可以做个相对统一的格式哈回复 都使用 Alink 格式方便后续扩展
// 设备服务调用 标准 JSON
// 请求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();
JSONObject request = buildServiceRequest(requestId, reqDTO.getIdentifier(), reqDTO.getParams());
// 发送消息
publishMessage(topic, request);
@ -82,9 +82,8 @@ public class IotDeviceDownstreamHandlerImpl implements IotDeviceDownstreamHandle
@Override
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) {
log.error("[setProperty][参数不完整][reqDTO: {}]", JSONUtil.toJsonStr(reqDTO));
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();
JSONObject request = buildPropertySetRequest(requestId, reqDTO.getProperties());
// 发送消息
publishMessage(topic, request);
@ -132,6 +130,7 @@ public class IotDeviceDownstreamHandlerImpl implements IotDeviceDownstreamHandle
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() {
return IdUtil.fastSimpleUUID();

View File

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

View File

@ -15,7 +15,7 @@ import java.util.Collections;
/**
* 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"}
* 以符合 EMQX 认证插件的要求因此不使用 IotStandardResponse 实体类
@ -31,7 +31,6 @@ public class IotDeviceAuthVertxHandler implements Handler<RoutingContext> {
private final IotDeviceUpstreamApi deviceUpstreamApi;
@Override
@SuppressWarnings("unchecked")
public void handle(RoutingContext routingContext) {
try {
// 构建认证请求 DTO

View File

@ -23,7 +23,7 @@ import java.util.Map;
/**
* 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
public class IotDeviceMqttMessageHandler {

View File

@ -18,7 +18,7 @@ import java.util.Collections;
/**
* 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"}
* 以符合 EMQX Webhook 插件的要求因此不使用 IotStandardResponse 实体类
@ -51,8 +51,7 @@ public class IotDeviceWebhookVertxHandler implements Handler<RoutingContext> {
handleClientDisconnected(clientId, username);
break;
default:
log.info("[handle][未处理的 Webhook 事件] event={}, clientId={}, username={}", event, clientId,
username);
log.info("[handle][未处理的 Webhook 事件] event={}, clientId={}, username={}", event, clientId, username);
break;
}

View File

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

View File

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

View File

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

View File

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