软件前端更新

This commit is contained in:
super
2026-05-12 23:25:12 +08:00
parent 648e7d2f14
commit 18b0c2d211
63 changed files with 312 additions and 181 deletions

View File

@@ -156,9 +156,6 @@ public class SimilarAsinCozeClient {
private InspectAttempt inspectOnce(List<SimilarAsinResultRowDto> rows, String prompt, String apiKey) throws Exception {
String raw = runWorkflowAsyncAndWait(rows, prompt, apiKey);
List<CozeResult> results = parseResults(raw);
if (rows.size() > 1 && !results.isEmpty() && results.stream().noneMatch(this::hasIdentity)) {
throw new PartialCozeResultException(0, rows.size(), results.size());
}
List<SimilarAsinResultRowDto> merged = mergeRows(rows, results);
return new InspectAttempt(raw, merged, resolvedCount(merged), results.size());
}
@@ -259,18 +256,14 @@ public class SimilarAsinCozeClient {
}
private Map<String, Object> buildParameters(List<SimilarAsinResultRowDto> rows, String prompt, String apiKey) {
List<String> groupKeys = rows.stream().map(row -> nonBlank(row.getGroupKey(), rowKey(row.getId(), row.getAsin(), row.getCountry()))).toList();
List<String> rowTokens = rows.stream().map(row -> nonBlank(row.getRowToken(), "")).toList();
List<String> rowIds = rows.stream().map(row -> nonBlank(row.getId(), "")).toList();
List<String> asins = rows.stream().map(row -> nonBlank(row.getAsin(), "")).toList();
List<String> countries = rows.stream().map(row -> nonBlank(row.getCountry(), "")).toList();
List<String> skus = rows.stream().map(row -> nonBlank(row.getSku(), "")).toList();
List<String> titles = rows.stream().map(row -> nonBlank(row.getTitle(), row.getAsin())).toList();
List<String> urls = rows.stream().map(row -> nonBlank(row.getUrl(), "")).toList();
List<List<String>> urlLists = rows.stream().map(SimilarAsinResultRowDto::getUrls).toList();
Map<String, Object> parameters = new LinkedHashMap<>();
parameters.put("items", buildItemObjects(groupKeys, rowTokens, rowIds, asins, countries, skus, titles, urls, urlLists));
parameters.put("items", buildItemObjects(asins, skus, titles, urls, urlLists));
parameters.put("prompt", prompt == null ? "" : prompt);
if (apiKey != null && !apiKey.isBlank()) {
parameters.put("api_key", apiKey.trim());
@@ -308,11 +301,7 @@ public class SimilarAsinCozeClient {
return normalized.substring(0, 6) + "***" + normalized.substring(normalized.length() - 4);
}
private List<Map<String, Object>> buildItemObjects(List<String> groupKeys,
List<String> rowTokens,
List<String> rowIds,
List<String> asins,
List<String> countries,
private List<Map<String, Object>> buildItemObjects(List<String> asins,
List<String> skus,
List<String> titles,
List<String> urls,
@@ -320,11 +309,7 @@ public class SimilarAsinCozeClient {
List<Map<String, Object>> items = new ArrayList<>(asins.size());
for (int i = 0; i < asins.size(); i++) {
Map<String, Object> item = new LinkedHashMap<>();
item.put("group_key", groupKeys.get(i));
item.put("row_token", rowTokens.get(i));
item.put("row_id", rowIds.get(i));
item.put("asin", asins.get(i));
item.put("country", countries.get(i));
item.put("sku", skus.get(i));
item.put("title", titles.get(i));
item.put("url", urls.get(i));

View File

@@ -37,7 +37,10 @@ import java.util.List;
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/similar-asin")
@Tag(name = "相似ASIN检测", description = "相似ASIN检测任务接口。前端上传 Excel 后由 Java 解析并创建任务Python 回传商品数据Java 负责攒批调用 Coze、补齐子行、生成最终 xlsx 并上传 OSS。")
@Tag(
name = "相似 ASIN 检测",
description = "前端上传 Excel 后由 Java 解析并创建任务Python 按分组抓取商品数据并回传Java 再按批调用 Coze 并生成最终 xlsx。"
)
public class SimilarAsinController {
private final SimilarAsinTaskService service;
@@ -69,13 +72,19 @@ public class SimilarAsinController {
}
@PostMapping("/parse")
@Operation(summary = "解析 Excel 并创建任务", description = "解析上传后的 Excel 文件,提取 id、ASIN、国家、URL、标题等字段。返回给前端的数据只包含整数 id 和 n_1 行n_2、n_3 等子行会保存在 OSS 解析载荷中,用于最终结果补齐。创建后的任务状态为 PENDING不会自动推送 Python。")
@Operation(
summary = "解析 Excel 并创建任务",
description = "解析上传后的 Excel 文件,提取 id、ASIN、国家、URL、标题、SKU 等字段。返回给前端的数据同时包含全量有效行 items以及按主数据块分组后的 groups便于 Python 按组抓取后再回传。创建后的任务状态为 PENDING不会自动推送 Python。"
)
public ApiResponse<SimilarAsinParseVo> parse(@Valid @RequestBody SimilarAsinParseRequest request) {
return ApiResponse.success(service.parseAndCreateTask(request));
}
@GetMapping("/tasks/{taskId}/parsed-payload")
@Operation(summary = "获取货源查询完整解析载荷", description = "给 Python 队列消费端使用。parse 接口只返回轻量预览,大文件完整行数据通过该接口按 taskId 拉取。")
@Operation(
summary = "获取货源查询完整解析载荷",
description = "给 Python 队列消费端使用。返回全量有效行 items/allItems 以及分组后的 groups便于 Python 按组抓取后回传。"
)
public ApiResponse<SimilarAsinParsedPayloadDto> parsedPayload(
@Parameter(description = "货源查询任务 ID", required = true, example = "7004")
@PathVariable Long taskId,
@@ -85,17 +94,17 @@ public class SimilarAsinController {
}
@GetMapping("/dashboard")
@Operation(summary = "查询相似ASIN检测总览", description = "查询当前用户的运行中、成功、失败和已结束任务数量。页面进入时请求一次即可,不需要持续轮询。")
@Operation(summary = "查询相似 ASIN 检测总览", description = "查询当前用户的运行中、成功、失败和已结束任务数量。")
public ApiResponse<SimilarAsinDashboardVo> dashboard(
@Parameter(description = "当前用户 ID,用于隔离不同用户的任务和历史记录。", required = true, example = "1")
@Parameter(description = "当前用户 ID", required = true, example = "1")
@RequestParam("user_id") Long userId) {
return ApiResponse.success(service.dashboard(userId));
}
@GetMapping("/history")
@Operation(summary = "查询相似ASIN检测历史", description = "查询当前用户最近的相似ASIN检测历史记录,包含源文件名、任务状态、行数、错误信息和最终 xlsx 下载地址")
@Operation(summary = "查询相似 ASIN 检测历史", description = "查询当前用户最近的相似 ASIN 检测历史记录。")
public ApiResponse<SimilarAsinHistoryVo> history(
@Parameter(description = "当前用户 ID,用于查询该用户自己的历史记录。", required = true, example = "1")
@Parameter(description = "当前用户 ID", required = true, example = "1")
@RequestParam("user_id") Long userId,
@Parameter(description = "history limit, default 50, max 100", example = "50")
@RequestParam(value = "limit", required = false, defaultValue = "50") Integer limit) {
@@ -103,26 +112,29 @@ public class SimilarAsinController {
}
@PostMapping("/tasks/progress/batch")
@Operation(summary = "批量查询任务进度", description = "前端对活跃任务调用该接口,建议 6 秒一次。接口只返回轻量任务状态,不返回明细结果")
@Operation(summary = "批量查询任务进度", description = "前端对活跃任务轮询该接口,返回轻量任务状态")
public ApiResponse<SimilarAsinTaskBatchVo> progress(@Valid @RequestBody SimilarAsinTaskBatchRequest request) {
return ApiResponse.success(service.progressBatch(request.getTaskIds()));
}
@PostMapping("/tasks/{taskId}/activate")
@Operation(summary = "激活任务", description = "前端手动推送 Python 队列成功后调用,将任务从 PENDING 改为 RUNNING,并记录后端内部活跃时间。后续活跃时间由 Python 回传结果接口自动刷新,不需要单独心跳接口")
@Operation(summary = "激活任务", description = "前端手动推送 Python 队列成功后调用,将任务从 PENDING 改为 RUNNING。")
public ApiResponse<Void> activate(
@Parameter(description = "相似ASIN检测任务 ID,即解析接口返回的 taskId。", required = true, example = "3938")
@Parameter(description = "相似 ASIN 检测任务 ID", required = true, example = "3938")
@PathVariable Long taskId,
@Parameter(description = "当前用户 ID,必须与创建任务的用户一致。", required = true, example = "1")
@Parameter(description = "当前用户 ID", required = true, example = "1")
@RequestParam("user_id") Long userId) {
service.activateTask(taskId, userId);
return ApiResponse.success(null);
}
@PostMapping("/tasks/{taskId}/result")
@Operation(summary = "提交 Python 回传结果", description = "Python 回传商品数据接口。请通过 groups[].items[] 回传分组结果Java 先原样保存回传数据,再内部攒批调用 Coze。done=true 表示 Python 已完成全部回传Java 会强制处理剩余未满批的数据并生成最终 xlsx。")
@Operation(
summary = "提交 Python 回传结果",
description = "Python 请通过 groups[].items[] 回传分组抓取结果Java 先原样保存回传数据,再内部攒批调用 Coze。done=true 表示 Python 已完成全部回传Java 会强制处理剩余未满批的数据并生成最终 xlsx。"
)
public ApiResponse<Void> result(
@Parameter(description = "相似ASIN检测任务 ID任务必须处于 RUNNING 状态", required = true, example = "3938")
@Parameter(description = "相似 ASIN 检测任务 ID任务必须处于 RUNNING 状态", required = true, example = "3938")
@PathVariable Long taskId,
@Valid @RequestBody SimilarAsinSubmitResultRequest request,
jakarta.servlet.http.HttpServletResponse response) {
@@ -133,29 +145,29 @@ public class SimilarAsinController {
}
@DeleteMapping("/tasks/{taskId}")
@Operation(summary = "删除任务", description = "删除当前用户的一条相似ASIN检测任务同时清理任务结果、scope 状态和分片记录。")
@Operation(summary = "删除任务", description = "删除当前用户的一条相似 ASIN 检测任务同时清理任务结果、scope 状态和分片记录。")
public ApiResponse<Void> deleteTask(
@Parameter(description = "相似ASIN检测任务 ID", required = true, example = "3938")
@Parameter(description = "相似 ASIN 检测任务 ID", required = true, example = "3938")
@PathVariable Long taskId,
@Parameter(description = "当前用户 ID,必须与创建任务的用户一致。", required = true, example = "1")
@Parameter(description = "当前用户 ID", required = true, example = "1")
@RequestParam("user_id") Long userId) {
service.deleteTask(taskId, userId);
return ApiResponse.success(null);
}
@DeleteMapping("/history/{resultId}")
@Operation(summary = "删除历史记录", description = "删除当前用户的一条相似ASIN检测历史记录。只删除 biz_file_result 记录,不主动删除任务主记录。")
@Operation(summary = "删除历史记录", description = "删除当前用户的一条相似 ASIN 检测历史记录。")
public ApiResponse<Void> deleteHistory(
@Parameter(description = "历史结果记录 ID,即 history 接口返回的 resultId。", required = true, example = "1001")
@Parameter(description = "历史结果记录 ID", required = true, example = "1001")
@PathVariable Long resultId,
@Parameter(description = "当前用户 ID", required = true, example = "1")
@Parameter(description = "当前用户 ID", required = true, example = "1")
@RequestParam("user_id") Long userId) {
service.deleteHistory(resultId, userId);
return ApiResponse.success(null);
}
@GetMapping("/results/{resultId}/download")
@Operation(summary = "下载相似ASIN检测结果文件")
@Operation(summary = "下载相似 ASIN 检测结果文件")
public void downloadResult(
@Parameter(description = "结果记录 ID", required = true, example = "1001")
@PathVariable Long resultId,

View File

@@ -9,7 +9,7 @@ import java.util.ArrayList;
import java.util.List;
@Data
@Schema(description = "相似ASIN检测解析载荷")
@Schema(description = "相似 ASIN 解析载荷")
public class SimilarAsinParsedPayloadDto {
@Schema(description = "AI 提示词")
private String aiPrompt;
@@ -23,12 +23,12 @@ public class SimilarAsinParsedPayloadDto {
@Schema(description = "Excel 原始表头列表")
private List<String> headers = new ArrayList<>();
@Schema(description = "兼容旧链路的平铺有效行,现为全部有效行")
@Schema(description = "平铺的全量有效行")
private List<SimilarAsinParsedRowVo> items = new ArrayList<>();
@Schema(description = "相邻主 ID 块分组后的完整数据")
@Schema(description = "主数据块分组后的完整数据,每个 group.items 都是需要 Python 抓取的全量子行")
private List<SimilarAsinParsedGroupVo> groups = new ArrayList<>();
@Schema(description = "完整有效行")
@Schema(description = "全量有效行,用于兼容旧链路与结果文件组装")
private List<SimilarAsinParsedRowVo> allItems = new ArrayList<>();
}

View File

@@ -2564,7 +2564,6 @@ public class SimilarAsinTaskService {
vo.setValues(readRowValues(row, headers, formatter));
allRows.add(vo);
}
hydratePromptFields(allRows);
if (allRows.isEmpty()) {
throw new BusinessException("no valid similar ASIN rows");
}
@@ -2577,29 +2576,6 @@ public class SimilarAsinTaskService {
}
}
private void hydratePromptFields(List<SimilarAsinParsedRowVo> rows) {
if (rows == null || rows.isEmpty()) {
return;
}
Map<String, List<SimilarAsinParsedRowVo>> rowsByBaseId = groupRowsByBaseId(rows);
for (List<SimilarAsinParsedRowVo> siblings : rowsByBaseId.values()) {
String title = "";
String url = "";
for (SimilarAsinParsedRowVo sibling : siblings) {
title = firstNonBlank(title, sibling.getTitle());
url = firstNonBlank(url, sibling.getUrl());
}
for (SimilarAsinParsedRowVo sibling : siblings) {
if (normalize(sibling.getTitle()).isBlank()) {
sibling.setTitle(title);
}
if (normalize(sibling.getUrl()).isBlank()) {
sibling.setUrl(url);
}
}
}
}
private boolean hasPromptFields(SimilarAsinParsedRowVo row) {
return row != null && (!normalize(row.getTitle()).isBlank() || !normalize(row.getUrl()).isBlank());
}