task-52: 生成结果文件按 chunk 一次读取
CollectDataResultDetailReader 从 biz_task_result_item 解析行:引用格式 按 pointer 缓存整 chunk 明细(同一 chunk 对象只 resolve 一次),再按 offset 取行;旧格式逐行兜底。CollectDataService.loadFinalRows 接入 reader,移除内联逐行读取循环与 detailCache。
This commit is contained in:
+12
-39
@@ -37,6 +37,7 @@ import com.nanri.aiimage.modules.collectdata.util.CollectDataExtraJsonCodec;
|
||||
import com.nanri.aiimage.modules.collectdata.util.CollectDataInvalidAsinBatchWriter;
|
||||
import com.nanri.aiimage.modules.collectdata.util.CollectDataParseLimits;
|
||||
import com.nanri.aiimage.modules.collectdata.util.CollectDataResultDetailCodec;
|
||||
import com.nanri.aiimage.modules.collectdata.util.CollectDataResultDetailReader;
|
||||
import com.nanri.aiimage.modules.collectdata.util.CollectDataResultItemBatchWriter;
|
||||
import com.nanri.aiimage.modules.invalidasin.mapper.InvalidAsinDataMapper;
|
||||
import com.nanri.aiimage.modules.file.service.LocalFileStorageService;
|
||||
@@ -149,6 +150,9 @@ public class CollectDataService {
|
||||
/** 结果明细批量 upsert 器:按唯一键 uk_task_scope_item 批量写入,替代逐行 select/insert/update。 */
|
||||
private final CollectDataResultItemBatchWriter resultItemBatchWriter;
|
||||
|
||||
/** 结果明细 chunk 级读取器:生成结果文件时按 chunk 一次读取,替代逐行对象读取。 */
|
||||
private final CollectDataResultDetailReader resultDetailReader;
|
||||
|
||||
@Value("${aiimage.collect-data.stale-timeout-minutes:30}")
|
||||
private long staleTimeoutMinutes;
|
||||
|
||||
@@ -891,47 +895,16 @@ public class CollectDataService {
|
||||
.eq(TaskResultItemEntity::getTaskId, taskId)
|
||||
.eq(TaskResultItemEntity::getModuleType, MODULE_TYPE)
|
||||
.orderByAsc(TaskResultItemEntity::getId));
|
||||
List<CollectDataResultRowVo> out = new ArrayList<>();
|
||||
if (rows == null) {
|
||||
return out;
|
||||
if (rows == null || rows.isEmpty()) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
TypeReference<List<CollectDataResultRowVo>> listType = new TypeReference<>() {
|
||||
};
|
||||
// chunk 级引用:同一 chunk 对象只解析一次,按 offset 取行。
|
||||
Map<String, List<CollectDataResultRowVo>> detailCache = new HashMap<>();
|
||||
for (TaskResultItemEntity row : rows) {
|
||||
try {
|
||||
CollectDataResultDetailCodec.ChunkRef ref = resultDetailCodec.parseRef(row.getPayloadJson());
|
||||
if (ref != null) {
|
||||
List<CollectDataResultRowVo> details = detailCache.get(ref.pointer());
|
||||
if (details == null) {
|
||||
String detailJson = transientPayloadStorageService.resolvePayload(
|
||||
ref.pointer(), "read collect data result detail failed");
|
||||
if (detailJson != null && !detailJson.isBlank()) {
|
||||
details = objectMapper.readValue(detailJson, listType);
|
||||
}
|
||||
detailCache.put(ref.pointer(), details);
|
||||
}
|
||||
if (details != null && ref.offset() >= 0 && ref.offset() < details.size()) {
|
||||
CollectDataResultRowVo value = details.get(ref.offset());
|
||||
if (value != null) {
|
||||
out.add(value);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// 旧格式:payload_json 直接是行 JSON。
|
||||
String payloadJson = transientPayloadStorageService.resolvePayload(
|
||||
row.getPayloadJson(), "read collect data result item failed");
|
||||
CollectDataResultRowVo value = objectMapper.readValue(payloadJson, CollectDataResultRowVo.class);
|
||||
if (value != null) {
|
||||
out.add(value);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
throw new BusinessException("读取采集结果明细失败");
|
||||
}
|
||||
try {
|
||||
// 按 chunk 一次读取:同一 chunk 对象只 resolve 一次,按 offset 取行,
|
||||
// 替代逐行对象读取(旧格式逐行兜底)。
|
||||
return resultDetailReader.readRows(rows);
|
||||
} catch (Exception ex) {
|
||||
throw new BusinessException("读取采集结果明细失败", ex);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
package com.nanri.aiimage.modules.collectdata.util;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.nanri.aiimage.modules.collectdata.model.vo.CollectDataResultRowVo;
|
||||
import com.nanri.aiimage.modules.task.model.entity.TaskResultItemEntity;
|
||||
import com.nanri.aiimage.modules.task.service.TransientPayloadStorageService;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 结果明细 chunk 级读取器:生成结果文件时按 chunk 一次读取整对象,
|
||||
* 同一 chunk 的明细只 resolve/解析一次,再按 offset 取行,替代逐行
|
||||
* 对象读取。旧格式(payload_json 直接是行 JSON)逐行兜底;空输入返回
|
||||
* 空列表,越界/损坏行安全跳过,读取失败抛可识别异常。
|
||||
*/
|
||||
@Component
|
||||
public class CollectDataResultDetailReader {
|
||||
|
||||
private static final String ERROR_DETAIL = "read collect data result detail failed";
|
||||
private static final String ERROR_ITEM = "read collect data result item failed";
|
||||
|
||||
private final CollectDataResultDetailCodec resultDetailCodec;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final TransientPayloadStorageService transientPayloadStorageService;
|
||||
private final TypeReference<List<CollectDataResultRowVo>> listType = new TypeReference<>() {
|
||||
};
|
||||
|
||||
public CollectDataResultDetailReader(CollectDataResultDetailCodec resultDetailCodec,
|
||||
ObjectMapper objectMapper,
|
||||
TransientPayloadStorageService transientPayloadStorageService) {
|
||||
this.resultDetailCodec = resultDetailCodec;
|
||||
this.objectMapper = objectMapper;
|
||||
this.transientPayloadStorageService = transientPayloadStorageService;
|
||||
}
|
||||
|
||||
public List<CollectDataResultRowVo> readRows(List<TaskResultItemEntity> items) {
|
||||
List<CollectDataResultRowVo> out = new ArrayList<>();
|
||||
if (items == null || items.isEmpty()) {
|
||||
return out;
|
||||
}
|
||||
// chunk 级引用:同一 chunk 对象只 resolve + 解析一次,按 offset 取行。
|
||||
Map<String, List<CollectDataResultRowVo>> detailCache = new HashMap<>();
|
||||
for (TaskResultItemEntity item : items) {
|
||||
if (item == null || item.getPayloadJson() == null || item.getPayloadJson().isBlank()) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
CollectDataResultDetailCodec.ChunkRef ref = resultDetailCodec.parseRef(item.getPayloadJson());
|
||||
if (ref != null) {
|
||||
List<CollectDataResultRowVo> details = detailCache.get(ref.pointer());
|
||||
if (details == null) {
|
||||
String detailJson = transientPayloadStorageService.resolvePayload(ref.pointer(), ERROR_DETAIL);
|
||||
details = (detailJson == null || detailJson.isBlank())
|
||||
? null
|
||||
: objectMapper.readValue(detailJson, listType);
|
||||
detailCache.put(ref.pointer(), details);
|
||||
}
|
||||
if (details != null && ref.offset() >= 0 && ref.offset() < details.size()) {
|
||||
CollectDataResultRowVo value = details.get(ref.offset());
|
||||
if (value != null) {
|
||||
out.add(value);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// 旧格式:payload_json 直接是行 JSON;内容缺失时安全跳过。
|
||||
String payloadJson = transientPayloadStorageService.resolvePayload(item.getPayloadJson(), ERROR_ITEM);
|
||||
if (payloadJson == null || payloadJson.isBlank()) {
|
||||
continue;
|
||||
}
|
||||
CollectDataResultRowVo value = objectMapper.readValue(payloadJson, CollectDataResultRowVo.class);
|
||||
if (value != null) {
|
||||
out.add(value);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalStateException("读取采集结果明细失败", ex);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user