【功能修改】IoT: 修改网络组件模块,包含 HTTP 和 EMQX 组件,重构相关配置和处理逻辑,更新文档说明。
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
// }
|
||||
}
|
||||
|
||||
// ========== 设备与插件的映射操作 ==========
|
||||
|
||||
Reference in New Issue
Block a user