fix(wle): 钉钉推送失败后自动重试,不再「一天只有一次机会」 #96

Merged
yanhui merged 1 commits from yanhui-wle-notify-retry-1111 into main 2026-11-14 09:51:21 +08:00
Collaborator

目的

11-11 09:10 的「仓储物流转运异常」推送没进群。查下来不是通道坏,是业务库当时连不上:

  • system_config.warehouse_logistics_exception_notify_state 里 lastFailedAt = 2026-11-11 09:12:57、
    lastError = "Failed to connect to 218.93.9.21:1433"、lastSentDate 还停在 2026-11-10;
  • 而调度逻辑用 chinaHourMinute(now) === config.sendTime 严格相等判定 —— 一天只有这一分钟有机会,
    失败后当天彻底不推。群里一整天没消息,也没人知道任务其实跑过。

改动范围

只动 backend/src/jobs/warehouseLogisticsExceptionNotifyJob.js 与对应单测。
无接口变更、无表结构变更、无前端改动、无新依赖。

  • 新增纯函数 resolveScheduledPushDecision({ now, sendTime, state }),tick 每分钟问它一次,返回:
    before_time / already_sent_today / window_closed / wait_interval / first_round / retry。
  • 窗口 = sendTime ~ sendTime + 30 分钟,窗口内每 5 分钟重试一次,成功即停
    (lastSentDate 落库后后续 tick 走 already_sent_today)。
  • 状态新增 attemptDate / attemptCount / lastAttemptAt:既做节流(避免每分钟重跑一次 240s 的全量核验),
    也做跨天归零。
  • 窗口不跨日(sendTime + window 超过 23:59 截到当天最后一分钟)—— 否则 pushDate 换天会把去重键带歪,
    同一天可能推两遍。
  • 窗口关闭且当天始终未成功 → 打一条 error 日志,明确「今天不会自己来了」。
  • 窗口 / 间隔支持 WLE_NOTIFY_RETRY_WINDOW_MINUTES(默认 30)、
    WLE_NOTIFY_RETRY_INTERVAL_MINUTES(默认 5)环境变量覆盖,现网调整不用发版。

同步与兼容性

  • 老状态对象没有新字段 → attemptDate !== pushDate 即视为首轮,行为与改造前一致,不需要迁移。
  • 推送时刻仍取页面配置的 sendTime,业务侧无感知;页面「立即推送」走 force 分支,不受影响。
  • 只影响 WLE 这一个 job,其它定时任务不共享这段逻辑。

验证

  • node --test backend/test/warehouseLogisticsExceptionNotify.test.js → 30 / 30 通过
    (新增 10 条:到点首轮 / 未到点 / 已推过 / 间隔内节流 / 间隔到点 / 窗口边界含端点 /
    窗口关闭 / 跨天归零 / 窗口不跨日 / 状态残缺不卡死 / sendTime 非法)
  • node --check backend/src/jobs/warehouseLogisticsExceptionNotifyJob.js 通过。
  • 时间断言一律用 UTC 构造(2026-11-11T01:10:00Z = 北京 09:10),不依赖本机时区。

风险与回退

  • 风险:窗口内最多重试 6 次,每次都会跑全量核验。11-11 那种「业务库连不上」是连接层失败,
    秒失败、不会堆叠;tickRunning 互斥也保证同一时刻只有一轮。
  • 回退:WLE_NOTIFY_RETRY_WINDOW_MINUTES=1 即退回「只试一分钟」的旧行为,无需回滚代码。
## 目的 11-11 09:10 的「仓储物流转运异常」推送没进群。查下来不是通道坏,是**业务库当时连不上**: - `system_config.warehouse_logistics_exception_notify_state` 里 `lastFailedAt = 2026-11-11 09:12:57`、 `lastError = "Failed to connect to 218.93.9.21:1433"`、`lastSentDate` 还停在 `2026-11-10`; - 而调度逻辑用 `chinaHourMinute(now) === config.sendTime` **严格相等**判定 —— **一天只有这一分钟有机会**, 失败后当天彻底不推。群里一整天没消息,也没人知道任务其实跑过。 ## 改动范围 只动 `backend/src/jobs/warehouseLogisticsExceptionNotifyJob.js` 与对应单测。 无接口变更、无表结构变更、无前端改动、无新依赖。 - 新增纯函数 `resolveScheduledPushDecision({ now, sendTime, state })`,tick 每分钟问它一次,返回: `before_time` / `already_sent_today` / `window_closed` / `wait_interval` / `first_round` / `retry`。 - 窗口 = `sendTime ~ sendTime + 30 分钟`,窗口内每 5 分钟重试一次,**成功即停** (`lastSentDate` 落库后后续 tick 走 `already_sent_today`)。 - 状态新增 `attemptDate` / `attemptCount` / `lastAttemptAt`:既做节流(避免每分钟重跑一次 240s 的全量核验), 也做跨天归零。 - 窗口**不跨日**(`sendTime + window` 超过 23:59 截到当天最后一分钟)—— 否则 `pushDate` 换天会把去重键带歪, 同一天可能推两遍。 - 窗口关闭且当天始终未成功 → 打一条 error 日志,明确「今天不会自己来了」。 - 窗口 / 间隔支持 `WLE_NOTIFY_RETRY_WINDOW_MINUTES`(默认 30)、 `WLE_NOTIFY_RETRY_INTERVAL_MINUTES`(默认 5)环境变量覆盖,现网调整不用发版。 ## 同步与兼容性 - **老状态对象没有新字段** → `attemptDate !== pushDate` 即视为首轮,行为与改造前一致,不需要迁移。 - 推送时刻仍取页面配置的 `sendTime`,业务侧无感知;页面「立即推送」走 `force` 分支,不受影响。 - 只影响 WLE 这一个 job,其它定时任务不共享这段逻辑。 ## 验证 - `node --test backend/test/warehouseLogisticsExceptionNotify.test.js` → **30 / 30 通过** (新增 10 条:到点首轮 / 未到点 / 已推过 / 间隔内节流 / 间隔到点 / 窗口边界含端点 / 窗口关闭 / 跨天归零 / 窗口不跨日 / 状态残缺不卡死 / `sendTime` 非法) - `node --check backend/src/jobs/warehouseLogisticsExceptionNotifyJob.js` 通过。 - 时间断言一律用 UTC 构造(`2026-11-11T01:10:00Z` = 北京 09:10),不依赖本机时区。 ## 风险与回退 - **风险**:窗口内最多重试 6 次,每次都会跑全量核验。11-11 那种「业务库连不上」是连接层失败, 秒失败、不会堆叠;`tickRunning` 互斥也保证同一时刻只有一轮。 - **回退**:`WLE_NOTIFY_RETRY_WINDOW_MINUTES=1` 即退回「只试一分钟」的旧行为,无需回滚代码。
yanhui added 1 commit 2026-11-11 11:15:58 +08:00
11-11 09:10 那次推送撞上业务库连不上(状态里 `Failed to connect to
218.93.9.21:1433`)。老逻辑用 `chinaHourMinute(now) === config.sendTime`
严格相等判定,只给这一分钟一次机会:失败后当天彻底不推,群里一整天
没有任何消息,也没人知道任务其实跑过。

改成「推送时刻 + 重试窗口」模型:

- 新增纯函数 `resolveScheduledPushDecision`,每分钟 tick 只需问它一次;
  窗口 = sendTime ~ sendTime+30min,窗口内每 5 分钟重试一次,成功即停
  (`lastSentDate` 落库后后续 tick 走 already_sent_today)。
- 状态新增 `attemptDate` / `attemptCount` / `lastAttemptAt`:既做节流
  (避免每分钟重跑 240s 的全量核验),也做跨天归零。
- 窗口不跨日(sendTime+window 超过 23:59 截到当天最后一分钟),
  否则 pushDate 换天会把去重键带歪、同一天可能推两遍。
- 窗口关闭且当天始终未成功时打一条 error 日志,明确「今天不会再自己来了」。
- 窗口 / 间隔支持 `WLE_NOTIFY_RETRY_WINDOW_MINUTES`、
  `WLE_NOTIFY_RETRY_INTERVAL_MINUTES` 环境变量覆盖,现网调整不用发版。

测试:`test/warehouseLogisticsExceptionNotify.test.js` 新增 10 条边界用例
(到点首轮 / 未到点 / 已推过 / 间隔内节流 / 间隔到点 / 窗口边界含端点 /
窗口关闭 / 跨天归零 / 窗口不跨日 / 状态残缺不卡死 / sendTime 非法),
30/30 通过。
yanhui merged commit 2f44cdce82 into main 2026-11-14 09:51:21 +08:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: lunanting/cangku#96
No description provided.