Files
easygoadmin-wiki/EasyGoAdmin_GoFrame_AntdVue/zh/modules/job.md
T
2026-10-20 21:53:26 +08:00

133 lines
6.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 6.11 定时任务
:::tip 概述
定时任务是数据驱动的常驻调度器:任务定义在 goframe_job 表,启动时加载 status=1 任务挂载 gcron,执行目标分双形态(HTTP 调用 / 本地处理器),三种执行策略叠加多实例执行锁,结果落 goframe_job_log。
:::
## 架构总览
```text
启动(cmd.go 延时 2s)→ DaemonInit 加载 goframe_job 中 status=1 的任务
→ 按 cron_expression 挂载 gcron → tick 触发 executeJob
→ 执行目标分派(HTTP 调用 或 本地处理器)→ 结果写 goframe_job_log
职责分层:
调度基础设施(挂载/摘除/锁/执行/落日志)在 internal/logic 的 job 模块
任务业务实现在 internal/task 包(单向依赖 task→logic,logic 禁止反向 import task)
由 internal/cmd 空白导入 internal/task 触发 init 注册
```
## 执行目标双形态(executeJob 读 url 字段分派)
```text
url 为 http:// 或 https:// 开头 → HTTP 调用目标地址:
params 为空 GET,非空 POST(application/json,超时 30s)
非 200 或网络错误 → 失败(exception 记录状态与响应前 1000 字符)
url 其余取值 → 本地处理器方法名,从 jobHandlers 注册表查找:
签名 func(ctx context.Context, params string) (string, error)
返回摘要写入任务日志 message
未注册 → 失败"未找到本地任务处理方法"
```
## 执行策略与执行方式
```text
execute_policy(列值):
1 立即执行 允许并发:gcron.Add,tick 即启动新执行;不加执行锁
2 执行一次 单例调度(gcron.AddSingleton),上次未结束跳过本次;叠加执行锁
3 放弃执行 并发 tick 照常触发,但抢不到执行锁则放弃本次
is_sync(列值):
1 同步 在调度协程内执行(阻塞本条目下一 tick 的派发)
0 异步 go 协程执行(context.Background())
执行锁 jobTryLock(策略 2/3 叠加):
键 job:lock:{id},utility/cache SET NX(TTL 300s 兜底——实例崩溃未释放自动过期;
正常路径 defer 立即释放)。多实例部署共享同一把锁(Redis 模式防跨实例并发;
内存降级时退化单机语义)
cron 表达式:gf gcron 格式,默认 6 段(秒 分 时 日 月 周);
# 前缀表示标准 5 段忽略秒(如 "# */5 * * * *" = 每 5 分钟)
```
## 调度流程与状态机
```text
Add(固定置0)
│
▼
┌─────────┐ Status(1) / Resume ┌─────────┐
│ 0 未发布 │─────────────────────▶│ 1 运行中 │◀──┐
└─────────┘ └────┬────┘ │
▲ Status(2)/Pause │ │ Resume
│ ─────────────────────▶│ │
│ 摘除调度 ▼ │
│ ┌─────────┐ │
│ Status(0) │ 2 暂停 │───┘
│◀─────────────────────────── └─────────┘
│ 摘除调度
Delete:软删 + 摘除调度
运行中 Update:摘除 → 按新 cron 重挂(失败降级置 2 并报 Cron 无效)
RunOnce:不看状态,异步 force 执行一次(不改状态)
```
## 管理接口(/api/job)
```text
七件套 sys:job:*
GET /api/job/runOnce/:id sys:job:execute 立即执行(异步,未发布也可执行)
GET /api/job/pause/:id sys:job:pause 摘除调度 + status=2
GET /api/job/resume/:id sys:job:resume 重新挂载 + status=1
POST /api/job/status sys:job:update 设状态(1→挂载,其他→摘除)
GET /api/job/daemonStatus 登录即可 调度器状态(running/jobIds/startTime)
业务钩子:BeforeAdd 固定 status=0;AfterUpdate 运行中先摘除再重挂;
BeforeDelete 存在性检查 + 摘除调度
字典:job_status / job_execute_policy / job_is_sync(SerializeMaps 驱动 {field}Text)
```
## 本地处理器注册(internal/task)
```text
logic.RegisterJobHandler(name, h):注册名全局唯一,重复注册进程 panic
注册名与任务记录 url 字段值一一对应(数据驱动查找)
internal/task 包约定:
每任务一个文件(snake_case),init 中注册
处理方法签名 func(ctx, params) (string, error),返回值写任务日志摘要
由 internal/cmd 空白导入触发加载(不在 HTTP 启动链路内)
内置任务:
HelloWorld(hello_world.go) 示例,仅打日志(验证调度链路)
SendSms(send_sms.go) 仅记录执行参数日志(不对接短信通道)
LogClean(log_clean.go) 日志清理:每日 02:30 分批物理删除三张日志表
参数 {"retentionDays": n} 可覆盖 job.logRetentionDays(默认 90)
单批 5000 行循环至不足一批;单表单次任务上限 200000 行(防长事务/runaway)
```
## 新增任务开发步骤
```text
1) internal/task/{任务名}.go 实现 handler:func(ctx, params) (string, error)
2) init() 中 logic.RegisterJobHandler("TaskName", handler)
3) 任务管理页新增记录:url 填 "TaskName",params 填 JSON,
cron_expression 填 6 段或 # 5 段,execute_policy/is_sync 按需
4) Status 置为运行中(发布)→ 调度器挂载;观察 job/log 执行日志
5) go build ./... 并重启(init 注册发生在进程加载期)
注意:handler 可复用 base 共享能力(WithTrans/ChunkQuery)与 dao;
长任务用分批+上限;HTTP 目标任务自带 30s 超时,长任务应改本地处理器
```
## 多实例部署语义
```text
单实例:gcron 各自挂载,正常运行
多实例 + Redis:策略 2/3 经 job:lock:{id} 全局互斥(同一时刻至多一个实例执行)
——仍会重复"触发"(各实例各自 tick),未抢到锁的实例直接放弃
多实例 + 内存降级:锁退化单机语义,可能跨实例并发执行——生产 Redis 必须可用
```
排障口径差异:`daemonStatus.running` 是 gcron 全部条目数(含 CleanTemp 等非任务条目);`server/info` 的 `Scheduler.mounted` 只统计 "job:" 前缀条目。