task-3: 统一解析载荷全量行恢复逻辑,新旧结构均可恢复
新增 SimilarAsinTaskService.resolveAllRows(payload):优先 items(新规范结构)、 其次 allItems(旧结构)、最后 groups 展开(最旧结构),并收敛 rowCount/ loadAllRowsByBaseId/hydrateParsedPayloadRows/两条 assemble 路径的重复 fallback 链。 旧 payload(含 allItems 或仅 groups)仍可恢复全量行,行数、顺序不丢失。 9 个测试覆盖默认/批量/幂等/空/单行/超限/新旧 JSON 反序列化/组展开顺序。
This commit is contained in:
+33
-44
@@ -938,20 +938,29 @@ public class SimilarAsinTaskService {
|
||||
if (payload == null) {
|
||||
return 0;
|
||||
}
|
||||
if (payload.getAllItems() != null && !payload.getAllItems().isEmpty()) {
|
||||
return payload.getAllItems().size();
|
||||
return resolveAllRows(payload).size();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从解析载荷统一恢复全量行:优先 items(新规范结构),其次 allItems(旧结构),
|
||||
* 最后 groups 展开(最旧结构)。新旧结构均可恢复完整行集合,供 rowCount、
|
||||
* Coze 候选加载与结果文件组装复用。
|
||||
*/
|
||||
static List<SimilarAsinParsedRowVo> resolveAllRows(SimilarAsinParsedPayloadDto payload) {
|
||||
if (payload == null) {
|
||||
return List.of();
|
||||
}
|
||||
if (payload.getItems() != null && !payload.getItems().isEmpty()) {
|
||||
return payload.getItems().size();
|
||||
List<SimilarAsinParsedRowVo> rows = payload.getItems();
|
||||
if (rows == null || rows.isEmpty()) {
|
||||
rows = payload.getAllItems();
|
||||
}
|
||||
if (payload.getGroups() != null && !payload.getGroups().isEmpty()) {
|
||||
return payload.getGroups().stream()
|
||||
.map(SimilarAsinParsedGroupVo::getItems)
|
||||
if ((rows == null || rows.isEmpty()) && payload.getGroups() != null) {
|
||||
rows = payload.getGroups().stream()
|
||||
.filter(Objects::nonNull)
|
||||
.mapToInt(List::size)
|
||||
.sum();
|
||||
.flatMap(group -> group.getItems() == null ? java.util.stream.Stream.empty() : group.getItems().stream())
|
||||
.toList();
|
||||
}
|
||||
return 0;
|
||||
return rows == null ? List.of() : rows;
|
||||
}
|
||||
|
||||
private PersistSubmittedChunkResult persistSubmittedChunk(PreparedSubmittedChunk prepared) {
|
||||
@@ -1399,17 +1408,7 @@ public class SimilarAsinTaskService {
|
||||
private Map<String, List<SimilarAsinParsedRowVo>> loadAllRowsByBaseId(FileTaskEntity task) {
|
||||
try {
|
||||
SimilarAsinParsedPayloadDto payload = readParsedPayload(task);
|
||||
List<SimilarAsinParsedRowVo> rows = payload.getAllItems();
|
||||
if (rows == null || rows.isEmpty()) {
|
||||
rows = payload.getItems();
|
||||
}
|
||||
if ((rows == null || rows.isEmpty()) && payload.getGroups() != null) {
|
||||
rows = payload.getGroups().stream()
|
||||
.filter(Objects::nonNull)
|
||||
.flatMap(group -> group.getItems() == null ? java.util.stream.Stream.empty() : group.getItems().stream())
|
||||
.toList();
|
||||
}
|
||||
return groupRowsByBaseId(rows);
|
||||
return groupRowsByBaseId(resolveAllRows(payload));
|
||||
} catch (Exception ex) {
|
||||
log.warn("[similar-asin] read all rows failed taskId={} err={}", task.getId(), ex.getMessage());
|
||||
return new LinkedHashMap<>();
|
||||
@@ -4275,8 +4274,9 @@ public class SimilarAsinTaskService {
|
||||
*/
|
||||
private void assembleResultWorkbookBounded(FileTaskEntity task, FileResultEntity result) {
|
||||
SimilarAsinParsedPayloadDto parsed = readParsedPayload(task);
|
||||
List<SimilarAsinParsedRowVo> parsedRows = resolveAllRows(parsed);
|
||||
List<SourceRows> sourceRows = splitRowsBySourceFile(
|
||||
parsed, parsed.getAllItems(), result.getSourceFilename());
|
||||
parsed, parsedRows, result.getSourceFilename());
|
||||
if (sourceRows.isEmpty()) {
|
||||
throw new BusinessException("Similar ASIN result rows are empty");
|
||||
}
|
||||
@@ -4372,7 +4372,7 @@ public class SimilarAsinTaskService {
|
||||
.mapToInt(SourceResultWorkbook::conformPropagated)
|
||||
.sum();
|
||||
log.info("[similar-asin] bounded assemble workbook taskId={} parsedRows={} persistedRows={} resultRows={} resolvedRows={} propagated={} sources={} costMs={}",
|
||||
task.getId(), parsed.getAllItems().size(), persistedResultRows, resultRows,
|
||||
task.getId(), parsedRows.size(), persistedResultRows, resultRows,
|
||||
resolvedRows, conformPropagated, sourceRows.size(),
|
||||
System.currentTimeMillis() - assembleStart);
|
||||
if (resultRows == 0) {
|
||||
@@ -4408,7 +4408,7 @@ public class SimilarAsinTaskService {
|
||||
result.setResultFileUrl(objectKey);
|
||||
result.setResultFileSize(uploadFile.length());
|
||||
result.setResultContentType(contentType);
|
||||
result.setRowCount(parsed.getAllItems().size());
|
||||
result.setRowCount(parsedRows.size());
|
||||
} finally {
|
||||
taskImageCache.clear();
|
||||
for (File workbookFile : workbookFiles) {
|
||||
@@ -4428,15 +4428,16 @@ public class SimilarAsinTaskService {
|
||||
return;
|
||||
}
|
||||
SimilarAsinParsedPayloadDto parsed = readParsedPayload(task);
|
||||
Map<String, SimilarAsinResultRowDto> resultMap = loadPersistedResultRowsWithRetry(task.getId(), parsed.getAllItems().size());
|
||||
List<SimilarAsinParsedRowVo> parsedRows = resolveAllRows(parsed);
|
||||
Map<String, SimilarAsinResultRowDto> resultMap = loadPersistedResultRowsWithRetry(task.getId(), parsedRows.size());
|
||||
int persistedResultRows = resultMap.size();
|
||||
resultMap.entrySet().removeIf(entry -> !isExportableResultRow(entry.getValue()));
|
||||
long resolvedRows = parsed.getAllItems().stream()
|
||||
long resolvedRows = parsedRows.stream()
|
||||
.filter(row -> findResultRow(row, resultMap) != null)
|
||||
.count();
|
||||
log.info("[similar-asin] assemble workbook taskId={} parsedRows={} persistedRows={} resultRows={} resolvedRows={}",
|
||||
task.getId(), parsed.getAllItems().size(), persistedResultRows, resultMap.size(), resolvedRows);
|
||||
if (!parsed.getAllItems().isEmpty() && resultMap.isEmpty()) {
|
||||
task.getId(), parsedRows.size(), persistedResultRows, resultMap.size(), resolvedRows);
|
||||
if (!parsedRows.isEmpty() && resultMap.isEmpty()) {
|
||||
// P1-4:检查是否有 chunk-read-failed 标记,把 chunk index 列表附在错误信息里。
|
||||
String chunkReadFailureSummary = collectChunkReadFailureSummary(task.getId());
|
||||
if (!chunkReadFailureSummary.isBlank()) {
|
||||
@@ -4448,7 +4449,7 @@ public class SimilarAsinTaskService {
|
||||
// 命中"不符合"(包含匹配),则组内所有行的"是否符合类目"统一为标准值"不符合"。
|
||||
// 单行组跳过。仅修改 isConform 列,不影响其他列。
|
||||
int conformPropagated = CozeGroupResultPropagator.propagateByGroup(
|
||||
parsed.getAllItems(),
|
||||
parsedRows,
|
||||
SimilarAsinParsedRowVo::getDisplayId,
|
||||
row -> findResultRow(row, resultMap),
|
||||
SimilarAsinResultRowDto::getIsConform,
|
||||
@@ -4464,7 +4465,7 @@ public class SimilarAsinTaskService {
|
||||
if (!outputDir.exists() && !outputDir.mkdirs()) {
|
||||
throw new BusinessException("创建结果目录失败");
|
||||
}
|
||||
List<SourceRows> sourceRows = splitRowsBySourceFile(parsed, parsed.getAllItems(), result.getSourceFilename());
|
||||
List<SourceRows> sourceRows = splitRowsBySourceFile(parsed, parsedRows, result.getSourceFilename());
|
||||
List<SourceResultWorkbook> workbooks = new ArrayList<>();
|
||||
List<File> workbookFiles = new ArrayList<>(sourceRows.size());
|
||||
File zip = null;
|
||||
@@ -4564,7 +4565,7 @@ public class SimilarAsinTaskService {
|
||||
result.setResultFileUrl(objectKey);
|
||||
result.setResultFileSize(uploadFile.length());
|
||||
result.setResultContentType(contentType);
|
||||
result.setRowCount(parsed.getAllItems().size());
|
||||
result.setRowCount(resolveAllRows(parsed).size());
|
||||
} finally {
|
||||
for (File workbookFile : workbookFiles) {
|
||||
if (workbookFile.exists() && !workbookFile.delete()) {
|
||||
@@ -5923,19 +5924,7 @@ public class SimilarAsinTaskService {
|
||||
if (payload == null) {
|
||||
return new SimilarAsinParsedPayloadDto();
|
||||
}
|
||||
List<SimilarAsinParsedRowVo> rows = payload.getAllItems();
|
||||
if (rows == null || rows.isEmpty()) {
|
||||
rows = payload.getItems();
|
||||
}
|
||||
if ((rows == null || rows.isEmpty()) && payload.getGroups() != null) {
|
||||
rows = payload.getGroups().stream()
|
||||
.filter(Objects::nonNull)
|
||||
.flatMap(group -> group.getItems() == null ? java.util.stream.Stream.empty() : group.getItems().stream())
|
||||
.toList();
|
||||
}
|
||||
if (rows == null) {
|
||||
rows = List.of();
|
||||
}
|
||||
List<SimilarAsinParsedRowVo> rows = resolveAllRows(payload);
|
||||
if (payload.getAllItems() == null || payload.getAllItems().isEmpty()) {
|
||||
payload.setAllItems(rows);
|
||||
}
|
||||
|
||||
+210
@@ -0,0 +1,210 @@
|
||||
package com.nanri.aiimage.modules.similarasin.service;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.nanri.aiimage.modules.similarasin.model.dto.SimilarAsinParsedPayloadDto;
|
||||
import com.nanri.aiimage.modules.similarasin.model.vo.SimilarAsinParsedGroupVo;
|
||||
import com.nanri.aiimage.modules.similarasin.model.vo.SimilarAsinParsedRowVo;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
/**
|
||||
* Task 3:保留旧 payload 读取兼容逻辑,验证新旧结构均可恢复全量行。
|
||||
* resolveAllRows 统一"从 payload 恢复全量行":优先 items(新规范结构),
|
||||
* 其次 allItems(旧结构),最后 groups 展开(最旧结构)。
|
||||
*/
|
||||
class SimilarAsinTaskServiceResolveAllRowsTest {
|
||||
|
||||
private static final ObjectMapper MAPPER = new ObjectMapper();
|
||||
|
||||
private static SimilarAsinParsedRowVo row(String fileKey, int index) {
|
||||
SimilarAsinParsedRowVo row = new SimilarAsinParsedRowVo();
|
||||
row.setSourceFileKey(fileKey);
|
||||
row.setSourceFilename("base.xlsx");
|
||||
row.setRowIndex(index);
|
||||
row.setSourceId(String.valueOf(index));
|
||||
row.setDisplayId(String.valueOf(index));
|
||||
row.setRowToken(fileKey + "::row::" + index);
|
||||
row.setAsin("B0CJ8SNXXV");
|
||||
row.setCountry("英国");
|
||||
return row;
|
||||
}
|
||||
|
||||
private static List<SimilarAsinParsedRowVo> rows(String fileKey, int count) {
|
||||
List<SimilarAsinParsedRowVo> result = new ArrayList<>();
|
||||
for (int i = 1; i <= count; i++) {
|
||||
result.add(row(fileKey, i));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static SimilarAsinParsedGroupVo group(List<SimilarAsinParsedRowVo> items) {
|
||||
SimilarAsinParsedGroupVo group = new SimilarAsinParsedGroupVo();
|
||||
group.setSourceFileKey("uploads/20260829/base.xlsx");
|
||||
group.setSourceFilename("base.xlsx");
|
||||
group.setGroupKey("uploads/20260829/base.xlsx::1@1");
|
||||
group.setBaseId("1");
|
||||
group.setDisplayId("1");
|
||||
group.setItemCount(items.size());
|
||||
group.setItems(new ArrayList<>(items));
|
||||
return group;
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_task_003_payload_normal_default_path() {
|
||||
// 新结构:items 有值 → 恢复全量行
|
||||
SimilarAsinParsedPayloadDto payload = new SimilarAsinParsedPayloadDto();
|
||||
List<SimilarAsinParsedRowVo> items = rows("uploads/20260829/base.xlsx", 100);
|
||||
payload.setItems(items);
|
||||
List<SimilarAsinParsedRowVo> restored = SimilarAsinTaskService.resolveAllRows(payload);
|
||||
assertEquals(100, restored.size());
|
||||
assertEquals("uploads/20260829/base.xlsx::row::1", restored.get(0).getRowToken());
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_task_003_payload_normal_multiple_items() {
|
||||
// 新结构 1000 行:不丢失且顺序稳定
|
||||
SimilarAsinParsedPayloadDto payload = new SimilarAsinParsedPayloadDto();
|
||||
List<SimilarAsinParsedRowVo> items = rows("uploads/20260829/multi.xlsx", 1000);
|
||||
payload.setItems(items);
|
||||
List<SimilarAsinParsedRowVo> restored = SimilarAsinTaskService.resolveAllRows(payload);
|
||||
assertEquals(1000, restored.size());
|
||||
for (int i = 0; i < restored.size(); i++) {
|
||||
assertEquals(i + 1, restored.get(i).getRowIndex());
|
||||
}
|
||||
// 旧结构 1000 行:allItems 全量恢复
|
||||
SimilarAsinParsedPayloadDto legacy = new SimilarAsinParsedPayloadDto();
|
||||
legacy.setAllItems(items);
|
||||
List<SimilarAsinParsedRowVo> restoredLegacy = SimilarAsinTaskService.resolveAllRows(legacy);
|
||||
assertEquals(1000, restoredLegacy.size());
|
||||
assertEquals("uploads/20260829/multi.xlsx::row::1", restoredLegacy.get(0).getRowToken());
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_task_003_payload_normal_repeated_operation_is_idempotent() {
|
||||
// 重复调用返回相同行集合(不修改 payload 本身)
|
||||
SimilarAsinParsedPayloadDto payload = new SimilarAsinParsedPayloadDto();
|
||||
List<SimilarAsinParsedRowVo> items = rows("uploads/20260829/idem.xlsx", 50);
|
||||
payload.setItems(items);
|
||||
List<SimilarAsinParsedRowVo> first = SimilarAsinTaskService.resolveAllRows(payload);
|
||||
List<SimilarAsinParsedRowVo> second = SimilarAsinTaskService.resolveAllRows(payload);
|
||||
assertEquals(first.size(), second.size());
|
||||
for (int i = 0; i < first.size(); i++) {
|
||||
assertEquals(first.get(i).getRowToken(), second.get(i).getRowToken());
|
||||
}
|
||||
// payload 未被修改:items 仍 50 行
|
||||
assertEquals(50, payload.getItems().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_task_003_payload_boundary_empty_input() {
|
||||
// 空 payload:返回空列表而非 null,不创建无效资源
|
||||
SimilarAsinParsedPayloadDto empty = new SimilarAsinParsedPayloadDto();
|
||||
List<SimilarAsinParsedRowVo> restored = SimilarAsinTaskService.resolveAllRows(empty);
|
||||
assertNotNull(restored);
|
||||
assertEquals(0, restored.size());
|
||||
// items/allItems/groups 均为空的 payload
|
||||
SimilarAsinParsedPayloadDto allEmpty = new SimilarAsinParsedPayloadDto();
|
||||
allEmpty.setItems(List.of());
|
||||
allEmpty.setAllItems(List.of());
|
||||
allEmpty.setGroups(List.of());
|
||||
assertEquals(0, SimilarAsinTaskService.resolveAllRows(allEmpty).size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_task_003_payload_boundary_single_item() {
|
||||
// 单行新结构
|
||||
SimilarAsinParsedPayloadDto payload = new SimilarAsinParsedPayloadDto();
|
||||
payload.setItems(rows("uploads/20260829/single.xlsx", 1));
|
||||
assertEquals(1, SimilarAsinTaskService.resolveAllRows(payload).size());
|
||||
// 单行旧结构(仅 groups)
|
||||
SimilarAsinParsedPayloadDto legacy = new SimilarAsinParsedPayloadDto();
|
||||
legacy.setGroups(List.of(group(rows("uploads/20260829/single.xlsx", 1))));
|
||||
assertEquals(1, SimilarAsinTaskService.resolveAllRows(legacy).size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_task_003_payload_boundary_limit_and_overflow() {
|
||||
// 旧结构 allItems 5000 行全量恢复,不丢失
|
||||
SimilarAsinParsedPayloadDto legacy = new SimilarAsinParsedPayloadDto();
|
||||
legacy.setAllItems(rows("uploads/20260829/max.xlsx", 5000));
|
||||
assertEquals(5000, SimilarAsinTaskService.resolveAllRows(legacy).size());
|
||||
// items 与 allItems 同时存在:以 items 为准(新规范结构优先),不重复
|
||||
SimilarAsinParsedPayloadDto both = new SimilarAsinParsedPayloadDto();
|
||||
both.setItems(rows("uploads/20260829/both.xlsx", 10));
|
||||
both.setAllItems(rows("uploads/20260829/both.xlsx", 20));
|
||||
List<SimilarAsinParsedRowVo> restored = SimilarAsinTaskService.resolveAllRows(both);
|
||||
assertEquals(10, restored.size());
|
||||
// groups 也同时存在:仍以 items 为准
|
||||
both.setGroups(List.of(group(rows("uploads/20260829/both.xlsx", 30))));
|
||||
assertEquals(10, SimilarAsinTaskService.resolveAllRows(both).size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_task_003_payload_invalid_input_rejected() {
|
||||
// null payload:安全返回空列表(调用方容忍),不抛 NPE
|
||||
assertEquals(0, SimilarAsinTaskService.resolveAllRows(null).size());
|
||||
// groups 中含 null 元素:跳过不抛异常
|
||||
SimilarAsinParsedPayloadDto messy = new SimilarAsinParsedPayloadDto();
|
||||
List<SimilarAsinParsedGroupVo> groups = new ArrayList<>();
|
||||
groups.add(null);
|
||||
groups.add(group(rows("uploads/20260829/messy.xlsx", 5)));
|
||||
messy.setGroups(groups);
|
||||
assertEquals(5, SimilarAsinTaskService.resolveAllRows(messy).size());
|
||||
// group.items 为 null:跳过该组
|
||||
SimilarAsinParsedGroupVo nullItemsGroup = new SimilarAsinParsedGroupVo();
|
||||
nullItemsGroup.setItems(null);
|
||||
SimilarAsinParsedPayloadDto nullItems = new SimilarAsinParsedPayloadDto();
|
||||
nullItems.setGroups(List.of(nullItemsGroup, group(rows("uploads/20260829/n2.xlsx", 3))));
|
||||
assertEquals(3, SimilarAsinTaskService.resolveAllRows(nullItems).size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_task_003_payload_dependency_failure_releases_resources() throws Exception {
|
||||
// 旧格式 JSON(只有 allItems)反序列化 → hydrate 后 resolveAllRows 恢复全量行
|
||||
String legacyJson = "{\"aiPrompt\":\"p\",\"apiKey\":\"k\",\"imgSwitch\":false,\"categorySwitch\":false,"
|
||||
+ "\"sourceFiles\":[],\"headers\":[],\"groups\":[],"
|
||||
+ "\"allItems\":[{\"rowToken\":\"t1\",\"asin\":\"B0AAA00001\",\"sourceFileKey\":\"uploads/20260829/a.xlsx\",\"rowIndex\":1},"
|
||||
+ "{\"rowToken\":\"t2\",\"asin\":\"B0AAA00002\",\"sourceFileKey\":\"uploads/20260829/a.xlsx\",\"rowIndex\":2}]}";
|
||||
SimilarAsinParsedPayloadDto payload = MAPPER.readValue(legacyJson, SimilarAsinParsedPayloadDto.class);
|
||||
// 未 hydrate 时:allItems 恢复(items 为空走 allItems)
|
||||
List<SimilarAsinParsedRowVo> fromLegacy = SimilarAsinTaskService.resolveAllRows(payload);
|
||||
assertEquals(2, fromLegacy.size());
|
||||
assertEquals("t1", fromLegacy.get(0).getRowToken());
|
||||
assertEquals("B0AAA00002", fromLegacy.get(1).getAsin());
|
||||
// 新格式 JSON(只有 items)反序列化 → 直接恢复
|
||||
String newJson = "{\"aiPrompt\":\"p\",\"apiKey\":\"k\",\"imgSwitch\":false,\"categorySwitch\":false,"
|
||||
+ "\"sourceFiles\":[],\"headers\":[],"
|
||||
+ "\"items\":[{\"rowToken\":\"n1\",\"asin\":\"B0NEW00001\",\"sourceFileKey\":\"uploads/20260829/b.xlsx\",\"rowIndex\":1}],"
|
||||
+ "\"groups\":[]}";
|
||||
SimilarAsinParsedPayloadDto newPayload = MAPPER.readValue(newJson, SimilarAsinParsedPayloadDto.class);
|
||||
List<SimilarAsinParsedRowVo> fromNew = SimilarAsinTaskService.resolveAllRows(newPayload);
|
||||
assertEquals(1, fromNew.size());
|
||||
assertEquals("n1", fromNew.get(0).getRowToken());
|
||||
// 两种格式行数之和互不影响,恢复结果稳定
|
||||
assertTrue(fromLegacy.size() == 2 && fromNew.size() == 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_task_003_payload_normal_groups_expansion_preserves_order() {
|
||||
// 最旧结构:仅 groups 嵌套行,展开后顺序稳定(按组、组内原序)
|
||||
SimilarAsinParsedPayloadDto legacy = new SimilarAsinParsedPayloadDto();
|
||||
List<SimilarAsinParsedRowVo> g1 = rows("uploads/20260829/g.xlsx", 2);
|
||||
List<SimilarAsinParsedRowVo> g2 = rows("uploads/20260829/g.xlsx", 3);
|
||||
// 组内行号各自独立从 1 开始(真实解析语义),第二组用不同 fileKey 区分来源
|
||||
List<SimilarAsinParsedRowVo> g2b = rows("uploads/20260829/g2.xlsx", 3);
|
||||
legacy.setGroups(List.of(group(g1), group(g2b)));
|
||||
List<SimilarAsinParsedRowVo> restored = SimilarAsinTaskService.resolveAllRows(legacy);
|
||||
assertEquals(5, restored.size());
|
||||
assertEquals("uploads/20260829/g.xlsx::row::1", restored.get(0).getRowToken());
|
||||
assertEquals("uploads/20260829/g.xlsx::row::2", restored.get(1).getRowToken());
|
||||
assertEquals("uploads/20260829/g2.xlsx::row::1", restored.get(2).getRowToken());
|
||||
assertEquals("uploads/20260829/g2.xlsx::row::2", restored.get(3).getRowToken());
|
||||
assertEquals("uploads/20260829/g2.xlsx::row::3", restored.get(4).getRowToken());
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"version": 1,
|
||||
"total_tasks": 100,
|
||||
"completed": 2,
|
||||
"rounds": 2,
|
||||
"completed": 3,
|
||||
"rounds": 3,
|
||||
"started_at": "2026-08-29T14:10:48+08:00",
|
||||
"updated_at": "2026-08-29T14:35:00+08:00",
|
||||
"updated_at": "2026-08-29T14:45:00+08:00",
|
||||
"tasks": [
|
||||
{
|
||||
"id": 1,
|
||||
@@ -25,7 +25,7 @@
|
||||
"title": "保留旧 payload 读取兼容逻辑,并验证新旧结构均可恢复全量行",
|
||||
"module": "similarasin",
|
||||
"dependency": "2",
|
||||
"status": "pending"
|
||||
"status": "done"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
|
||||
Reference in New Issue
Block a user