| | |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | import com.ruoyi.device.bts.BtsDevelopmentSimulator; |
| | | import com.ruoyi.device.simulator.BtsSimulatorStateStore.BtsHistoryRecord; |
| | | import com.ruoyi.device.simulator.BtsSimulatorStateStore.CollectorRealtimeMeasurement; |
| | | import com.ruoyi.device.simulator.DevelopmentCollectorSimulation.CollectorRealtimeSnapshot; |
| | | import com.ruoyi.system.domain.TTest; |
| | | import com.ruoyi.system.domain.TTestBattery; |
| | | import com.ruoyi.system.domain.TTestChannelBinding; |
| | |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void captureDevelopmentCollectorMeasurements() |
| | | { |
| | | List<Long> 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; |
| | | for (TTestBattery battery : test.getBatteries() == null ? List.<TTestBattery>of() : test.getBatteries()) |
| | | captureCollectorMeasurements(test, battery); |
| | | } |
| | | } |
| | | |
| | | private void captureTest(TTest test) |
| | | { |
| | | Date startedAt = test.getStartedAtUtc(); |
| | |
| | | if (startedAt != null && record.deviceTime().isBefore(startedAt.toInstant())) continue; |
| | | save(test, binding, record); |
| | | } |
| | | } |
| | | } |
| | | |
| | | private void captureCollectorMeasurements(TTest test, TTestBattery battery) |
| | | { |
| | | if (battery.getCollectorId() == null || battery.getCollectorChannels() == null |
| | | || battery.getCollectorChannels().isEmpty()) return; |
| | | final CollectorRealtimeSnapshot realtime; |
| | | try { realtime = simulator.collectorRealtime(battery.getCollectorId().intValue()); } |
| | | catch (IllegalArgumentException ignored) { return; } |
| | | if (!realtime.online()) return; |
| | | Date startedAt = test.getStartedAtUtc(); |
| | | for (CollectorRealtimeMeasurement measurement : realtime.measurements()) |
| | | { |
| | | if (startedAt != null && measurement.deviceTime().isBefore(startedAt.toInstant())) continue; |
| | | // 继续接收只接受恢复动作之后查询到的快照;暂停期间采集器自行产生的历史不回补。 |
| | | if (test.getUpdateTime() != null && measurement.serverReceivedAt().isBefore(test.getUpdateTime().toInstant())) continue; |
| | | boolean mapped = battery.getCollectorChannels().stream().anyMatch(mapping -> |
| | | java.util.Objects.equals(mapping.getChannelCode(), measurement.channelCode()) |
| | | && metricMatches(mapping.getMeasurementCode(), measurement.metricCode())); |
| | | if (!mapped) continue; |
| | | saveCollector(test, battery, measurement); |
| | | } |
| | | } |
| | | |
| | | private void saveCollector(TTest test, TTestBattery battery, CollectorRealtimeMeasurement measurement) |
| | | { |
| | | byte[] hash = hash(measurement); |
| | | String old = mapper.selectCollectorSampleContentHash(battery.getCollectorId(), measurement.channelCode(), |
| | | measurement.sequenceId(), measurement.metricCode()); |
| | | String expected = HexFormat.of().formatHex(hash); |
| | | if (old != null) |
| | | { |
| | | if (!old.equalsIgnoreCase(expected)) |
| | | mapper.insertSampleConflict(test.getTestId(), battery.getBinding().getChannelId(), |
| | | "采集器实时接收内容冲突 collector=" + battery.getCollectorId() + ", channel=" |
| | | + measurement.channelCode() + ", metric=" + measurement.metricCode() |
| | | + ", seqid=" + measurement.sequenceId()); |
| | | return; |
| | | } |
| | | Date receivedAt = new Date(); |
| | | int inserted = mapper.insertCollectorSample(test.getTestId(), battery.getBinding().getBindingId(), battery.getCollectorId(), |
| | | measurement.adapterCode(), |
| | | measurement.channelCode(), measurement.sequenceId(), Date.from(measurement.deviceTime()), |
| | | measurement.deviceTimeRaw(), receivedAt, measurement.metricCode(), |
| | | measurement.value(), measurement.unit(), hash); |
| | | // 唯一键负责并发幂等;内容冲突保留首次事实,不覆盖原始记录。 |
| | | if (inserted == 0) |
| | | { |
| | | String actual = mapper.selectCollectorSampleContentHash(battery.getCollectorId(), measurement.channelCode(), |
| | | measurement.sequenceId(), measurement.metricCode()); |
| | | if (actual != null && !actual.equalsIgnoreCase(expected)) |
| | | mapper.insertSampleConflict(test.getTestId(), battery.getBinding().getChannelId(), |
| | | "采集器实时并发内容冲突 collector=" + battery.getCollectorId() + ", channel=" |
| | | + measurement.channelCode() + ", metric=" + measurement.metricCode() |
| | | + ", seqid=" + measurement.sequenceId()); |
| | | } |
| | | } |
| | | |
| | |
| | | } |
| | | catch (Exception exception) { throw new IllegalStateException("无法计算采样内容摘要", exception); } |
| | | } |
| | | |
| | | private byte[] hash(CollectorRealtimeMeasurement measurement) |
| | | { |
| | | try |
| | | { |
| | | String value = measurement.adapterCode() + "|" + measurement.collectorCode() + "|" |
| | | + measurement.channelCode() + "|" + measurement.sequenceId() + "|" |
| | | + measurement.deviceTimeRaw() + "|" + measurement.metricCode() + "|" |
| | | + measurement.value() + "|" + measurement.unit(); |
| | | return MessageDigest.getInstance("SHA-256").digest(value.getBytes(StandardCharsets.UTF_8)); |
| | | } |
| | | catch (Exception exception) { throw new IllegalStateException("无法计算采集器采样内容摘要", exception); } |
| | | } |
| | | |
| | | private boolean metricMatches(String configured, String actual) |
| | | { |
| | | if (configured == null || actual == null) return false; |
| | | if (configured.equals(actual)) return true; |
| | | String normalized = configured.toUpperCase(java.util.Locale.ROOT); |
| | | return (normalized.contains("TEMPERATURE") && "TEMPERATURE".equals(actual.toUpperCase(java.util.Locale.ROOT))) |
| | | || (normalized.contains("POSITIVE_POTENTIAL") && "POSITIVE_POTENTIAL".equals(actual.toUpperCase(java.util.Locale.ROOT))) |
| | | || (normalized.contains("NEGATIVE_POTENTIAL") && "NEGATIVE_POTENTIAL".equals(actual.toUpperCase(java.util.Locale.ROOT))); |
| | | } |
| | | } |