package com.battery.device.api.discovery; import java.time.Instant; import java.util.List; import java.util.Optional; import com.battery.device.api.model.ChannelIdentity; import com.battery.device.api.model.DeviceWorkStatus; import com.battery.device.api.model.RealtimeSnapshot; /** 保存未登记发现候选和正式设备的实时观测快照。 */ public interface DeviceDiscoveryCandidateStore { /** upsert 本轮检测柜观测,未返回设备保留到逻辑过期。 */ void recordCabinets(List candidates); /** 替换指定适配器的采集器观测,使已删除模拟采集器立即消失。 */ void replaceCollectors(String adapterCode, List candidates); /** 删除指定适配器的全部发现候选;仅供人工重置或实机联调清理。 */ void removeAdapter(String adapterCode); /** 返回指定时刻尚未过期的候选和实时状态。 */ CandidateSnapshot snapshot(Instant now); /** 按设备编码查询指定时刻未过期的检测柜观测;在线和登记状态由业务层校验。 */ default Optional findCabinet(String deviceCode, Instant now) { return snapshot(now).cabinets().stream().filter(item -> item.deviceCode().equals(deviceCode)).findFirst(); } /** 按设备编码查询指定时刻未过期的采集器观测;在线和登记状态由业务层校验。 */ default Optional findCollector(String deviceCode, Instant now) { return snapshot(now).collectors().stream().filter(item -> item.deviceCode().equals(deviceCode)).findFirst(); } record CandidateSnapshot(List cabinets, List collectors) { public CandidateSnapshot { cabinets = List.copyOf(cabinets); collectors = List.copyOf(collectors); } } /** * 未登记的候选设备。{@code lastDataAt} 取该设备各通道最近的采样行时间, * 供操作员判断哪几台设备现在真的在产出数据;它不改变候选的取舍,登记范围由操作员决定。 */ record CabinetCandidate(String adapterCode, Long adapterId, String deviceCode, int devtype, int devid, String ip, String mac, String name, String manufacturer, String model, List channels, Instant observedAt, Instant expiresAt, Instant lastDataAt) { public CabinetCandidate { channels = List.copyOf(channels); } public boolean online() { return channels.stream().anyMatch(ChannelCandidate::online); } /** 兼容不含最近数据时间的既有候选。 */ public CabinetCandidate(String adapterCode, Long adapterId, String deviceCode, int devtype, int devid, String ip, String mac, String name, String manufacturer, String model, List channels, Instant observedAt, Instant expiresAt) { this(adapterCode, adapterId, deviceCode, devtype, devid, ip, mac, name, manufacturer, model, channels, observedAt, expiresAt, null); } } /** 通道候选;{@code lastDataAt} 是该通道来源里最近一次采样行的设备时间。 */ record ChannelCandidate(ChannelIdentity identity, boolean online, DeviceWorkStatus workStatus, Instant observedAt, RealtimeSnapshot latestSnapshot, Instant lastDataAt) { /** 兼容仅有状态的旧发现候选。 */ public ChannelCandidate(ChannelIdentity identity, boolean online, DeviceWorkStatus workStatus, Instant observedAt) { this(identity, online, workStatus, observedAt, null, null); } /** 兼容不含最近数据时间的既有候选。 */ public ChannelCandidate(ChannelIdentity identity, boolean online, DeviceWorkStatus workStatus, Instant observedAt, RealtimeSnapshot latestSnapshot) { this(identity, online, workStatus, observedAt, latestSnapshot, null); } } record CollectorCandidate(String adapterCode, String deviceCode, String ip, String mac, String name, String manufacturer, String model, int channelCount, boolean online, Instant observedAt, Instant expiresAt) { } }