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