package com.ruoyi.system.service.impl; import java.math.BigDecimal; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.util.Date; import java.util.HexFormat; import java.util.List; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.ruoyi.device.bts.BtsDevelopmentSimulator; import com.ruoyi.device.simulator.BtsSimulatorStateStore.BtsHistoryRecord; import com.ruoyi.system.domain.TTest; import com.ruoyi.system.domain.TTestBattery; import com.ruoyi.system.domain.TTestChannelBinding; import com.ruoyi.system.mapper.TTestMapper; import com.ruoyi.system.service.ITTestService; import com.ruoyi.system.service.ITestDataReceptionService; /** 接收时确定测试归属,并以现有采样表追加保存检测柜事实。 */ @Service public class TestDataReceptionServiceImpl implements ITestDataReceptionService { private final TTestMapper mapper; private final ITTestService tests; private final BtsDevelopmentSimulator simulator; public TestDataReceptionServiceImpl(TTestMapper mapper, ITTestService tests, BtsDevelopmentSimulator simulator) { this.mapper = mapper; this.tests = tests; this.simulator = simulator; } @Override @Transactional(rollbackFor = Exception.class) public void captureDevelopmentCabinetRecords() { List testIds = mapper.selectReceivingTestIds(); if (testIds == null) return; for (Long testId : testIds) { // 锁住测试行直到本轮采样写入完成,暂停/停止动作只能在本轮接收提交后生效。 TTest locked = mapper.selectTestByIdForUpdate(testId); if (locked == null || !"RECEIVING".equals(locked.getReceptionStatus())) continue; TTest test = tests.selectTestById(testId); if (test == null || !"RECEIVING".equals(test.getReceptionStatus())) continue; captureTest(test); } } private void captureTest(TTest test) { for (TTestBattery battery : test.getBatteries() == null ? List.of() : test.getBatteries()) { TTestChannelBinding binding = battery.getBinding(); if (binding == null || binding.getDevid() == null || binding.getChlid() == null) continue; List records; try { records = simulator.btsRecords(binding.getDevid(), binding.getChlid()); } catch (IllegalArgumentException ignored) { continue; } for (BtsHistoryRecord record : records) { save(test, binding, record); } } } private void save(TTest test, TTestChannelBinding binding, BtsHistoryRecord record) { // 模拟器历史 testid 按通道固定;接收事实必须再带测试作用域,避免顺序测试共用全局幂等键。 String btsTestId = "SIM-" + binding.getDevid() + "-" + binding.getChlid() + "-T" + test.getTestId(); byte[] hash = hash(record); String old = mapper.selectBtsSampleContentHash(binding.getChannelId(), btsTestId, record.sequenceId()); if (old != null) { if (!old.equalsIgnoreCase(HexFormat.of().formatHex(hash))) mapper.insertSampleConflict(test.getTestId(), binding.getChannelId(), "实时接收内容冲突 testid=" + btsTestId + ", seqid=" + record.sequenceId()); return; } int inserted = mapper.insertBtsSample(test.getTestId(), binding.getBindingId(), binding.getChannelId(), btsTestId, record.sequenceId(), Date.from(record.deviceTime()), record.deviceTime().toString(), new Date(), record.cycleNo(), record.stepNo(), record.stepType(), record.stepName(), null, BigDecimal.valueOf(record.voltageV()), BigDecimal.valueOf(record.currentA()), BigDecimal.valueOf(record.capacityAh()), BigDecimal.valueOf(record.energyWh()), hash); if (inserted == 0) { String actual = mapper.selectBtsSampleContentHash(binding.getChannelId(), btsTestId, record.sequenceId()); if (actual != null && !actual.equalsIgnoreCase(HexFormat.of().formatHex(hash))) mapper.insertSampleConflict(test.getTestId(), binding.getChannelId(), "实时接收并发内容冲突 testid=" + btsTestId + ", seqid=" + record.sequenceId()); } } private byte[] hash(BtsHistoryRecord record) { try { String value = record.sequenceId() + "|" + record.deviceTime() + "|" + record.currentA() + "|" + record.voltageV() + "|" + record.capacityAh() + "|" + record.energyWh() + "|" + record.cycleNo() + "|" + record.stepNo() + "|" + record.stepType(); return MessageDigest.getInstance("SHA-256").digest(value.getBytes(StandardCharsets.UTF_8)); } catch (Exception exception) { throw new IllegalStateException("无法计算采样内容摘要", exception); } } }