| | |
| | | import com.battery.device.api.model.Measurement; |
| | | import com.battery.business.domain.TTest; |
| | | import com.battery.business.domain.TTestBattery; |
| | | import com.battery.business.domain.TDeviceAdapter; |
| | | import com.battery.business.service.ITestLiveService; |
| | | import com.battery.business.service.ITestLiveService.TestPlanRow; |
| | | import com.battery.business.service.ITestLiveService.TestPlanSnapshot; |
| | | import com.battery.business.service.ITTestService; |
| | | import com.battery.business.mapper.TDeviceAdapterMapper; |
| | | import com.battery.business.mapper.TTestMapper; |
| | | |
| | | /** 将设备最新快照转换为测试视角,按测试接收状态决定是否展示采集参数。 */ |
| | |
| | | private final DeviceDiscoveryCandidateStore candidates; |
| | | private final TestDataObservationAdapter adapter; |
| | | private final TTestMapper mapper; |
| | | private final TDeviceAdapterMapper adapters; |
| | | |
| | | @Autowired |
| | | public TTestLiveServiceImpl(ITTestService tests, DeviceDiscoveryCandidateStore candidates, |
| | | @Nullable TestDataObservationAdapter adapter, TTestMapper mapper) |
| | | { this.tests = tests; this.candidates = candidates; this.adapter = adapter; this.mapper = mapper; } |
| | | @Nullable TestDataObservationAdapter adapter, TTestMapper mapper, TDeviceAdapterMapper adapters) |
| | | { this.tests = tests; this.candidates = candidates; this.adapter = adapter; this.mapper = mapper; this.adapters = adapters; } |
| | | |
| | | public TTestLiveServiceImpl(ITTestService tests, DeviceDiscoveryCandidateStore candidates, |
| | | TestDataObservationAdapter adapter) |
| | | { this(tests, candidates, adapter, null); } |
| | | { this(tests, candidates, adapter, null, null); } |
| | | |
| | | /** 返回测试当前设备事实;接收中展示测量和实时提醒,历史异常处理状态不参与判定。 */ |
| | | @Override public LiveState current(Long testId) |
| | |
| | | return value; |
| | | } |
| | | |
| | | /** |
| | | * 采集器来源:测量按 ADR-0064 由同一来源实例作为辅助测量提供,所以它的在线事实就是该来源的可达性 |
| | | * (ADR-0063:在线只表示来源可达),不再依赖只有开发模拟器会产出的发现候选。 |
| | | * |
| | | * <p>来源不提供辅助测量、或来源可达但这一刻还没有任何辅助测量值时,明确标注为「无辅助数据」: |
| | | * 这是正常取值而不是故障,主测量接收不受影响,也不因此生成离线异常。来源不可达时不标这个标记—— |
| | | * 那种情况是来源离线,由既有的离线异常路径表达。 |
| | | */ |
| | | private LiveSource collectorSource(TTestBattery battery, boolean receiving) |
| | | { |
| | | if (adapter == null || mapper == null) return new LiveSource("COLLECTOR", battery.getCollectorId(), battery.getBatteryNo(), |
| | | "OFFLINE", Map.of()); |
| | | String collectorCode = mapper.selectCollectorCode(battery.getCollectorId()); |
| | | // 非接收状态只读设备发现事实,不能先请求测量再隐藏,否则仍会产生新观测。 |
| | | String adapterCode = battery.getBinding() == null ? null : battery.getBinding().getAdapterCode(); |
| | | boolean sourceOnline = sourceOnline(adapterCode); |
| | | // 非接收状态只读来源可达性,不能先请求测量再隐藏,否则仍会产生新观测。 |
| | | if (!receiving) |
| | | { |
| | | boolean online = candidates.findCollector(collectorCode, Instant.now()) |
| | | .map(DeviceDiscoveryCandidateStore.CollectorCandidate::online).orElse(false); |
| | | return new LiveSource("COLLECTOR", battery.getCollectorId(), battery.getBatteryNo(), |
| | | online ? "ONLINE" : "OFFLINE", Map.of()); |
| | | sourceOnline ? "ONLINE" : "OFFLINE", Map.of()); |
| | | } |
| | | var collector = adapter.latestCollector( |
| | | battery.getBinding() == null ? null : battery.getBinding().getAdapterCode(), collectorCode); |
| | | if (collector != null) |
| | | var collector = adapter.latestCollector(adapterCode, collectorCode); |
| | | Map<String, Object> value = new LinkedHashMap<>(); |
| | | value.put("source", "采集器"); |
| | | if (collector == null) |
| | | { |
| | | Map<String, Object> value = new LinkedHashMap<>(); |
| | | value.put("source", "采集器"); value.put("sampleIntervalMs", collector.sampleIntervalMs()); |
| | | // 在业务响应边界转换毫秒时间,设备时间与服务器接收时间分别保留。 |
| | | value.put("measurements", collector.measurements().stream().filter(item -> battery.getCollectorChannels() != null |
| | | && battery.getCollectorChannels().stream().anyMatch(binding -> binding.getChannelCode().equals(item.channelCode()) |
| | | && TestWarningExceptionService.matchesCollectorMetric(binding.getMeasurementCode(), item.metricCode()))).map(item -> { |
| | | Map<String, Object> measurement = new LinkedHashMap<>(); |
| | | measurement.put("metricCode", item.metricCode()); |
| | | measurement.put("channelCode", item.channelCode()); |
| | | measurement.put("value", item.value()); |
| | | measurement.put("unit", item.unit()); |
| | | measurement.put("deviceTime", item.deviceTime().toEpochMilli()); |
| | | measurement.put("serverReceivedAt", item.serverReceivedAt().toEpochMilli()); |
| | | return measurement; |
| | | }).toList()); |
| | | value.put("units", collector.measurements().stream().map(item -> item.unit()).distinct().toList()); |
| | | collector.measurements().stream().map(item -> item.serverReceivedAt()).max(Instant::compareTo) |
| | | .ifPresent(observedAt -> value.put("observedAt", observedAt.toEpochMilli())); |
| | | // 该来源根本不提供辅助测量(新威实测无辅助数据;推式来源的列语义确认前同样如此)。 |
| | | if (sourceOnline) value.put("auxiliaryDataAvailable", false); |
| | | return new LiveSource("COLLECTOR", battery.getCollectorId(), battery.getBatteryNo(), |
| | | collector.online() ? "ONLINE" : "OFFLINE", collector.online() ? value : Map.of()); |
| | | sourceOnline ? "ONLINE" : "OFFLINE", value); |
| | | } |
| | | return new LiveSource("COLLECTOR", battery.getCollectorId(), battery.getBatteryNo(), "OFFLINE", Map.of()); |
| | | value.put("sampleIntervalMs", collector.sampleIntervalMs()); |
| | | // 在业务响应边界转换毫秒时间,设备时间与服务器接收时间分别保留。 |
| | | value.put("measurements", collector.measurements().stream().filter(item -> battery.getCollectorChannels() != null |
| | | && battery.getCollectorChannels().stream().anyMatch(binding -> binding.getChannelCode().equals(item.channelCode()) |
| | | && TestWarningExceptionService.matchesCollectorMetric(binding.getMeasurementCode(), item.metricCode()))).map(item -> { |
| | | Map<String, Object> measurement = new LinkedHashMap<>(); |
| | | measurement.put("metricCode", item.metricCode()); |
| | | measurement.put("channelCode", item.channelCode()); |
| | | measurement.put("value", item.value()); |
| | | measurement.put("unit", item.unit()); |
| | | measurement.put("deviceTime", item.deviceTime().toEpochMilli()); |
| | | measurement.put("serverReceivedAt", item.serverReceivedAt().toEpochMilli()); |
| | | return measurement; |
| | | }).toList()); |
| | | value.put("units", collector.measurements().stream().map(item -> item.unit()).distinct().toList()); |
| | | collector.measurements().stream().map(item -> item.serverReceivedAt()).max(Instant::compareTo) |
| | | .ifPresent(observedAt -> value.put("observedAt", observedAt.toEpochMilli())); |
| | | // 来源可达但这一刻还没有任何辅助测量值:同样是"无辅助数据",按空值展示而不是占位。 |
| | | if (collector.online() && collector.measurements().isEmpty()) value.put("auxiliaryDataAvailable", false); |
| | | return new LiveSource("COLLECTOR", battery.getCollectorId(), battery.getBatteryNo(), |
| | | collector.online() ? "ONLINE" : "OFFLINE", collector.online() ? value : Map.of()); |
| | | } |
| | | |
| | | /** 来源实例是否可达;来源编码缺失或查询不到时按不可达处理,不猜也不回退到其他实例。 */ |
| | | private boolean sourceOnline(String adapterCode) |
| | | { |
| | | if (adapters == null || adapterCode == null || adapterCode.isBlank()) return false; |
| | | TDeviceAdapter adapter = adapters.selectDeviceAdapterByCode(adapterCode); |
| | | return adapter != null && "ONLINE".equalsIgnoreCase(adapter.getConnectionStatus()); |
| | | } |
| | | |
| | | } |