From 52c59755ab6ca7272d95bce070b6f2cf6d3f5805 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E8=87=AA=E8=BE=BE?= <980324341@qq.com> Date: Wed, 2 Sep 2026 13:47:58 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=B4=A7=E6=BA=90=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2=20404=20=E8=A1=8C=E8=A2=AB=E8=BF=87=E6=BB=A4=E4=B8=94?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E5=88=97=E7=BD=AE=E7=A9=BA=EF=BC=9APython=20?= =?UTF-8?q?404=20=E7=9A=84=20ASIN=20=E4=BF=9D=E7=95=99=E5=AF=BC=E5=87=BA?= =?UTF-8?q?=E5=B9=B6=E6=A0=87=E8=AE=B0=E3=80=8CASIN=E6=9C=AA=E6=89=BE?= =?UTF-8?q?=E5=88=B0=E3=80=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 线上现象:货源采集任务 26402(1212.xlsx)结果文件中存在 Python 端 404 Not Found 的行没有图,且状态列空白——这些行实际已完成采集但被后端过滤。 根因链: 1. Python 404 行 title/url/图全空 → isMissingRequired 必填校验拦截 → markRowsFailed(status=FAILED,error 保留 "404 Not Found"); 2. isExportableResultRow 对「其它错误需带 title/url/图」的判断把这类 404 行 过滤掉,即使存在也被 isSilentOutputFailure 把状态列置空。 修复: - 新增 isAsinNotFound 判定(404 / not found / 商品未找到 等文案,大小写不敏感); - resolveResultStatus 在图片判定后、rawStatus→失败 映射前插入 「ASIN未找到」状态;isExportableResultRow 对 404 行直接放行; - writeResultWorkbook 状态列 gate 排除 404 行(不再被置空); - 新增 2 条单元测试(404/中文文案命中、普通技术失败不误判)。 --- .../service/SimilarAsinTaskService.java | 30 +++++++++++++++- .../service/SimilarAsinTaskServiceTest.java | 35 +++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/similarasin/service/SimilarAsinTaskService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/similarasin/service/SimilarAsinTaskService.java index 80f0aae0..fad009ad 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/similarasin/service/SimilarAsinTaskService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/similarasin/service/SimilarAsinTaskService.java @@ -4623,7 +4623,8 @@ public class SimilarAsinTaskService { String isConform = resultRow == null ? "" : userFacingLlmCellValue(resultRow, resultRow.getIsConform()); String reason = resultRow == null ? "" : userFacingLlmCellValue(resultRow, resultRow.getReason()); String category = resultRow == null ? "" : userFacingLlmCellValue(resultRow, resultRow.getCategory()); - String status = resultRow == null || isSilentOutputFailure(resultRow) + String status = resultRow == null + || (isSilentOutputFailure(resultRow) && !isAsinNotFound(resultRow.getError())) ? "" : resolveResultStatus(resultRow, isStock, similarity, isConform, reason, category); row.createCell(col++).setCellValue(isStock); @@ -4858,6 +4859,12 @@ public class SimilarAsinTaskService { if (hasText(row.getMainUrl()) || hasText(row.getPuzzleImg1()) || hasText(row.getPuzzleImg2())) { return "\u6210\u529f"; } + // Python \u7aef\u641c\u4e0d\u5230\u5546\u54c1\uff08404\uff09\u7684\u884c\uff1a\u72b6\u6001\u5217\u660e\u786e\u6807\u8bb0\uff0c\u4eba\u5de5\u590d\u6838\u65f6\u4e00\u773c\u53ef\u8fa8\u3002 + // \u653e\u5728\u56fe\u7247/\u72b6\u6001\u5224\u65ad\u4e4b\u524d\uff1a404 \u884c\u5fc5\u7136\u65e0\u56fe\u4e14 status \u5df2\u88ab markRowFailed \u7f6e\u4e3a FAILED\uff0c + // \u4e0d\u80fd\u843d\u56de"\u5931\u8d25"\uff08\u90a3\u662f LLM/\u751f\u6210\u94fe\u8def\u5931\u8d25\u8bed\u4e49\uff0c\u4e0e"ASIN \u641c\u4e0d\u5230"\u662f\u4e24\u56de\u4e8b\uff09\u3002 + if (isAsinNotFound(row.getError())) { + return "ASIN\u672a\u627e\u5230"; + } String rawStatus = row.getStatus() == null ? "" : row.getStatus().trim(); if (!rawStatus.isBlank()) { return isFailedStatus(rawStatus) ? "\u5931\u8d25" : "\u6210\u529f"; @@ -4872,6 +4879,22 @@ public class SimilarAsinTaskService { return hasText(row.getConclusion()) ? "\u6210\u529f" : "\u5931\u8d25"; } + /** + * \u5224\u5b9a\u662f\u5426"ASIN \u672a\u627e\u5230"\uff1aPython \u7aef\u6293\u53d6 404\uff08error="404 Not Found"\uff09\u6216\u7b49\u4ef7\u6587\u6848\u3002 + * \u547d\u4e2d\u8be5\u5224\u5b9a\u7684\u884c\u4e0d\u4f1a\u88ab\u5f53\u6210\u751f\u6210\u5931\u8d25\u8ba1\u91cd\u8bd5\uff0c\u4e5f\u4e0d\u4f1a\u88ab\u5bfc\u51fa\u8fc7\u6ee4\uff0c\u72b6\u6001\u5217\u663e\u793a"ASIN\u672a\u627e\u5230"\u3002 + */ + static boolean isAsinNotFound(String error) { + String normalized = error == null ? "" : error.toLowerCase(Locale.ROOT).trim(); + if (normalized.isBlank()) { + return false; + } + return normalized.contains("404") + || normalized.contains("not found") + || normalized.contains("asin \u672a\u627e\u5230") + || normalized.contains("asin\u4e0d\u5b58\u5728") + || normalized.contains("\u5546\u54c1\u672a\u627e\u5230"); + } + private static boolean isFailedStatus(String status) { String normalized = status == null ? "" : status.trim().toLowerCase(Locale.ROOT); return normalized.contains("fail") @@ -4895,6 +4918,11 @@ public class SimilarAsinTaskService { if (error.isBlank()) { return false; } + // Python 端搜不到商品的 404 行:虽然 title/url/图全空,也必须导出, + // 并在状态列标记"ASIN未找到",供人工复核。在"其它错误需带 title/url/图"之前判断。 + if (isAsinNotFound(error)) { + return true; + } return !normalize(row.getTitle()).isBlank() || !normalize(row.getUrl()).isBlank() || row.hasImageUrl(); diff --git a/backend-java/src/test/java/com/nanri/aiimage/modules/similarasin/service/SimilarAsinTaskServiceTest.java b/backend-java/src/test/java/com/nanri/aiimage/modules/similarasin/service/SimilarAsinTaskServiceTest.java index 158a31a3..81419bc9 100644 --- a/backend-java/src/test/java/com/nanri/aiimage/modules/similarasin/service/SimilarAsinTaskServiceTest.java +++ b/backend-java/src/test/java/com/nanri/aiimage/modules/similarasin/service/SimilarAsinTaskServiceTest.java @@ -54,6 +54,41 @@ class SimilarAsinTaskServiceTest { assertEquals("\u6210\u529f", SimilarAsinTaskService.resolveResultStatus(withVisibleResultData, "", "80%", "", "", "")); } + @Test + void resultStatusMarksAsinNotFoundRows() { + // Python \u7aef 404 \u884c\uff1aerror="404 Not Found"\uff0cstatus \u88ab markRowFailed \u7f6e\u4e3a FAILED\uff0c + // \u4f46\u72b6\u6001\u5217\u5fc5\u987b\u663e\u793a"ASIN\u672a\u627e\u5230"\uff0c\u4e0d\u80fd\u843d\u56de\u666e\u901a"\u5931\u8d25"\u3002 + SimilarAsinResultRowDto notFound = new SimilarAsinResultRowDto(); + notFound.setError("404 Not Found"); + notFound.setStatus("FAILED"); + notFound.setReason("required field missing (asin/title/url), skip LLM"); + assertEquals("ASIN\u672a\u627e\u5230", SimilarAsinTaskService.resolveResultStatus(notFound)); + + // \u5176\u5b83\u7b49\u4ef7\u6587\u6848\u540c\u6837\u547d\u4e2d + SimilarAsinResultRowDto chinese = new SimilarAsinResultRowDto(); + chinese.setError("\u5546\u54c1\u672a\u627e\u5230"); + assertEquals("ASIN\u672a\u627e\u5230", SimilarAsinTaskService.resolveResultStatus(chinese)); + + // \u666e\u901a\u6280\u672f\u6027\u5931\u8d25\u4e0d\u8bef\u5224\uff1aerror \u4e0d\u542b 404/not found + SimilarAsinResultRowDto llmFailure = new SimilarAsinResultRowDto(); + llmFailure.setError("LLM \u8c03\u7528\u8d85\u65f6"); + llmFailure.setStatus("FAILED"); + assertEquals("\u5931\u8d25", SimilarAsinTaskService.resolveResultStatus(llmFailure)); + } + + @Test + void asinNotFoundDetectionMatchesPythonErrors() { + assertTrue(SimilarAsinTaskService.isAsinNotFound("404 Not Found")); + assertTrue(SimilarAsinTaskService.isAsinNotFound("404 not found")); + assertTrue(SimilarAsinTaskService.isAsinNotFound("404")); + assertTrue(SimilarAsinTaskService.isAsinNotFound("\u5546\u54c1\u672a\u627e\u5230")); + assertTrue(SimilarAsinTaskService.isAsinNotFound("ASIN \u672a\u627e\u5230")); + assertFalse(SimilarAsinTaskService.isAsinNotFound(null)); + assertFalse(SimilarAsinTaskService.isAsinNotFound("")); + assertFalse(SimilarAsinTaskService.isAsinNotFound("LLM \u8c03\u7528\u8d85\u65f6")); + assertFalse(SimilarAsinTaskService.isAsinNotFound("\u4e0a\u4f20\u5931\u8d25")); + } + @Test void fileBuildProgressIsTerminalOnlyAfterTaskAndStageComplete() { assertTrue(SimilarAsinTaskService.isTerminalFileBuildProgress("SUCCESS", 3, 3));