fix(站内通知): 麦象异常扫描滞留检查改用 update_time 过滤(18960 console 新增 update_time_end)——原按 create_time 过滤 + desc(id) 分页只能看到最新批次,积压深处的老任务永远看不到(2026-09-15 滞留在队 9 小时未被告警指出的漏报根因)
This commit is contained in:
+10
-6
@@ -44,23 +44,27 @@ public class MaixiangConsoleClient {
|
|||||||
return hasText(properties.getPriceTrackApiUrl()) && hasText(properties.getMaixiangConsoleToken());
|
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",
|
String url = buildUrl("/api/console/batch/tasks",
|
||||||
"status=" + status,
|
"status=" + status,
|
||||||
"page=1",
|
"page=1",
|
||||||
"page_size=" + pageSize,
|
"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));
|
return parseBatchTaskPage(fetch(url, "批量任务列表 status=" + status));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 单任务列表(task_record)。 */
|
/** 单任务列表(task_record)。updateTimeBefore 语义同 {@link #batchTasks(int, LocalDateTime, int)}。 */
|
||||||
public SingleTaskPage singleTasks(int status, LocalDateTime createdBefore, int pageSize) {
|
public SingleTaskPage singleTasks(int status, LocalDateTime updateTimeBefore, int pageSize) {
|
||||||
String url = buildUrl("/api/console/tasks",
|
String url = buildUrl("/api/console/tasks",
|
||||||
"status=" + status,
|
"status=" + status,
|
||||||
"page=1",
|
"page=1",
|
||||||
"page_size=" + pageSize,
|
"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));
|
return parseSingleTaskPage(fetch(url, "单任务列表 status=" + status));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -104,7 +104,7 @@ public class MaixiangAnomalyScanner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 批量任务停滞:创建早于阈值、仍处 status=0/1 且 update_time 不再推进。 */
|
/** 批量任务停滞:最后更新早于阈值、仍处 status=0/1(服务端按 update_time 过滤后取回)。 */
|
||||||
void checkStuckBatchTasks(NotificationDispatchService.AdminAudience audience, LocalDateTime now) {
|
void checkStuckBatchTasks(NotificationDispatchService.AdminAudience audience, LocalDateTime now) {
|
||||||
int thresholdMinutes = Math.max(1, properties.getMaixiangStuckMinutes());
|
int thresholdMinutes = Math.max(1, properties.getMaixiangStuckMinutes());
|
||||||
LocalDateTime cutoff = now.minusMinutes(thresholdMinutes);
|
LocalDateTime cutoff = now.minusMinutes(thresholdMinutes);
|
||||||
@@ -112,7 +112,7 @@ public class MaixiangAnomalyScanner {
|
|||||||
for (int status : new int[]{0, 1}) {
|
for (int status : new int[]{0, 1}) {
|
||||||
candidates.addAll(consoleClient.batchTasks(status, cutoff, PAGE_SIZE).items());
|
candidates.addAll(consoleClient.batchTasks(status, cutoff, PAGE_SIZE).items());
|
||||||
}
|
}
|
||||||
// 创建时间早于 cutoff 但仍在正常推进的大任务(update_time 晚于 cutoff)不算停滞
|
// 服务端已按 update_time ≤ cutoff 过滤;这里再兜一层,防止接口参数被忽略时误报正常推进的大任务
|
||||||
List<BatchTaskItem> stuck = new ArrayList<>();
|
List<BatchTaskItem> stuck = new ArrayList<>();
|
||||||
for (BatchTaskItem task : candidates) {
|
for (BatchTaskItem task : candidates) {
|
||||||
if (task.updateTime() != null && !task.updateTime().isAfter(cutoff)) {
|
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));
|
push(audience, "麦象批量任务停滞", content, "maixiang_stuck_batch:" + now.format(DAY_FORMAT));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 单任务滞留:创建早于阈值、仍处 status=0/1(跟价等单任务应秒级完成)。 */
|
/** 单任务滞留:最后更新早于阈值、仍处 status=0/1(跟价等单任务应秒级完成)。 */
|
||||||
void checkStuckSingleTasks(NotificationDispatchService.AdminAudience audience, LocalDateTime now) {
|
void checkStuckSingleTasks(NotificationDispatchService.AdminAudience audience, LocalDateTime now) {
|
||||||
int thresholdMinutes = Math.max(1, properties.getMaixiangSingleStuckMinutes());
|
int thresholdMinutes = Math.max(1, properties.getMaixiangSingleStuckMinutes());
|
||||||
LocalDateTime cutoff = now.minusMinutes(thresholdMinutes);
|
LocalDateTime cutoff = now.minusMinutes(thresholdMinutes);
|
||||||
|
|||||||
+4
-4
@@ -132,7 +132,7 @@ class MaixiangConsoleClientTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void buildUrlEncodesTimeParameterExactlyOnce() {
|
void buildUrlEncodesTimeParameterExactlyOnce() {
|
||||||
// 回归:end_time 里的空格必须编码成 %20(单次),否则提交时会被二次编码成 %2520,
|
// 回归:update_time_end 里的空格必须编码成 %20(单次),否则提交时会被二次编码成 %2520,
|
||||||
// 服务端拿到字面 "%20" 直接报 Incorrect DATETIME value(联调时实际踩到过)
|
// 服务端拿到字面 "%20" 直接报 Incorrect DATETIME value(联调时实际踩到过)
|
||||||
NotificationProperties properties = new NotificationProperties();
|
NotificationProperties properties = new NotificationProperties();
|
||||||
properties.setPriceTrackApiUrl("http://192.168.0.170:18960");
|
properties.setPriceTrackApiUrl("http://192.168.0.170:18960");
|
||||||
@@ -140,13 +140,13 @@ class MaixiangConsoleClientTest {
|
|||||||
MaixiangConsoleClient underTest = new MaixiangConsoleClient(properties, new ObjectMapper());
|
MaixiangConsoleClient underTest = new MaixiangConsoleClient(properties, new ObjectMapper());
|
||||||
|
|
||||||
String url = underTest.buildUrl("/api/console/tasks",
|
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).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).doesNotContain("%2520");
|
||||||
assertThat(url).contains("token=tok%20en+%E7%89%B9%E6%AE%8A");
|
assertThat(url).contains("token=tok%20en+%E7%89%B9%E6%AE%8A");
|
||||||
// 组装结果必须是可直接提交的合法 URI
|
// 组装结果必须是可直接提交的合法 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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user