From 5e5816cd749736cd81a54bd7e7328376d6265e38 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, 16 Sep 2026 01:05:26 +0800 Subject: [PATCH] =?UTF-8?q?fix(=E7=AB=99=E5=86=85=E9=80=9A=E7=9F=A5):=20?= =?UTF-8?q?=E9=BA=A6=E8=B1=A1=E5=BC=82=E5=B8=B8=E6=89=AB=E6=8F=8F=E6=BB=9E?= =?UTF-8?q?=E7=95=99=E6=A3=80=E6=9F=A5=E6=94=B9=E7=94=A8=20update=5Ftime?= =?UTF-8?q?=20=E8=BF=87=E6=BB=A4=EF=BC=8818960=20console=20=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=20update=5Ftime=5Fend=EF=BC=89=E2=80=94=E2=80=94?= =?UTF-8?q?=E5=8E=9F=E6=8C=89=20create=5Ftime=20=E8=BF=87=E6=BB=A4=20+=20d?= =?UTF-8?q?esc(id)=20=E5=88=86=E9=A1=B5=E5=8F=AA=E8=83=BD=E7=9C=8B?= =?UTF-8?q?=E5=88=B0=E6=9C=80=E6=96=B0=E6=89=B9=E6=AC=A1=EF=BC=8C=E7=A7=AF?= =?UTF-8?q?=E5=8E=8B=E6=B7=B1=E5=A4=84=E7=9A=84=E8=80=81=E4=BB=BB=E5=8A=A1?= =?UTF-8?q?=E6=B0=B8=E8=BF=9C=E7=9C=8B=E4=B8=8D=E5=88=B0=EF=BC=882026-09-1?= =?UTF-8?q?5=20=E6=BB=9E=E7=95=99=E5=9C=A8=E9=98=9F=209=20=E5=B0=8F?= =?UTF-8?q?=E6=97=B6=E6=9C=AA=E8=A2=AB=E5=91=8A=E8=AD=A6=E6=8C=87=E5=87=BA?= =?UTF-8?q?=E7=9A=84=E6=BC=8F=E6=8A=A5=E6=A0=B9=E5=9B=A0=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../client/MaixiangConsoleClient.java | 16 ++++++++++------ .../service/MaixiangAnomalyScanner.java | 6 +++--- .../client/MaixiangConsoleClientTest.java | 8 ++++---- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/notification/client/MaixiangConsoleClient.java b/backend-java/src/main/java/com/nanri/aiimage/modules/notification/client/MaixiangConsoleClient.java index 0196a90e..2fe49281 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/notification/client/MaixiangConsoleClient.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/notification/client/MaixiangConsoleClient.java @@ -44,23 +44,27 @@ public class MaixiangConsoleClient { return hasText(properties.getPriceTrackApiUrl()) && hasText(properties.getMaixiangConsoleToken()); } - /** 批量任务列表(all_task)。createdBefore 非空时只取创建时间不晚于该时刻的任务(SQL 字符串比较)。 */ - public BatchTaskPage batchTasks(int status, LocalDateTime createdBefore, int pageSize) { + /** + * 批量任务列表(all_task)。updateTimeBefore 非空时只取「最后更新」不晚于该时刻的任务 + * (SQL 字符串比较)。用 update_time 过滤而不是 create_time:desc(id) 分页下按创建时间过滤 + * 只能看到最新创建的一批,积压深处的老任务(真正停滞的)永远看不到 —— 2026-09-15 滞留漏报的根因。 + */ + public BatchTaskPage batchTasks(int status, LocalDateTime updateTimeBefore, int pageSize) { String url = buildUrl("/api/console/batch/tasks", "status=" + status, "page=1", "page_size=" + pageSize, - createdBefore == null ? null : "end_time=" + TIME_FORMAT.format(createdBefore)); + updateTimeBefore == null ? null : "update_time_end=" + TIME_FORMAT.format(updateTimeBefore)); return parseBatchTaskPage(fetch(url, "批量任务列表 status=" + status)); } - /** 单任务列表(task_record)。 */ - public SingleTaskPage singleTasks(int status, LocalDateTime createdBefore, int pageSize) { + /** 单任务列表(task_record)。updateTimeBefore 语义同 {@link #batchTasks(int, LocalDateTime, int)}。 */ + public SingleTaskPage singleTasks(int status, LocalDateTime updateTimeBefore, int pageSize) { String url = buildUrl("/api/console/tasks", "status=" + status, "page=1", "page_size=" + pageSize, - createdBefore == null ? null : "end_time=" + TIME_FORMAT.format(createdBefore)); + updateTimeBefore == null ? null : "update_time_end=" + TIME_FORMAT.format(updateTimeBefore)); return parseSingleTaskPage(fetch(url, "单任务列表 status=" + status)); } diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/notification/service/MaixiangAnomalyScanner.java b/backend-java/src/main/java/com/nanri/aiimage/modules/notification/service/MaixiangAnomalyScanner.java index 655e3647..33c19ab1 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/notification/service/MaixiangAnomalyScanner.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/notification/service/MaixiangAnomalyScanner.java @@ -104,7 +104,7 @@ public class MaixiangAnomalyScanner { } } - /** 批量任务停滞:创建早于阈值、仍处 status=0/1 且 update_time 不再推进。 */ + /** 批量任务停滞:最后更新早于阈值、仍处 status=0/1(服务端按 update_time 过滤后取回)。 */ void checkStuckBatchTasks(NotificationDispatchService.AdminAudience audience, LocalDateTime now) { int thresholdMinutes = Math.max(1, properties.getMaixiangStuckMinutes()); LocalDateTime cutoff = now.minusMinutes(thresholdMinutes); @@ -112,7 +112,7 @@ public class MaixiangAnomalyScanner { for (int status : new int[]{0, 1}) { candidates.addAll(consoleClient.batchTasks(status, cutoff, PAGE_SIZE).items()); } - // 创建时间早于 cutoff 但仍在正常推进的大任务(update_time 晚于 cutoff)不算停滞 + // 服务端已按 update_time ≤ cutoff 过滤;这里再兜一层,防止接口参数被忽略时误报正常推进的大任务 List stuck = new ArrayList<>(); for (BatchTaskItem task : candidates) { if (task.updateTime() != null && !task.updateTime().isAfter(cutoff)) { @@ -135,7 +135,7 @@ public class MaixiangAnomalyScanner { push(audience, "麦象批量任务停滞", content, "maixiang_stuck_batch:" + now.format(DAY_FORMAT)); } - /** 单任务滞留:创建早于阈值、仍处 status=0/1(跟价等单任务应秒级完成)。 */ + /** 单任务滞留:最后更新早于阈值、仍处 status=0/1(跟价等单任务应秒级完成)。 */ void checkStuckSingleTasks(NotificationDispatchService.AdminAudience audience, LocalDateTime now) { int thresholdMinutes = Math.max(1, properties.getMaixiangSingleStuckMinutes()); LocalDateTime cutoff = now.minusMinutes(thresholdMinutes); diff --git a/backend-java/src/test/java/com/nanri/aiimage/modules/notification/client/MaixiangConsoleClientTest.java b/backend-java/src/test/java/com/nanri/aiimage/modules/notification/client/MaixiangConsoleClientTest.java index 98a27651..52c6f481 100644 --- a/backend-java/src/test/java/com/nanri/aiimage/modules/notification/client/MaixiangConsoleClientTest.java +++ b/backend-java/src/test/java/com/nanri/aiimage/modules/notification/client/MaixiangConsoleClientTest.java @@ -132,7 +132,7 @@ class MaixiangConsoleClientTest { @Test void buildUrlEncodesTimeParameterExactlyOnce() { - // 回归:end_time 里的空格必须编码成 %20(单次),否则提交时会被二次编码成 %2520, + // 回归:update_time_end 里的空格必须编码成 %20(单次),否则提交时会被二次编码成 %2520, // 服务端拿到字面 "%20" 直接报 Incorrect DATETIME value(联调时实际踩到过) NotificationProperties properties = new NotificationProperties(); properties.setPriceTrackApiUrl("http://192.168.0.170:18960"); @@ -140,13 +140,13 @@ class MaixiangConsoleClientTest { MaixiangConsoleClient underTest = new MaixiangConsoleClient(properties, new ObjectMapper()); String url = underTest.buildUrl("/api/console/tasks", - "status=0", "page=1", "page_size=100", "end_time=2026-09-15 10:58:39"); + "status=0", "page=1", "page_size=100", "update_time_end=2026-09-15 10:58:39"); assertThat(url).startsWith("http://192.168.0.170:18960/api/console/tasks?"); - assertThat(url).contains("end_time=2026-09-15%2010:58:39"); + assertThat(url).contains("update_time_end=2026-09-15%2010:58:39"); assertThat(url).doesNotContain("%2520"); assertThat(url).contains("token=tok%20en+%E7%89%B9%E6%AE%8A"); // 组装结果必须是可直接提交的合法 URI - assertThat(java.net.URI.create(url).getRawQuery()).contains("end_time=2026-09-15%2010:58:39"); + assertThat(java.net.URI.create(url).getRawQuery()).contains("update_time_end=2026-09-15%2010:58:39"); } }