完成测试生命周期与接收边界
收紧测试和接收状态转换,增加接收服务器时间边界并阻止暂停历史回补。
| | |
| | | { |
| | | return item.stepName() == null || item.stepName().isBlank() |
| | | ? new BtsHistoryRecord(item.sequenceId(), item.deviceTime(), item.currentA(), item.voltageV(), |
| | | item.capacityAh(), item.energyWh()) : item; |
| | | item.capacityAh(), item.energyWh(), item.cycleNo(), item.stepNo(), item.stepName(), item.stepType(), |
| | | item.serverReceivedAt()) : item; |
| | | } |
| | | |
| | | private String btsKey(DeviceState device, int channelId) |
| | |
| | | budgetMillis = Math.max(0L, budgetMillis - deltaMillis); |
| | | simulated = simulated.plusSeconds(deltaSeconds); |
| | | BtsHistoryRecord record = new BtsHistoryRecord(fixture.sequenceId(), simulated, fixture.currentA(), fixture.voltageV(), |
| | | fixture.capacityAh(), fixture.energyWh(), fixture.cycleNo(), fixture.stepNo(), fixture.stepName(), fixture.stepType()); |
| | | fixture.capacityAh(), fixture.energyWh(), fixture.cycleNo(), fixture.stepNo(), fixture.stepName(), fixture.stepType(), now); |
| | | history.add(record); |
| | | appended.add(record); |
| | | lastOffset = fixture.offsetSeconds(); |
| | |
| | | } |
| | | |
| | | record BtsHistoryRecord(long sequenceId, Instant deviceTime, double currentA, double voltageV, |
| | | double capacityAh, double energyWh, int cycleNo, int stepNo, String stepName, String stepType) |
| | | double capacityAh, double energyWh, int cycleNo, int stepNo, String stepName, String stepType, |
| | | Instant serverReceivedAt) |
| | | { |
| | | /** 兼容现有调用方;新生成记录应传入服务器接收时间。 */ |
| | | public BtsHistoryRecord(long sequenceId, Instant deviceTime, double currentA, double voltageV, |
| | | double capacityAh, double energyWh, int cycleNo, int stepNo, String stepName, String stepType) |
| | | { |
| | | this(sequenceId, deviceTime, currentA, voltageV, capacityAh, energyWh, cycleNo, stepNo, |
| | | stepName, stepType, Instant.now()); |
| | | } |
| | | |
| | | /** 兼容 Ticket13 之前仅包含核心测量值的历史记录。 */ |
| | | public BtsHistoryRecord(long sequenceId, Instant deviceTime, double currentA, double voltageV, |
| | | double capacityAh, double energyWh) |
| | |
| | | reception_status VARCHAR(32) NOT NULL DEFAULT 'NOT_STARTED', |
| | | monitor_timeout_minutes INT NOT NULL DEFAULT 30, |
| | | started_at_utc DATETIME(3) NULL, |
| | | reception_boundary_at_utc DATETIME(3) NULL, |
| | | ended_at_utc DATETIME(3) NULL, |
| | | logic_step_mappings_json JSON NULL, |
| | | console_detail_json JSON NULL, |
| | |
| | | } |
| | | |
| | | @Test |
| | | void deleteShouldRejectMismatchedTestAndReceptionStates() |
| | | { |
| | | TTest current = validTest(); |
| | | current.setTestId(10L); |
| | | current.setTestStatus("RUNNING"); |
| | | current.setReceptionStatus("NOT_STARTED"); |
| | | when(testMapper.selectTestByIdForUpdate(10L)).thenReturn(current); |
| | | |
| | | ServiceException exception = assertThrows(ServiceException.class, |
| | | () -> testService.deleteTestByIds(new Long[] { 10L }, 7L, "operator")); |
| | | |
| | | assertEquals("开始接收后不能删除测试", exception.getMessage()); |
| | | verify(testMapper, never()).deleteTestById(anyLong(), any(String.class)); |
| | | } |
| | | |
| | | @Test |
| | | void startReceptionShouldPersistReceptionWithoutLegacyIntervals() |
| | | { |
| | | TTest current = validTest(); |
| | | current.setTestId(10L); |
| | | current.setTestStatus("WAITING"); |
| | | current.setReceptionStatus("NOT_STARTED"); |
| | | current.getBatteries().get(0).getBinding().setBindingId(30L); |
| | | when(testMapper.selectTestByIdForUpdate(10L)).thenReturn(current); |
| | |
| | | { |
| | | TTest current = validTest(); |
| | | current.setTestId(10L); |
| | | current.setTestStatus("WAITING"); |
| | | current.setReceptionStatus("NOT_STARTED"); |
| | | current.getBatteries().get(0).getBinding().setBindingId(30L); |
| | | when(testMapper.selectTestByIdForUpdate(10L)).thenReturn(current); |
| | |
| | | { |
| | | TTest current = validTest(); |
| | | current.setTestId(10L); |
| | | current.setTestStatus("WAITING"); |
| | | current.setReceptionStatus("NOT_STARTED"); |
| | | current.getBatteries().get(0).getBinding().setBindingId(30L); |
| | | when(testMapper.selectTestByIdForUpdate(10L)).thenReturn(current); |
| | |
| | | { |
| | | TTest current = validTest(); |
| | | current.setTestId(10L); |
| | | current.setTestStatus("WAITING"); |
| | | current.setReceptionStatus("NOT_STARTED"); |
| | | current.getBatteries().get(0).getBinding().setBindingId(30L); |
| | | when(testMapper.selectTestByIdForUpdate(10L)).thenReturn(current); |
| | |
| | | { |
| | | TTest current = validTest(); |
| | | current.setTestId(10L); |
| | | current.setTestStatus("WAITING"); |
| | | current.setReceptionStatus("NOT_STARTED"); |
| | | current.getBatteries().get(0).getBinding().setBindingId(30L); |
| | | when(testMapper.selectTestByIdForUpdate(10L)).thenReturn(current); |
| | |
| | | { |
| | | TTest current = validTest(); |
| | | current.setTestId(10L); |
| | | current.setTestStatus("WAITING"); |
| | | current.setReceptionStatus("NOT_STARTED"); |
| | | current.getBatteries().get(0).getBinding().setBindingId(30L); |
| | | current.getBatteries().get(0).setCollectorChannels(Collections.emptyList()); |
| | |
| | | } |
| | | |
| | | @Test |
| | | void startReceptionShouldRejectMismatchedTestAndReceptionStates() |
| | | { |
| | | TTest current = validTest(); |
| | | current.setTestId(10L); |
| | | current.setTestStatus("COMPLETED"); |
| | | current.setReceptionStatus("NOT_STARTED"); |
| | | when(testMapper.selectTestByIdForUpdate(10L)).thenReturn(current); |
| | | |
| | | ServiceException exception = assertThrows(ServiceException.class, |
| | | () -> testService.startReception(10L, 7L, "operator")); |
| | | |
| | | assertEquals("当前测试不是未开始接收状态", exception.getMessage()); |
| | | verify(testMapper, never()).updateReceptionStarted(any(), any(), anyBoolean(), any()); |
| | | } |
| | | |
| | | @Test |
| | | void pauseReceptionShouldRejectMismatchedTestAndReceptionStates() |
| | | { |
| | | TTest current = validTest(); |
| | | current.setTestId(10L); |
| | | current.setTestStatus("PAUSED"); |
| | | current.setReceptionStatus("RECEIVING"); |
| | | when(testMapper.selectTestByIdForUpdate(10L)).thenReturn(current); |
| | | |
| | | ServiceException exception = assertThrows(ServiceException.class, |
| | | () -> testService.pauseReception(10L, 7L, "operator")); |
| | | |
| | | assertEquals("当前测试不是接收中状态", exception.getMessage()); |
| | | verify(testMapper, never()).updateReceptionPaused(any(), any(), any()); |
| | | } |
| | | |
| | | @Test |
| | | void pauseReceptionShouldStopPollingAndAppendAudit() |
| | | { |
| | | TTest current = validTest(); |
| | | current.setTestId(10L); |
| | | current.setTestStatus("RUNNING"); |
| | | current.setReceptionStatus("RECEIVING"); |
| | | when(testMapper.selectTestByIdForUpdate(10L)).thenReturn(current); |
| | | when(testMapper.updateReceptionPaused(eq(10L), eq("operator"), any())).thenReturn(1); |
| | |
| | | { |
| | | TTest current = validTest(); |
| | | current.setTestId(10L); |
| | | current.setTestStatus("PAUSED"); |
| | | current.setReceptionStatus("PAUSED"); |
| | | List<TTestBattery> configuredBatteries = current.getBatteries(); |
| | | current.setBatteries(null); |
| | |
| | | { |
| | | TTest current = validTest(); |
| | | current.setTestId(10L); |
| | | current.setTestStatus("PAUSED"); |
| | | current.setReceptionStatus("PAUSED"); |
| | | when(testMapper.selectTestByIdForUpdate(10L)).thenReturn(current); |
| | | when(testMapper.updateReceptionStopped(eq(10L), eq("operator"), any())).thenReturn(1); |
| | |
| | | { |
| | | TTest current = validTest(); |
| | | current.setTestId(10L); |
| | | current.setTestStatus("RUNNING"); |
| | | current.setReceptionStatus("RECEIVING"); |
| | | when(testMapper.selectTestByIdForUpdate(10L)).thenReturn(current); |
| | | when(testMapper.updateReceptionStopped(eq(10L), eq("operator"), any())).thenReturn(1); |
| | |
| | | test.setPrincipalId(9L); |
| | | test.setTestTypeId(3L); |
| | | test.setTestPurpose("验证低温循环性能"); |
| | | test.setTestStatus("WAITING"); |
| | | test.setReceptionStatus("NOT_STARTED"); |
| | | test.setMonitorTimeoutMinutes(30); |
| | | test.setBatteries(Collections.singletonList(validBattery("BAT-001", 1L, 100L))); |
| | | test.setPlanSteps(Collections.singletonList(step)); |
| | |
| | | } |
| | | |
| | | @Test |
| | | void ordinaryEditsDoNotMoveReceptionBoundary() |
| | | { |
| | | TTestMapper mapper = mock(TTestMapper.class); |
| | | ITTestService tests = mock(ITTestService.class); |
| | | BtsDevelopmentSimulator simulator = mock(BtsDevelopmentSimulator.class); |
| | | TTest test = test(Date.from(Instant.parse("2026-09-02T04:00:00Z"))); |
| | | test.setUpdateTime(Date.from(Instant.parse("2026-09-02T04:00:10Z"))); |
| | | when(mapper.selectReceivingTestIds()).thenReturn(List.of(1L)); |
| | | when(mapper.selectTestByIdForUpdate(1L)).thenReturn(test); |
| | | when(tests.selectTestById(1L)).thenReturn(test); |
| | | when(simulator.btsRecords(1, 1)).thenReturn(List.of( |
| | | record(1L, "2026-09-02T04:00:05Z"), record(2L, "2026-09-02T04:00:11Z"))); |
| | | when(mapper.selectBtsSampleContentHash(any(), anyString(), any())).thenReturn(null); |
| | | when(mapper.insertBtsSample(any(), any(), any(), anyString(), any(), any(Date.class), anyString(), |
| | | any(Date.class), anyInt(), anyInt(), anyString(), anyString(), isNull(), any(BigDecimal.class), |
| | | any(BigDecimal.class), any(BigDecimal.class), any(BigDecimal.class), any(byte[].class))).thenReturn(1); |
| | | |
| | | new TestDataReceptionServiceImpl(mapper, tests, simulator).captureDevelopmentCabinetRecords(); |
| | | |
| | | verify(mapper, times(2)).insertBtsSample(eq(1L), eq(2L), eq(3L), anyString(), any(), any(Date.class), |
| | | anyString(), any(Date.class), anyInt(), anyInt(), anyString(), anyString(), isNull(), |
| | | any(BigDecimal.class), any(BigDecimal.class), any(BigDecimal.class), any(BigDecimal.class), |
| | | any(byte[].class)); |
| | | } |
| | | |
| | | @Test |
| | | void capturesOnlyMappedCollectorRealtimeMeasurementsAfterReceptionStart() |
| | | { |
| | | TTestMapper mapper = mock(TTestMapper.class); |
| | | ITTestService tests = mock(ITTestService.class); |
| | | BtsDevelopmentSimulator simulator = mock(BtsDevelopmentSimulator.class); |
| | | TTest test = test(Date.from(Instant.parse("2026-09-02T04:00:00Z"))); |
| | | test.setReceptionBoundaryAtUtc(Date.from(Instant.parse("2026-09-02T04:00:01.500Z"))); |
| | | test.getBatteries().get(0).setCollectorId(9L); |
| | | TTestCollectorChannel mapping = new TTestCollectorChannel(); |
| | | mapping.setMeasurementCode("temperature"); mapping.setChannelCode("S-01"); |
| | |
| | | CollectorRealtimeMeasurement ignored = new CollectorRealtimeMeasurement("dev-sim", "collector-9", "S-02", |
| | | 3L, matched.deviceTime(), matched.deviceTimeRaw(), matched.serverReceivedAt(), "temperature", |
| | | new BigDecimal("25.2"), "℃"); |
| | | CollectorRealtimeMeasurement beforeBoundary = new CollectorRealtimeMeasurement("dev-sim", "collector-9", "S-01", |
| | | 2L, Instant.parse("2026-09-02T04:00:00Z"), "2026-09-02 12:00:00.000", |
| | | Instant.parse("2026-09-02T04:00:01Z"), "temperature", new BigDecimal("24.9"), "℃"); |
| | | when(simulator.collectorRealtime(9)).thenReturn(new CollectorRealtimeSnapshot("collector-9", true, 1000, |
| | | List.of(matched, ignored))); |
| | | List.of(matched, ignored, beforeBoundary))); |
| | | when(mapper.selectCollectorSampleContentHash(any(), anyString(), any(), anyString())).thenReturn(null); |
| | | when(mapper.insertCollectorSample(any(), any(), any(), anyString(), anyString(), any(), any(Date.class), anyString(), |
| | | any(Date.class), anyString(), any(BigDecimal.class), anyString(), any(byte[].class))).thenReturn(1); |
| | |
| | | battery.setBinding(binding); |
| | | TTest test = new TTest(); |
| | | test.setTestId(1L); |
| | | test.setTestStatus("RUNNING"); |
| | | test.setReceptionStatus("RECEIVING"); |
| | | test.setStartedAtUtc(startedAt); |
| | | test.setBatteries(List.of(battery)); |
| | |
| | | |
| | | private static BtsHistoryRecord record(long sequenceId, String deviceTime) |
| | | { |
| | | return new BtsHistoryRecord(sequenceId, Instant.parse(deviceTime), 1d, 2d, 3d, 4d, |
| | | 1, 1, "恒流充电", "cc"); |
| | | Instant parsed = Instant.parse(deviceTime); |
| | | return new BtsHistoryRecord(sequenceId, parsed, 1d, 2d, 3d, 4d, |
| | | 1, 1, "恒流充电", "cc", parsed); |
| | | } |
| | | } |
| | |
| | | private String receptionStatus; |
| | | private Integer monitorTimeoutMinutes; |
| | | private Date startedAtUtc; |
| | | /** 最近一次开始或继续接收动作建立的服务器时间边界。 */ |
| | | private Date receptionBoundaryAtUtc; |
| | | private Date endedAtUtc; |
| | | /** 是否从已运行设备的后续观测边界接入。 */ |
| | | private Boolean runtimeAttach; |
| | |
| | | public void setMonitorTimeoutMinutes(Integer value) { monitorTimeoutMinutes = value; } |
| | | public Date getStartedAtUtc() { return startedAtUtc; } |
| | | public void setStartedAtUtc(Date value) { startedAtUtc = value; } |
| | | public Date getReceptionBoundaryAtUtc() { return receptionBoundaryAtUtc; } |
| | | public void setReceptionBoundaryAtUtc(Date value) { receptionBoundaryAtUtc = value; } |
| | | public Date getEndedAtUtc() { return endedAtUtc; } |
| | | public void setEndedAtUtc(Date value) { endedAtUtc = value; } |
| | | public Boolean getRuntimeAttach() { return runtimeAttach; } |
| | |
| | | { |
| | | private static final String WAITING = "WAITING"; |
| | | private static final String NOT_STARTED = "NOT_STARTED"; |
| | | private static final Set<String> TEST_STATUSES = Set.of( |
| | | WAITING, "RUNNING", "PAUSED", "RUNNING_ABNORMAL", "COMPLETION_PENDING", "COMPLETED", "TERMINATED"); |
| | | /** 新流程只允许四种测试状态;历史状态仅在旧数据展示层做兼容映射。 */ |
| | | private static final Set<String> TEST_STATUSES = Set.of(WAITING, "RUNNING", "PAUSED", "COMPLETED"); |
| | | private static final Set<String> RECEPTION_STATUSES = Set.of(NOT_STARTED, "RECEIVING", "PAUSED", "ENDED"); |
| | | private static final Set<String> THRESHOLD_UNITS = Set.of("A", "V", "Ah", "Wh", "℃"); |
| | | private static final Set<String> REQUIRED_COLLECTOR_MEASUREMENTS = Set.of( |
| | |
| | | TTest current = testMapper.selectTestByIdForUpdate(test.getTestId()); |
| | | if (current == null) throw new ServiceException("测试不存在"); |
| | | loadConfiguration(current); |
| | | if (!NOT_STARTED.equals(current.getReceptionStatus())) |
| | | if (!stateMatches(current, WAITING, NOT_STARTED)) |
| | | throw new ServiceException("开始接收后不能修改测试配置"); |
| | | |
| | | test.setCreatorId(current.getCreatorId()); |
| | |
| | | if (operatorId == null) throw new ServiceException("操作人不能为空"); |
| | | TTest current = testMapper.selectTestByIdForUpdate(testId); |
| | | if (current == null) throw new ServiceException("测试不存在"); |
| | | if (!NOT_STARTED.equals(current.getReceptionStatus())) |
| | | if (!stateMatches(current, WAITING, NOT_STARTED)) |
| | | throw new ServiceException("当前测试不是未开始接收状态"); |
| | | loadConfiguration(current); |
| | | if (current.getBatteries().isEmpty()) throw new ServiceException("测试未配置电池号"); |
| | |
| | | if (channel == null || !isEnabled(channel.getStatus())) |
| | | throw new ServiceException("检测柜通道 " + binding.getChannelId() + " 不存在或未启用"); |
| | | // 只有设备事实为运行中时才标记“运行中接入”;设备暂停属于独立设备状态。 |
| | | runtimeAttach |= "WORKING".equalsIgnoreCase(channel.getWorkStatus()); |
| | | runtimeAttach |= "WORKING".equalsIgnoreCase(channel.getWorkStatus()) |
| | | || "RUNNING".equalsIgnoreCase(channel.getWorkStatus()); |
| | | if (battery.getCollectorId() == null || testMapper.countCollectorEnabled(battery.getCollectorId()) == 0) |
| | | throw new ServiceException("电池号 " + battery.getBatteryNo() + " 的采集器不存在或未启用"); |
| | | Integer collectorChannelCount = testMapper.selectCollectorChannelCount(battery.getCollectorId()); |
| | |
| | | if (operatorId == null) throw new ServiceException("操作人不能为空"); |
| | | TTest current = testMapper.selectTestByIdForUpdate(testId); |
| | | if (current == null) throw new ServiceException("测试不存在"); |
| | | if (!"RECEIVING".equals(current.getReceptionStatus())) |
| | | if (!stateMatches(current, "RUNNING", "RECEIVING")) |
| | | throw new ServiceException("当前测试不是接收中状态"); |
| | | |
| | | Date actionAt = new Date(); |
| | |
| | | if (operatorId == null) throw new ServiceException("操作人不能为空"); |
| | | TTest current = testMapper.selectTestByIdForUpdate(testId); |
| | | if (current == null) throw new ServiceException("测试不存在"); |
| | | if (!"PAUSED".equals(current.getReceptionStatus())) |
| | | if (!stateMatches(current, "PAUSED", "PAUSED")) |
| | | throw new ServiceException("当前测试不是暂停接收状态"); |
| | | // 加锁查询只返回测试主表,继续接收前重新加载绑定配置以校验来源仍可用。 |
| | | loadConfiguration(current); |
| | |
| | | if (operatorId == null) throw new ServiceException("操作人不能为空"); |
| | | TTest current = testMapper.selectTestByIdForUpdate(testId); |
| | | if (current == null) throw new ServiceException("测试不存在"); |
| | | if (NOT_STARTED.equals(current.getReceptionStatus()) || "ENDED".equals(current.getReceptionStatus())) |
| | | if (!(stateMatches(current, "RUNNING", "RECEIVING") |
| | | || stateMatches(current, "PAUSED", "PAUSED"))) |
| | | throw new ServiceException("当前测试不能停止"); |
| | | Date actionAt = new Date(); |
| | | if (testMapper.updateReceptionStopped(testId, updateBy, actionAt) != 1) |
| | |
| | | return status == null || "0".equals(status); |
| | | } |
| | | |
| | | /** 生命周期动作必须同时匹配测试状态和接收状态,避免并发请求后两个状态字段互相矛盾。 */ |
| | | private boolean stateMatches(TTest test, String expectedTestStatus, String expectedReceptionStatus) |
| | | { |
| | | return expectedReceptionStatus.equals(test.getReceptionStatus()) |
| | | && expectedTestStatus.equals(test.getTestStatus()); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public int deleteTestByIds(Long[] testIds, Long operatorId, String updateBy) |
| | |
| | | { |
| | | TTest current = testMapper.selectTestByIdForUpdate(testId); |
| | | if (current == null) throw new ServiceException("测试不存在"); |
| | | if (!NOT_STARTED.equals(current.getReceptionStatus())) throw new ServiceException("开始接收后不能删除测试"); |
| | | if (!stateMatches(current, WAITING, NOT_STARTED)) throw new ServiceException("开始接收后不能删除测试"); |
| | | loadConfiguration(current); |
| | | // 测试整体删除是无痕例外:先清理全部测试绑定和阈值,再删除主记录,不写审计。 |
| | | testMapper.deleteTestCollectorChannelsByTestId(testId); |
| | |
| | | for (Long testId : testIds) |
| | | { |
| | | TTest locked = mapper.selectTestByIdForUpdate(testId); |
| | | if (locked == null || !"RECEIVING".equals(locked.getReceptionStatus())) continue; |
| | | if (locked == null || !"RUNNING".equals(locked.getTestStatus()) |
| | | || !"RECEIVING".equals(locked.getReceptionStatus())) continue; |
| | | TTest test = tests.selectTestById(testId); |
| | | if (test == null || !"RECEIVING".equals(test.getReceptionStatus())) continue; |
| | | if (test == null || !"RUNNING".equals(test.getTestStatus()) |
| | | || !"RECEIVING".equals(test.getReceptionStatus())) continue; |
| | | // 完整快照与原始事实在同一接收事务内写入;原始事实按发现频率限流,避免 100ms 开发调度重复扫描历史。 |
| | | Instant lastRaw = lastRawCaptureAt.get(testId); |
| | | if (lastRaw == null || !now.toInstant().isBefore(lastRaw.plusSeconds(5))) |
| | |
| | | try { collector = simulator.collectorRealtime(battery.getCollectorId().intValue()); } catch (IllegalArgumentException ignored) { return; } |
| | | if (cabinet == null || !collector.online()) return; |
| | | if (test.getStartedAtUtc() != null && cabinet.deviceTime().isBefore(test.getStartedAtUtc().toInstant())) return; |
| | | if (test.getUpdateTime() != null && cabinet.deviceTime().isBefore(test.getUpdateTime().toInstant())) return; |
| | | if (test.getReceptionBoundaryAtUtc() != null && cabinet.serverReceivedAt() != null |
| | | && cabinet.serverReceivedAt().isBefore(test.getReceptionBoundaryAtUtc().toInstant())) return; |
| | | Map<String, CollectorRealtimeMeasurement> metrics = new LinkedHashMap<>(); |
| | | for (CollectorRealtimeMeasurement item : collector.measurements()) |
| | | if (battery.getCollectorChannels() != null && battery.getCollectorChannels().stream().anyMatch(mapping -> |
| | |
| | | if (test.getStartedAtUtc() != null && (temp.deviceTime().isBefore(test.getStartedAtUtc().toInstant()) |
| | | || pos.deviceTime().isBefore(test.getStartedAtUtc().toInstant()) |
| | | || neg.deviceTime().isBefore(test.getStartedAtUtc().toInstant()))) return; |
| | | if (test.getUpdateTime() != null && (temp.serverReceivedAt().isBefore(test.getUpdateTime().toInstant()) |
| | | || pos.serverReceivedAt().isBefore(test.getUpdateTime().toInstant()) |
| | | || neg.serverReceivedAt().isBefore(test.getUpdateTime().toInstant()))) return; |
| | | if (test.getReceptionBoundaryAtUtc() != null |
| | | && (temp.serverReceivedAt().isBefore(test.getReceptionBoundaryAtUtc().toInstant()) |
| | | || pos.serverReceivedAt().isBefore(test.getReceptionBoundaryAtUtc().toInstant()) |
| | | || neg.serverReceivedAt().isBefore(test.getReceptionBoundaryAtUtc().toInstant()))) return; |
| | | Date slot = new Date((now.getTime() / interval) * interval); |
| | | byte[] hash = snapshotHash(cabinet, temp, pos, neg, slot.toInstant()); |
| | | mapper.insertRealtimeSnapshot(test.getTestId(), binding.getBindingId(), binding.getChannelId(), battery.getCollectorId(), slot, |
| | |
| | | { |
| | | // 锁住测试行直到本轮采样写入完成,暂停/停止动作只能在本轮接收提交后生效。 |
| | | TTest locked = mapper.selectTestByIdForUpdate(testId); |
| | | if (locked == null || !"RECEIVING".equals(locked.getReceptionStatus())) continue; |
| | | if (locked == null || !"RUNNING".equals(locked.getTestStatus()) |
| | | || !"RECEIVING".equals(locked.getReceptionStatus())) continue; |
| | | TTest test = tests.selectTestById(testId); |
| | | if (test == null || !"RECEIVING".equals(test.getReceptionStatus())) continue; |
| | | if (test == null || !"RUNNING".equals(test.getTestStatus()) |
| | | || !"RECEIVING".equals(test.getReceptionStatus())) continue; |
| | | captureTest(test); |
| | | } |
| | | } |
| | |
| | | for (Long testId : testIds) |
| | | { |
| | | TTest locked = mapper.selectTestByIdForUpdate(testId); |
| | | if (locked == null || !"RECEIVING".equals(locked.getReceptionStatus())) continue; |
| | | if (locked == null || !"RUNNING".equals(locked.getTestStatus()) |
| | | || !"RECEIVING".equals(locked.getReceptionStatus())) continue; |
| | | TTest test = tests.selectTestById(testId); |
| | | if (test == null || !"RECEIVING".equals(test.getReceptionStatus())) continue; |
| | | if (test == null || !"RUNNING".equals(test.getTestStatus()) |
| | | || !"RECEIVING".equals(test.getReceptionStatus())) continue; |
| | | for (TTestBattery battery : test.getBatteries() == null ? List.<TTestBattery>of() : test.getBatteries()) |
| | | captureCollectorMeasurements(test, battery); |
| | | } |
| | |
| | | { |
| | | // 模拟器保留通道历史;只接收本次测试开始后产生的设备时间记录,避免顺序测试串入旧事实。 |
| | | if (startedAt != null && record.deviceTime().isBefore(startedAt.toInstant())) continue; |
| | | if (test.getReceptionBoundaryAtUtc() != null && record.serverReceivedAt() != null |
| | | && record.serverReceivedAt().isBefore(test.getReceptionBoundaryAtUtc().toInstant())) continue; |
| | | if (save(test, binding, record)) { |
| | | String source = String.join(":", java.util.Objects.toString(binding.getAdapterCode(), ""), |
| | | java.util.Objects.toString(binding.getDevtype(), ""), java.util.Objects.toString(binding.getDevid(), ""), |
| | |
| | | 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; |
| | | // 继续接收只接受恢复动作之后服务器收到的采样;暂停期间采集器历史不回补。 |
| | | if (test.getReceptionBoundaryAtUtc() != null |
| | | && measurement.serverReceivedAt().isBefore(test.getReceptionBoundaryAtUtc().toInstant())) continue; |
| | | boolean mapped = battery.getCollectorChannels().stream().anyMatch(mapping -> |
| | | java.util.Objects.equals(mapping.getChannelCode(), measurement.channelCode()) |
| | | && metricMatches(mapping.getMeasurementCode(), measurement.metricCode())); |
| | |
| | | <result property="receptionStatus" column="reception_status" /> |
| | | <result property="monitorTimeoutMinutes" column="monitor_timeout_minutes" /> |
| | | <result property="startedAtUtc" column="started_at_utc" /> |
| | | <result property="receptionBoundaryAtUtc" column="reception_boundary_at_utc" /> |
| | | <result property="endedAtUtc" column="ended_at_utc" /> |
| | | <result property="runtimeAttach" column="runtime_attach" /> |
| | | <result property="delFlag" column="del_flag" /> |
| | |
| | | t.test_type_id, ty.test_type_name, t.test_purpose, |
| | | t.test_status, |
| | | t.reception_status, t.monitor_timeout_minutes, |
| | | t.started_at_utc, t.ended_at_utc, t.del_flag, |
| | | t.started_at_utc, t.reception_boundary_at_utc, t.ended_at_utc, t.del_flag, |
| | | t.create_by, t.create_time, t.update_by, t.update_time, t.remark, |
| | | cast(t.logic_step_mappings_json as char) logic_step_mappings_json, |
| | | cast(t.console_detail_json as char) console_detail_json, |
| | |
| | | set test_name = #{testName}, principal_id = #{principalId}, test_type_id = #{testTypeId}, |
| | | test_purpose = #{testPurpose}, monitor_timeout_minutes = #{monitorTimeoutMinutes}, |
| | | remark = #{remark}, update_by = #{updateBy}, update_time = #{updateTime} |
| | | where test_id = #{testId} and reception_status = 'NOT_STARTED' and del_flag = 0 |
| | | where test_id = #{testId} and test_status = 'WAITING' and reception_status = 'NOT_STARTED' and del_flag = 0 |
| | | </update> |
| | | |
| | | <delete id="deleteTestById"> |
| | | delete from t_test where test_id = #{testId} and reception_status = 'NOT_STARTED' and del_flag = 0 |
| | | delete from t_test |
| | | where test_id = #{testId} and test_status = 'WAITING' and reception_status = 'NOT_STARTED' and del_flag = 0 |
| | | </delete> |
| | | |
| | | <delete id="deleteTestBindingsByTestId">delete from t_test_channel_binding where test_id = #{testId}</delete> |
| | |
| | | where exception_id=#{exceptionId} and exception_status='OPEN' |
| | | </update> |
| | | <select id="selectReceivingTestIds" resultType="long"> |
| | | select test_id from t_test where del_flag = 0 and reception_status = 'RECEIVING' order by test_id |
| | | select test_id from t_test |
| | | where del_flag = 0 and test_status = 'RUNNING' and reception_status = 'RECEIVING' |
| | | order by test_id |
| | | </select> |
| | | <insert id="insertRealtimeSnapshot"> |
| | | insert ignore into t_test_realtime_snapshot |
| | |
| | | update t_test |
| | | set test_status = 'RUNNING', reception_status = 'RECEIVING', |
| | | started_at_utc = coalesce(started_at_utc, #{startedAtUtc}), |
| | | reception_boundary_at_utc = #{startedAtUtc}, |
| | | update_by = #{updateBy}, update_time = #{startedAtUtc} |
| | | where test_id = #{testId} and reception_status = 'NOT_STARTED' and del_flag = 0 |
| | | where test_id = #{testId} and test_status = 'WAITING' and reception_status = 'NOT_STARTED' and del_flag = 0 |
| | | </update> |
| | | <update id="updateReceptionPaused"> |
| | | update t_test |
| | | set test_status = 'PAUSED', reception_status = 'PAUSED', update_by = #{updateBy}, update_time = #{updatedAtUtc} |
| | | where test_id = #{testId} and reception_status = 'RECEIVING' and del_flag = 0 |
| | | where test_id = #{testId} and test_status = 'RUNNING' and reception_status = 'RECEIVING' and del_flag = 0 |
| | | </update> |
| | | <update id="updateReceptionResumed"> |
| | | update t_test |
| | | set test_status = 'RUNNING', reception_status = 'RECEIVING', update_by = #{updateBy}, update_time = #{updatedAtUtc} |
| | | where test_id = #{testId} and reception_status = 'PAUSED' and del_flag = 0 |
| | | set test_status = 'RUNNING', reception_status = 'RECEIVING', |
| | | reception_boundary_at_utc = #{updatedAtUtc}, update_by = #{updateBy}, update_time = #{updatedAtUtc} |
| | | where test_id = #{testId} and test_status = 'PAUSED' and reception_status = 'PAUSED' and del_flag = 0 |
| | | </update> |
| | | <update id="updateReceptionStopped"> |
| | | update t_test |
| | | set test_status = 'COMPLETED', reception_status = 'ENDED', ended_at_utc = #{endedAtUtc}, |
| | | update_by = #{updateBy}, update_time = #{endedAtUtc} |
| | | where test_id = #{testId} and reception_status in ('RECEIVING', 'PAUSED') and del_flag = 0 |
| | | where test_id = #{testId} |
| | | and ((test_status = 'RUNNING' and reception_status = 'RECEIVING') |
| | | or (test_status = 'PAUSED' and reception_status = 'PAUSED')) |
| | | and del_flag = 0 |
| | | </update> |
| | | |
| | | <resultMap id="TTestDataOverrideResultMap" type="com.ruoyi.system.domain.TTestDataOverride"> |