【功能修改】IoT: 修改网络组件模块,包含 HTTP 和 EMQX 组件,重构相关配置和处理逻辑,更新文档说明。

This commit is contained in:
安浩浩
2025-04-04 19:21:37 +08:00
parent 72d8511d6b
commit ae96ff4a25
53 changed files with 1635 additions and 1151 deletions

View File

@@ -16,7 +16,7 @@ import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
*/
@RestController
@Validated
@Primary // 保证优先匹配,因为 yudao-module-iot-component-core 也有 IotDeviceUpstreamApi 的实现,并且也可能会被 biz 引入
@Primary // 保证优先匹配,因为 yudao-module-iot-net-component-core 也有 IotDeviceUpstreamApi 的实现,并且也可能会被 biz 引入
public class IoTDeviceUpstreamApiImpl implements IotDeviceUpstreamApi {
@Resource

View File

@@ -1,46 +0,0 @@
package cn.iocoder.yudao.module.iot.framework.plugin.config;
import cn.iocoder.yudao.module.iot.framework.plugin.core.IotPluginStartRunner;
import cn.iocoder.yudao.module.iot.framework.plugin.core.IotPluginStateListener;
import cn.iocoder.yudao.module.iot.service.plugin.IotPluginConfigService;
import lombok.extern.slf4j.Slf4j;
import org.pf4j.spring.SpringPluginManager;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.nio.file.Paths;
/**
* IoT 插件配置类
*
* @author haohao
*/
@Configuration
@Slf4j
public class IotPluginConfiguration {
@Bean
public IotPluginStartRunner pluginStartRunner(SpringPluginManager pluginManager,
IotPluginConfigService pluginConfigService) {
return new IotPluginStartRunner(pluginManager, pluginConfigService);
}
// TODO @芋艿:需要 review 下
@Bean
public SpringPluginManager pluginManager(@Value("${pf4j.pluginsDir:pluginsDir}") String pluginsDir) {
log.info("[init][实例化 SpringPluginManager]");
SpringPluginManager springPluginManager = new SpringPluginManager(Paths.get(pluginsDir)) {
@Override
public void startPlugins() {
// 禁用插件启动,避免插件启动时,启动所有插件
log.info("[init][禁用默认启动所有插件]");
}
};
springPluginManager.addPluginStateListener(new IotPluginStateListener());
return springPluginManager;
}
}

View File

@@ -1,52 +0,0 @@
package cn.iocoder.yudao.module.iot.framework.plugin.core;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
import cn.iocoder.yudao.module.iot.dal.dataobject.plugin.IotPluginConfigDO;
import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginDeployTypeEnum;
import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginStatusEnum;
import cn.iocoder.yudao.module.iot.service.plugin.IotPluginConfigService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.pf4j.spring.SpringPluginManager;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import java.util.List;
/**
* IoT 插件启动 Runner
*
* 用于 Spring Boot 启动时,启动 {@link IotPluginDeployTypeEnum#JAR} 部署类型的插件
*/
@RequiredArgsConstructor
@Slf4j
public class IotPluginStartRunner implements ApplicationRunner {
private final SpringPluginManager springPluginManager;
private final IotPluginConfigService pluginConfigService;
@Override
public void run(ApplicationArguments args) {
List<IotPluginConfigDO> pluginConfigList = TenantUtils.executeIgnore(
() -> pluginConfigService.getPluginConfigListByStatusAndDeployType(
IotPluginStatusEnum.RUNNING.getStatus(), IotPluginDeployTypeEnum.JAR.getDeployType()));
if (CollUtil.isEmpty(pluginConfigList)) {
log.info("[run][没有需要启动的插件]");
return;
}
// 遍历插件列表,逐个启动
pluginConfigList.forEach(pluginConfig -> {
try {
log.info("[run][插件({}) 启动开始]", pluginConfig.getPluginKey());
springPluginManager.startPlugin(pluginConfig.getPluginKey());
log.info("[run][插件({}) 启动完成]", pluginConfig.getPluginKey());
} catch (Exception e) {
log.error("[run][插件({}) 启动异常]", pluginConfig.getPluginKey(), e);
}
});
}
}

View File

@@ -1,21 +0,0 @@
package cn.iocoder.yudao.module.iot.framework.plugin.core;
import lombok.extern.slf4j.Slf4j;
import org.pf4j.PluginStateEvent;
import org.pf4j.PluginStateListener;
/**
* IoT 插件状态监听器,用于 log 插件的状态变化
*
* @author haohao
*/
@Slf4j
public class IotPluginStateListener implements PluginStateListener {
@Override
public void pluginStateChanged(PluginStateEvent event) {
log.info("[pluginStateChanged][插件({}) 状态变化,从 {} 变为 {}]", event.getPlugin().getPluginId(),
event.getOldState().toString(), event.getPluginState().toString());
}
}

View File

@@ -9,8 +9,6 @@ import cn.iocoder.yudao.module.iot.dal.mysql.plugin.IotPluginConfigMapper;
import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginStatusEnum;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.pf4j.PluginWrapper;
import org.pf4j.spring.SpringPluginManager;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.multipart.MultipartFile;
@@ -35,8 +33,8 @@ public class IotPluginConfigServiceImpl implements IotPluginConfigService {
@Resource
private IotPluginInstanceService pluginInstanceService;
@Resource
private SpringPluginManager springPluginManager;
// @Resource
// private SpringPluginManager springPluginManager;
@Override
public Long createPluginConfig(PluginConfigSaveReqVO createReqVO) {
@@ -130,16 +128,16 @@ public class IotPluginConfigServiceImpl implements IotPluginConfigService {
validatePluginConfigFile(pluginKeyNew);
// 4. 更新插件配置
IotPluginConfigDO updatedPluginConfig = new IotPluginConfigDO()
.setId(pluginConfigDO.getId())
.setPluginKey(pluginKeyNew)
.setStatus(IotPluginStatusEnum.STOPPED.getStatus()) // TODO @haohao这个状态是不是非 stop 哈?
.setFileName(file.getOriginalFilename())
.setScript("") // TODO @haohao这个设置为 "" 会不会覆盖数据里的哈?应该从插件里读取?未来?
.setConfigSchema(springPluginManager.getPlugin(pluginKeyNew).getDescriptor().getPluginDescription())
.setVersion(springPluginManager.getPlugin(pluginKeyNew).getDescriptor().getVersion())
.setDescription(springPluginManager.getPlugin(pluginKeyNew).getDescriptor().getPluginDescription());
pluginConfigMapper.updateById(updatedPluginConfig);
// IotPluginConfigDO updatedPluginConfig = new IotPluginConfigDO()
// .setId(pluginConfigDO.getId())
// .setPluginKey(pluginKeyNew)
// .setStatus(IotPluginStatusEnum.STOPPED.getStatus()) // TODO @haohao这个状态是不是非 stop 哈?
// .setFileName(file.getOriginalFilename())
// .setScript("") // TODO @haohao这个设置为 "" 会不会覆盖数据里的哈?应该从插件里读取?未来?
// .setConfigSchema(springPluginManager.getPlugin(pluginKeyNew).getDescriptor().getPluginDescription())
// .setVersion(springPluginManager.getPlugin(pluginKeyNew).getDescriptor().getVersion())
// .setDescription(springPluginManager.getPlugin(pluginKeyNew).getDescriptor().getPluginDescription());
// pluginConfigMapper.updateById(updatedPluginConfig);
}
/**
@@ -149,13 +147,13 @@ public class IotPluginConfigServiceImpl implements IotPluginConfigService {
*/
private void validatePluginConfigFile(String pluginKeyNew) {
// TODO @haohao校验 file 相关参数是否完整类似version 之类是不是可以解析到
PluginWrapper plugin = springPluginManager.getPlugin(pluginKeyNew);
if (plugin == null) {
throw exception(PLUGIN_INSTALL_FAILED);
}
if (plugin.getDescriptor().getVersion() == null) {
throw exception(PLUGIN_INSTALL_FAILED);
}
// PluginWrapper plugin = springPluginManager.getPlugin(pluginKeyNew);
// if (plugin == null) {
// throw exception(PLUGIN_INSTALL_FAILED);
// }
// if (plugin.getDescriptor().getVersion() == null) {
// throw exception(PLUGIN_INSTALL_FAILED);
// }
}
@Override

View File

@@ -1,7 +1,6 @@
package cn.iocoder.yudao.module.iot.service.plugin;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotPluginInstanceHeartbeatReqDTO;
@@ -9,13 +8,8 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.plugin.IotPluginConfigDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.plugin.IotPluginInstanceDO;
import cn.iocoder.yudao.module.iot.dal.mysql.plugin.IotPluginInstanceMapper;
import cn.iocoder.yudao.module.iot.dal.redis.plugin.DevicePluginProcessIdRedisDAO;
import cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants;
import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginStatusEnum;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.pf4j.PluginState;
import org.pf4j.PluginWrapper;
import org.pf4j.spring.SpringPluginManager;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
@@ -23,17 +17,10 @@ import org.springframework.validation.annotation.Validated;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.TimeUnit;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
/**
* IoT 插件实例 Service 实现类
*
@@ -54,8 +41,8 @@ public class IotPluginInstanceServiceImpl implements IotPluginInstanceService {
@Resource
private DevicePluginProcessIdRedisDAO devicePluginProcessIdRedisDAO;
@Resource
private SpringPluginManager pluginManager;
// @Resource
// private SpringPluginManager pluginManager;
@Value("${pf4j.pluginsDir}")
private String pluginsDir;
@@ -120,17 +107,17 @@ public class IotPluginInstanceServiceImpl implements IotPluginInstanceService {
@Override
public void stopAndUnloadPlugin(String pluginKey) {
PluginWrapper plugin = pluginManager.getPlugin(pluginKey);
if (plugin == null) {
log.warn("插件不存在或已卸载: {}", pluginKey);
return;
}
if (plugin.getPluginState().equals(PluginState.STARTED)) {
pluginManager.stopPlugin(pluginKey); // 停止插件
log.info("已停止插件: {}", pluginKey);
}
pluginManager.unloadPlugin(pluginKey); // 卸载插件
log.info("已卸载插件: {}", pluginKey);
// PluginWrapper plugin = pluginManager.getPlugin(pluginKey);
// if (plugin == null) {
// log.warn("插件不存在或已卸载: {}", pluginKey);
// return;
// }
// if (plugin.getPluginState().equals(PluginState.STARTED)) {
// pluginManager.stopPlugin(pluginKey); // 停止插件
// log.info("已停止插件: {}", pluginKey);
// }
// pluginManager.unloadPlugin(pluginKey); // 卸载插件
// log.info("已卸载插件: {}", pluginKey);
}
@Override
@@ -151,65 +138,66 @@ public class IotPluginInstanceServiceImpl implements IotPluginInstanceService {
@Override
public String uploadAndLoadNewPlugin(MultipartFile file) {
String pluginKeyNew;
// TODO @haohao多节点是不是要上传 s3 之类的存储器;然后定时去加载
Path pluginsPath = Paths.get(pluginsDir);
try {
FileUtil.mkdir(pluginsPath.toFile()); // 创建插件目录
String filename = file.getOriginalFilename();
if (filename != null) {
Path jarPath = pluginsPath.resolve(filename);
Files.copy(file.getInputStream(), jarPath, StandardCopyOption.REPLACE_EXISTING); // 保存上传的 JAR 文件
pluginKeyNew = pluginManager.loadPlugin(jarPath.toAbsolutePath()); // 加载插件
log.info("已加载插件: {}", pluginKeyNew);
} else {
throw exception(ErrorCodeConstants.PLUGIN_INSTALL_FAILED);
}
} catch (IOException e) {
log.error("[uploadAndLoadNewPlugin][上传插件文件失败]", e);
throw exception(ErrorCodeConstants.PLUGIN_INSTALL_FAILED, e);
} catch (Exception e) {
log.error("[uploadAndLoadNewPlugin][加载插件失败]", e);
throw exception(ErrorCodeConstants.PLUGIN_INSTALL_FAILED, e);
}
return pluginKeyNew;
// String pluginKeyNew;
// // TODO @haohao多节点是不是要上传 s3 之类的存储器;然后定时去加载
// Path pluginsPath = Paths.get(pluginsDir);
// try {
// FileUtil.mkdir(pluginsPath.toFile()); // 创建插件目录
// String filename = file.getOriginalFilename();
// if (filename != null) {
// Path jarPath = pluginsPath.resolve(filename);
// Files.copy(file.getInputStream(), jarPath, StandardCopyOption.REPLACE_EXISTING); // 保存上传的 JAR 文件
//// pluginKeyNew = pluginManager.loadPlugin(jarPath.toAbsolutePath()); // 加载插件
//// log.info("已加载插件: {}", pluginKeyNew);
// } else {
// throw exception(ErrorCodeConstants.PLUGIN_INSTALL_FAILED);
// }
// } catch (IOException e) {
// log.error("[uploadAndLoadNewPlugin][上传插件文件失败]", e);
// throw exception(ErrorCodeConstants.PLUGIN_INSTALL_FAILED, e);
// } catch (Exception e) {
// log.error("[uploadAndLoadNewPlugin][加载插件失败]", e);
// throw exception(ErrorCodeConstants.PLUGIN_INSTALL_FAILED, e);
// }
// return pluginKeyNew;
return null;
}
@Override
public void updatePluginStatus(IotPluginConfigDO pluginConfigDO, Integer status) {
String pluginKey = pluginConfigDO.getPluginKey();
PluginWrapper plugin = pluginManager.getPlugin(pluginKey);
if (plugin == null) {
// 插件不存在且状态为停止,抛出异常
if (IotPluginStatusEnum.STOPPED.getStatus().equals(pluginConfigDO.getStatus())) {
throw exception(ErrorCodeConstants.PLUGIN_STATUS_INVALID);
}
return;
}
// 启动插件
if (status.equals(IotPluginStatusEnum.RUNNING.getStatus())
&& plugin.getPluginState() != PluginState.STARTED) {
try {
pluginManager.startPlugin(pluginKey);
} catch (Exception e) {
log.error("[updatePluginStatus][启动插件({}) 失败]", pluginKey, e);
throw exception(ErrorCodeConstants.PLUGIN_START_FAILED, e);
}
log.info("已启动插件: {}", pluginKey);
}
// 停止插件
else if (status.equals(IotPluginStatusEnum.STOPPED.getStatus())
&& plugin.getPluginState() == PluginState.STARTED) {
try {
pluginManager.stopPlugin(pluginKey);
} catch (Exception e) {
log.error("[updatePluginStatus][停止插件({}) 失败]", pluginKey, e);
throw exception(ErrorCodeConstants.PLUGIN_STOP_FAILED, e);
}
log.info("已停止插件: {}", pluginKey);
}
// String pluginKey = pluginConfigDO.getPluginKey();
// PluginWrapper plugin = pluginManager.getPlugin(pluginKey);
//
// if (plugin == null) {
// // 插件不存在且状态为停止,抛出异常
// if (IotPluginStatusEnum.STOPPED.getStatus().equals(pluginConfigDO.getStatus())) {
// throw exception(ErrorCodeConstants.PLUGIN_STATUS_INVALID);
// }
// return;
// }
//
// // 启动插件
// if (status.equals(IotPluginStatusEnum.RUNNING.getStatus())
// && plugin.getPluginState() != PluginState.STARTED) {
// try {
// pluginManager.startPlugin(pluginKey);
// } catch (Exception e) {
// log.error("[updatePluginStatus][启动插件({}) 失败]", pluginKey, e);
// throw exception(ErrorCodeConstants.PLUGIN_START_FAILED, e);
// }
// log.info("已启动插件: {}", pluginKey);
// }
// // 停止插件
// else if (status.equals(IotPluginStatusEnum.STOPPED.getStatus())
// && plugin.getPluginState() == PluginState.STARTED) {
// try {
// pluginManager.stopPlugin(pluginKey);
// } catch (Exception e) {
// log.error("[updatePluginStatus][停止插件({}) 失败]", pluginKey, e);
// throw exception(ErrorCodeConstants.PLUGIN_STOP_FAILED, e);
// }
// log.info("已停止插件: {}", pluginKey);
// }
}
// ========== 设备与插件的映射操作 ==========