| | |
| | | import java.util.HashMap; |
| | | import java.util.HashSet; |
| | | import java.util.List; |
| | | import java.util.Locale; |
| | | import java.util.Map; |
| | | import java.util.Set; |
| | | import com.alibaba.fastjson2.JSON; |
| | |
| | | import com.ruoyi.system.domain.TTestResult; |
| | | import com.ruoyi.system.domain.TTestFieldSnapshot; |
| | | import com.ruoyi.system.domain.TTestWarningThreshold; |
| | | import com.ruoyi.system.domain.TTestLogicStepMapping; |
| | | import com.ruoyi.system.domain.TDetectionChannel; |
| | | import com.ruoyi.system.domain.TTestReceptionInterval; |
| | | import com.ruoyi.system.domain.vo.TestConfigurationOptionsVo; |
| | | import com.ruoyi.system.mapper.TTestMapper; |
| | | import com.ruoyi.system.service.ITTestService; |
| | |
| | | if (current.getBatteries().isEmpty()) throw new ServiceException("测试未配置电池号"); |
| | | |
| | | Date actionAt = new Date(); |
| | | List<TTestReceptionInterval> intervals = new ArrayList<>(); |
| | | Set<Long> channelIds = new HashSet<>(); |
| | | Set<String> collectorChannels = new HashSet<>(); |
| | | boolean runtimeAttach = false; |
| | |
| | | TDetectionChannel channel = testMapper.selectReceptionChannelForUpdate(binding.getChannelId()); |
| | | if (channel == null || !isEnabled(channel.getStatus()) || !isOnline(channel.getOnlineStatus())) |
| | | throw new ServiceException("检测柜通道 " + binding.getChannelId() + " 当前离线或不可观测"); |
| | | if (channel.getLastSeenAtUtc() == null) |
| | | throw new ServiceException("检测柜通道 " + binding.getChannelId() + " 尚未取得成功观测边界"); |
| | | runtimeAttach |= "WORKING".equalsIgnoreCase(channel.getWorkStatus()) |
| | | || "PAUSED".equalsIgnoreCase(channel.getWorkStatus()); |
| | | if (testMapper.selectChannelLeaseForUpdate(binding.getChannelId()) != null) |
| | | throw new ServiceException("检测柜通道 " + binding.getChannelId() + " 已被其他测试占用"); |
| | | // 只有设备事实为运行中时才标记“运行中接入”;设备暂停属于独立设备状态。 |
| | | runtimeAttach |= "WORKING".equalsIgnoreCase(channel.getWorkStatus()); |
| | | if (battery.getCollectorId() == null || testMapper.countCollectorEnabled(battery.getCollectorId()) == 0) |
| | | throw new ServiceException("电池号 " + battery.getBatteryNo() + " 的采集器不存在或未启用"); |
| | | if (!testMapper.collectorOnline(battery.getCollectorId())) |
| | |
| | | validateCollectorChannels(battery.getCollectorId(), collectorChannelCount == null ? 0 : collectorChannelCount, |
| | | battery.getCollectorChannels(), collectorChannels); |
| | | |
| | | intervals.add(interval(current, binding.getBindingId(), "CABINET_CHANNEL", binding.getChannelId(), null, |
| | | actionAt, Date.from(channel.getLastSeenAtUtc()), runtimeAttach |
| | | ? "运行中接入:采用检测柜最近成功观测边界" : "采用检测柜最近成功观测边界")); |
| | | for (TTestCollectorChannel mapping : mutable(battery.getCollectorChannels())) |
| | | { |
| | | intervals.add(interval(current, binding.getBindingId(), "COLLECTOR_CHANNEL", battery.getCollectorId(), |
| | | mapping.getChannelCode(), actionAt, actionAt, "采集器开始接收确认边界")); |
| | | } |
| | | } |
| | | |
| | | try |
| | | { |
| | | for (TTestBattery battery : current.getBatteries()) |
| | | { |
| | | TTestChannelBinding binding = battery.getBinding(); |
| | | testMapper.insertChannelLease(binding.getChannelId(), testId, binding.getBindingId(), operatorId, actionAt); |
| | | for (TTestCollectorChannel mapping : mutable(battery.getCollectorChannels())) |
| | | testMapper.insertCollectorChannelLease(battery.getCollectorId(), mapping.getChannelCode(), testId, |
| | | binding.getBindingId(), operatorId, actionAt); |
| | | } |
| | | for (TTestReceptionInterval interval : intervals) testMapper.insertReceptionInterval(interval); |
| | | if (testMapper.updateReceptionStarted(testId, actionAt, updateBy) != 1) |
| | | 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", "intervalCount", intervals.size(), |
| | | "runtimeAttach", runtimeAttach, "intervals", intervals))); |
| | | JSON.toJSONString(Map.of("receptionStatus", "RECEIVING", "runtimeAttach", runtimeAttach))); |
| | | return 1; |
| | | } |
| | | catch (DuplicateKeyException exception) |
| | |
| | | } |
| | | } |
| | | |
| | | private TTestReceptionInterval interval(TTest test, Long bindingId, String sourceType, Long sourceId, |
| | | String sourceChannelCode, Date actionAt, Date boundaryAt, String note) |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public int pauseReception(Long testId, Long operatorId, String updateBy) |
| | | { |
| | | TTestReceptionInterval interval = new TTestReceptionInterval(); |
| | | interval.setTestId(test.getTestId()); |
| | | interval.setBindingId(bindingId); |
| | | interval.setSourceType(sourceType); |
| | | interval.setSourceId(sourceId); |
| | | interval.setSourceChannelCode(sourceChannelCode); |
| | | interval.setActionAtUtc(actionAt); |
| | | interval.setStartBoundaryAtUtc(boundaryAt); |
| | | interval.setBoundaryNote(note); |
| | | interval.setIntervalStatus("OPEN"); |
| | | return interval; |
| | | if (testId == null) throw new ServiceException("测试ID不能为空"); |
| | | if (operatorId == null) throw new ServiceException("操作人不能为空"); |
| | | TTest current = testMapper.selectTestByIdForUpdate(testId); |
| | | if (current == null) throw new ServiceException("测试不存在"); |
| | | if (!"RECEIVING".equals(current.getReceptionStatus())) |
| | | throw new ServiceException("当前测试不是接收中状态"); |
| | | |
| | | Date actionAt = new Date(); |
| | | 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; |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public int resumeReception(Long testId, Long operatorId, String updateBy) |
| | | { |
| | | if (testId == null) throw new ServiceException("测试ID不能为空"); |
| | | if (operatorId == null) throw new ServiceException("操作人不能为空"); |
| | | TTest current = testMapper.selectTestByIdForUpdate(testId); |
| | | if (current == null) throw new ServiceException("测试不存在"); |
| | | if (!"PAUSED".equals(current.getReceptionStatus())) |
| | | throw new ServiceException("当前测试不是暂停接收状态"); |
| | | // 加锁查询只返回测试主表,继续接收前重新加载绑定配置以校验来源仍可用。 |
| | | loadConfiguration(current); |
| | | 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; |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public int stopTest(Long testId, Long operatorId, String updateBy) |
| | | { |
| | | if (testId == null) throw new ServiceException("测试ID不能为空"); |
| | | 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())) |
| | | throw new ServiceException("当前测试不能停止"); |
| | | 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; |
| | | } |
| | | |
| | | private boolean isOnline(String status) |
| | |
| | | if (result == null || result.getTestId() == null) throw new ServiceException("测试ID不能为空"); |
| | | selectTestById(result.getTestId()); |
| | | return testMapper.selectTestResults(result); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public int saveLogicStepMappings(Long testId, List<TTestLogicStepMapping> mappings, Long operatorId, String updateBy) |
| | | { |
| | | if (testId == null || operatorId == null) throw new ServiceException("测试和操作人不能为空"); |
| | | TTest current = testMapper.selectTestByIdForUpdate(testId); |
| | | if (current == null) throw new ServiceException("测试不存在"); |
| | | List<TTestLogicStepMapping> normalized = mappings == null ? List.of() : mappings; |
| | | int previousEnd = 0; |
| | | int expectedNo = 1; |
| | | for (TTestLogicStepMapping mapping : normalized) |
| | | { |
| | | if (mapping == null || mapping.getLogicalNo() == null || mapping.getLogicalNo() != expectedNo |
| | | || mapping.getStartStepNo() == null || mapping.getEndStepNo() == null |
| | | || mapping.getStartStepNo() < 1 || mapping.getEndStepNo() < mapping.getStartStepNo() |
| | | || mapping.getStartStepNo() <= previousEnd || StringUtils.isEmpty(trimToNull(mapping.getLogicalName()))) |
| | | throw new ServiceException("逻辑工步必须按循环内连续位置升序且互不重叠"); |
| | | mapping.setLogicalName(trimToNull(mapping.getLogicalName())); |
| | | 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; |
| | | } |
| | | |
| | | private void loadConfiguration(TTest test) |
| | |
| | | test.setThresholds(loadedThresholds == null ? mutable(test.getThresholds()) : mutable(loadedThresholds)); |
| | | List<TTestFieldSnapshot> loadedSnapshots = sharedFieldSnapshotService.selectByTestId(testId); |
| | | test.setFieldSnapshots(loadedSnapshots == null ? mutable(test.getFieldSnapshots()) : loadedSnapshots); |
| | | String mappingJson = test.getLogicStepMappingsJson(); |
| | | try { test.setLogicStepMappings(mappingJson == null || mappingJson.isBlank() |
| | | ? new ArrayList<>() : JSON.parseArray(mappingJson, TTestLogicStepMapping.class)); } |
| | | catch (JSONException exception) { throw new ServiceException("逻辑工步设置无法解析"); } |
| | | } |
| | | |
| | | private void validateBase(TTest test, boolean update) |
| | |
| | | for (TTestCollectorChannel mapping : mappings) |
| | | { |
| | | if (mapping == null) throw new ServiceException("采集器通道映射不完整"); |
| | | mapping.setMeasurementCode(trimToNull(mapping.getMeasurementCode())); |
| | | mapping.setMeasurementCode(normalizeMeasurementCode(mapping.getMeasurementCode())); |
| | | mapping.setMeasurementName(trimToNull(mapping.getMeasurementName())); |
| | | mapping.setChannelCode(normalizeCollectorChannel(mapping.getChannelCode())); |
| | | if (mapping.getMeasurementCode() == null || mapping.getMeasurementName() == null |
| | |
| | | } |
| | | } |
| | | |
| | | /** 测量编码与数据库不区分大小写排序保持一致,避免校验通过后插入冲突。 */ |
| | | private String normalizeMeasurementCode(String value) |
| | | { |
| | | String normalized = trimToNull(value); |
| | | return normalized == null ? null : normalized.toUpperCase(Locale.ROOT); |
| | | } |
| | | |
| | | private <T> List<T> mutable(List<T> values) |
| | | { |
| | | return values == null ? new ArrayList<>() : new ArrayList<>(values); |