| | |
| | | import com.ruoyi.common.exception.ServiceException; |
| | | import com.ruoyi.common.utils.StringUtils; |
| | | import com.ruoyi.system.domain.TTest; |
| | | import com.ruoyi.system.domain.TBatteryGroup; |
| | | import com.ruoyi.system.domain.TBatteryGroupRelationField; |
| | | 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.TTestFieldSnapshot; |
| | | import com.ruoyi.system.domain.TTestPlanStep; |
| | | import com.ruoyi.system.domain.TTestResult; |
| | | import com.ruoyi.system.domain.TTestWarningThreshold; |
| | | import com.ruoyi.system.domain.vo.TestConfigurationOptionsVo; |
| | | import com.ruoyi.system.mapper.TTestMapper; |
| | | import com.ruoyi.system.service.ITTestService; |
| | | import com.ruoyi.system.service.ITBatteryGroupService; |
| | | |
| | | /** 测试配置服务,负责状态边界、关系校验、事务写入和只追加审计。 */ |
| | | @Service |
| | | public class TTestServiceImpl implements ITTestService |
| | | { |
| | | private static final String WAITING = "WAITING"; |
| | | private static final String NOT_MONITORED = "NOT_MONITORED"; |
| | | 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> MONITORING_STATUSES = Set.of( |
| | | NOT_MONITORED, "WAITING_DEVICE_START", "MONITORING", "BACKFILLING", "MONITORING_ENDED", "CANCELLED"); |
| | | 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( |
| | | "PRIMARY_TEMPERATURE", "PRIMARY_POSITIVE_POTENTIAL", "PRIMARY_NEGATIVE_POTENTIAL"); |
| | | |
| | | @Autowired |
| | | private TTestMapper testMapper; |
| | | |
| | | @Autowired |
| | | private ITBatteryGroupService batteryGroupService; |
| | | |
| | | @Override |
| | | public TTest selectTestById(Long testId) |
| | |
| | | test.setTestName(trimToNull(test.getTestName())); |
| | | test.setBatteryNo(trimToNull(test.getBatteryNo())); |
| | | validateQueryStatus(test.getTestStatus(), TEST_STATUSES, "测试状态无效"); |
| | | validateQueryStatus(test.getMonitoringStatus(), MONITORING_STATUSES, "监测状态无效"); |
| | | validateQueryStatus(test.getReceptionStatus(), RECEPTION_STATUSES, "接收状态无效"); |
| | | return testMapper.selectTestList(test); |
| | | } |
| | | |
| | |
| | | validateBase(test, false); |
| | | validateConfiguration(test); |
| | | test.setTestStatus(WAITING); |
| | | test.setMonitoringStatus(NOT_MONITORED); |
| | | test.setReceptionStatus(NOT_STARTED); |
| | | test.setFieldSnapshots(snapshotGroupFields(test.getBatteries().get(0).getGroupId())); |
| | | test.setDelFlag("0"); |
| | | test.setCreateTime(new Date()); |
| | | testMapper.insertTest(test); |
| | |
| | | TTest current = testMapper.selectTestByIdForUpdate(test.getTestId()); |
| | | if (current == null) throw new ServiceException("测试不存在"); |
| | | loadConfiguration(current); |
| | | if (!NOT_MONITORED.equals(current.getMonitoringStatus())) |
| | | throw new ServiceException("开始监测后不能修改测试配置"); |
| | | if (!NOT_STARTED.equals(current.getReceptionStatus())) |
| | | throw new ServiceException("开始接收后不能修改测试配置"); |
| | | |
| | | test.setCreatorId(current.getCreatorId()); |
| | | test.setTestStatus(current.getTestStatus()); |
| | | test.setMonitoringStatus(current.getMonitoringStatus()); |
| | | test.setReceptionStatus(current.getReceptionStatus()); |
| | | if (test.getMonitorTimeoutMinutes() == null) |
| | | test.setMonitorTimeoutMinutes(current.getMonitorTimeoutMinutes()); |
| | | validateBase(test, true); |
| | | validateConfiguration(test); |
| | | test.setFieldSnapshots(resolveUpdatedFieldSnapshots(test, current)); |
| | | test.setUpdateTime(new Date()); |
| | | int rows = testMapper.updateTest(test); |
| | | if (rows != 1) throw new ServiceException("测试配置已变更,请刷新后重试"); |
| | | |
| | | // 监测开始前没有原始采集事实,配置关系可在同一事务内整体替换,审计记录仍只追加。 |
| | | // 开始接收前没有数据归属事实,配置关系可在同一事务内整体替换,审计记录仍只追加。 |
| | | testMapper.deleteTestCollectorChannelsByTestId(test.getTestId()); |
| | | testMapper.deleteTestBindingsByTestId(test.getTestId()); |
| | | testMapper.deleteTestPlanStepsByTestId(test.getTestId()); |
| | | testMapper.deleteTestFieldsByTestId(test.getTestId()); |
| | | testMapper.deleteTestBatteriesByTestId(test.getTestId()); |
| | | testMapper.deleteTestThresholdsByTestId(test.getTestId()); |
| | | testMapper.deleteTestFieldSnapshotsByTestId(test.getTestId()); |
| | | saveConfiguration(test); |
| | | testMapper.insertTestAudit(test.getTestId(), "TEST_CONFIGURATION_UPDATED", test.getOperatorId(), |
| | | test.getUpdateBy(), JSON.toJSONString(current), JSON.toJSONString(test)); |
| | |
| | | @Override |
| | | public int startTest(Long testId, String updateBy) |
| | | { |
| | | throw new ServiceException("开始监测由后续监测动作处理"); |
| | | throw new ServiceException("开始接收由后续原子动作处理"); |
| | | } |
| | | |
| | | @Override |
| | |
| | | { |
| | | TTest current = testMapper.selectTestByIdForUpdate(testId); |
| | | if (current == null) throw new ServiceException("测试不存在"); |
| | | if (!NOT_MONITORED.equals(current.getMonitoringStatus())) throw new ServiceException("开始监测后不能删除测试"); |
| | | if (!NOT_STARTED.equals(current.getReceptionStatus())) throw new ServiceException("开始接收后不能删除测试"); |
| | | loadConfiguration(current); |
| | | int deleted = testMapper.deleteTestById(testId, updateBy); |
| | | if (deleted != 1) throw new ServiceException("测试配置已变更,请刷新后重试"); |
| | |
| | | |
| | | List<TTestBattery> batteries = mutable(testMapper.selectTestBatteries(testId)); |
| | | List<TTestBatteryField> fields = mutable(testMapper.selectTestBatteryFields(testId)); |
| | | List<TTestCollectorChannel> collectorChannels = mutable(testMapper.selectTestCollectorChannels(testId)); |
| | | Map<Long, TTestChannelBinding> bindings = new HashMap<>(); |
| | | for (TTestChannelBinding binding : mutable(testMapper.selectTestChannelBindings(testId))) |
| | | bindings.put(binding.getBatteryRelationId(), binding); |
| | |
| | | battery.setFields(fields.stream() |
| | | .filter(field -> battery.getRelationId().equals(field.getRelationId())) |
| | | .toList()); |
| | | battery.setCollectorChannels(collectorChannels.stream() |
| | | .filter(mapping -> battery.getRelationId().equals(mapping.getBatteryRelationId())) |
| | | .toList()); |
| | | } |
| | | test.setPlanSteps(planSteps); |
| | | test.setBatteries(batteries); |
| | | test.setThresholds(mutable(testMapper.selectTestThresholds(testId))); |
| | | test.setFieldSnapshots(mutable(testMapper.selectTestFieldSnapshots(testId))); |
| | | } |
| | | |
| | | private void validateBase(TTest test, boolean update) |
| | |
| | | |
| | | private void validateConfiguration(TTest test) |
| | | { |
| | | // 新建测试只建立业务身份和通道关系,计划工步可在开始监测前补充。 |
| | | // 新建测试只建立业务身份和通道关系,计划工步可在开始接收前补充。 |
| | | if (test.getPlanSteps() == null) test.setPlanSteps(new ArrayList<>()); |
| | | validateBatteries(test.getBatteries()); |
| | | validatePlanSteps(test.getPlanSteps()); |
| | |
| | | if (batteries == null || batteries.isEmpty()) throw new ServiceException("测试至少绑定一个电池号"); |
| | | Set<String> batteryNos = new HashSet<>(); |
| | | Set<Long> channelIds = new HashSet<>(); |
| | | Set<String> collectorChannelIdentities = new HashSet<>(); |
| | | Map<Long, Integer> collectorChannelCounts = new HashMap<>(); |
| | | Long groupId = null; |
| | | for (TTestBattery battery : batteries) |
| | | { |
| | |
| | | binding.setExpectedBarcode(trimToNull(binding.getExpectedBarcode())); |
| | | if (binding.getExpectedBarcode() != null && binding.getExpectedBarcode().length() > 100) |
| | | throw new ServiceException("预期条码长度不能超过100个字符"); |
| | | if (battery.getCollectorId() != null && testMapper.countCollectorEnabled(battery.getCollectorId()) == 0) |
| | | if (battery.getCollectorId() == null) throw new ServiceException("采集器不能为空"); |
| | | if (testMapper.countCollectorEnabled(battery.getCollectorId()) == 0) |
| | | throw new ServiceException("采集器不存在或未启用"); |
| | | Integer channelCount = collectorChannelCounts.computeIfAbsent(battery.getCollectorId(), |
| | | testMapper::selectCollectorChannelCount); |
| | | if (channelCount == null || channelCount < 1) throw new ServiceException("采集器通道配置无效"); |
| | | validateCollectorChannels(battery.getCollectorId(), channelCount, battery.getCollectorChannels(), |
| | | collectorChannelIdentities); |
| | | validateBatteryFields(battery.getFields()); |
| | | } |
| | | } |
| | | |
| | | private void validateCollectorChannels(Long collectorId, int channelCount, List<TTestCollectorChannel> mappings, |
| | | Set<String> collectorChannelIdentities) |
| | | { |
| | | if (mappings == null || mappings.isEmpty()) |
| | | throw new ServiceException("每个电池号必须绑定主温度、正电位主通道和负电位主通道"); |
| | | Set<String> measurementCodes = new HashSet<>(); |
| | | for (TTestCollectorChannel mapping : mappings) |
| | | { |
| | | if (mapping == null) throw new ServiceException("采集器通道映射不完整"); |
| | | mapping.setMeasurementCode(trimToNull(mapping.getMeasurementCode())); |
| | | mapping.setMeasurementName(trimToNull(mapping.getMeasurementName())); |
| | | mapping.setChannelCode(normalizeCollectorChannel(mapping.getChannelCode())); |
| | | if (mapping.getMeasurementCode() == null || mapping.getMeasurementName() == null |
| | | || mapping.getChannelCode() == null) |
| | | throw new ServiceException("采集器通道映射不完整"); |
| | | if (mapping.getMeasurementCode().length() > 100 || mapping.getMeasurementName().length() > 100) |
| | | throw new ServiceException("采集器测量项长度不能超过100个字符"); |
| | | int channelNumber = Integer.parseInt(mapping.getChannelCode().substring(2)); |
| | | if (channelNumber < 1 || channelNumber > channelCount) |
| | | throw new ServiceException("采集器通道不存在或超出登记范围"); |
| | | if (!measurementCodes.add(mapping.getMeasurementCode())) |
| | | throw new ServiceException("同一电池号的采集器测量项不可重复"); |
| | | if (!collectorChannelIdentities.add(collectorId + ":" + mapping.getChannelCode())) |
| | | throw new ServiceException("同一采集器通道不可重复映射"); |
| | | } |
| | | if (!measurementCodes.containsAll(REQUIRED_COLLECTOR_MEASUREMENTS)) |
| | | throw new ServiceException("每个电池号必须绑定主温度、正电位主通道和负电位主通道"); |
| | | } |
| | | |
| | | private void validateBatteryFields(List<TTestBatteryField> fields) |
| | |
| | | if (field.getFieldName() == null || !fieldNames.add(field.getFieldName())) |
| | | throw new ServiceException("扩展属性名称不能为空且不可重复"); |
| | | } |
| | | } |
| | | |
| | | private List<TTestFieldSnapshot> resolveUpdatedFieldSnapshots(TTest test, TTest current) |
| | | { |
| | | Long nextGroupId = test.getBatteries().get(0).getGroupId(); |
| | | Long currentGroupId = current.getBatteries().isEmpty() ? null : current.getBatteries().get(0).getGroupId(); |
| | | if (nextGroupId.equals(currentGroupId)) |
| | | return mutable(current.getFieldSnapshots()).stream().map(this::copySnapshot).toList(); |
| | | return snapshotGroupFields(nextGroupId); |
| | | } |
| | | |
| | | private List<TTestFieldSnapshot> snapshotGroupFields(Long groupId) |
| | | { |
| | | TBatteryGroup group = batteryGroupService.selectBatteryGroupById(groupId); |
| | | List<TTestFieldSnapshot> snapshots = new ArrayList<>(); |
| | | for (TBatteryGroupRelationField field : mutable(group.getTemplateFields())) |
| | | { |
| | | TTestFieldSnapshot snapshot = new TTestFieldSnapshot(); |
| | | snapshot.setFieldDefinitionId(field.getBatteryGroupFieldId()); |
| | | snapshot.setFieldName(field.getFieldName()); |
| | | snapshot.setFieldType(field.getFieldType()); |
| | | snapshot.setFieldValue(field.getFieldValue()); |
| | | snapshots.add(snapshot); |
| | | } |
| | | return snapshots; |
| | | } |
| | | |
| | | private TTestFieldSnapshot copySnapshot(TTestFieldSnapshot source) |
| | | { |
| | | TTestFieldSnapshot copy = new TTestFieldSnapshot(); |
| | | copy.setFieldDefinitionId(source.getFieldDefinitionId()); |
| | | copy.setFieldName(source.getFieldName()); |
| | | copy.setFieldType(source.getFieldType()); |
| | | copy.setFieldValue(source.getFieldValue()); |
| | | return copy; |
| | | } |
| | | |
| | | private void validatePlanSteps(List<TTestPlanStep> planSteps) |
| | |
| | | binding.setBatteryRelationId(battery.getRelationId()); |
| | | binding.setClaimStatus("PENDING"); |
| | | testMapper.insertTestChannelBinding(binding); |
| | | for (TTestCollectorChannel mapping : battery.getCollectorChannels()) |
| | | { |
| | | mapping.setTestId(test.getTestId()); |
| | | mapping.setBatteryRelationId(battery.getRelationId()); |
| | | mapping.setCollectorId(battery.getCollectorId()); |
| | | testMapper.insertTestCollectorChannel(mapping); |
| | | } |
| | | } |
| | | for (TTestFieldSnapshot snapshot : mutable(test.getFieldSnapshots())) |
| | | { |
| | | snapshot.setTestId(test.getTestId()); |
| | | testMapper.insertTestFieldSnapshot(snapshot); |
| | | } |
| | | for (TTestWarningThreshold threshold : test.getThresholds()) |
| | | { |
| | |
| | | return StringUtils.isEmpty(trimmed) ? null : trimmed; |
| | | } |
| | | |
| | | private String normalizeCollectorChannel(String value) |
| | | { |
| | | String normalized = trimToNull(value); |
| | | if (normalized == null || !normalized.toUpperCase().matches("S-\\d+")) |
| | | throw new ServiceException("采集器通道必须使用S-01...S-N演示编码"); |
| | | try |
| | | { |
| | | int channelNumber = Integer.parseInt(normalized.substring(2)); |
| | | return String.format("S-%02d", channelNumber); |
| | | } |
| | | catch (NumberFormatException exception) |
| | | { |
| | | throw new ServiceException("采集器通道必须使用S-01...S-N演示编码"); |
| | | } |
| | | } |
| | | |
| | | private <T> List<T> mutable(List<T> values) |
| | | { |
| | | return values == null ? new ArrayList<>() : new ArrayList<>(values); |