package com.ruoyi.system.service.impl;
|
|
import java.util.Date;
|
import java.util.List;
|
import java.util.Set;
|
import java.util.Map;
|
import java.util.HashSet;
|
import com.alibaba.fastjson2.JSON;
|
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
import com.ruoyi.common.exception.ServiceException;
|
import com.ruoyi.system.domain.TTestDataOverride;
|
import com.ruoyi.system.domain.TTestDataRevisionAction;
|
import com.ruoyi.system.domain.TTestConsoleImportRecord;
|
import com.ruoyi.system.mapper.TTestMapper;
|
import com.ruoyi.system.service.ITestConsoleDataService;
|
|
/** 控制台数据操作的边界校验和事务协调。 */
|
@Service
|
public class TestConsoleDataServiceImpl implements ITestConsoleDataService
|
{
|
private static final Set<String> DATA_TYPES = Set.of("REALTIME", "STEP", "CYCLE");
|
private static final Set<String> OPERATIONS = Set.of("EDIT", "IMPORT", "DELETE");
|
|
@Autowired
|
private TTestMapper mapper;
|
|
@Override
|
public List<TTestDataRevisionAction> selectActions(TTestDataRevisionAction query)
|
{
|
if (query == null) query = new TTestDataRevisionAction();
|
if (query.getDataType() != null && !query.getDataType().isBlank()
|
&& !DATA_TYPES.contains(query.getDataType())) throw new ServiceException("控制台数据类型无效");
|
if (query.getOperationType() != null && !query.getOperationType().isBlank()
|
&& !OPERATIONS.contains(query.getOperationType())) throw new ServiceException("数据操作类型无效");
|
return mapper.selectDataRevisionActions(query);
|
}
|
|
@Override
|
public TTestDataRevisionAction selectActionById(Long actionId)
|
{
|
if (actionId == null || actionId < 1) throw new ServiceException("修订行为ID无效");
|
return mapper.selectDataRevisionActionById(actionId);
|
}
|
|
@Override
|
public List<TTestDataOverride> selectOverrides(Long testId, String dataType)
|
{
|
validateType(testId, dataType);
|
return mapper.selectDataOverrides(testId, dataType);
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public Long submitAction(TTestDataRevisionAction action, List<TTestDataOverride> overrides,
|
List<Long> btsSampleIds, List<Long> collectorSampleIds)
|
{
|
if (action == null || action.getTestId() == null) throw new ServiceException("测试ID不能为空");
|
validateType(action.getTestId(), action.getDataType());
|
if (!OPERATIONS.contains(action.getOperationType())) throw new ServiceException("数据操作类型无效");
|
if ("IMPORT".equals(action.getOperationType()))
|
{
|
com.ruoyi.system.domain.TTest test = mapper.selectTestById(action.getTestId());
|
if (test == null || !"ENDED".equals(test.getReceptionStatus()) || !"COMPLETED".equals(test.getTestStatus()))
|
throw new ServiceException("测试完成后才可以导入数据");
|
}
|
if ("EDIT".equals(action.getOperationType())) validateEditableFields(action);
|
if (action.getReason() == null || action.getReason().trim().isEmpty()) throw new ServiceException("数据操作原因不能为空");
|
validateActionSummaries(action, overrides);
|
if (action.getOperatorName() == null || action.getOperatorName().trim().isEmpty()) action.setOperatorName("未知用户");
|
action.setReason(action.getReason().trim());
|
action.setCreatedAtUtc(new Date());
|
mapper.insertDataRevisionAction(action);
|
if ("IMPORT".equals(action.getOperationType()) && action.getChangesSummary() != null)
|
{
|
try
|
{
|
@SuppressWarnings("unchecked")
|
List<Map<String, Object>> rows = (List<Map<String, Object>>) (List<?>) JSON.parseArray(action.getChangesSummary(), Map.class);
|
Set<String> identities = new HashSet<>();
|
for (Map<String, Object> row : rows)
|
{
|
Object identity = row.get("recordNo");
|
if (identity == null || !identities.add(String.valueOf(identity)))
|
throw new ServiceException("导入数据存在重复稳定身份");
|
Object batteryNo = row.get("batteryNo");
|
if (batteryNo != null && !String.valueOf(batteryNo).isBlank()
|
&& mapper.countTestBatteryNo(action.getTestId(), String.valueOf(batteryNo)) == 0)
|
throw new ServiceException("导入数据包含不属于当前测试的电池号: " + batteryNo);
|
TTestConsoleImportRecord record = new TTestConsoleImportRecord();
|
record.setTestId(action.getTestId()); record.setDataType(action.getDataType());
|
record.setStableIdentity(String.valueOf(identity)); record.setRowValues(JSON.toJSONString(row));
|
record.setActionId(action.getActionId()); record.setCreatedAtUtc(action.getCreatedAtUtc());
|
mapper.insertConsoleImportRecord(record);
|
}
|
}
|
catch (ServiceException exception) { throw exception; }
|
catch (Exception exception) { throw new ServiceException("导入数据格式无效"); }
|
}
|
if (overrides != null)
|
{
|
Set<String> identities = new HashSet<>();
|
for (TTestDataOverride item : overrides)
|
{
|
if (item == null || item.getStableIdentity() == null || item.getStableIdentity().trim().isEmpty())
|
throw new ServiceException("数据稳定身份不能为空");
|
item.setStableIdentity(item.getStableIdentity().trim());
|
if (!identities.add(item.getStableIdentity())) throw new ServiceException("同一批数据存在重复稳定身份");
|
validateOverride(item, action.getOperationType(), action.getDataType());
|
item.setTestId(action.getTestId()); item.setDataType(action.getDataType());
|
item.setActionId(action.getActionId()); item.setCreatedAtUtc(action.getCreatedAtUtc());
|
mapper.upsertDataOverride(item);
|
}
|
}
|
// 删除仅写入 deleted 覆盖,保留设备原始采样和采集器事实供追溯。
|
// btsSampleIds/collectorSampleIds 保留在接口中兼容旧调用方,但不再执行物理删除。
|
return action.getActionId();
|
}
|
|
private void validateType(Long testId, String dataType)
|
{
|
if (testId == null || testId < 1) throw new ServiceException("测试ID无效");
|
if (!DATA_TYPES.contains(dataType)) throw new ServiceException("控制台数据类型无效");
|
}
|
|
private void validateEditableFields(TTestDataRevisionAction action)
|
{
|
if (action.getChangesSummary() == null || action.getChangesSummary().isBlank())
|
throw new ServiceException("修改内容不能为空");
|
try
|
{
|
@SuppressWarnings("unchecked")
|
List<Map<String, Object>> changes = (List<Map<String, Object>>) (List<?>) JSON.parseArray(action.getChangesSummary(), Map.class);
|
Set<String> editable = switch (action.getDataType())
|
{
|
case "REALTIME" -> Set.of("voltage", "current", "capacity", "energy");
|
case "STEP" -> Set.of("stepDuration", "capacity", "energy", "startVoltage", "endVoltage",
|
"chargePeakVoltage", "dischargeValleyVoltage");
|
case "CYCLE" -> Set.of("chargeCapacity", "dischargeCapacity", "efficiency", "chargeEnergy",
|
"dischargeEnergy");
|
default -> Set.of();
|
};
|
Set<String> immutable = Set.of("recordNo", "batteryNo", "cycleNo", "stepNo", "stepName",
|
"sequenceId", "recordedAt", "absoluteTime");
|
for (Map<String, Object> item : changes)
|
{
|
Map<String, Object> before = objectMap(item.get("before"));
|
Map<String, Object> after = objectMap(item.get("after"));
|
for (String key : immutable)
|
if (!java.util.Objects.equals(before.get(key), after.get(key)))
|
throw new ServiceException("记录身份字段不可修改: " + key);
|
for (String key : after.keySet())
|
if (!java.util.Objects.equals(before.get(key), after.get(key)) && !editable.contains(key))
|
throw new ServiceException("字段不可修改: " + key);
|
}
|
}
|
catch (ServiceException exception) { throw exception; }
|
catch (Exception exception) { throw new ServiceException("修改内容格式无效"); }
|
}
|
|
private void validateOverride(TTestDataOverride item, String operation, String dataType)
|
{
|
if ("DELETE".equals(operation) && parseDeleteIdentity(item.getStableIdentity(), dataType) == null)
|
throw new ServiceException("删除数据稳定身份无效");
|
if (!"EDIT".equals(operation) || item.getFieldValues() == null || item.getFieldValues().isBlank()) return;
|
try
|
{
|
Map<String, Object> values = JSON.parseObject(item.getFieldValues(), Map.class);
|
for (String key : values.keySet())
|
if (!editableFields(dataType).contains(key)) throw new ServiceException("字段不可修改: " + key);
|
}
|
catch (ServiceException exception) { throw exception; }
|
catch (Exception exception) { throw new ServiceException("修改字段格式无效"); }
|
}
|
|
/** 防止客户端提交与实际覆盖身份不一致的伪行为摘要。 */
|
private void validateActionSummaries(TTestDataRevisionAction action, List<TTestDataOverride> overrides)
|
{
|
if (action.getScopeSummary() == null || action.getScopeSummary().trim().isEmpty())
|
throw new ServiceException("数据操作范围不能为空");
|
Set<String> summaryIdentities = new HashSet<>();
|
try
|
{
|
Object parsed = JSON.parse(action.getIdentitySummary());
|
if (!(parsed instanceof List<?> values)) throw new ServiceException("身份摘要格式无效");
|
for (Object value : values)
|
if (value == null || !summaryIdentities.add(String.valueOf(value)))
|
throw new ServiceException("身份摘要存在重复或空值");
|
}
|
catch (ServiceException exception) { throw exception; }
|
catch (Exception exception) { throw new ServiceException("身份摘要格式无效"); }
|
if (overrides == null) return;
|
Set<String> overrideIdentities = new HashSet<>();
|
for (TTestDataOverride item : overrides)
|
if (item == null || !overrideIdentities.add(item.getStableIdentity())
|
|| !summaryIdentities.contains(item.getStableIdentity()))
|
throw new ServiceException("身份摘要与数据覆盖明细不一致");
|
if (!summaryIdentities.equals(overrideIdentities))
|
throw new ServiceException("身份摘要与数据覆盖明细不一致");
|
if ("EDIT".equals(action.getOperationType()))
|
{
|
try
|
{
|
Object parsed = JSON.parse(action.getChangesSummary());
|
if (!(parsed instanceof List<?> changes) || changes.size() < overrides.size())
|
throw new ServiceException("字段变化摘要与数据覆盖明细不一致");
|
}
|
catch (ServiceException exception) { throw exception; }
|
catch (Exception exception) { throw new ServiceException("字段变化摘要格式无效"); }
|
}
|
}
|
|
private Set<String> editableFields(String dataType)
|
{
|
return switch (dataType)
|
{
|
case "REALTIME" -> Set.of("voltage", "current", "capacity", "energy");
|
case "STEP" -> Set.of("stepDuration", "capacity", "energy", "startVoltage", "endVoltage",
|
"chargePeakVoltage", "dischargeValleyVoltage");
|
case "CYCLE" -> Set.of("chargeCapacity", "dischargeCapacity", "efficiency", "chargeEnergy",
|
"dischargeEnergy");
|
default -> Set.of();
|
};
|
}
|
|
private Long[] parseDeleteIdentity(String value, String dataType)
|
{
|
String[] parts = value.split(":", -1);
|
int expectedLength = "STEP".equals(dataType) ? 5 : "CYCLE".equals(dataType) ? 4 : 3;
|
if (parts.length != expectedLength) return null;
|
int offset = "REALTIME".equals(dataType) ? 0 : 1;
|
try
|
{
|
if ("STEP".equals(dataType))
|
return new Long[] { Long.valueOf(parts[offset]), Long.valueOf(parts[offset + 1]),
|
Long.valueOf(parts[offset + 2]), Long.valueOf(parts[offset + 3]) };
|
return new Long[] { Long.valueOf(parts[offset]), Long.valueOf(parts[offset + 1]),
|
Long.valueOf(parts[offset + 2]) };
|
}
|
catch (NumberFormatException exception) { return null; }
|
}
|
|
@SuppressWarnings("unchecked")
|
private Map<String, Object> objectMap(Object value)
|
{
|
return value instanceof Map ? (Map<String, Object>) value : Map.of();
|
}
|
}
|