package com.battery.business.web.device; import java.math.BigDecimal; import java.util.List; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.server.ResponseStatusException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Profile; import com.ruoyi.common.annotation.Anonymous; import com.battery.device.simulator.BtsDevelopmentSimulator; import com.battery.device.simulator.BtsDevelopmentSimulator.StateSnapshot; import com.battery.device.api.discovery.DeviceDiscoveryCandidateStore; import com.battery.device.api.model.DeviceWorkStatus; import com.battery.device.simulator.BtsSimulatorStateStore.CollectorMetricState; import com.battery.device.simulator.DevelopmentCollectorSimulation.CollectorRealtimeSnapshot; /** 本机临时 BTS 模拟器控制 API。 */ @Anonymous @RestController @Profile("dev-simulator") public class BtsDevelopmentSimulatorController { private final BtsDevelopmentSimulator simulator; private final DeviceDiscoveryCandidateStore candidateStore; private final com.battery.business.runtime.DevelopmentCollectorStatusSynchronizer statusSynchronizer; @Autowired public BtsDevelopmentSimulatorController(BtsDevelopmentSimulator simulator, DeviceDiscoveryCandidateStore candidateStore, com.battery.business.runtime.DevelopmentCollectorStatusSynchronizer statusSynchronizer) { this.simulator = simulator; this.candidateStore = candidateStore; this.statusSynchronizer = statusSynchronizer; } /** 保留独立模拟器测试和工具调用的构造入口,不触发数据库同步。 */ public BtsDevelopmentSimulatorController(BtsDevelopmentSimulator simulator, DeviceDiscoveryCandidateStore candidateStore) { this(simulator, candidateStore, null); } /** 返回临时控制页;匿名放行后仍由本机拦截器限制来源。 */ @GetMapping(value = "/dev/bts-simulator.html", produces = MediaType.TEXT_HTML_VALUE) public Resource page() { return new ClassPathResource("static/dev/bts-simulator.html"); } /** 返回模拟器、虚拟设备、通道和最近请求的当前快照。 */ @GetMapping("/dev/bts-simulator/state") public StateSnapshot state() { return simulator.snapshot(); } /** 人工清空模拟配置并恢复默认检测柜和采集器。 */ @PostMapping("/dev/bts-simulator/reset") public StateSnapshot reset() { simulator.reset(); candidateStore.removeAdapter(BtsDevelopmentSimulator.ADAPTER_CODE); return simulator.snapshot(); } /** 模拟现场人工统一开关全部检测柜和采集器,不发送任何设备控制命令。 */ @PostMapping("/dev/bts-simulator/power") public StateSnapshot setPower(@RequestBody SimulatorPowerCommand command) { if (command == null || command.poweredOn() == null) throw badRequest(new IllegalArgumentException("poweredOn must be true or false")); simulator.setAllDevicesPoweredOn(command.poweredOn()); synchronizeOnlineStatusIfConfigured(); return simulator.snapshot(); } /** 添加一台虚拟设备。 */ @PostMapping("/dev/bts-simulator/devices") public StateSnapshot createDevice(@RequestBody CreateDeviceCommand command) { try { if (command.model() == null || command.model().isBlank()) simulator.addDevice(command.ip(), command.channelCount()); else simulator.addNewareDevice(command.ip(), command.model()); return simulator.snapshot(); } catch (IllegalArgumentException exception) { throw badRequest(exception); } } /** 修改指定虚拟设备的地址、通道数和在线状态。 */ @PutMapping("/dev/bts-simulator/devices/{deviceId}") public StateSnapshot updateDevice(@PathVariable int deviceId, @RequestBody DeviceStateCommand command) { try { simulator.updateDevice(deviceId, command.ip(), command.channelCount(), command.online()); if (command.model() != null && !command.model().isBlank()) simulator.configureNewareProfile(deviceId, command.model()); return simulator.snapshot(); } catch (IllegalArgumentException exception) { throw badRequest(exception); } } /** 删除指定虚拟设备。 */ @DeleteMapping("/dev/bts-simulator/devices/{deviceId}") public StateSnapshot deleteDevice(@PathVariable int deviceId) { try { simulator.deleteDevice(deviceId); return simulator.snapshot(); } catch (IllegalArgumentException exception) { throw badRequest(exception); } } /** 添加一台与检测柜分开配置的虚拟采集器。 */ @PostMapping("/dev/bts-simulator/collectors") public StateSnapshot createCollector(@RequestBody CreateCollectorCommand command) { try { simulator.addCollector(command.ip(), command.channelCount()); return simulator.snapshot(); } catch (IllegalArgumentException exception) { throw badRequest(exception); } } /** 修改虚拟采集器的地址、通道数和在线状态。 */ @PutMapping("/dev/bts-simulator/collectors/{collectorId}") public StateSnapshot updateCollector(@PathVariable int collectorId, @RequestBody CollectorStateCommand command) { try { simulator.updateCollector(collectorId, command.ip(), command.channelCount(), command.online()); synchronizeOnlineStatusIfConfigured(); return simulator.snapshot(); } catch (IllegalArgumentException exception) { throw badRequest(exception); } } /** 配置稳定 S 通道的固定和动态测量项。 */ @PutMapping("/dev/bts-simulator/collectors/{collectorId}/channels/{channelCode}") public StateSnapshot configureCollectorChannel(@PathVariable int collectorId, @PathVariable String channelCode, @RequestBody CollectorChannelCommand command) { try { List metrics = command.metrics().stream() .map(metric -> new CollectorMetricState(metric.metricCode(), metric.value(), metric.unit())) .toList(); simulator.configureCollectorChannel(collectorId, channelCode, metrics); return simulator.snapshot(); } catch (IllegalArgumentException | NullPointerException exception) { throw badRequest(new IllegalArgumentException("Invalid collector channel metrics", exception)); } } /** 请求采集器当前实时快照;不可访问或离线时返回空测量。 */ @GetMapping("/dev/bts-simulator/collectors/{collectorId}/realtime") public CollectorRealtimeSnapshot collectorRealtime(@PathVariable int collectorId) { try { return simulator.collectorRealtime(collectorId); } catch (IllegalArgumentException exception) { throw badRequest(exception); } } /** 删除指定虚拟采集器。 */ @DeleteMapping("/dev/bts-simulator/collectors/{collectorId}") public StateSnapshot deleteCollector(@PathVariable int collectorId) { try { simulator.deleteCollector(collectorId); return simulator.snapshot(); } catch (IllegalArgumentException exception) { throw badRequest(exception); } } /** 修改指定虚拟设备通道的在线和工作状态。 */ @PutMapping("/dev/bts-simulator/devices/{deviceId}/channels/{channelId}") public StateSnapshot updateChannel(@PathVariable int deviceId, @PathVariable int channelId, @RequestBody ChannelStateCommand command) { try { simulator.updateChannel(deviceId, channelId, command.online(), command.workStatus()); return simulator.snapshot(); } catch (IllegalArgumentException exception) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, exception.getMessage(), exception); } } /** 保存单通道的当前观测参数和工步方案,不改变设备在线或工作状态。 */ @PutMapping("/dev/bts-simulator/devices/{deviceId}/channels/{channelId}/configuration") public StateSnapshot configureChannel(@PathVariable int deviceId, @PathVariable int channelId, @RequestBody com.battery.device.simulator.BtsSimulatorStateStore.ChannelConfiguration configuration) { try { simulator.configureChannel(deviceId, channelId, configuration); return simulator.snapshot(); } catch (IllegalArgumentException exception) { throw badRequest(exception); } } /** 修改软件可访问性,设备配置继续保留。 */ @PutMapping("/dev/bts-simulator/software") public StateSnapshot software(@RequestBody SoftwareCommand command) { if (command == null || command.accessible() == null) throw badRequest(new IllegalArgumentException("accessible must be true or false")); simulator.setSoftwareAccessible(command.accessible()); synchronizeOnlineStatusIfConfigured(); return simulator.snapshot(); } /** 修改采集器通道当前工作状态。 */ @PutMapping("/dev/bts-simulator/collectors/{collectorId}/channels/{channelCode}/status") public StateSnapshot collectorChannelStatus(@PathVariable int collectorId, @PathVariable String channelCode, @RequestBody CollectorChannelStatusCommand command) { try { simulator.updateCollectorChannelStatus(collectorId, channelCode, command.workStatus()); return simulator.snapshot(); } catch (IllegalArgumentException exception) { throw badRequest(exception); } } private ResponseStatusException badRequest(IllegalArgumentException exception) { return new ResponseStatusException(HttpStatus.BAD_REQUEST, exception.getMessage(), exception); } private void synchronizeOnlineStatusIfConfigured() { if (statusSynchronizer != null) statusSynchronizer.synchronizeOnlineStatus(); } public record CreateDeviceCommand(String ip, int channelCount, String model) { public CreateDeviceCommand(String ip, int channelCount) { this(ip, channelCount, null); } } public record SimulatorPowerCommand(Boolean poweredOn) { } public record DeviceStateCommand(String ip, int channelCount, boolean online, String model) { public DeviceStateCommand(String ip, int channelCount, boolean online) { this(ip, channelCount, online, null); } } public record ChannelStateCommand(boolean online, DeviceWorkStatus workStatus) { } public record CreateCollectorCommand(String ip, int channelCount) { } public record CollectorStateCommand(String ip, int channelCount, boolean online) { } public record CollectorChannelCommand(List metrics) { } public record CollectorMetricCommand(String metricCode, BigDecimal value, String unit) { } public record SoftwareCommand(Boolean accessible) { } public record CollectorChannelStatusCommand(DeviceWorkStatus workStatus) { } }