package com.battery.business.runtime; import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import org.springframework.context.annotation.Profile; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; import com.battery.device.simulator.BtsSimulatorStateStore; import com.battery.device.simulator.BtsSimulatorStateStore.BtsDeviceProfile; import com.battery.device.simulator.BtsSimulatorStateStore.ChannelConfiguration; import com.battery.device.simulator.BtsSimulatorStateStore.SimulatorPlanStep; import com.battery.device.api.model.DeviceWorkStatus; /** 将开发模拟器完整状态写入 Redis,主状态不设置普通 TTL。 */ @Component @Profile("dev-simulator") public class RedisBtsSimulatorStateStore implements BtsSimulatorStateStore { static final String STATE_KEY = "dev:bts-simulator:state:v1"; static final String RECENT_REQUESTS_KEY = "dev:bts-simulator:recent-requests:v1"; private static final long RECENT_REQUESTS_TTL_HOURS = 1; private final RedisTemplate redisTemplate; public RedisBtsSimulatorStateStore(RedisTemplate redisTemplate) { this.redisTemplate = redisTemplate; } @Override public SimulatorState loadOrInitialize(SimulatorState defaultState) { Object stored = redisTemplate.opsForValue().get(STATE_KEY); if (stored != null) return restoreState(stored); String initial = JSON.toJSONString(defaultState); if (Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(STATE_KEY, initial))) return defaultState; stored = redisTemplate.opsForValue().get(STATE_KEY); if (stored == null) throw new IllegalStateException("BTS simulator state initialization lost in Redis"); return restoreState(stored); } @Override public void save(SimulatorState state) { redisTemplate.opsForValue().set(STATE_KEY, JSON.toJSONString(state)); } @Override public List loadRecentRequests() { Object stored = redisTemplate.opsForValue().get(RECENT_REQUESTS_KEY); if (stored == null) return List.of(); return List.copyOf(JSON.parseArray(stored.toString(), RecentRequest.class)); } @Override public void saveRecentRequests(List requests) { redisTemplate.opsForValue().set(RECENT_REQUESTS_KEY, JSON.toJSONString(requests), RECENT_REQUESTS_TTL_HOURS, TimeUnit.HOURS); } @Override public void reset(SimulatorState defaultState) { redisTemplate.delete(RECENT_REQUESTS_KEY); save(defaultState); } private SimulatorState restoreState(Object stored) { try { // 通用 RedisTemplate 在可执行包中可能将泛型数组恢复为 JSONObject,必须在存储边界逐层重建。 JSONObject root = jsonObject(stored); Integer version = root.getInteger("version"); if (version == null || version != CURRENT_VERSION) throw new IllegalStateException("Unsupported BTS simulator state version: " + version); List devices = new ArrayList<>(); requiredArray(root, "devices").forEach(item -> devices.add(deviceState(item))); List collectors = new ArrayList<>(); requiredArray(root, "collectors").forEach(item -> collectors.add(collectorState(jsonObject(item)))); List tombstones = new ArrayList<>(); requiredArray(root, "tombstones").forEach(item -> tombstones.add(channelTombstone(item))); SimulatorState state = new SimulatorState(version, devices, collectors, requiredInteger(root, "nextDeviceId"), requiredInteger(root, "nextCollectorId"), tombstones, root.getBoolean("softwareAccessible"), root.getLongValue("observationSequence")); return state; } catch (IllegalStateException exception) { throw exception; } catch (Exception exception) { throw new IllegalStateException("Invalid BTS simulator state in Redis", exception); } } private JSONObject jsonObject(Object value) { if (value instanceof JSONObject object) return object; if (value instanceof String text) { Object parsed = JSON.parse(text); if (parsed instanceof String nested) parsed = JSON.parse(nested); if (parsed instanceof JSONObject object) return object; return JSON.parseObject(JSON.toJSONString(parsed)); } return JSON.parseObject(JSON.toJSONString(value)); } private JSONArray requiredArray(JSONObject object, String field) { JSONArray array = object.getJSONArray(field); if (array == null) throw new IllegalStateException("Missing BTS simulator state field: " + field); return array; } private int requiredInteger(JSONObject object, String field) { Integer value = object.getInteger(field); if (value == null) throw new IllegalStateException("Missing BTS simulator state field: " + field); return value; } private DeviceState deviceState(Object value) { JSONObject object = jsonObject(value); List channels = new ArrayList<>(); requiredArray(object, "channels").forEach(item -> channels.add(channelState(item))); BtsDeviceProfile profile = object.containsKey("profile") ? deviceProfile(object.getJSONObject("profile")) : null; return new DeviceState(requiredInteger(object, "deviceId"), object.getString("ip"), object.getBooleanValue("online"), channels, profile); } private BtsDeviceProfile deviceProfile(JSONObject object) { if (object == null) throw new IllegalStateException("Invalid BTS simulator device profile"); return new BtsDeviceProfile(requiredInteger(object, "devtype"), requiredInteger(object, "subDeviceId"), object.getString("name"), object.getString("manufacturer"), object.getString("model"), object.getString("serialNumber"), object.getString("firmwareVersion"), requiredInteger(object, "channelCount"), requiredDecimal(object, "currentMinA"), requiredDecimal(object, "currentMaxA"), requiredDecimal(object, "voltageMinV"), requiredDecimal(object, "voltageMaxV"), requiredInteger(object, "sampleIntervalMs")); } private CollectorState collectorState(JSONObject object) { int count = requiredInteger(object, "channelCount"); List channels = new ArrayList<>(); JSONArray configured = object.getJSONArray("channels"); if (configured == null) channels.addAll(com.battery.device.simulator.DevelopmentCollectorSimulation.defaultChannels(count)); else configured.forEach(item -> channels.add(collectorChannelState(item))); // 重启只恢复配置,不把上次响应或已废止的历史作为新观测。 return new CollectorState(requiredInteger(object, "collectorId"), object.getString("deviceCode"), object.getString("ip"), object.getString("mac"), object.getString("name"), object.getString("manufacturer"), object.getString("model"), count, object.getBooleanValue("online"), com.battery.device.simulator.DevelopmentCollectorSimulation.DEFAULT_SAMPLE_INTERVAL_MS, channels, List.of()); } private CollectorChannelState collectorChannelState(Object value) { JSONObject object = jsonObject(value); List metrics = new ArrayList<>(); requiredArray(object, "metrics").forEach(item -> metrics.add(collectorMetricState(item))); return new CollectorChannelState(object.getString("channelCode"), metrics, object.getString("workStatus") == null ? DeviceWorkStatus.WORKING : DeviceWorkStatus.valueOf(object.getString("workStatus"))); } private CollectorMetricState collectorMetricState(Object value) { JSONObject object = jsonObject(value); BigDecimal measurement = object.getBigDecimal("value"); if (measurement == null) throw new IllegalStateException("Missing BTS simulator state field: value"); return new CollectorMetricState(object.getString("metricCode"), measurement, object.getString("unit")); } private ChannelState channelState(Object value) { JSONObject object = jsonObject(value); String workStatus = object.getString("workStatus"); if (workStatus == null) throw new IllegalStateException("Missing BTS simulator state field: workStatus"); JSONObject configuration = object.getJSONObject("configuration"); return new ChannelState(requiredInteger(object, "channelId"), object.getBooleanValue("online"), DeviceWorkStatus.valueOf(workStatus), channelConfiguration(configuration)); } /** Redis 通用反序列化会把泛型 steps 恢复为 JSONObject,不能让 record 构造器直接强转。 */ private ChannelConfiguration channelConfiguration(JSONObject object) { if (object == null) return null; JSONArray configuredSteps = requiredArray(object, "steps"); List steps = new ArrayList<>(); configuredSteps.forEach(item -> { JSONObject step = jsonObject(item); steps.add(new SimulatorPlanStep(step.getString("name"), step.getString("stepType"), step.getDoubleValue("durationSeconds"), step.getDoubleValue("voltageV"))); }); return new ChannelConfiguration(requiredDecimal(object, "currentA"), requiredDecimal(object, "voltageV"), requiredDecimal(object, "capacityAh"), requiredDecimal(object, "energyWh"), requiredInteger(object, "cycleNo"), requiredInteger(object, "stepNo"), requiredInteger(object, "plannedCycleCount"), object.getLongValue("totalTimeSeconds"), object.getLongValue("relativeTimeSeconds"), steps); } private ChannelTombstone channelTombstone(Object value) { JSONObject object = jsonObject(value); return new ChannelTombstone(requiredInteger(object, "deviceId"), object.getString("ip"), requiredInteger(object, "channelId"), requiredInteger(object, "remainingDiscoveries")); } private BigDecimal requiredDecimal(JSONObject object, String field) { BigDecimal value = object.getBigDecimal(field); if (value == null) throw new IllegalStateException("Missing BTS simulator state field: " + field); return value; } }