From 986df86e8976736f4fc7929ead639cd366673c5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E8=87=AA=E8=BE=BE?= <980324341@qq.com> Date: Fri, 18 Sep 2026 15:48:33 +0800 Subject: [PATCH] =?UTF-8?q?fix(=E4=BB=BB=E5=8A=A1=E6=81=A2=E5=A4=8D):=20?= =?UTF-8?q?=E5=81=9C=E6=AD=A2=E6=84=8F=E5=9B=BE=E4=BC=98=E5=85=88=E4=BA=8E?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E7=BB=AD=E8=B7=91=20+=20=E6=9C=AA=E8=A6=86?= =?UTF-8?q?=E7=9B=96=E6=A8=A1=E5=9D=97=E7=9A=84=E4=B8=AD=E6=96=AD=E5=8F=AF?= =?UTF-8?q?=E8=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 自查上一提交时发现两处遗漏: - 用户点了「停止循环」而子任务恰好因客户端中断失败时,续派逻辑跑在停止检查之前, 会把循环从停止意图里拉回 RUNNING 自己转起来。改为停止请求优先:直接 STOPPED。 - 模块不支持自动续跑的中断任务此前完全无痕迹(用户只看到失败)。 这类模块(上架/改价/审批/商品管理采集/跟价非循环任务)的续跑载荷需要用户在页面上 选的执行参数(ziniao_version 等),而这份选择只存在于派发那一刻的浏览器里、没落到 request_json —— 自动重排队会用错参数,所以它们**不纳入**续跑(白名单的设计原则就是 「执行参数全在服务端」)。现补一条计数查询把这类中断数量带进 stale-check summary 的 resume(u=N),运维据此人工重跑;将来把参数回写落库后即可纳入白名单。 测试:PriceTrackLoopRunServiceTest 新增「已请求停止时中断失败不续派而是停止」, TaskResumeServiceTest 新增「统计不支持续跑的中断任务数」。 --- .../service/DeleteBrandStaleTaskService.java | 3 +- .../service/PriceTrackLoopRunService.java | 5 +++ .../task/service/TaskResumeService.java | 26 +++++++++++++ .../service/PriceTrackLoopRunServiceTest.java | 38 +++++++++++++++++++ .../task/service/TaskResumeServiceTest.java | 13 +++++++ 5 files changed, 84 insertions(+), 1 deletion(-) diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/deletebrand/service/DeleteBrandStaleTaskService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/deletebrand/service/DeleteBrandStaleTaskService.java index d51c93fe..baeb27a3 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/deletebrand/service/DeleteBrandStaleTaskService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/deletebrand/service/DeleteBrandStaleTaskService.java @@ -126,7 +126,7 @@ public class DeleteBrandStaleTaskService { // 周期每 2 分钟一轮,各模块 summary 合并为单行,避免定期刷屏 // 注意:每段占位符数量必须与实参一致——此前每段 5 个占位符只传 4 个参数, // 导致 withdraw 之后的取值整体错位、末尾 elapsedMs/thread 打成字面量 - log.info("[stale-check] summary product-risk(s={} f={} x={} p={}) price-track(s={} f={} x={} p={}) shop-match(s={} f={} x={} p={}) patrol-delete(s={} f={} x={} p={}) query-asin(s={} f={} x={} p={}) withdraw(s={} f={} x={} p={}) no-upload(c={} f={} x={}) resume(s={} r={} k={}) elapsedMs={} thread={}", + log.info("[stale-check] summary product-risk(s={} f={} x={} p={}) price-track(s={} f={} x={} p={}) shop-match(s={} f={} x={} p={}) patrol-delete(s={} f={} x={} p={}) query-asin(s={} f={} x={} p={}) withdraw(s={} f={} x={} p={}) no-upload(c={} f={} x={}) resume(s={} r={} k={} u={}) elapsedMs={} thread={}", stats.scannedTaskCount, stats.finalizedTaskCount, stats.failedTaskCount, stats.skippedTaskCount, priceTrackStats.scannedTaskCount, priceTrackStats.finalizedTaskCount, priceTrackStats.failedTaskCount, priceTrackStats.skippedTaskCount, shopMatchStats.scannedTaskCount, shopMatchStats.finalizedTaskCount, shopMatchStats.failedTaskCount, shopMatchStats.skippedTaskCount, @@ -135,6 +135,7 @@ public class DeleteBrandStaleTaskService { withdrawStats.scannedTaskCount, withdrawStats.finalizedTaskCount, withdrawStats.failedTaskCount, withdrawStats.skippedTaskCount, noUploadStats.scannedTaskCount, noUploadStats.failedTaskCount, noUploadStats.skippedTaskCount, resumeStats.scannedTaskCount, resumeStats.resumedTaskCount, resumeStats.skippedTaskCount, + resumeStats.unsupportedTaskCount, System.currentTimeMillis() - startedAt, Thread.currentThread().getName()); } diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/pricetrack/service/PriceTrackLoopRunService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/pricetrack/service/PriceTrackLoopRunService.java index ac5149f6..57ea03d8 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/pricetrack/service/PriceTrackLoopRunService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/pricetrack/service/PriceTrackLoopRunService.java @@ -267,6 +267,11 @@ public class PriceTrackLoopRunService { entity.setActiveTaskId(null); entity.setUpdatedAt(LocalDateTime.now()); if (STATUS_FAILED.equals(task.getStatus())) { + // 用户已请求停止时绝不续派:停止意图优先于自动恢复(否则点完停止循环还会自己转起来) + if (Boolean.TRUE.equals(entity.getStopRequested())) { + markStopped(entity, null); + return; + } // 客户端重启中断(markInterrupted 写入的前缀)不终止循环:清空 active_task_id 后保持 // RUNNING,客户端下次 dispatchNext 会拿到**同一店铺、同一轮次**的 childTaskRequest, // 等于原地续跑——页内已处理的 ASIN 由服务端 skip_asins 去重,不会重复改价。 diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/task/service/TaskResumeService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/task/service/TaskResumeService.java index a881bc65..ba4144a8 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/task/service/TaskResumeService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/task/service/TaskResumeService.java @@ -96,8 +96,10 @@ public class TaskResumeService { .orderByAsc(FileTaskEntity::getId) .last("limit " + safeLimit)); if (candidates.isEmpty()) { + stats.unsupportedTaskCount = countUnsupportedInterrupts(cutoff); return stats; } + stats.unsupportedTaskCount = countUnsupportedInterrupts(cutoff); stats.scannedTaskCount = candidates.size(); for (FileTaskEntity original : candidates) { if (original.getUserId() == null || original.getUserId() <= 0) { @@ -126,6 +128,28 @@ public class TaskResumeService { return stats; } + /** + * 统计「因客户端中断而失败、但模块不支持自动续跑」的任务数。 + * + *
这些模块(上架/改价/审批/商品管理采集/跟价的非循环任务等)的续跑载荷需要**用户在页面上选的
+ * 执行参数**(如 ziniao_version),而这份选择只存在于派发那一刻的浏览器里、没落到服务端
+ * request_json —— 自动重排队会用错参数。因此它们只做**可见**:数量进巡检 summary,
+ * 运维据此人工重跑;将来把这类参数回写落库后即可纳入续跑白名单。
+ */
+ private int countUnsupportedInterrupts(LocalDateTime cutoff) {
+ try {
+ Long count = fileTaskMapper.selectCount(new LambdaQueryWrapper