From e38aca8134570513435c003c25dc05f1ae51c9c9 Mon Sep 17 00:00:00 2001
From: yeg <1347747024@qq.com>
Date: 星期四, 08 十月 2026 13:56:28 +0800
Subject: [PATCH] 实现 Ticket04:采集器辅助测量与登记入口,拆除开发模拟器

---
 battery-business/src/main/java/com/battery/business/service/impl/TCollectorServiceImpl.java |  118 ++++++++++++++++++++++++++++++-----------------------------
 1 files changed, 60 insertions(+), 58 deletions(-)

diff --git a/battery-business/src/main/java/com/battery/business/service/impl/TCollectorServiceImpl.java b/battery-business/src/main/java/com/battery/business/service/impl/TCollectorServiceImpl.java
index 8cf4d0b..8edfbbe 100644
--- a/battery-business/src/main/java/com/battery/business/service/impl/TCollectorServiceImpl.java
+++ b/battery-business/src/main/java/com/battery/business/service/impl/TCollectorServiceImpl.java
@@ -3,22 +3,23 @@
 import java.net.InetAddress;
 import java.util.Date;
 import java.util.List;
-import java.util.Set;
 import java.util.regex.Pattern;
-import java.time.Instant;
-import java.util.Map;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import com.ruoyi.common.exception.ServiceException;
 import com.ruoyi.common.utils.StringUtils;
 import com.battery.business.domain.TCollector;
+import com.battery.business.domain.TDeviceAdapter;
 import com.battery.business.mapper.TCollectorMapper;
+import com.battery.business.mapper.TDeviceAdapterMapper;
 import com.battery.business.service.ITCollectorService;
-import com.battery.device.api.discovery.DeviceDiscoveryCandidateStore;
-import com.battery.device.api.discovery.DeviceDiscoveryCandidateStore.CollectorCandidate;
 
 /**
  * 采集器 Service 实现。
+ *
+ * <p>采集器按 ADR-0064 不建独立设备模型:它的测量是「同一来源的辅助测量」,
+ * 由所链接的来源实例提供。因此这里不再走发现候选(真实来源根本不产出采集器候选),
+ * 而是由操作员手工登记来源身份,在线状态直接取所链接来源实例的连接结果。
  *
  * @author ruoyi
  */
@@ -28,7 +29,6 @@
     private static final String ENABLED = "0";
     private static final String DISABLED = "1";
     private static final String OFFLINE = "0";
-    private static final String ONLINE = "1";
     private static final String IDLE = "0";
     private static final String TESTING = "1";
     private static final Pattern IPV4_PATTERN = Pattern.compile(
@@ -36,12 +36,12 @@
     private static final Pattern MAC_PATTERN = Pattern.compile("^[0-9A-F]{2}(:[0-9A-F]{2}){5}$");
 
     private final TCollectorMapper collectorMapper;
-    private final DeviceDiscoveryCandidateStore candidateStore;
+    private final TDeviceAdapterMapper adapterMapper;
 
-    public TCollectorServiceImpl(TCollectorMapper collectorMapper, DeviceDiscoveryCandidateStore candidateStore)
+    public TCollectorServiceImpl(TCollectorMapper collectorMapper, TDeviceAdapterMapper adapterMapper)
     {
         this.collectorMapper = collectorMapper;
-        this.candidateStore = candidateStore;
+        this.adapterMapper = adapterMapper;
     }
 
     /**
@@ -59,7 +59,7 @@
         {
             throw new ServiceException("采集器不存在");
         }
-        return applyRealtimeState(collector, candidatesByCode());
+        return collector;
     }
 
     /**
@@ -77,32 +77,26 @@
         collector.setEndCreateTime(trimToNull(collector.getEndCreateTime()));
         String requestedOnlineStatus = collector.getOnlineStatus();
         validateOptionalStatus(requestedOnlineStatus, "在线状态");
+        // 在线状态由 SQL 从所链接来源实例的连接结果派生,这里不再从存储列过滤。
         collector.setOnlineStatus(null);
         validateOptionalStatus(collector.getTestStatus(), "测试状态");
         validateOptionalStatus(collector.getStatus(), "状态");
-        Map<String, CollectorCandidate> candidates = candidatesByCode();
-        return collectorMapper.selectCollectorList(collector).stream()
-                .map(item -> applyRealtimeState(item, candidates))
-                .filter(item -> StringUtils.isEmpty(requestedOnlineStatus)
-                        || requestedOnlineStatus.equals(item.getOnlineStatus()))
-                .toList();
+        return collectorMapper.selectCollectorList(collector);
     }
 
     /**
      * 新增采集器并初始化运行状态。
+     *
+     * <p>前置条件:设备编码与名称非空且设备编码未登记过;IP 与 MAC 可留空(辅助测量不是独立硬件,
+     * 系统不连它),填了则校验格式。在线状态取所链接来源实例的连接结果,未链接来源时保持离线。
      */
     @Override
     @Transactional(rollbackFor = Exception.class)
     public int insertCollector(TCollector collector)
     {
         if (collector == null) throw new ServiceException("采集器信息不能为空");
-        collector.setCollectorCode(trimToNull(collector.getCollectorCode()));
-        CollectorCandidate candidate = candidateStore.findCollector(collector.getCollectorCode(), Instant.now())
-                .orElseThrow(() -> new ServiceException("请先选择有效的发现候选设备"));
-        collector.setIpAddress(candidate.ip());
-        collector.setMacAddress(candidate.mac());
-        collector.setChannelCount(candidate.channelCount());
         validateCollector(collector, false);
+        requireLinkedAdapter(collector);
         collector.setOnlineStatus(OFFLINE);
         // 测试占用由 t_test_collector_channel + t_test 派生,采集器登记表不写入这些字段。
         collector.setTestStatus(IDLE);
@@ -118,11 +112,8 @@
         }
         collector.setDelFlag("0");
         collector.setCreateTime(new Date());
-        int rows = collectorMapper.insertCollector(collector);
-        // 登记只确认来源身份,在线与离线事实都按候选原样同步。
-        collectorMapper.updateRegisteredObservation(collector.getCollectorCode(),
-                candidate.online() ? "ONLINE" : "OFFLINE");
-        return rows;
+        // 登记只确认来源身份:在线事实由所链接来源实例的连接结果表达,登记时不改写它。
+        return collectorMapper.insertCollector(collector);
     }
 
     /**
@@ -136,7 +127,10 @@
         if (current == null) throw new ServiceException("采集器不存在");
         if (collector.getCollectorCode() != null && !collector.getCollectorCode().trim().equals(current.getCollectorCode()))
             throw new ServiceException("设备编码不可修改");
+        // 编辑只提交可改字段:未提交的 IP/MAC 不能被当成"清空"。
+        fillMissingFromCurrent(collector, current);
         validateCollector(collector, true);
+        requireLinkedAdapter(collector);
         validateStatus(collector.getStatus(), "状态");
         if (DISABLED.equals(collector.getStatus()))
         {
@@ -168,13 +162,6 @@
         }
         collector.setUpdateTime(new Date());
         return collectorMapper.updateCollectorStatus(collector);
-    }
-
-    /** 查询全部未删除采集器的设备编码,供发现候选排除已登记设备。 */
-    @Override
-    public Set<String> selectRegisteredCollectorCodes()
-    {
-        return Set.copyOf(collectorMapper.selectRegisteredCollectorCodes());
     }
 
     /**
@@ -215,7 +202,7 @@
         }
         collector.setCollectorCode(trimToNull(collector.getCollectorCode()));
         collector.setIpAddress(trimToNull(collector.getIpAddress()));
-        collector.setMacAddress(normalizeMacAddress(collector.getMacAddress()));
+        collector.setMacAddress(normalizeOptionalMacAddress(collector.getMacAddress()));
         collector.setCollectorName(trimToNull(collector.getCollectorName()));
         collector.setManufacturer(trimToNull(collector.getManufacturer()));
         collector.setModel(trimToNull(collector.getModel()));
@@ -229,7 +216,7 @@
         {
             throw new ServiceException("采集器名称不能为空");
         }
-        validateIpAddress(collector.getIpAddress());
+        validateOptionalIpAddress(collector.getIpAddress());
         if (collector.getChannelCount() == null || collector.getChannelCount() < 1
                 || collector.getChannelCount() > 10000)
         {
@@ -238,8 +225,34 @@
         validateUnique(collector);
     }
 
+    /** 编辑时用既有值补齐未提交的字段,避免把"没传"当成"清空"。 */
+    private void fillMissingFromCurrent(TCollector collector, TCollector current)
+    {
+        if (collector.getCollectorCode() == null) collector.setCollectorCode(current.getCollectorCode());
+        if (collector.getIpAddress() == null) collector.setIpAddress(current.getIpAddress());
+        if (collector.getMacAddress() == null) collector.setMacAddress(current.getMacAddress());
+        if (collector.getCollectorName() == null) collector.setCollectorName(current.getCollectorName());
+        if (collector.getChannelCount() == null) collector.setChannelCount(current.getChannelCount());
+        if (collector.getStatus() == null) collector.setStatus(current.getStatus());
+        if (collector.getAdapterId() == null) collector.setAdapterId(current.getAdapterId());
+    }
+
+    /** 校验所链接的来源实例存在;来源是采集器测量的提供方,链接后可读它的连接结果。 */
+    private void requireLinkedAdapter(TCollector collector)
+    {
+        if (collector.getAdapterId() == null)
+        {
+            return;
+        }
+        TDeviceAdapter adapter = adapterMapper.selectDeviceAdapterById(collector.getAdapterId());
+        if (adapter == null)
+        {
+            throw new ServiceException("所链接的数据来源不存在");
+        }
+    }
+
     /**
-     * 校验设备编码和 MAC 地址在未删除数据中唯一。
+     * 校验设备编码唯一;MAC 地址只在填写时参与唯一性。
      */
     private void validateUnique(TCollector collector)
     {
@@ -247,6 +260,10 @@
         if (isDifferentCollector(sameCode, collector.getCollectorId()))
         {
             throw new ServiceException("设备编码已存在");
+        }
+        if (collector.getMacAddress() == null)
+        {
+            return;
         }
         TCollector sameMac = collectorMapper.selectCollectorByMacAddress(collector.getMacAddress());
         if (isDifferentCollector(sameMac, collector.getCollectorId()))
@@ -264,14 +281,14 @@
     }
 
     /**
-     * 规范化并校验 MAC 地址。
+     * 规范化并校验 MAC 地址;未填写时保持为空。
      */
-    private String normalizeMacAddress(String macAddress)
+    private String normalizeOptionalMacAddress(String macAddress)
     {
         String normalized = trimToNull(macAddress);
         if (normalized == null)
         {
-            throw new ServiceException("MAC地址不能为空");
+            return null;
         }
         normalized = normalized.replace('-', ':').toUpperCase();
         if (!MAC_PATTERN.matcher(normalized).matches())
@@ -282,13 +299,13 @@
     }
 
     /**
-     * 校验 IPv4 或 IPv6 地址,不接受主机名。
+     * 校验 IPv4 或 IPv6 地址,不接受主机名;未填写时跳过。
      */
-    private void validateIpAddress(String ipAddress)
+    private void validateOptionalIpAddress(String ipAddress)
     {
         if (StringUtils.isEmpty(ipAddress))
         {
-            throw new ServiceException("IP地址不能为空");
+            return;
         }
         if (IPV4_PATTERN.matcher(ipAddress).matches())
         {
@@ -351,20 +368,5 @@
     private String trimToNull(String value)
     {
         return StringUtils.isEmpty(StringUtils.trim(value)) ? null : StringUtils.trim(value);
-    }
-
-    private Map<String, CollectorCandidate> candidatesByCode()
-    {
-        return candidateStore.snapshot(Instant.now()).collectors().stream()
-                .collect(java.util.stream.Collectors.toMap(CollectorCandidate::deviceCode, item -> item));
-    }
-
-    private TCollector applyRealtimeState(TCollector collector, Map<String, CollectorCandidate> candidates)
-    {
-        CollectorCandidate candidate = candidates.get(collector.getCollectorCode());
-        collector.setOnlineStatus(candidate != null && candidate.online() ? ONLINE : OFFLINE);
-        // 候选过期只改变当前在线展示,不能抹掉 MySQL 保存的最近观测时间。
-        if (candidate != null) collector.setLastSeenAtUtc(candidate.observedAt());
-        return collector;
     }
 }

--
Gitblit v1.7.1