package com.ruoyi.web.controller.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.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.server.ResponseStatusException; import com.ruoyi.common.annotation.Anonymous; import com.ruoyi.device.bts.BtsDevelopmentSimulator; import com.ruoyi.device.bts.BtsDevelopmentSimulator.StateSnapshot; import com.ruoyi.device.discovery.DeviceDiscoveryCandidateStore; import com.ruoyi.device.model.DeviceWorkStatus; import com.ruoyi.device.simulator.BtsSimulatorStateStore.CollectorHistoryFaults; import com.ruoyi.device.simulator.BtsSimulatorStateStore.CollectorMetricState; import com.ruoyi.device.simulator.DevelopmentCollectorSimulation.CollectorHistoryPage; import com.ruoyi.device.simulator.DevelopmentCollectorSimulation.CollectorRealtimeSnapshot; /** 本机临时 BTS 模拟器控制 API。 */ @Anonymous @RestController public class BtsDevelopmentSimulatorController { private final BtsDevelopmentSimulator simulator; private final DeviceDiscoveryCandidateStore candidateStore; public BtsDevelopmentSimulatorController(BtsDevelopmentSimulator simulator, DeviceDiscoveryCandidateStore candidateStore) { this.simulator = simulator; this.candidateStore = candidateStore; } /** 返回临时控制页;匿名放行后仍由本机拦截器限制来源。 */ @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()); return simulator.snapshot(); } /** 添加一台虚拟设备。 */ @PostMapping("/dev/bts-simulator/devices") public StateSnapshot createDevice(@RequestBody CreateDeviceCommand command) { try { simulator.addDevice(command.ip(), command.channelCount()); 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()); 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()); return simulator.snapshot(); } catch (IllegalArgumentException exception) { throw badRequest(exception); } } /** 配置采集器采样频率和可重复的历史下载故障场景。 */ @PutMapping("/dev/bts-simulator/collectors/{collectorId}/sampling") public StateSnapshot configureCollectorSampling(@PathVariable int collectorId, @RequestBody CollectorSamplingCommand command) { try { simulator.configureCollectorSampling(collectorId, command.sampleIntervalMs(), new CollectorHistoryFaults(command.missingSequences(), command.duplicateSequence(), command.conflictingSequence(), command.outOfOrder())); 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); } } /** 按偏移量分页下载采集器模拟历史,每页最多 1000 条。 */ @GetMapping("/dev/bts-simulator/collectors/{collectorId}/history") public CollectorHistoryPage collectorHistory(@PathVariable int collectorId, @RequestParam(defaultValue = "0") long offset, @RequestParam(defaultValue = "100") int limit) { try { return simulator.collectorHistory(collectorId, offset, limit); } 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); } } private ResponseStatusException badRequest(IllegalArgumentException exception) { return new ResponseStatusException(HttpStatus.BAD_REQUEST, exception.getMessage(), exception); } public record CreateDeviceCommand(String ip, int channelCount) { } public record SimulatorPowerCommand(Boolean poweredOn) { } public record DeviceStateCommand(String ip, int channelCount, boolean online) { } 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 CollectorSamplingCommand(int sampleIntervalMs, List missingSequences, Long duplicateSequence, Long conflictingSequence, boolean outOfOrder) { public CollectorSamplingCommand { missingSequences = missingSequences == null ? List.of() : List.copyOf(missingSequences); } } public record CollectorChannelCommand(List metrics) { } public record CollectorMetricCommand(String metricCode, BigDecimal value, String unit) { } }