文件
hl-api-changelog/changelogs-v2/2026-09/24_7804_定时任务健康自检-新增接口-管理后台.md
T
jw和Claude Opus 5.5 63d29f8538
changelog-filename-gate / validate (push) Failing after 2s
docs(changelog): #7804 定时任务健康自检 GET /admin/job/health(新增接口)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-24 14:57:27 +08:00

9.5 KiB
原始文件 Blame 文件历史

schema, ticket, title, consumer, author, change_type, backend_status, gateway_status, frontend_status, frontend_owner, frontend_ref, target_release, verified_at, status_note, updated_at, base
schema ticket title consumer author change_type backend_status gateway_status frontend_status frontend_owner frontend_ref target_release verified_at status_note updated_at base
hl-changelog/v2 7804 定时任务健康自检 admin jw(GIT) 新增接口 deployed verified not_required 后端已部署 TEST 并经网关实测;运维排查接口,管理后台无需接入页面 2026-09-24 dev-v3

定时任务健康自检(#7804)

服务: hl-user-service(经网关调用,无需关心服务端口) PR: #8337 Issue: #7804 日期: 2026-09-24 影响范围: 定时任务运维排查(超级管理员),无前端页面


⚠️ 关键变化

新增只读端点,逐个定时任务并列返回三方状态:期望状态、sys_job.status(意图)、Quartz 触发器实况。三方不一致的条目 healthy=false,并附告警原文。

三条会直接影响你怎么用的点:

  1. 🔴 判断任务是否真在跑,要看 quartzState,不能看 dbStatus。sys_job.status 只是意图:用裸 SQL 把它置 PAUSED,任务照跑(TEST 上 1044 就这样连跑了 4 天)。原有的 GET /admin/job 列表只有 sys_job.status,不能用来判断启停。
  2. 只有 SUPER_ADMIN 能调;其他角色返回业务码 210301。
  3. 成功码是 code: 200;业务失败也返回 HTTP 200,一律按 body.code 判。

一、背景(选填)

SysJobService.initScheduledJobs() 为了集群安全是纯增量的,只补注册 ACTIVE 任务,从不移除已有的 Quartz 触发器。所以「裸 SQL 置 PAUSED」停不掉任务,而原来的 [JobHealth] 自检只读 sys_job,这种漂移下自检是绿的。本单把自检改为以 Quartz 为准,并把结果开放成接口。


二、变更接口清单

# 接口 方法 路径 变更类型 说明
1 定时任务健康自检 GET /admin/job/health 新增接口 只读,逐个任务返回期望 / sys_job / Quartz 三方状态与告警

三、接口详情

1. 定时任务健康自检 GET /admin/job/health

VO: SysJobHealthVO

使用场景

运维或超级管理员排查「某个定时任务到底有没有在跑」「sys_job 与调度器是否一致」时调用。结果与服务日志里的 [JobHealth] WARN 同源同文:启动时和每天 09:30 自动跑一次,日志里只记不一致的条目;本接口返回全部条目,健康的也在内。

入参

字段 位置 类型 必填 约束 说明
Authorization Header String ✅ Bearer <token> 管理端 token,角色须为 SUPER_ADMIN

出参

Result<List<SysJobHealthVO>>,按 jobId 升序排列。另有两类条目追加在末尾:名单内但 sys_job 里不存在的 job,以及 Quartz 里有调度而 sys_job 没有这一行的 job。

字段 类型 说明
jobId Long 任务 ID。🔴 部分任务是 19 位雪花 ID(如 2067459972995731458),前端按字符串处理
jobName String / null 任务名;sys_job 中不存在该行时为 null
expectedStatus String / null 期望状态,来自配置 hl.job.health-check.expected-status;未列入名单的任务为 null。取值 ACTIVE / PAUSED
dbStatus String / null sys_job.status,即意图;sys_job 中不存在该行时为 null。取值 ACTIVE / PAUSED
quartzState String Quartz 触发器实况,取值见「六.5 枚举」
healthy Boolean 期望、sys_job、Quartz 三方一致时为 true
message String / null 告警原文;健康时为 null

请求示例

GET /admin/job/health
Authorization: Bearer <SUPER_ADMIN token>

响应示例

{
  "code": 200,
  "message": "成功",
  "data": [
    {
      "jobId": 1039,
      "jobName": "Fleet导入临时文件收敛",
      "expectedStatus": null,
      "dbStatus": "PAUSED",
      "quartzState": "NORMAL",
      "healthy": false,
      "message": "jobId=1039 name=Fleet导入临时文件收敛 期望=未列入名单 DB=PAUSED Quartz=NORMAL:sys_job 与 Quartz 漂移(sys_job 只是意图,Quartz 才是实况)"
    },
    {
      "jobId": 1041,
      "jobName": "团期出发推进",
      "expectedStatus": "ACTIVE",
      "dbStatus": "ACTIVE",
      "quartzState": "NORMAL",
      "healthy": true,
      "message": null
    }
  ]
}

示例说明:两条取自测试环境 2026-09-24 14:54 的真实调用(当时 1039 是人为构造的漂移),实际返回全部任务(TEST 上 34 条)。

空数据 / 降级响应

sys_job 为空且 Quartz 无孤儿调度时,data 为空数组:

{ "code": 200, "message": "成功", "data": [] }

读某个任务的 Quartz 状态失败时不降级为「健康」,而是返回一条 healthy=false、quartzState=UNKNOWN、message 含「Quartz 状态读取失败」的条目;孤儿扫描失败时同样返回一条「Quartz 孤儿触发器扫描失败」。

错误响应

非超级管理员:

{ "code": 210301, "message": "仅超级管理员可管理定时任务", "data": null, "success": false }

未带 token:

{ "code": 401, "message": "缺少有效的 Authorization 头", "data": null, "success": false }

查 sys_job 表失败时不会返回「全绿」,而是走全局异常处理返回错误码。

业务边界

  • 只读,不改 sys_job,也不改 Quartz。
  • 全部 sys_job 行都会比对 sys_job 与 Quartz,不只期望名单内的。
  • 名单内的任务额外比对期望值:期望 ≠ Quartz 实况、期望 ≠ sys_job 都会报。
  • Quartz 实况折算口径:NORMAL / BLOCKED 会继续触发,算 ACTIVE;PAUSED / COMPLETE / NONE 不会再触发,算 PAUSED;ERROR / UNKNOWN 一律不健康。
  • POST /admin/job/{id}/trigger 在任务未注册时建的一次性调度(trigger_<id>)不算孤儿。

四、契约约束与正确调用方式

✅ 正确 / ❌ 错误请求对照

做法 结论
✅ 用 quartzState / healthy 判断任务是否真在跑 权威源
❌ 用 GET /admin/job 列表里的 status 判断启停 那只是 sys_job 意图,会与实况永久漂移
✅ 停任务走 POST /admin/job/{id}/pause 会同时摘掉 Quartz 触发器
❌ 裸 SQL UPDATE sys_job SET status='PAUSED'(+ 重启) 触发器保留,任务照跑;本接口会报「漂移」

六、边界行为

  • 期望名单为空时仍会比对全部任务的 sys_job 与 Quartz 一致性。
  • 配置 hl.job.health-check.enabled=false 只关闭启动时和每日的自检日志,不影响本接口。
  • 经网关落到 primary / secondary 任一实例,结果相同:Quartz 是 JDBC 集群,状态读的是共享的 QRTZ_* 表。

六.5 枚举 / 数据字典

quartzState

所属字段: quartzState | 类型: String

值 含义 折算
NORMAL 调度中(库里显示 WAITING / ACQUIRED / EXECUTING) ACTIVE
BLOCKED 上一次执行尚未结束 ACTIVE
PAUSED 已暂停 PAUSED
COMPLETE 不再触发 PAUSED
NONE 未注册 PAUSED
ERROR 触发器出错 不健康
UNKNOWN 读取失败 不健康

七、不影响范围

  • 既有 /admin/job/** 端点(增删改查、pause / resume / trigger / logs)的入参、出参、行为均不变。
  • initScheduledJobs() 的启动注册逻辑不变,仍然只增不减,不会自动摘除漂移的触发器。
  • 无数据库结构变更,无网关路由变更(沿用 Path=/admin/job/**)。

八、测试环境已验证

真实网关调用(https://api.test.1814.love),hl-user-service 已部署 dev-v3 @ 1ffd758db(本单合并提交,两个实例在 14:38 完成滚动重启)。token 是自签的(adminId=1002 / test_admin),经 SSH 临时写入 Redis 令牌位,窗口为 14:54:10–14:54:13,用完即还原。

构造:UPDATE sys_job SET status='PAUSED' WHERE job_id=1039   → sys_job=PAUSED,QRTZ_TRIGGERS 仍 WAITING
修复前代码重启 → [JobHealth] 关键定时任务状态自检通过,共核 5 项           (阴性对照:旧版测不出)
修复后代码重启 → [JobHealth] jobId=1039 … DB=PAUSED Quartz=NORMAL:sys_job 与 Quartz 漂移 ✓
GET /admin/job/health(SUPER_ADMIN)→ 200,34 条中仅 1039 healthy=false;1041/1042 healthy=true ✓
还原 1039 为 ACTIVE 后再调 → 200,34 条全部 healthy=true ✓
role=ADMIN → code 210301「仅超级管理员可管理定时任务」 ✓
不带 token → code 401「缺少有效的 Authorization 头」 ✓

本地单测:mvn -o -pl hl-user-service -am test 共 5051 例,0 失败 / 0 错误;另做 4 个变异体验证,每个都被测试杀死。


九、相关历史 PR

  • #7538 / #7640:引入 [JobHealth] 自检
  • #8268:用 Flyway 下线 1044,同时清理了 QRTZ_*

十、相关文档

  • 关联 Issue: wx/HL#7804
  • 关联 PR: wx/HL#8337
  • 运维须知: hl-user-service/docs/deployment.md(定时任务启停)

关联 / 联系人

链接

联系人

  • 后端负责人: @jw