【代码修复】IoT:网络组件相关

This commit is contained in:
YunaiV 2025-04-12 21:05:49 +08:00
parent 44b835bd4a
commit d83af87f9f
24 changed files with 54 additions and 25 deletions

View File

@ -107,6 +107,7 @@ public class IotProductScriptController {
@PreAuthorize("@ss.hasPermission('iot:product-script:query')") @PreAuthorize("@ss.hasPermission('iot:product-script:query')")
public CommonResult<String> getSampleScript(@RequestParam("type") Integer type) { public CommonResult<String> getSampleScript(@RequestParam("type") Integer type) {
String sample; String sample;
// TODO @haohao要不枚举下
switch (type) { switch (type) {
case 1: case 1:
sample = scriptSamples.getPropertyParserSample(); sample = scriptSamples.getPropertyParserSample();
@ -118,6 +119,7 @@ public class IotProductScriptController {
sample = scriptSamples.getCommandEncoderSample(); sample = scriptSamples.getCommandEncoderSample();
break; break;
default: default:
// TODO @haohao不支持返回 error 会不会好点哈例如说参数不正确
sample = "// 不支持的脚本类型"; sample = "// 不支持的脚本类型";
} }
return success(sample); return success(sample);

View File

@ -17,6 +17,7 @@ import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*;
/** /**
* IoT 插件配置 Service 实现类 * IoT 插件配置 Service 实现类
* *

View File

@ -13,6 +13,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.EnableScheduling;
// TODO @haohao应该不用写 spring.factories 因为被 imports 替代啦
/** /**
* IoT 网络组件的通用自动配置类 * IoT 网络组件的通用自动配置类
* *

View File

@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.iot.net.component.core.constants;
import lombok.Getter; import lombok.Getter;
// TODO @haohao要不放到 enums 包下
/** /**
* IoT 设备主题枚举 * IoT 设备主题枚举
* <p> * <p>
@ -12,6 +13,7 @@ import lombok.Getter;
@Getter @Getter
public enum IotDeviceTopicEnum { public enum IotDeviceTopicEnum {
// TODO @haohaoSYS_TOPIC_PREFIXSERVICE_TOPIC_PREFIXREPLY_SUFFIX 类似这种要不搞成这个里面的静态变量不是枚举值
/** /**
* 系统主题前缀 * 系统主题前缀
*/ */
@ -22,6 +24,7 @@ public enum IotDeviceTopicEnum {
*/ */
SERVICE_TOPIC_PREFIX("/thing/service/", "服务调用主题前缀"), SERVICE_TOPIC_PREFIX("/thing/service/", "服务调用主题前缀"),
// TODO @haohao注释时中英文之间有个空格
/** /**
* 设备属性设置主题 * 设备属性设置主题
* 请求Topic/sys/${productKey}/${deviceName}/thing/service/property/set * 请求Topic/sys/${productKey}/${deviceName}/thing/service/property/set
@ -75,6 +78,7 @@ public enum IotDeviceTopicEnum {
private final String topic; private final String topic;
private final String description; private final String description;
// TODO @haohao使用 lombok 去除
IotDeviceTopicEnum(String topic, String description) { IotDeviceTopicEnum(String topic, String description) {
this.topic = topic; this.topic = topic;
this.description = description; this.description = description;
@ -89,6 +93,7 @@ public enum IotDeviceTopicEnum {
* @return 完整的主题路径 * @return 完整的主题路径
*/ */
public static String buildServiceTopic(String productKey, String deviceName, String serviceIdentifier) { public static String buildServiceTopic(String productKey, String deviceName, String serviceIdentifier) {
// TODO @haohao貌似 SYS_TOPIC_PREFIX.getTopic() + productKey + "/" + deviceName 是统一的
return SYS_TOPIC_PREFIX.getTopic() + productKey + "/" + deviceName + return SYS_TOPIC_PREFIX.getTopic() + productKey + "/" + deviceName +
SERVICE_TOPIC_PREFIX.getTopic() + serviceIdentifier; SERVICE_TOPIC_PREFIX.getTopic() + serviceIdentifier;
} }
@ -127,7 +132,7 @@ public enum IotDeviceTopicEnum {
} }
/** /**
* 构建设备OTA升级主题 * 构建设备 OTA 升级主题
* *
* @param productKey 产品Key * @param productKey 产品Key
* @param deviceName 设备名称 * @param deviceName 设备名称
@ -170,4 +175,5 @@ public enum IotDeviceTopicEnum {
public static String getReplyTopic(String requestTopic) { public static String getReplyTopic(String requestTopic) {
return requestTopic + REPLY_SUFFIX.getTopic(); return requestTopic + REPLY_SUFFIX.getTopic();
} }
} }

View File

@ -11,6 +11,7 @@ import java.util.Map;
* IoT Alink 消息模型 * IoT Alink 消息模型
* <p> * <p>
* 基于阿里云 Alink 协议规范实现的标准消息格式 * 基于阿里云 Alink 协议规范实现的标准消息格式
* @see <a href="https://help.aliyun.com/zh/iot/user-guide/alink-protocol-1">阿里云物联网 Alink 协议</a>
* *
* @author haohao * @author haohao
*/ */

View File

@ -12,7 +12,7 @@ import lombok.experimental.Accessors;
* @author haohao * @author haohao
*/ */
@Data @Data
@Accessors(chain = true) @Accessors(chain = true) // TODO @haohao貌似不用写 @Accessors(chain = true)我全局加啦可见 lombok.config
public class IotStandardResponse { public class IotStandardResponse {
/** /**

View File

@ -28,13 +28,13 @@ import org.springframework.context.event.EventListener;
* *
* @author haohao * @author haohao
*/ */
@Slf4j
@AutoConfiguration @AutoConfiguration
@EnableConfigurationProperties(IotNetComponentEmqxProperties.class) @EnableConfigurationProperties(IotNetComponentEmqxProperties.class)
@ConditionalOnProperty(prefix = "yudao.iot.component.emqx", name = "enabled", havingValue = "true", matchIfMissing = false) @ConditionalOnProperty(prefix = "yudao.iot.component.emqx", name = "enabled", havingValue = "true")
@ComponentScan(basePackages = { @ComponentScan(basePackages = {
"cn.iocoder.yudao.module.iot.net.component.emqx" // 只扫描 EMQX 组件包 "cn.iocoder.yudao.module.iot.net.component.emqx" // 只扫描 EMQX 组件包
}) }) // TODO @haohao自动配置后不需要这个哈
@Slf4j
public class IotNetComponentEmqxAutoConfiguration { public class IotNetComponentEmqxAutoConfiguration {
/** /**
@ -42,6 +42,7 @@ public class IotNetComponentEmqxAutoConfiguration {
*/ */
private static final String PLUGIN_KEY = "emqx"; private static final String PLUGIN_KEY = "emqx";
// TODO @haohao这个是不是要去掉哈
public IotNetComponentEmqxAutoConfiguration() { public IotNetComponentEmqxAutoConfiguration() {
// 构造函数中不输出日志移到 initialize 方法中 // 构造函数中不输出日志移到 initialize 方法中
} }

View File

@ -64,6 +64,7 @@ public class IotNetComponentEmqxProperties {
@NotNull(message = "认证端口不能为空") @NotNull(message = "认证端口不能为空")
private Integer authPort; private Integer authPort;
// TODO @haohao可以使用 Duration 类型可读性更好
/** /**
* 重连延迟时间(毫秒) * 重连延迟时间(毫秒)
* <p> * <p>
@ -77,4 +78,5 @@ public class IotNetComponentEmqxProperties {
* 默认值10000 毫秒 * 默认值10000 毫秒
*/ */
private Integer connectionTimeoutMs = 10000; private Integer connectionTimeoutMs = 10000;
} }

View File

@ -82,6 +82,7 @@ public class IotDeviceUpstreamServer {
log.info("[start][开始启动服务]"); log.info("[start][开始启动服务]");
// 检查 authPort 是否为 null // 检查 authPort 是否为 null
// TODO @haohaoauthPort 里面搞默认值包括下面这个类不搞任何默认值都交给 emqxProperties
Integer authPort = emqxProperties.getAuthPort(); Integer authPort = emqxProperties.getAuthPort();
if (authPort == null) { if (authPort == null) {
log.warn("[start][authPort 为 null使用默认端口 8080]"); log.warn("[start][authPort 为 null使用默认端口 8080]");

View File

@ -30,6 +30,7 @@ import java.util.Map;
@Slf4j @Slf4j
public class IotDeviceMqttMessageHandler { public class IotDeviceMqttMessageHandler {
// TODO @haohao下面的有办法也抽到 IotDeviceTopicEnum 想的是尽量把这些 methodtopicurl 统一化
private static final String PROPERTY_METHOD = "thing.event.property.post"; private static final String PROPERTY_METHOD = "thing.event.property.post";
private static final String EVENT_METHOD_PREFIX = "thing.event."; private static final String EVENT_METHOD_PREFIX = "thing.event.";
private static final String EVENT_METHOD_SUFFIX = ".post"; private static final String EVENT_METHOD_SUFFIX = ".post";
@ -223,6 +224,7 @@ public class IotDeviceMqttMessageHandler {
* @return 设备属性上报请求对象 * @return 设备属性上报请求对象
*/ */
private IotDevicePropertyReportReqDTO buildPropertyReportDTO(JSONObject jsonObject, String[] topicParts) { private IotDevicePropertyReportReqDTO buildPropertyReportDTO(JSONObject jsonObject, String[] topicParts) {
// TODO @haohaoIotDevicePropertyReportReqDTO 可以考虑链式哈其它也是尽量让同类参数在一行这样阅读起来更聚焦
IotDevicePropertyReportReqDTO reportReqDTO = new IotDevicePropertyReportReqDTO(); IotDevicePropertyReportReqDTO reportReqDTO = new IotDevicePropertyReportReqDTO();
reportReqDTO.setRequestId(jsonObject.getStr("id")); reportReqDTO.setRequestId(jsonObject.getStr("id"));
reportReqDTO.setProcessId(IotNetComponentCommonUtils.getProcessId()); reportReqDTO.setProcessId(IotNetComponentCommonUtils.getProcessId());
@ -230,7 +232,7 @@ public class IotDeviceMqttMessageHandler {
reportReqDTO.setProductKey(topicParts[2]); reportReqDTO.setProductKey(topicParts[2]);
reportReqDTO.setDeviceName(topicParts[3]); reportReqDTO.setDeviceName(topicParts[3]);
// 只使用标准JSON格式处理属性数据 // 只使用标准 JSON格式处理属性数据
JSONObject params = jsonObject.getJSONObject("params"); JSONObject params = jsonObject.getJSONObject("params");
if (params == null) { if (params == null) {
log.warn("[buildPropertyReportDTO][消息格式不正确缺少params字段][jsonObject: {}]", jsonObject); log.warn("[buildPropertyReportDTO][消息格式不正确缺少params字段][jsonObject: {}]", jsonObject);

View File

@ -61,13 +61,14 @@ public class IotNetComponentHttpAutoConfiguration {
// 设置当前组件的核心标识 // 设置当前组件的核心标识
// 注意这里只为当前 HTTP 组件设置 pluginKey不影响其他组件 // 注意这里只为当前 HTTP 组件设置 pluginKey不影响其他组件
// TODO @haohao多个会存在冲突的问题哇
commonProperties.setPluginKey(PLUGIN_KEY); commonProperties.setPluginKey(PLUGIN_KEY);
// HTTP 组件注册到组件注册表 // HTTP 组件注册到组件注册表
componentRegistry.registerComponent( componentRegistry.registerComponent(
PLUGIN_KEY, PLUGIN_KEY,
SystemUtil.getHostInfo().getAddress(), SystemUtil.getHostInfo().getAddress(),
0, // 内嵌模式固定为 0 0, // 内嵌模式固定为 0自动生成对应的 port 端口号
IotNetComponentCommonUtils.getProcessId()); IotNetComponentCommonUtils.getProcessId());
log.info("[initialize][IoT HTTP 组件初始化完成]"); log.info("[initialize][IoT HTTP 组件初始化完成]");
@ -115,4 +116,5 @@ public class IotNetComponentHttpAutoConfiguration {
public IotDeviceDownstreamHandler deviceDownstreamHandler() { public IotDeviceDownstreamHandler deviceDownstreamHandler() {
return new IotDeviceDownstreamHandlerImpl(); return new IotDeviceDownstreamHandlerImpl();
} }
} }

View File

@ -5,12 +5,13 @@ import io.vertx.ext.web.RoutingContext;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
// TODO @haohao待实现或者不需要
/** /**
* IoT 设备认证提供者 * IoT 设备认证提供者
* <p> * <p>
* 用于 HTTP 设备接入时的身份认证 * 用于 HTTP 设备接入时的身份认证
* *
* @author 芋道源码 * @author haohao
*/ */
@Slf4j @Slf4j
public class IotDeviceAuthProvider { public class IotDeviceAuthProvider {

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.iot.net.component.http.upstream.router; package cn.iocoder.yudao.module.iot.net.component.http.upstream.router;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil; import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.ObjUtil;
@ -35,6 +36,8 @@ 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你说咱要不要把 "/sys/:productKey/:deviceName"
// + IotDeviceTopicEnum.PROPERTY_POST_TOPIC.getTopic()也抽到 IotDeviceTopicEnum build 这种尽量都收敛掉
/** /**
* 属性上报路径 * 属性上报路径
*/ */
@ -254,8 +257,8 @@ public class IotDeviceUpstreamVertxHandler implements Handler<RoutingContext> {
return PROPERTY_METHOD; return PROPERTY_METHOD;
} }
return EVENT_METHOD_PREFIX + return EVENT_METHOD_PREFIX
(routingContext.pathParams().containsKey("identifier") + (routingContext.pathParams().containsKey("identifier")
? routingContext.pathParam("identifier") ? routingContext.pathParam("identifier")
: "unknown") : "unknown")
+ +
@ -275,7 +278,6 @@ public class IotDeviceUpstreamVertxHandler implements Handler<RoutingContext> {
.setReportTime(LocalDateTime.now()) .setReportTime(LocalDateTime.now())
.setProductKey(productKey) .setProductKey(productKey)
.setDeviceName(deviceName)).setState(IotDeviceStateEnum.ONLINE.getState()); .setDeviceName(deviceName)).setState(IotDeviceStateEnum.ONLINE.getState());
deviceUpstreamApi.updateDeviceState(reqDTO); deviceUpstreamApi.updateDeviceState(reqDTO);
} }
@ -311,8 +313,7 @@ public class IotDeviceUpstreamVertxHandler implements Handler<RoutingContext> {
private Map<String, Object> parsePropertiesFromBody(JsonObject body) { private Map<String, Object> parsePropertiesFromBody(JsonObject body) {
Map<String, Object> properties = MapUtil.newHashMap(); Map<String, Object> properties = MapUtil.newHashMap();
JsonObject params = body.getJsonObject("params"); JsonObject params = body.getJsonObject("params");
if (CollUtil.isEmpty(params)) {
if (params == null) {
return properties; return properties;
} }
@ -327,7 +328,6 @@ public class IotDeviceUpstreamVertxHandler implements Handler<RoutingContext> {
properties.put(key, valueObj); properties.put(key, valueObj);
} }
} }
return properties; return properties;
} }
@ -364,15 +364,13 @@ public class IotDeviceUpstreamVertxHandler implements Handler<RoutingContext> {
private Map<String, Object> parseParamsFromBody(JsonObject body) { private Map<String, Object> parseParamsFromBody(JsonObject body) {
Map<String, Object> params = MapUtil.newHashMap(); Map<String, Object> params = MapUtil.newHashMap();
JsonObject paramsJson = body.getJsonObject("params"); JsonObject paramsJson = body.getJsonObject("params");
if (CollUtil.isEmpty(paramsJson)) {
if (paramsJson == null) {
return params; return params;
} }
for (String key : paramsJson.fieldNames()) { for (String key : paramsJson.fieldNames()) {
params.put(key, paramsJson.getValue(key)); params.put(key, paramsJson.getValue(key));
} }
return params; return params;
} }
} }

View File

@ -32,6 +32,7 @@ public class IotNetComponentServerConfiguration {
* @return RestTemplate * @return RestTemplate
*/ */
@Bean @Bean
// TODO @haohao貌似要独立一个 restTemplate 的名字不然容易冲突
public RestTemplate restTemplate(IotNetComponentServerProperties properties) { public RestTemplate restTemplate(IotNetComponentServerProperties properties) {
return new RestTemplateBuilder() return new RestTemplateBuilder()
.connectTimeout(properties.getUpstreamConnectTimeout()) .connectTimeout(properties.getUpstreamConnectTimeout())
@ -104,6 +105,7 @@ public class IotNetComponentServerConfiguration {
return new Object(); return new Object();
} }
// TODO @haohao这个是不是木有用呀
/** /**
* 配置默认的组件实例注册客户端 * 配置默认的组件实例注册客户端
* *

View File

@ -7,6 +7,7 @@ import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
// TODO @haohao这个是必须的哇可以考虑基于 spring boot actuator
/** /**
* 健康检查接口 * 健康检查接口
* *

View File

@ -7,14 +7,12 @@ import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotPluginInst
import cn.iocoder.yudao.module.iot.net.component.server.config.IotNetComponentServerProperties; import cn.iocoder.yudao.module.iot.net.component.server.config.IotNetComponentServerProperties;
import cn.iocoder.yudao.module.iot.net.component.server.downstream.IotComponentDownstreamServer; import cn.iocoder.yudao.module.iot.net.component.server.downstream.IotComponentDownstreamServer;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import java.time.LocalDateTime;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.lang.ProcessHandle;
// TODO @haohao有办法服用 yudao-module-iot-net-component-core 的么就是 server只是一个启动器没什么特殊的功能
/** /**
* IoT 组件心跳任务 * IoT 组件心跳任务
* <p> * <p>

View File

@ -44,6 +44,7 @@
</dependency> </dependency>
<!-- JavaScript 引擎 - 使用 GraalJS 替代 Nashorn --> <!-- JavaScript 引擎 - 使用 GraalJS 替代 Nashorn -->
<!-- TODO @haohao得考虑下jdk8 可能不支持 graalvm后续哈【优先级低】 -->
<dependency> <dependency>
<groupId>org.graalvm.sdk</groupId> <groupId>org.graalvm.sdk</groupId>
<artifactId>graal-sdk</artifactId> <artifactId>graal-sdk</artifactId>

View File

@ -10,6 +10,7 @@ import org.springframework.stereotype.Component;
import java.util.Map; import java.util.Map;
// TODO @haohao挪到 test 目录下
/** /**
* 脚本使用示例类 * 脚本使用示例类
*/ */

View File

@ -63,6 +63,7 @@ public class JsScriptEngine extends AbstractScriptEngine implements AutoCloseabl
.build(); .build();
// 创建隔离的临时目录路径 // 创建隔离的临时目录路径
// TODO @haohao貌似没用到
Path tempDirectory = Path.of(System.getProperty("java.io.tmpdir"), "graaljs-" + IdUtil.fastSimpleUUID()); Path tempDirectory = Path.of(System.getProperty("java.io.tmpdir"), "graaljs-" + IdUtil.fastSimpleUUID());
// 初始化 GraalJS 上下文 // 初始化 GraalJS 上下文
@ -94,6 +95,7 @@ public class JsScriptEngine extends AbstractScriptEngine implements AutoCloseabl
Source source = getOrCreateSource(script); Source source = getOrCreateSource(script);
// 执行脚本并捕获结果添加超时控制 // 执行脚本并捕获结果添加超时控制
// TODO @haohao通过线程池 + future 会好点
Value result; Value result;
Thread executionThread = Thread.currentThread(); Thread executionThread = Thread.currentThread();
Thread watchdogThread = new Thread(() -> { Thread watchdogThread = new Thread(() -> {
@ -236,11 +238,14 @@ public class JsScriptEngine extends AbstractScriptEngine implements AutoCloseabl
if (result.isNumber()) { if (result.isNumber()) {
if (result.fitsInInt()) { if (result.fitsInInt()) {
return result.asInt(); return result.asInt();
} else if (result.fitsInLong()) { }
if (result.fitsInLong()) {
return result.asLong(); return result.asLong();
} else if (result.fitsInFloat()) { }
if (result.fitsInFloat()) {
return result.asFloat(); return result.asFloat();
} else if (result.fitsInDouble()) { }
if (result.fitsInDouble()) {
return result.asDouble(); return result.asDouble();
} }
} }

View File

@ -79,7 +79,6 @@ public class ScriptEngineFactory implements DisposableBean {
@Override @Override
public void destroy() { public void destroy() {
// 应用关闭时释放所有引擎资源
log.info("应用关闭,释放所有脚本引擎资源..."); log.info("应用关闭,释放所有脚本引擎资源...");
releaseAllEngines(); releaseAllEngines();
} }

View File

@ -14,6 +14,7 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
// TODO @haohao搞到 test 里面哈
/** /**
* GraalJS 脚本引擎示例 * GraalJS 脚本引擎示例
* <p> * <p>

View File

@ -56,6 +56,7 @@ public class JsSandbox implements ScriptSandbox {
*/ */
public JsSandbox() { public JsSandbox() {
// 初始化 Java 相关的不安全关键字 // 初始化 Java 相关的不安全关键字
// TODO @haohao可以使用 addAll
Arrays.asList( Arrays.asList(
"java.lang.System", "java.lang.System",
"java.io", "java.io",

View File

@ -40,6 +40,7 @@ public class ScriptServiceImpl implements ScriptService {
try { try {
return engine.execute(script, context); return engine.execute(script, context);
} catch (Exception e) { } catch (Exception e) {
// TODO @haohao最好打印一些参数下面类似的也是
log.error("执行脚本失败: {}", e.getMessage(), e); log.error("执行脚本失败: {}", e.getMessage(), e);
throw new RuntimeException("执行脚本失败: " + e.getMessage(), e); throw new RuntimeException("执行脚本失败: " + e.getMessage(), e);
} }

View File

@ -28,6 +28,7 @@ public class ScriptUtils {
return INSTANCE; return INSTANCE;
} }
// TODO @haohao使用 lombok 简化掉
private ScriptUtils() { private ScriptUtils() {
// 私有构造函数 // 私有构造函数
} }