From 90b6d6768ebeebc44863850289a4696203a0cda9 Mon Sep 17 00:00:00 2001
From: yeg <1347747024@qq.com>
Date: 星期三, 09 九月 2026 12:25:33 +0800
Subject: [PATCH] 按三类数据分别保存工作簿导入修订
---
battery-business/src/main/java/com/battery/business/service/impl/TestConsoleDataServiceImpl.java | 37 ++++++++---
battery-business/src/main/java/com/battery/business/service/ITestConsoleDataService.java | 2
ruoyi-admin/src/test/java/com/battery/business/web/ConsoleSingleSheetImportDatabaseTest.java | 51 +++++++++++++++--
ruoyi-admin/src/main/java/com/battery/business/web/TTestController.java | 5 +
ruoyi-admin/src/test/java/com/battery/business/service/impl/TestConsoleDataRevisionQueryServiceTest.java | 7 ++
battery-business/src/main/java/com/battery/business/domain/TTestDataRevisionAction.java | 6 ++
battery-business/src/main/resources/mapper/battery/TTestMapper.xml | 2
ruoyi-admin/src/test/java/com/battery/business/web/ConsoleWorkbookImportMysqlTest.java | 30 +++++-----
8 files changed, 104 insertions(+), 36 deletions(-)
diff --git a/battery-business/src/main/java/com/battery/business/domain/TTestDataRevisionAction.java b/battery-business/src/main/java/com/battery/business/domain/TTestDataRevisionAction.java
index c54404d..b4e72cd 100644
--- a/battery-business/src/main/java/com/battery/business/domain/TTestDataRevisionAction.java
+++ b/battery-business/src/main/java/com/battery/business/domain/TTestDataRevisionAction.java
@@ -38,6 +38,12 @@
/** 本次提交结果,不持久化到修订事实。 */
private int importedCount;
private int skippedCount;
+ /** 本次提交生成的全部修订编号,仅用于提交响应,不持久化。 */
+ @JsonIgnore
+ private java.util.List<Long> actionIds = java.util.List.of();
+
+ public java.util.List<Long> getActionIds() { return actionIds; }
+ public void setActionIds(java.util.List<Long> value) { actionIds = value; }
public int getImportedCount() { return importedCount; }
public void setImportedCount(int value) { importedCount = value; }
diff --git a/battery-business/src/main/java/com/battery/business/service/ITestConsoleDataService.java b/battery-business/src/main/java/com/battery/business/service/ITestConsoleDataService.java
index 8fbfc33..7754946 100644
--- a/battery-business/src/main/java/com/battery/business/service/ITestConsoleDataService.java
+++ b/battery-business/src/main/java/com/battery/business/service/ITestConsoleDataService.java
@@ -15,6 +15,6 @@
- /** 在测试行锁和事务内执行一次修订;ALL 导入共用修订编号作为三 Sheet 批次号。 */
+ /** 在测试行锁和事务内执行提交;三 Sheet 按类型各生成修订,全部编号写入 action。 */
Long submitAction(TTestDataRevisionAction action, List<DataRevisionItem> overrides);
}
diff --git a/battery-business/src/main/java/com/battery/business/service/impl/TestConsoleDataServiceImpl.java b/battery-business/src/main/java/com/battery/business/service/impl/TestConsoleDataServiceImpl.java
index 7bf81d0..547d62e 100644
--- a/battery-business/src/main/java/com/battery/business/service/impl/TestConsoleDataServiceImpl.java
+++ b/battery-business/src/main/java/com/battery/business/service/impl/TestConsoleDataServiceImpl.java
@@ -33,7 +33,7 @@
@Override public List<TTestDataRevisionAction> selectActions(TTestDataRevisionAction query)
{
if (query == null) query = new TTestDataRevisionAction();
- if (query.getDataType() != null && !Set.of("REALTIME", "STEP", "CYCLE", "ALL").contains(query.getDataType())) throw new ServiceException("数据类型无效");
+ if (query.getDataType() != null && !Set.of("REALTIME", "STEP", "CYCLE").contains(query.getDataType())) throw new ServiceException("数据类型无效");
if (query.getOperationType() != null && !Set.of("EDIT", "IMPORT", "DELETE").contains(query.getOperationType())) throw new ServiceException("操作类型无效");
return mapper.selectDataRevisionActions(query);
}
@@ -44,7 +44,7 @@
/**
* 一次提交与接收、测试整体删除互斥;任一目标无效时整次动作回滚。导入仅允许检测完成的测试。
- * @return 新增修订编号;导入全部重复时返回 null,不新增修订,跳过数量写入 action。
+ * @return 首条新增修订编号,全部编号写入 action;导入全部重复时返回 null,不新增修订。
*/
@Override @Transactional(rollbackFor = Exception.class)
public Long submitAction(TTestDataRevisionAction action, List<DataRevisionItem> items)
@@ -59,6 +59,7 @@
if (test == null) throw new ServiceException("测试不存在");
action.setCreatedAtUtc(new Date());
action.setReason(action.getReason().trim());
+ action.setActionIds(List.of());
List<Map<String, Object>> before = new ArrayList<>();
List<Map<String, Object>> changes = new ArrayList<>();
List<Runnable> mutations = new ArrayList<>();
@@ -68,23 +69,38 @@
boolean workbook = "ALL".equals(action.getDataType());
Map<String, List<Map<String, Object>>> sheets = importSheets(action, workbook);
List<String> errors = new ArrayList<>();
- List<String> addedIdentities = new ArrayList<>();
+ List<TTestDataRevisionAction> revisions = new ArrayList<>();
Map<String, Map<String, Object>> bindings = new LinkedHashMap<>();
Set<String> expectedBatteries = null;
action.setImportedCount(0); action.setSkippedCount(0);
for (var sheet : sheets.entrySet())
{
+ List<String> addedIdentities = new ArrayList<>();
+ List<Map<String, Object>> sheetChanges = new ArrayList<>();
Set<String> batteries = prepareImport(action, sheet.getKey(), sheet.getValue(), workbook, bindings,
- errors, addedIdentities, changes, mutations);
+ errors, addedIdentities, sheetChanges, mutations);
if (workbook && expectedBatteries != null && !expectedBatteries.equals(batteries))
errors.add(sheetLabel(sheet.getKey()) + "工作表,列「电池号」:三个 Sheet 的电池号集合不一致");
expectedBatteries = batteries;
+ var revision = new TTestDataRevisionAction();
+ revision.setTestId(action.getTestId()); revision.setDataType(sheet.getKey()); revision.setOperationType("IMPORT");
+ revision.setScopeSummary("导入" + sheetChanges.size() + "条" + sheetLabel(sheet.getKey()) + "数据");
+ revision.setIdentitySummary(JSON.toJSONString(addedIdentities));
+ revision.setChangesSummary(JSON.toJSONString(sheetChanges)); revision.setBeforeContentJson("[]");
+ revision.setOperatorId(action.getOperatorId()); revision.setOperatorName(action.getOperatorName());
+ revision.setReason(action.getReason()); revision.setCreatedAtUtc(action.getCreatedAtUtc());
+ revisions.add(revision);
+ action.setImportedCount(action.getImportedCount() + sheetChanges.size());
}
- // 三类全量校验后才执行写入,共享测试行锁、事务和唯一修订编号;失败不留下部分数据或修订。
+ // ALL 仅为请求调度标记;三类全量校验后按类型写修订,共享测试行锁和事务。
+ // 整批全重复不留修订;部分重复仍分别记录各类实际新增数量,失败全部回滚。
if (!errors.isEmpty()) throw new ServiceException(String.join(";", errors));
- action.setImportedCount(changes.size());
- action.setIdentitySummary(JSON.toJSONString(addedIdentities));
- if (changes.isEmpty()) return null;
+ if (action.getImportedCount() == 0) return null;
+ revisions.forEach(mapper::insertDataRevisionAction);
+ mutations.forEach(Runnable::run);
+ action.setActionIds(revisions.stream().map(TTestDataRevisionAction::getActionId).toList());
+ action.setActionId(action.getActionIds().get(0));
+ return action.getActionId();
}
else
{
@@ -129,6 +145,7 @@
action.setChangesSummary(JSON.toJSONString("DELETE".equals(action.getOperationType()) ? before : changes));
mapper.insertDataRevisionAction(action);
mutations.forEach(Runnable::run);
+ action.setActionIds(List.of(action.getActionId()));
return action.getActionId();
}
@@ -186,11 +203,9 @@
continue;
}
identities.put(identity, values);
- String revisionIdentity = workbook ? type + ":" + identity : identity;
- addedIdentities.add(revisionIdentity);
+ addedIdentities.add(identity);
mutations.add(() -> mapper.insertImportedDataRecord(type, values));
Map<String, Object> change = new LinkedHashMap<>(row);
- if (workbook) { change.put("dataType", type); change.put("identity", revisionIdentity); }
changes.add(change);
}
catch (ServiceException ex) { errors.add((workbook ? sheetLabel(type) + "工作表," : "") + "第" + sourceRow + "行," + ex.getMessage()); }
diff --git a/battery-business/src/main/resources/mapper/battery/TTestMapper.xml b/battery-business/src/main/resources/mapper/battery/TTestMapper.xml
index 0f82dbb..00e2240 100644
--- a/battery-business/src/main/resources/mapper/battery/TTestMapper.xml
+++ b/battery-business/src/main/resources/mapper/battery/TTestMapper.xml
@@ -201,7 +201,7 @@
<where>
<if test="testId != null">and a.test_id = #{testId}</if>
<if test="testName != null and testName != ''">and t.test_name like concat('%', #{testName}, '%')</if>
- <if test="dataType != null and dataType != ''">and (a.data_type = #{dataType} or a.data_type = 'ALL')</if>
+ <if test="dataType != null and dataType != ''">and a.data_type = #{dataType}</if>
<if test="operationType != null and operationType != ''">and a.operation_type = #{operationType}</if>
<if test="beginCreatedAt != null">and a.created_at_utc >= #{beginCreatedAt}</if>
<if test="endCreatedAt != null">and a.created_at_utc < #{endCreatedAt}</if>
diff --git a/ruoyi-admin/src/main/java/com/battery/business/web/TTestController.java b/ruoyi-admin/src/main/java/com/battery/business/web/TTestController.java
index 909c96a..e57caa6 100644
--- a/ruoyi-admin/src/main/java/com/battery/business/web/TTestController.java
+++ b/ruoyi-admin/src/main/java/com/battery/business/web/TTestController.java
@@ -199,7 +199,7 @@
return success(testDataQueryService.cycles(testId));
}
- /** 一次提交编辑、导入或删除;服务端只追加一条修订行为。 */
+ /** 一次提交编辑、导入或删除;三 Sheet 按数据类型分别追加修订,共享同一事务。 */
@PreAuthorize("@ss.hasPermi('system:test:edit')")
@Log(title = "测试控制台数据修订", businessType = BusinessType.UPDATE)
@PostMapping("/{testId}/console-data/actions")
@@ -213,7 +213,8 @@
action.setOperatorId(getUserId()); action.setOperatorName(getUsername());
Long actionId = consoleDataService.submitAction(action,
request.overrides() == null ? java.util.Collections.emptyList() : request.overrides());
- return success().put("actionId", actionId).put("importedCount", action.getImportedCount()).put("skippedCount", action.getSkippedCount());
+ return success().put("actionId", actionId).put("actionIds", action.getActionIds())
+ .put("importedCount", action.getImportedCount()).put("skippedCount", action.getSkippedCount());
}
public record ConsoleDataActionRequest(String dataType, String operationType, String scopeSummary,
diff --git a/ruoyi-admin/src/test/java/com/battery/business/service/impl/TestConsoleDataRevisionQueryServiceTest.java b/ruoyi-admin/src/test/java/com/battery/business/service/impl/TestConsoleDataRevisionQueryServiceTest.java
index 181e2da..8791830 100644
--- a/ruoyi-admin/src/test/java/com/battery/business/service/impl/TestConsoleDataRevisionQueryServiceTest.java
+++ b/ruoyi-admin/src/test/java/com/battery/business/service/impl/TestConsoleDataRevisionQueryServiceTest.java
@@ -18,6 +18,13 @@
private final TTestMapper mapper = mock(TTestMapper.class);
private final TestConsoleDataServiceImpl service = new TestConsoleDataServiceImpl(mapper);
+ @org.junit.jupiter.api.BeforeEach void generatedRevisionId()
+ {
+ when(mapper.insertDataRevisionAction(any())).thenAnswer(call -> {
+ ((TTestDataRevisionAction) call.getArgument(0)).setActionId(1L); return 1;
+ });
+ }
+
@Test void savesDatabaseBeforeContentInsteadOfClientClaims()
{
var action = action("EDIT", "realtime:7");
diff --git a/ruoyi-admin/src/test/java/com/battery/business/web/ConsoleSingleSheetImportDatabaseTest.java b/ruoyi-admin/src/test/java/com/battery/business/web/ConsoleSingleSheetImportDatabaseTest.java
index bc52da0..9290b0e 100644
--- a/ruoyi-admin/src/test/java/com/battery/business/web/ConsoleSingleSheetImportDatabaseTest.java
+++ b/ruoyi-admin/src/test/java/com/battery/business/web/ConsoleSingleSheetImportDatabaseTest.java
@@ -177,7 +177,7 @@
assertEquals(1, service.selectActions(new TTestDataRevisionAction()).size());
}
- @Test void workbookImportsThreeSheetsWithOneRevisionAndSkipsRetry()
+ @Test void workbookImportsThreeSheetsWithSeparateRevisionsAndSkipsRetry()
{
var sheets = Map.of("REALTIME", List.of(row(), row()), "STEP", List.of(row()), "CYCLE", List.of(row()));
var result = submitWorkbook(sheets);
@@ -191,14 +191,22 @@
assertEquals(1, service.selectActions(query).size());
}
var actions = service.selectActions(new TTestDataRevisionAction());
- assertEquals(1, actions.size());
- assertEquals("ALL", actions.get(0).getDataType());
+ assertEquals(3, actions.size());
+ assertEquals(3, ((List<?>) result.get("actionIds")).size());
+ assertEquals(java.util.Set.of("REALTIME", "STEP", "CYCLE"), actions.stream().map(TTestDataRevisionAction::getDataType).collect(java.util.stream.Collectors.toSet()));
+ for (var revision : actions)
+ {
+ String label = switch (revision.getDataType()) { case "REALTIME" -> "实时"; case "STEP" -> "工步"; default -> "循环"; };
+ assertEquals("导入1条" + label + "数据", revision.getScopeSummary());
+ assertEquals(1L, service.selectActionById(revision.getActionId()).getAffectedRecordCount());
+ }
assertEquals("三页验收", actions.get(0).getReason());
var retry = submitWorkbook(sheets);
assertEquals(0, retry.get("importedCount"));
assertEquals(4, retry.get("skippedCount"));
assertNull(retry.get("actionId"));
- assertEquals(1, service.selectActions(new TTestDataRevisionAction()).size());
+ assertEquals(List.of(), retry.get("actionIds"));
+ assertEquals(3, service.selectActions(new TTestDataRevisionAction()).size());
}
private com.ruoyi.common.core.domain.AjaxResult submitWorkbook(Map<String, List<Map<String, Object>>> sheets)
@@ -213,6 +221,29 @@
jdbc.execute("alter table t_test_cycle_record add check (discharge_energy_wh <> 999)");
assertThrows(RuntimeException.class, () -> submitWorkbook(Map.of("REALTIME", List.of(row()), "STEP", List.of(row()), "CYCLE", List.of(cycle))));
assertWorkbookEmpty();
+ }
+
+ @Test void workbookRevisionFailureRollsBackEarlierRevisionsAndAllData()
+ {
+ jdbc.execute("alter table t_test_data_revision_action add check (data_type <> 'CYCLE')");
+ assertThrows(RuntimeException.class, () -> submitWorkbook(Map.of("REALTIME", List.of(row()), "STEP", List.of(row()), "CYCLE", List.of(row()))));
+ assertWorkbookEmpty();
+ }
+
+ @Test void partiallyRepeatedWorkbookRecordsActualPerTypeCounts()
+ {
+ submit("REALTIME", List.of(row()));
+ var result = submitWorkbook(Map.of("REALTIME", List.of(row()), "STEP", List.of(row()), "CYCLE", List.of(row())));
+ assertEquals(2, result.get("importedCount"));
+ assertEquals(1, result.get("skippedCount"));
+ assertEquals(3, ((List<?>) result.get("actionIds")).size());
+ var realtime = new TTestDataRevisionAction(); realtime.setDataType("REALTIME");
+ var revisions = service.selectActions(realtime);
+ assertEquals(2, revisions.size());
+ assertEquals("导入0条实时数据", revisions.get(0).getScopeSummary());
+ assertEquals(0L, service.selectActionById(revisions.get(0).getActionId()).getAffectedRecordCount());
+ var invalid = new TTestDataRevisionAction(); invalid.setDataType("ALL");
+ assertThrows(RuntimeException.class, () -> service.selectActions(invalid));
}
@Test void workbookMissingEmptyInvalidOrConflictingSheetNeverWritesAnySheet()
@@ -302,13 +333,21 @@
void suppliedPartyWorkbookImportsAllRowsAndSkipsEveryRetry() throws Exception
{
var sample = JSON.parseObject(java.nio.file.Files.readString(java.nio.file.Path.of(System.getenv("TICKET03_SAMPLE_JSON"))));
+ Map<String, List<Map<String, Object>>> sheets = new LinkedHashMap<>();
+ for (String type : List.of("REALTIME", "STEP", "CYCLE")) sheets.put(type, (List) sample.getJSONArray(type).toJavaList(Map.class));
+ assertEquals(7767, submitWorkbook(sheets).get("importedCount"));
+ assertEquals(7767, submitWorkbook(sheets).get("skippedCount"));
for (String type : List.of("REALTIME", "STEP", "CYCLE"))
{
List<Map<String, Object>> rows = (List) sample.getJSONArray(type).toJavaList(Map.class);
int count = switch (type) { case "REALTIME" -> 7605; case "STEP" -> 129; default -> 33; };
- assertEquals(count, submit(type, rows).get("importedCount"));
assertEquals(count, mapper.selectImportDataRecords(1L, type).size());
- assertEquals(count, submit(type, rows).get("skippedCount"));
+ var query = new TTestDataRevisionAction(); query.setDataType(type);
+ var actions = service.selectActions(query);
+ assertEquals(1, actions.size());
+ assertEquals((long) count, service.selectActionById(actions.get(0).getActionId()).getAffectedRecordCount());
+ String label = switch (type) { case "REALTIME" -> "实时"; case "STEP" -> "工步"; default -> "循环"; };
+ assertEquals("导入" + count + "条" + label + "数据", actions.get(0).getScopeSummary());
if ("STEP".equals(type))
{
var persisted = mapper.selectImportDataRecords(1L, type).stream().collect(java.util.stream.Collectors.toMap(
diff --git a/ruoyi-admin/src/test/java/com/battery/business/web/ConsoleWorkbookImportMysqlTest.java b/ruoyi-admin/src/test/java/com/battery/business/web/ConsoleWorkbookImportMysqlTest.java
index 08efa77..99ebdc8 100644
--- a/ruoyi-admin/src/test/java/com/battery/business/web/ConsoleWorkbookImportMysqlTest.java
+++ b/ruoyi-admin/src/test/java/com/battery/business/web/ConsoleWorkbookImportMysqlTest.java
@@ -102,20 +102,20 @@
var firstRecord = json.readTree(json.writeValueAsString(controller.persistedRecords(1L))).get("data").get(7604);
assertEquals(1683517331000L, firstRecord.get("deviceTime").longValue());
var actions = service.selectActions(new TTestDataRevisionAction());
- assertEquals(1, actions.size());
- var revision = service.selectActionById(actions.get(0).getActionId());
- assertEquals(imported.get("actionId"), revision.getActionId());
- assertEquals("三 Sheet 共用原因", revision.getReason());
- assertEquals(7767L, revision.getAffectedRecordCount());
- assertNull(revision.getIdentitySummary());
- assertNull(revision.getChangesSummary());
- assertNull(revision.getBeforeContentJson());
- // 页面仅返回摘要;仍从数据库原文验证三类审计完整保存。
- var changes = JSON.parseArray(jdbc.queryForObject("select cast(changes_summary as char) from t_test_data_revision_action where action_id=?", String.class, revision.getActionId()));
- assertEquals(7767, changes.size());
- assertEquals("REALTIME", changes.getJSONObject(0).getString("dataType"));
- assertEquals("STEP", changes.getJSONObject(7605).getString("dataType"));
- assertEquals("CYCLE", changes.getJSONObject(7734).getString("dataType"));
+ assertEquals(3, actions.size());
+ assertEquals(3, ((List<?>) imported.get("actionIds")).size());
+ for (var item : actions) {
+ var revision = service.selectActionById(item.getActionId());
+ assertEquals("三 Sheet 共用原因", revision.getReason());
+ int expected = switch (revision.getDataType()) { case "REALTIME" -> 7605; case "STEP" -> 129; default -> 33; };
+ assertEquals((long) expected, revision.getAffectedRecordCount());
+ assertNull(revision.getIdentitySummary());
+ assertNull(revision.getChangesSummary());
+ assertNull(revision.getBeforeContentJson());
+ // 页面只返回各类摘要,数据库仍保留该类全部导入明细。
+ var changes = JSON.parseArray(jdbc.queryForObject("select cast(changes_summary as char) from t_test_data_revision_action where action_id=?", String.class, revision.getActionId()));
+ assertEquals(expected, changes.size());
+ }
for (String type : sheets.keySet()) {
var query = new TTestDataRevisionAction(); query.setTestId(1L); query.setDataType(type);
assertEquals(1, service.selectActions(query).size());
@@ -124,7 +124,7 @@
assertEquals(0, retry.get("importedCount")); assertEquals(7767, retry.get("skippedCount")); assertNull(retry.get("actionId"));
sheets.get("CYCLE").get(32).put("dischargeCapacity", 99);
assertThrows(RuntimeException.class, () -> submit(controller, sheets));
- assertEquals(1, service.selectActions(new TTestDataRevisionAction()).size());
+ assertEquals(3, service.selectActions(new TTestDataRevisionAction()).size());
assertTrue(mapper.selectWarningExceptions(1L, null).isEmpty());
assertEquals("COMPLETED", mapper.selectTestById(1L).getTestStatus());
assertEquals("ENDED", mapper.selectTestById(1L).getReceptionStatus());
--
Gitblit v1.7.1