|
|
@@ -0,0 +1,434 @@
|
|
|
+package com.shkpr.service.bespokegateway.core.components;
|
|
|
+
|
|
|
+import com.fasterxml.jackson.databind.ObjectMapper;
|
|
|
+import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
|
|
|
+import com.global.base.log.LogLevelFlag;
|
|
|
+import com.global.base.log.LogPrintMgr;
|
|
|
+import com.shkpr.service.bespokegateway.core.constants.DeviceField;
|
|
|
+import com.shkpr.service.bespokegateway.core.constants.DeviceKind;
|
|
|
+import com.shkpr.service.bespokegateway.core.constants.ExcelType;
|
|
|
+import com.shkpr.service.bespokegateway.core.constants.LogFlagBusiType;
|
|
|
+import com.shkpr.service.bespokegateway.core.domain.Device;
|
|
|
+import com.shkpr.service.bespokegateway.core.domain.DeviceExcel;
|
|
|
+import com.shkpr.service.bespokegateway.core.domain.DeviceTag;
|
|
|
+import com.shkpr.service.bespokegateway.core.service.DeviceTagsService;
|
|
|
+import com.shkpr.service.bespokegateway.core.utils.ExcelUtil;
|
|
|
+import org.apache.commons.collections4.CollectionUtils;
|
|
|
+import org.apache.commons.lang3.ObjectUtils;
|
|
|
+import org.apache.commons.lang3.StringUtils;
|
|
|
+
|
|
|
+import java.io.File;
|
|
|
+import java.io.IOException;
|
|
|
+import java.io.OutputStream;
|
|
|
+import java.nio.file.Files;
|
|
|
+import java.nio.file.Path;
|
|
|
+import java.util.*;
|
|
|
+import java.util.stream.Collectors;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 设备管理器
|
|
|
+ *
|
|
|
+ * @author 欧阳劲驰
|
|
|
+ * @since 1.0.0
|
|
|
+ */
|
|
|
+public class DeviceRegistry {
|
|
|
+ /**
|
|
|
+ * log
|
|
|
+ */
|
|
|
+ private static final String CLASS_NAME = "DeviceRegistry";
|
|
|
+ private static final String BIZ_TYPE = LogFlagBusiType.BUSI_ALL.toStrValue();
|
|
|
+
|
|
|
+ /**
|
|
|
+ * yml对象映射
|
|
|
+ */
|
|
|
+ private final ObjectMapper yamlMapper;
|
|
|
+ /**
|
|
|
+ * 配置文件
|
|
|
+ */
|
|
|
+ private final File configFile;
|
|
|
+ /**
|
|
|
+ * 标签服务
|
|
|
+ */
|
|
|
+ private final DeviceTagsService tagsService;
|
|
|
+ /**
|
|
|
+ * 设备列表
|
|
|
+ */
|
|
|
+ private List<Device> devices;
|
|
|
+
|
|
|
+ public DeviceRegistry(Builder builder) {
|
|
|
+ this.yamlMapper = builder.yamlMapper;
|
|
|
+ this.configFile = builder.configFile;
|
|
|
+ this.tagsService = builder.tagsService;
|
|
|
+ this.devices = new ArrayList<>();
|
|
|
+ if (builder.autoLoad) this.loadDevices();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @return 构建
|
|
|
+ */
|
|
|
+ public static Builder builder() {
|
|
|
+ return new Builder();
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 注册设备
|
|
|
+ *
|
|
|
+ * @param device 设备
|
|
|
+ */
|
|
|
+ public boolean registerDevice(Device device) {
|
|
|
+ if (!ObjectUtils.allNotNull(device, device.getDeviceId(), device.getDeviceSn())) return false;
|
|
|
+
|
|
|
+ // 检查设备是否已存在
|
|
|
+ if (containsDevice(device.getDeviceId())) {
|
|
|
+ return updateDevice(device);
|
|
|
+ }
|
|
|
+
|
|
|
+ //添加设备并同步
|
|
|
+ devices.add(device);
|
|
|
+ syncDevices();
|
|
|
+
|
|
|
+
|
|
|
+ LogPrintMgr.getInstance().printLogMsg(LogLevelFlag.LOG_INFO, BIZ_TYPE, CLASS_NAME,
|
|
|
+ String.format("设备注册成功: %s", device.getDeviceId()));
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 注销设备
|
|
|
+ *
|
|
|
+ * @param deviceId 设备id
|
|
|
+ */
|
|
|
+ public boolean unregisterDevice(String deviceId) {
|
|
|
+ if (StringUtils.isBlank(deviceId)) return false;
|
|
|
+
|
|
|
+ //删除设备
|
|
|
+ boolean removed = devices.removeIf(device -> Objects.equals(device.getDeviceId(), deviceId));
|
|
|
+ if (removed) {
|
|
|
+ syncDevices();
|
|
|
+ LogPrintMgr.getInstance().printLogMsg(LogLevelFlag.LOG_INFO, BIZ_TYPE, CLASS_NAME,
|
|
|
+ String.format("设备注销成功: %s", deviceId));
|
|
|
+ }
|
|
|
+
|
|
|
+ return removed;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 更新设备
|
|
|
+ *
|
|
|
+ * @param updatedDevice 设备
|
|
|
+ */
|
|
|
+ public boolean updateDevice(Device updatedDevice) {
|
|
|
+ if (!ObjectUtils.allNotNull(updatedDevice, updatedDevice.getDeviceId(), updatedDevice.getDeviceSn()))
|
|
|
+ return false;
|
|
|
+ //设备id
|
|
|
+ String deviceId = updatedDevice.getDeviceId();
|
|
|
+ //更新状态
|
|
|
+ boolean updated = false;
|
|
|
+
|
|
|
+ //查找并更新现有设备
|
|
|
+ for (int i = 0; i < devices.size(); i++) {
|
|
|
+ if (Objects.equals(devices.get(i).getDeviceId(), deviceId)) {
|
|
|
+ devices.set(i, updatedDevice);
|
|
|
+ updated = true;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!updated) return false;
|
|
|
+
|
|
|
+ syncDevices();
|
|
|
+ LogPrintMgr.getInstance().printLogMsg(LogLevelFlag.LOG_INFO, BIZ_TYPE, CLASS_NAME,
|
|
|
+ String.format("设备更新成功: %s", deviceId));
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 查询全部
|
|
|
+ *
|
|
|
+ * @return 设备列表
|
|
|
+ */
|
|
|
+ public List<Device> findAll(String platform) {
|
|
|
+ //采集标签
|
|
|
+ List<String> tags = tagsService.findAll(platform);
|
|
|
+ //返回过滤的设备
|
|
|
+ return new ArrayList<>(devices).stream()
|
|
|
+ //过滤平台
|
|
|
+ .filter(device -> device != null && Objects.equals(device.getPlatform(), platform))
|
|
|
+ //过滤采集标签
|
|
|
+ .peek(device -> device.setTags(
|
|
|
+ device.getTags().stream()
|
|
|
+ .filter(tag -> tags.contains(tag.getTag()))
|
|
|
+ .collect(Collectors.toList())))
|
|
|
+ .filter(device -> CollectionUtils.isNotEmpty(device.getTags()))
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 根据设备id查询
|
|
|
+ *
|
|
|
+ * @return 设备
|
|
|
+ */
|
|
|
+ public Device findById(String deviceId) {
|
|
|
+ return devices.stream()
|
|
|
+ .filter(device -> Objects.equals(device.getDeviceId(), deviceId))
|
|
|
+ .findFirst()
|
|
|
+ .orElse(null);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取设备数量
|
|
|
+ */
|
|
|
+ public int getDeviceCount() {
|
|
|
+ return devices.size();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 检查设备是否存在
|
|
|
+ */
|
|
|
+ public boolean containsDevice(String deviceId) {
|
|
|
+ return devices.stream().anyMatch(device -> Objects.equals(device.getDeviceId(), deviceId));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 查找需要新增的设备
|
|
|
+ *
|
|
|
+ * @param deviceSns 新设备远传id
|
|
|
+ * @return 需要新增的设备远传id
|
|
|
+ */
|
|
|
+ public Set<String> findAddedDevices(Set<String> deviceSns) {
|
|
|
+ //已存在的设备id
|
|
|
+ Set<String> existingDeviceSns = devices.stream()
|
|
|
+ .map(Device::getDeviceSn)
|
|
|
+ .collect(Collectors.toSet());
|
|
|
+
|
|
|
+ //过滤不存在的设备
|
|
|
+ return deviceSns.stream()
|
|
|
+ .filter(StringUtils::isNoneBlank)
|
|
|
+ .filter(sn -> !existingDeviceSns.contains(sn))
|
|
|
+ .collect(Collectors.toSet());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 查找需要更新的设备
|
|
|
+ *
|
|
|
+ * @param deviceInfo 设备
|
|
|
+ * @return 需要新增的设备远传id
|
|
|
+ */
|
|
|
+ public Set<Device> findUpdatedDevices(Map<String, Set<String>> deviceInfo) {
|
|
|
+ //已存在的设备,k:设备SN,v:标签字段集合
|
|
|
+ Map<String, Set<String>> existingDeviceInfo = this.devices.stream()
|
|
|
+ .collect(Collectors.toMap(
|
|
|
+ Device::getDeviceSn,
|
|
|
+ it -> it.getTags().stream()
|
|
|
+ .map(DeviceTag::getField)
|
|
|
+ .collect(Collectors.toSet())
|
|
|
+ ));
|
|
|
+
|
|
|
+ //过滤不同的设备
|
|
|
+ return deviceInfo.entrySet().stream()
|
|
|
+ //过滤不通标签
|
|
|
+ .filter(entry -> {
|
|
|
+ //新标签
|
|
|
+ Set<String> newTags = entry.getValue();
|
|
|
+ //已存在的标签
|
|
|
+ Set<String> existingTags = existingDeviceInfo.get(entry.getKey());
|
|
|
+ if (existingTags == null) return true;
|
|
|
+
|
|
|
+ //比较标签
|
|
|
+ return !existingTags.equals(newTags);
|
|
|
+ })
|
|
|
+ //提取当前设备
|
|
|
+ .map(entry -> this.devices.stream()
|
|
|
+ .filter(device -> device.getDeviceSn().equals(entry.getKey()))
|
|
|
+ .findFirst()
|
|
|
+ )
|
|
|
+ .filter(Optional::isPresent)
|
|
|
+ .map(Optional::get)
|
|
|
+ .collect(Collectors.toSet());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 加载设备
|
|
|
+ */
|
|
|
+ public void loadDevices() {
|
|
|
+ try {
|
|
|
+ if (configFile.exists() && configFile.isFile()) {
|
|
|
+ //配置文件存在,读取并设置设备列表
|
|
|
+ devices = yamlMapper.readValue(configFile, yamlMapper.getTypeFactory()
|
|
|
+ .constructCollectionType(List.class, Device.class));
|
|
|
+ LogPrintMgr.getInstance().printLogMsg(LogLevelFlag.LOG_INFO, BIZ_TYPE, CLASS_NAME
|
|
|
+ , String.format("加载设备列表成功,设备数量:%d", devices.size()));
|
|
|
+ } else {
|
|
|
+ //不存在,设置空列表,并同步
|
|
|
+ devices = new ArrayList<>();
|
|
|
+ syncDevices();
|
|
|
+ LogPrintMgr.getInstance().printLogMsg(LogLevelFlag.LOG_INFO, BIZ_TYPE, CLASS_NAME,
|
|
|
+ "配置文件不存在或为空,初始化空设备列表");
|
|
|
+ }
|
|
|
+ } catch (IOException e) {
|
|
|
+ LogPrintMgr.getInstance().printLogMsg(LogLevelFlag.LOG_ERROR, BIZ_TYPE, CLASS_NAME
|
|
|
+ , String.format("加载设备列表失败,error:%s", e));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 同步设备
|
|
|
+ */
|
|
|
+ private void syncDevices() {
|
|
|
+ try {
|
|
|
+ //确保父目录存在
|
|
|
+ File parentDir = configFile.getParentFile();
|
|
|
+ if (parentDir != null && !parentDir.exists()) {
|
|
|
+ boolean made = configFile.getParentFile().mkdirs();
|
|
|
+ if (made) LogPrintMgr.getInstance().printLogMsg(LogLevelFlag.LOG_INFO, BIZ_TYPE, CLASS_NAME
|
|
|
+ , String.format("创建设备列表配置文件成功:%s", configFile.getAbsoluteFile()));
|
|
|
+ }
|
|
|
+
|
|
|
+ //配置文件存在,写入设备列表
|
|
|
+ yamlMapper.writerWithDefaultPrettyPrinter().writeValue(configFile, devices);
|
|
|
+ LogPrintMgr.getInstance().printLogMsg(LogLevelFlag.LOG_INFO, BIZ_TYPE, CLASS_NAME
|
|
|
+ , String.format("同步设备列表成功,设备数量:%d", devices.size()));
|
|
|
+ } catch (IOException e) {
|
|
|
+ LogPrintMgr.getInstance().printLogMsg(LogLevelFlag.LOG_ERROR, BIZ_TYPE, CLASS_NAME
|
|
|
+ , String.format("同步设备列表失败,error:%s", e));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 导出设备
|
|
|
+ *
|
|
|
+ * @param path 路径
|
|
|
+ */
|
|
|
+ public void exportDevice(Path path) {
|
|
|
+ //种类映射
|
|
|
+ Map<String, String> kindMap = Arrays.stream(DeviceKind.values())
|
|
|
+ .collect(Collectors.toMap(DeviceKind::getKey, DeviceKind::getName));
|
|
|
+ //字段映射
|
|
|
+ Map<String, String> fieldMap = Arrays.stream(DeviceField.values())
|
|
|
+ .collect(Collectors.toMap(DeviceField::getKey, DeviceField::getName));
|
|
|
+ List<DeviceExcel> excels = this.devices.stream().map(device -> {
|
|
|
+ //构建excel对象
|
|
|
+ DeviceExcel deviceExcel = new DeviceExcel();
|
|
|
+ deviceExcel.setDeviceId(device.getDeviceId());
|
|
|
+ deviceExcel.setDeviceName(device.getDeviceName());
|
|
|
+ deviceExcel.setDeviceSn(device.getDeviceSn());
|
|
|
+ deviceExcel.setDeviceKind(kindMap.get(device.getDeviceKind()));
|
|
|
+ deviceExcel.setMfrs(device.getMfrs());
|
|
|
+ //设置字段
|
|
|
+ deviceExcel.setField(
|
|
|
+ device.getTags().stream()
|
|
|
+ .map(it -> fieldMap.get(it.getField()))
|
|
|
+ .collect(Collectors.joining("/"))
|
|
|
+ );
|
|
|
+ //设置标签
|
|
|
+ deviceExcel.setTags(
|
|
|
+ device.getTags().stream()
|
|
|
+ .map(DeviceTag::getTag)
|
|
|
+ .collect(Collectors.joining("/"))
|
|
|
+ );
|
|
|
+ return deviceExcel;
|
|
|
+ }).collect(Collectors.toList());
|
|
|
+
|
|
|
+ try (OutputStream outputStream = Files.newOutputStream(path.toFile().toPath())) {
|
|
|
+
|
|
|
+ ExcelUtil.writeExcel(excels, DeviceExcel.class, outputStream, ExcelType.XLSX);
|
|
|
+
|
|
|
+ } catch (IOException e) {
|
|
|
+ throw new RuntimeException(e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构建器
|
|
|
+ */
|
|
|
+ public static class Builder {
|
|
|
+ /**
|
|
|
+ * 映射
|
|
|
+ */
|
|
|
+ private ObjectMapper yamlMapper;
|
|
|
+ /**
|
|
|
+ * 配置文件
|
|
|
+ */
|
|
|
+ private File configFile;
|
|
|
+ /**
|
|
|
+ * 标签服务
|
|
|
+ */
|
|
|
+ private DeviceTagsService tagsService;
|
|
|
+ /**
|
|
|
+ * 自动加载配置文件
|
|
|
+ */
|
|
|
+ private Boolean autoLoad = true;
|
|
|
+
|
|
|
+ public Builder() {
|
|
|
+ // 默认的mapper
|
|
|
+ YAMLFactory yamlFactory = new YAMLFactory();
|
|
|
+ this.yamlMapper = new ObjectMapper(yamlFactory);
|
|
|
+ this.yamlMapper.findAndRegisterModules();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * object映射
|
|
|
+ */
|
|
|
+ public Builder yamlMapper(ObjectMapper yamlMapper) {
|
|
|
+ this.yamlMapper = yamlMapper;
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 配置文件路径
|
|
|
+ */
|
|
|
+ public Builder configFile(String configPath) {
|
|
|
+ this.configFile = new File(configPath);
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 配置文件对象
|
|
|
+ */
|
|
|
+ public Builder configFile(File configFile) {
|
|
|
+ this.configFile = configFile;
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 标签服务
|
|
|
+ */
|
|
|
+ public Builder tagsService(DeviceTagsService tagsService) {
|
|
|
+ this.tagsService = tagsService;
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 是否自动加载设备列表
|
|
|
+ */
|
|
|
+ public Builder autoLoad(boolean autoLoad) {
|
|
|
+ this.autoLoad = autoLoad;
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构建
|
|
|
+ */
|
|
|
+ public DeviceRegistry build() {
|
|
|
+ // 参数验证
|
|
|
+ if (yamlMapper == null) throw new IllegalStateException("YAML Mapper 不能为空");
|
|
|
+ if (configFile == null) throw new IllegalStateException("配置文件路径不能为空");
|
|
|
+ if (tagsService == null) throw new IllegalStateException("采集标签服务不能为空");
|
|
|
+ if (autoLoad == null) autoLoad = true;
|
|
|
+
|
|
|
+ //文件验证
|
|
|
+ if (configFile.exists()) {
|
|
|
+ if (!configFile.isFile())
|
|
|
+ throw new IllegalStateException("配置文件路径不是文件: " + configFile.getAbsolutePath());
|
|
|
+ if (!configFile.canRead() || !configFile.canWrite())
|
|
|
+ throw new IllegalStateException("配置文件无法读写: " + configFile.getAbsolutePath());
|
|
|
+ } else LogPrintMgr.getInstance().printLogMsg(LogLevelFlag.LOG_INFO, BIZ_TYPE, CLASS_NAME,
|
|
|
+ "配置文件不存在,将在初始化时创建");
|
|
|
+
|
|
|
+ return new DeviceRegistry(this);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|