task-6: 分组数据改为索引/范围引用,避免 groups 嵌套复制完整行对象
buildParsedGroups 只写 [startIndex, endIndex) 半开区间引用(同 baseId 行在 items 中天然连续),行对象仅存在于 items 一次;resolveAllRows 优先按引用 展开并安全跳过越界/非法区间;响应组仍内嵌预览行兼容前端。旧 payload 内嵌 items 格式继续可读。新增 8 个测试覆盖引用写入、批量、幂等、空输入、单组、 越界 clamp、非法区间、依赖失败恢复。
This commit is contained in:
+17
-1
@@ -1,5 +1,6 @@
|
||||
package com.nanri.aiimage.modules.similarasin.model.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@@ -27,6 +28,21 @@ public class SimilarAsinParsedGroupVo {
|
||||
@Schema(description = "分组内行数")
|
||||
private Integer itemCount;
|
||||
|
||||
@Schema(description = "分组内全部行,顺序与原 Excel 保持一致")
|
||||
/**
|
||||
* 组内行在载荷 items 中的起始下标(含)。写入侧只输出引用,
|
||||
* 行对象仅存在于 items 一次,避免 groups 嵌套复制完整行对象。
|
||||
*/
|
||||
@Schema(description = "组内行在 items 中的起始下标(含)")
|
||||
private Integer startIndex;
|
||||
|
||||
/**
|
||||
* 组内行在载荷 items 中的结束下标(不含),半开区间 [startIndex, endIndex)。
|
||||
*/
|
||||
@Schema(description = "组内行在 items 中的结束下标(不含),半开区间 [startIndex, endIndex)")
|
||||
private Integer endIndex;
|
||||
|
||||
/** 兼容字段:旧 payload 内嵌的完整行。新写入不再输出,仅读取旧 payload 时使用。 */
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
@Schema(description = "兼容旧链路字段:旧 payload 内嵌的完整行,新写入不再输出", hidden = true)
|
||||
private List<SimilarAsinParsedRowVo> items = new ArrayList<>();
|
||||
}
|
||||
|
||||
+71
-4
@@ -511,7 +511,7 @@ public class SimilarAsinTaskService {
|
||||
vo.setImgSwitch(Boolean.TRUE.equals(request.getImgSwitch()));
|
||||
vo.setCategorySwitch(Boolean.TRUE.equals(request.getCategorySwitch()));
|
||||
vo.setItems(buildResponsePreviewRows(allRows));
|
||||
vo.setGroups(buildResponsePreviewGroups(groups));
|
||||
vo.setGroups(buildResponsePreviewGroups(groups, allRows));
|
||||
long finishedAt = System.nanoTime();
|
||||
log.info("[similar-asin] parse timing taskId={} files={} rows={} groups={} totalMs={} parseMs={} groupMs={} taskInsertMs={} payloadJsonMs={} payloadStoreMs={} persistMs={} responseMs={}",
|
||||
task.getId(),
|
||||
@@ -963,6 +963,10 @@ public class SimilarAsinTaskService {
|
||||
if (payload == null) {
|
||||
return List.of();
|
||||
}
|
||||
if (containsGroupRefs(payload)) {
|
||||
// 新格式:分组携带索引引用,信任引用展开结果(越界/非法区间安全跳过)
|
||||
return expandGroupRefs(payload);
|
||||
}
|
||||
List<SimilarAsinParsedRowVo> rows = payload.getItems();
|
||||
if (rows == null || rows.isEmpty()) {
|
||||
rows = payload.getAllItems();
|
||||
@@ -976,6 +980,48 @@ public class SimilarAsinTaskService {
|
||||
return rows == null ? List.of() : rows;
|
||||
}
|
||||
|
||||
private static boolean containsGroupRefs(SimilarAsinParsedPayloadDto payload) {
|
||||
if (payload.getGroups() == null) {
|
||||
return false;
|
||||
}
|
||||
for (SimilarAsinParsedGroupVo group : payload.getGroups()) {
|
||||
if (group != null && (group.getStartIndex() != null || group.getEndIndex() != null)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Task 6:按分组引用 [startIndex, endIndex) 从 items 展开组内行。
|
||||
* 引用越界或区间非法时安全跳过,不抛异常;展开不修改 payload 内部状态。
|
||||
*/
|
||||
static List<SimilarAsinParsedRowVo> expandGroupRefs(SimilarAsinParsedPayloadDto payload) {
|
||||
if (payload == null || payload.getGroups() == null || payload.getGroups().isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
List<SimilarAsinParsedRowVo> rows = payload.getItems();
|
||||
if (rows == null || rows.isEmpty()) {
|
||||
rows = payload.getAllItems();
|
||||
}
|
||||
if (rows == null || rows.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
List<SimilarAsinParsedRowVo> expanded = new ArrayList<>();
|
||||
for (SimilarAsinParsedGroupVo group : payload.getGroups()) {
|
||||
if (group == null) {
|
||||
continue;
|
||||
}
|
||||
int start = group.getStartIndex() == null ? 0 : group.getStartIndex();
|
||||
int end = group.getEndIndex() == null ? 0 : group.getEndIndex();
|
||||
if (start < 0 || end <= start || end > rows.size()) {
|
||||
continue;
|
||||
}
|
||||
expanded.addAll(rows.subList(start, end));
|
||||
}
|
||||
return expanded;
|
||||
}
|
||||
|
||||
private PersistSubmittedChunkResult persistSubmittedChunk(PreparedSubmittedChunk prepared) {
|
||||
Long taskId = prepared.taskId();
|
||||
FileTaskEntity task = fileTaskMapper.selectById(taskId);
|
||||
@@ -1461,8 +1507,15 @@ public class SimilarAsinTaskService {
|
||||
return candidates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Task 6:分组数据改为索引/范围引用。组内行在原 items 中必然连续
|
||||
* (parseWorkbook 按行遍历、同 baseId 块连续收集),因此只需要
|
||||
* [startIndex, endIndex) 半开区间即可唯一定位组内行,
|
||||
* 行对象仅存在于 items 一次,避免 groups 嵌套复制完整行对象。
|
||||
*/
|
||||
private List<SimilarAsinParsedGroupVo> buildParsedGroups(List<SimilarAsinParsedRowVo> rows) {
|
||||
List<SimilarAsinParsedGroupVo> groups = new ArrayList<>();
|
||||
int cursor = 0;
|
||||
for (List<SimilarAsinParsedRowVo> siblings : groupRowsByBaseId(rows).values()) {
|
||||
if (siblings == null || siblings.isEmpty()) {
|
||||
continue;
|
||||
@@ -1475,13 +1528,16 @@ public class SimilarAsinTaskService {
|
||||
group.setBaseId(baseId(first.getDisplayId()));
|
||||
group.setDisplayId(firstNonBlank(first.getDisplayId(), first.getSourceId()));
|
||||
group.setItemCount(siblings.size());
|
||||
group.setItems(new ArrayList<>(siblings));
|
||||
int end = cursor + siblings.size();
|
||||
group.setStartIndex(cursor);
|
||||
group.setEndIndex(end);
|
||||
cursor = end;
|
||||
groups.add(group);
|
||||
}
|
||||
return groups;
|
||||
}
|
||||
|
||||
private List<SimilarAsinParsedGroupVo> buildResponsePreviewGroups(List<SimilarAsinParsedGroupVo> groups) {
|
||||
private List<SimilarAsinParsedGroupVo> buildResponsePreviewGroups(List<SimilarAsinParsedGroupVo> groups, List<SimilarAsinParsedRowVo> allRows) {
|
||||
if (groups == null || groups.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
@@ -1499,7 +1555,18 @@ public class SimilarAsinTaskService {
|
||||
copy.setBaseId(group.getBaseId());
|
||||
copy.setDisplayId(group.getDisplayId());
|
||||
copy.setItemCount(group.getItemCount());
|
||||
copy.setItems(buildResponsePreviewRows(group.getItems()));
|
||||
copy.setStartIndex(group.getStartIndex());
|
||||
copy.setEndIndex(group.getEndIndex());
|
||||
// 响应组携带内嵌预览行(切片自全量行),不暴露索引引用语义
|
||||
int start = group.getStartIndex() == null ? 0 : group.getStartIndex();
|
||||
int end = group.getEndIndex() == null ? 0 : group.getEndIndex();
|
||||
if (start >= 0 && end > start && end <= allRows.size()) {
|
||||
copy.setItems(buildResponsePreviewRows(allRows.subList(start, end)));
|
||||
} else if (group.getItems() != null && !group.getItems().isEmpty()) {
|
||||
copy.setItems(buildResponsePreviewRows(group.getItems()));
|
||||
} else {
|
||||
copy.setItems(List.of());
|
||||
}
|
||||
preview.add(copy);
|
||||
}
|
||||
return preview;
|
||||
|
||||
Reference in New Issue
Block a user