yeg
2026-09-09 7100c8a4f7dd3fc7d53ffead4475d7a8abc51c12
battery-device-simulator/src/main/java/com/battery/device/simulator/BtsSimulatorStateStore.java
@@ -3,80 +3,17 @@
import java.math.BigDecimal;
import java.time.Instant;
import java.util.List;
import com.battery.device.model.DeviceWorkStatus;
import com.battery.device.api.model.DeviceWorkStatus;
/** 持久化开发模拟器配置;实现必须在返回前确认写入成功。 */
public interface BtsSimulatorStateStore
{
    int CURRENT_VERSION = 1;
    int MAX_HISTORY_BATCH_SIZE = 1000;
    /** 开发演示只保留有限历史,避免 Redis 随运行时间无限增长。 */
    long MAX_HISTORY_RECORDS = 20_000L;
    /** 读取已有状态;仅在状态不存在时原子写入并返回默认状态。 */
    /** 读取已有状态,仅在不存在时保存默认拓扑。 */
    SimulatorState loadOrInitialize(SimulatorState defaultState);
    /** 开发模拟器启动时清理上一轮演示历史;正式业务存储不需要实现该动作。 */
    default SimulatorState resetHistoryForDemoStartup(SimulatorState state) { return state; }
    /** 完整保存下一版模拟器状态。 */
    /** 保存设备配置和观测序号;返回前必须完成写入。 */
    void save(SimulatorState state);
    /** 状态和独立历史全部校验通过后,提交兼容旧格式所需的迁移。 */
    default void completeValidatedRestore(SimulatorState state) { }
    /** 原子保存主状态并追加本轮新产生的原始历史。 */
    default void save(SimulatorState state, List<CollectorHistoryAppend> historyAppends)
    {
        if (!historyAppends.isEmpty())
            throw new UnsupportedOperationException("Collector history append is not supported");
        save(state);
    }
    /** 按原始写入顺序或反向顺序读取有界历史批次。 */
    default CollectorHistoryBatch loadCollectorHistoryPage(String collectorCode, long offset, int limit,
            boolean reverse)
    {
        if (offset < 0) throw new IllegalArgumentException("offset must not be negative");
        if (limit < 1 || limit > MAX_HISTORY_BATCH_SIZE)
            throw new IllegalArgumentException("limit must be between 1 and " + MAX_HISTORY_BATCH_SIZE);
        return new CollectorHistoryBatch(offset, 0, false, 0, List.of());
    }
    /** 读取检测柜模拟历史;正式适配器不依赖该演示扩展。 */
    default BtsHistoryBatch loadBtsHistoryPage(String historyKey, long offset, int limit)
    {
        return new BtsHistoryBatch(offset, 0, false, 0, List.of());
    }
    /** 追加检测柜模拟历史,供重启续采合同使用。 */
    default void appendBtsHistory(String historyKey, BtsHistoryRecord record) { }
    /** 读取检测柜通道的播放状态;实现可在不支持播放持久化时返回 null。 */
    default BtsPlaybackState loadBtsPlaybackState(String historyKey) { return null; }
    /** 保存检测柜通道的播放状态。 */
    default void saveBtsPlaybackState(String historyKey, BtsPlaybackState state) { }
    /** 删除检测柜通道的播放状态。 */
    default void deleteBtsPlaybackState(String historyKey) { }
    /** 删除检测柜通道的持久化历史和对应索引。 */
    default void deleteBtsHistory(String historyKey) { }
    /** 原子提交检测柜新增历史与推进后的播放状态。 */
    default void saveBtsPlaybackAndAppendHistory(String historyKey, BtsPlaybackState state,
            List<BtsHistoryRecord> records)
    {
        for (BtsHistoryRecord record : records) appendBtsHistory(historyKey, record);
        saveBtsPlaybackState(historyKey, state);
    }
    /** 原子保存删除采集器后的主状态,并删除该采集器的独立历史。 */
    default void saveAndDeleteCollectorHistory(SimulatorState state, String collectorCode)
    {
        throw new UnsupportedOperationException("Atomic collector deletion is not supported");
    }
    /** 读取短期保留的最近请求。 */
    List<RecentRequest> loadRecentRequests();
@@ -88,12 +25,20 @@
    void reset(SimulatorState defaultState);
    record SimulatorState(int version, List<DeviceState> devices, List<CollectorState> collectors,
            int nextDeviceId, int nextCollectorId, List<ChannelTombstone> tombstones)
            int nextDeviceId, int nextCollectorId, List<ChannelTombstone> tombstones,
            Boolean softwareAccessible, long observationSequence)
    {
        public SimulatorState(int version, List<DeviceState> devices, List<CollectorState> collectors,
                int nextDeviceId, int nextCollectorId, List<ChannelTombstone> tombstones)
        {
            this(version, devices, collectors, nextDeviceId, nextCollectorId, tombstones, true, 0L);
        }
        public SimulatorState
        {
            if (version != CURRENT_VERSION)
                throw new IllegalStateException("Unsupported BTS simulator state version: " + version);
            softwareAccessible = softwareAccessible == null ? true : softwareAccessible;
            devices = List.copyOf(devices);
            collectors = List.copyOf(collectors);
            tombstones = List.copyOf(tombstones);
@@ -118,31 +63,35 @@
    record CollectorState(int collectorId, String deviceCode, String ip, String mac, String name,
            String manufacturer, String model, int channelCount, boolean online, int sampleIntervalMs,
            long nextSequence, Instant lastSampledAt, List<CollectorChannelState> channels,
            List<CollectorRealtimeMeasurement> latestMeasurements,
            long historyRecordCount, CollectorHistoryFaults historyFaults)
            List<CollectorChannelState> channels, List<CollectorRealtimeMeasurement> latestMeasurements)
    {
        public CollectorState
        {
            channels = List.copyOf(channels);
            latestMeasurements = List.copyOf(latestMeasurements);
            historyFaults = historyFaults == null ? CollectorHistoryFaults.none() : historyFaults;
        }
        /** 兼容只包含发现配置的旧模拟状态;Redis 读取边界会补齐采样合同。 */
        public CollectorState(int collectorId, String deviceCode, String ip, String mac, String name,
                String manufacturer, String model, int channelCount, boolean online)
        {
            this(collectorId, deviceCode, ip, mac, name, manufacturer, model, channelCount, online,
                    DevelopmentCollectorSimulation.DEFAULT_SAMPLE_INTERVAL_MS, 1L, null,
                    DevelopmentCollectorSimulation.defaultChannels(channelCount), List.of(), 0L,
                    CollectorHistoryFaults.none());
                    DevelopmentCollectorSimulation.DEFAULT_SAMPLE_INTERVAL_MS,
                    DevelopmentCollectorSimulation.defaultChannels(channelCount), List.of());
        }
    }
    record CollectorChannelState(String channelCode, List<CollectorMetricState> metrics)
    record CollectorChannelState(String channelCode, List<CollectorMetricState> metrics, DeviceWorkStatus workStatus)
    {
        public CollectorChannelState { metrics = List.copyOf(metrics); }
        public CollectorChannelState
        {
            metrics = List.copyOf(metrics);
            workStatus = workStatus == null ? DeviceWorkStatus.WORKING : workStatus;
        }
        public CollectorChannelState(String channelCode, List<CollectorMetricState> metrics)
        {
            this(channelCode, metrics, DeviceWorkStatus.WORKING);
        }
    }
    record CollectorMetricState(String metricCode, BigDecimal value, String unit) { }
@@ -151,70 +100,58 @@
            long sequenceId, Instant deviceTime, String deviceTimeRaw, Instant serverReceivedAt,
            String metricCode, BigDecimal value, String unit) { }
    record CollectorMeasurementRecord(String adapterCode, String collectorCode, String channelCode,
            long sequenceId, Instant deviceTime, String deviceTimeRaw, Instant serverReceivedAt,
            String metricCode, BigDecimal value, String unit) { }
    record CollectorHistoryAppend(String collectorCode, List<CollectorMeasurementRecord> records)
    record ChannelState(int channelId, boolean online, DeviceWorkStatus workStatus,
            ChannelConfiguration configuration)
    {
        public CollectorHistoryAppend { records = List.copyOf(records); }
    }
    record CollectorHistoryBatch(long offset, long nextOffset, boolean hasMore, long total,
            List<CollectorMeasurementRecord> records)
    {
        public CollectorHistoryBatch { records = List.copyOf(records); }
    }
    record BtsHistoryRecord(long sequenceId, Instant deviceTime, double currentA, double voltageV,
            double capacityAh, double energyWh, int cycleNo, int stepNo, String stepName, String stepType)
    {
        /** 兼容 Ticket13 之前仅包含核心测量值的历史记录。 */
        public BtsHistoryRecord(long sequenceId, Instant deviceTime, double currentA, double voltageV,
                double capacityAh, double energyWh)
        public ChannelState
        {
            this(sequenceId, deviceTime, currentA, voltageV, capacityAh, energyWh, 1, 1, "恒流充电", "cc");
            configuration = configuration == null ? ChannelConfiguration.defaults() : configuration;
        }
        public ChannelState(int channelId, boolean online, DeviceWorkStatus workStatus)
        { this(channelId, online, workStatus, null); }
    }
    /** 通道当前观测配置与方案一起保存,避免当前工步引用不存在的方案行。 */
    record ChannelConfiguration(BigDecimal currentA, BigDecimal voltageV, BigDecimal capacityAh,
            BigDecimal energyWh, int cycleNo, int stepNo, int plannedCycleCount,
            long totalTimeSeconds, long relativeTimeSeconds, List<SimulatorPlanStep> steps)
    {
        public ChannelConfiguration
        {
            for (BigDecimal value : new BigDecimal[] { currentA, voltageV, capacityAh, energyWh })
                if (value == null || !Double.isFinite(value.doubleValue()))
                    throw new IllegalArgumentException("通道测量值必须是有效数值");
            if (steps == null || steps.isEmpty() || steps.stream().anyMatch(java.util.Objects::isNull))
                throw new IllegalArgumentException("工步方案至少包含一个有效工步");
            steps = List.copyOf(steps);
            if (cycleNo < 1 || plannedCycleCount < cycleNo || stepNo < 1 || stepNo > steps.size()
                    || totalTimeSeconds < 0 || relativeTimeSeconds < 0 || relativeTimeSeconds > totalTimeSeconds)
                throw new IllegalArgumentException("循环、当前工步或时间配置无效");
        }
        /** 为新增通道和既有配置补齐固定观测值,不产生成功接收记录。 */
        public static ChannelConfiguration defaults()
        {
            return new ChannelConfiguration(BigDecimal.ZERO, new BigDecimal("3.7"), new BigDecimal("1.2"),
                    new BigDecimal("4.44"), 1, 1, 1, 0, 0,
                    List.of(new SimulatorPlanStep("恒流充电", "cc", 60d, 3.7d)));
        }
    }
    /** 检测柜单通道演示播放状态;与测试的接收状态相互独立。 */
    record BtsPlaybackState(boolean playing, int speedMultiplier, int cycleNo, int stepNo,
            String stepName, String stepType, Instant simulatedAt, Instant wallClockAt,
            long nextSequence, boolean naturallyFinished)
    record SimulatorPlanStep(String name, String stepType, double durationSeconds, double voltageV)
    {
        public BtsPlaybackState
        public SimulatorPlanStep
        {
            if (speedMultiplier != 1 && speedMultiplier != 60 && speedMultiplier != 600)
                throw new IllegalArgumentException("speedMultiplier must be 1, 60 or 600");
            if (cycleNo < 1 || stepNo < 1 || nextSequence < 1 || stepName == null || stepName.isBlank()
                    || stepType == null || stepType.isBlank())
                throw new IllegalArgumentException("Invalid BTS playback state");
            if (name == null || name.isBlank() || stepType == null
                    || !List.of("cc", "dc", "cv", "rest", "end", "cccv").contains(stepType)
                    || !Double.isFinite(durationSeconds) || durationSeconds < 0 || !Double.isFinite(voltageV))
                throw new IllegalArgumentException("工步名称、类型、时间或电压无效");
            name = name.trim();
        }
    }
    record BtsHistoryBatch(long offset, long nextOffset, boolean hasMore, long total,
            List<BtsHistoryRecord> records)
    {
        public BtsHistoryBatch { records = List.copyOf(records); }
    }
    record CollectorHistoryFaults(List<Long> missingSequences, Long duplicateSequence,
            Long conflictingSequence, boolean outOfOrder)
    {
        public CollectorHistoryFaults
        {
            missingSequences = missingSequences == null ? List.of() : List.copyOf(missingSequences);
        }
        public static CollectorHistoryFaults none()
        {
            return new CollectorHistoryFaults(List.of(), null, null, false);
        }
    }
    record ChannelState(int channelId, boolean online, DeviceWorkStatus workStatus) { }
    /** BTS 检测柜的可配置厂商 profile;参数用于发现展示和边界内的演示数据生成。 */
    /** BTS 检测柜的可配置厂商 profile,用于设备发现元数据展示。 */
    record BtsDeviceProfile(int devtype, int subDeviceId, String name, String manufacturer, String model,
            String serialNumber, String firmwareVersion, int channelCount, BigDecimal currentMinA,
            BigDecimal currentMaxA, BigDecimal voltageMinV, BigDecimal voltageMaxV, int sampleIntervalMs)
@@ -242,5 +179,5 @@
    record ChannelTombstone(int deviceId, String ip, int channelId, int remainingDiscoveries) { }
    record RecentRequest(Instant receivedAt, String command, String request) { }
    record RecentRequest(Instant receivedAt, String command, String request, String response) { }
}