| File was renamed from ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TTestServiceImpl.java |
| | |
| | | package com.ruoyi.system.service.impl; |
| | | package com.battery.business.service.impl; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.Date; |
| | |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | import com.ruoyi.common.exception.ServiceException; |
| | | import com.ruoyi.common.utils.StringUtils; |
| | | import com.ruoyi.system.domain.TTest; |
| | | import com.ruoyi.system.domain.TTestBattery; |
| | | import com.ruoyi.system.domain.TTestBatteryField; |
| | | import com.ruoyi.system.domain.TTestChannelBinding; |
| | | import com.ruoyi.system.domain.TTestCollectorChannel; |
| | | import com.ruoyi.system.domain.TTestPlanStep; |
| | | import com.ruoyi.system.domain.TTestResult; |
| | | import com.ruoyi.system.domain.TTestWarningThreshold; |
| | | import com.ruoyi.system.domain.TTestLogicStepMapping; |
| | | import com.ruoyi.system.domain.TTestConsoleDetail; |
| | | import com.ruoyi.system.domain.TDetectionChannel; |
| | | import com.ruoyi.system.domain.vo.TestConfigurationOptionsVo; |
| | | import com.ruoyi.system.mapper.TTestMapper; |
| | | import com.ruoyi.system.service.ITTestService; |
| | | import com.battery.business.domain.TTest; |
| | | import com.battery.business.domain.TTestBattery; |
| | | import com.battery.business.domain.CurrentBatteryField; |
| | | import com.battery.business.domain.TTestChannelBinding; |
| | | import com.battery.business.domain.TTestCollectorChannel; |
| | | import com.battery.business.domain.TTestWarningThreshold; |
| | | import com.battery.business.domain.TTestLogicStepMapping; |
| | | import com.battery.business.domain.TTestConsoleDetail; |
| | | import com.battery.business.domain.TDetectionChannel; |
| | | import com.battery.business.domain.vo.TestConfigurationOptionsVo; |
| | | import com.battery.business.mapper.TTestMapper; |
| | | import com.battery.business.service.ITTestService; |
| | | |
| | | /** 测试配置服务,负责状态边界、关系校验、事务写入和只追加审计。 */ |
| | | @Service |
| | |
| | | return rows; |
| | | } |
| | | |
| | | @Override |
| | | public int startTest(Long testId, String updateBy) |
| | | { |
| | | throw new ServiceException("旧开始入口已停用,请使用原子开始接收接口并提供操作人"); |
| | | } |
| | | |
| | | /** |
| | | * 建立测试接收边界;来源离线不阻断状态流转,只有成功观测数据才会写入测试事实。 |
| | | */ |
| | |
| | | { |
| | | if (testMapper.updateReceptionStarted(testId, actionAt, runtimeAttach, updateBy) != 1) |
| | | throw new ServiceException("测试状态已变更,请刷新后重试"); |
| | | testMapper.insertTestAudit(testId, "RECEPTION_STARTED", operatorId, updateBy, null, |
| | | JSON.toJSONString(Map.of("receptionStatus", "RECEIVING", "runtimeAttach", runtimeAttach))); |
| | | return 1; |
| | | } |
| | | catch (DuplicateKeyException exception) |
| | |
| | | if (testMapper.updateReceptionPaused(testId, updateBy, actionAt) != 1) |
| | | throw new ServiceException("测试状态已变更,请刷新后重试"); |
| | | // 暂停期间停止请求数据;继续接收后仅追加新的成功接收事实。 |
| | | testMapper.insertTestAudit(testId, "RECEPTION_PAUSED", operatorId, updateBy, null, |
| | | JSON.toJSONString(Map.of("receptionStatus", "PAUSED", "polling", "STOPPED"))); |
| | | return 1; |
| | | } |
| | | |
| | |
| | | Date actionAt = new Date(); |
| | | if (testMapper.updateReceptionResumed(testId, updateBy, actionAt) != 1) |
| | | throw new ServiceException("测试状态已变更,请刷新后重试"); |
| | | testMapper.insertTestAudit(testId, "RECEPTION_RESUMED", operatorId, updateBy, null, |
| | | JSON.toJSONString(Map.of("receptionStatus", "RECEIVING", "polling", "RESUMED"))); |
| | | return 1; |
| | | } |
| | | |
| | |
| | | Date actionAt = new Date(); |
| | | if (testMapper.updateReceptionStopped(testId, updateBy, actionAt) != 1) |
| | | throw new ServiceException("测试状态已变更,请刷新后重试"); |
| | | testMapper.insertTestAudit(testId, "TEST_STOPPED", operatorId, updateBy, null, |
| | | JSON.toJSONString(Map.of("testStatus", "COMPLETED", "receptionStatus", "ENDED", |
| | | "polling", "STOPPED", "release", "IMMEDIATE"))); |
| | | return 1; |
| | | } |
| | | |
| | |
| | | { |
| | | TTest current = testMapper.selectTestByIdForUpdate(testId); |
| | | if (current == null) throw new ServiceException("测试不存在"); |
| | | if (!stateMatches(current, WAITING, NOT_STARTED)) throw new ServiceException("开始接收后不能删除测试"); |
| | | loadConfiguration(current); |
| | | // 测试整体删除是无痕例外:先清理全部测试绑定和阈值,再删除主记录,不写审计。 |
| | | // 与接收事务使用同一测试行锁,删除提交后不能再向该测试写入数据;整体删除不留审计或墓碑。 |
| | | testMapper.deleteTestRealtimeRecordsByTestId(testId); |
| | | testMapper.deleteTestRealtimeSnapshotsByTestId(testId); |
| | | testMapper.deleteTestStepRecordsByTestId(testId); |
| | | testMapper.deleteTestCycleRecordsByTestId(testId); |
| | | testMapper.deleteTestDataRevisionActionsByTestId(testId); |
| | | testMapper.deleteTestDataOverridesByTestId(testId); |
| | | testMapper.deleteTestConsoleImportRecordsByTestId(testId); |
| | | testMapper.deleteTestWarningExceptionsByTestId(testId); |
| | | testMapper.deleteTestLogicStepMappingsByTestId(testId); |
| | | testMapper.deleteTestCollectorChannelsByTestId(testId); |
| | | testMapper.deleteTestBindingsByTestId(testId); |
| | | testMapper.deleteTestThresholdRowsByTestId(testId); |
| | |
| | | rows += deleted; |
| | | } |
| | | return rows; |
| | | } |
| | | |
| | | @Override |
| | | public List<TTestResult> selectTestResults(TTestResult result) |
| | | { |
| | | if (result == null || result.getTestId() == null) throw new ServiceException("测试ID不能为空"); |
| | | selectTestById(result.getTestId()); |
| | | return testMapper.selectTestResults(result); |
| | | } |
| | | |
| | | @Override |
| | |
| | | previousEnd = mapping.getEndStepNo(); |
| | | expectedNo++; |
| | | } |
| | | String oldJson = current.getLogicStepMappingsJson(); |
| | | String newJson = JSON.toJSONString(normalized); |
| | | int rows = testMapper.updateLogicStepMappings(testId, newJson, updateBy, new Date()); |
| | | if (rows != 1) throw new ServiceException("工步设置已变更,请刷新后重试"); |
| | | testMapper.insertTestAudit(testId, "LOGIC_STEP_MAPPING_UPDATED", operatorId, updateBy, oldJson, newJson); |
| | | return rows; |
| | | } |
| | | |
| | |
| | | List<TTestBattery> batteries = loadedBatteries == null || (loadedBatteries.isEmpty() && test.getBatteries() != null |
| | | && !test.getBatteries().isEmpty()) ? mutable(test.getBatteries()) : mutable(loadedBatteries); |
| | | // 字段属于电池组当前配置;每次查询测试都重新读取,避免产生测试字段快照。 |
| | | List<TTestBatteryField> currentFields = testMapper.selectCurrentBatteryFields(testId); |
| | | Map<Long, List<TTestBatteryField>> fieldsByBattery = new HashMap<>(); |
| | | for (TTestBatteryField field : mutable(currentFields)) |
| | | List<CurrentBatteryField> currentFields = testMapper.selectCurrentBatteryFields(testId); |
| | | Map<Long, List<CurrentBatteryField>> fieldsByBattery = new HashMap<>(); |
| | | for (CurrentBatteryField field : mutable(currentFields)) |
| | | fieldsByBattery.computeIfAbsent(field.getRelationId(), ignored -> new ArrayList<>()).add(field); |
| | | List<TTestCollectorChannel> loadedCollectorChannels = testMapper.selectTestCollectorChannels(testId); |
| | | List<TTestCollectorChannel> collectorChannels = loadedCollectorChannels == null ? List.of() : mutable(loadedCollectorChannels); |
| | |
| | | List<TTestWarningThreshold> loadedThresholds = testMapper.selectTestThresholds(testId); |
| | | test.setThresholds(loadedThresholds == null ? mutable(test.getThresholds()) : mutable(loadedThresholds)); |
| | | // 测试不保存字段快照;字段值始终随电池组当前配置读取。 |
| | | test.setFieldSnapshots(new ArrayList<>()); |
| | | String mappingJson = test.getLogicStepMappingsJson(); |
| | | try { test.setLogicStepMappings(mappingJson == null || mappingJson.isBlank() |
| | | ? new ArrayList<>() : JSON.parseArray(mappingJson, TTestLogicStepMapping.class)); } |
| | |
| | | private void validateConfiguration(TTest test) |
| | | { |
| | | // 计划工步来自设备观测,不属于测试创建配置,也不写入业务库。 |
| | | test.setPlanSteps(new ArrayList<>()); |
| | | validateBatteries(test.getBatteries()); |
| | | validatePlanSteps(test.getPlanSteps()); |
| | | validateThresholds(test.getThresholds()); |
| | | } |
| | | |
| | |
| | | throw new ServiceException("每个电池号必须绑定主温度、正电位主通道和负电位主通道"); |
| | | } |
| | | |
| | | private void validateBatteryFields(List<TTestBatteryField> fields) |
| | | private void validateBatteryFields(List<CurrentBatteryField> fields) |
| | | { |
| | | if (fields == null) return; |
| | | Set<String> fieldNames = new HashSet<>(); |
| | | for (TTestBatteryField field : fields) |
| | | for (CurrentBatteryField field : fields) |
| | | { |
| | | if (field == null) throw new ServiceException("扩展属性不能为空"); |
| | | field.setFieldName(trimToNull(field.getFieldName())); |
| | |
| | | return mutable(batteries).stream().map(TTestBattery::getBatteryNo).collect(java.util.stream.Collectors.joining("、")); |
| | | } |
| | | |
| | | private void validatePlanSteps(List<TTestPlanStep> planSteps) |
| | | { |
| | | if (planSteps.isEmpty()) return; |
| | | Set<String> identities = new HashSet<>(); |
| | | Set<Integer> orders = new HashSet<>(); |
| | | for (TTestPlanStep step : planSteps) |
| | | { |
| | | if (step == null || step.getCycleNo() == null || step.getCycleNo() < 1 |
| | | || step.getStepNo() == null || step.getStepNo() < 1 |
| | | || step.getSortOrder() == null || step.getSortOrder() < 1) |
| | | throw new ServiceException("计划工步序号必须为正整数"); |
| | | step.setStepType(trimToNull(step.getStepType())); |
| | | step.setStepName(trimToNull(step.getStepName())); |
| | | if (step.getStepType() == null || step.getStepName() == null || step.getParameters() == null) |
| | | throw new ServiceException("计划工步配置不完整"); |
| | | if (!identities.add(step.getCycleNo() + ":" + step.getStepNo()) || !orders.add(step.getSortOrder())) |
| | | throw new ServiceException("计划工步序号不可重复"); |
| | | step.setParametersJson(JSON.toJSONString(step.getParameters())); |
| | | } |
| | | } |
| | | |
| | | |
| | | private void validateThresholds(List<TTestWarningThreshold> thresholds) |
| | | { |