Files
Neo-ZQYY/docs/prd/specs/P16-task-min-run-interval.md
Neo 6f8f12314f feat: 累积功能变更 — 聊天集成、租户管理、小程序更新、ETL 增强、迁移脚本
包含多个会话的累积代码变更:
- backend: AI 聊天服务、触发器调度、认证增强、WebSocket、调度器最小间隔
- admin-web: ETL 状态页、任务管理、调度配置、登录优化
- miniprogram: 看板页面、聊天集成、UI 组件、导航更新
- etl: DWS 新任务(finance_area_daily/board_cache)、连接器增强
- tenant-admin: 项目初始化
- db: 19 个迁移脚本(etl_feiqiu 11 + zqyy_app 8)
- packages/shared: 枚举和工具函数更新
- tools: 数据库工具、报表生成、健康检查
- docs: PRD/架构/部署/合约文档更新

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-06 00:03:48 +08:00

10 KiB
Raw Blame History

P16调度任务最小运行间隔机制 — task-min-run-interval

优先级P2调度系统增强 来源Session 58_5de84e40_195620 用户需求 预估工作量:中 依赖:无新增外部依赖


背景

现有调度系统(scheduled_tasks 表 + scheduler.py)支持 5 种调度类型once/interval/daily/weekly/cron但缺少"最小运行间隔"维度。部分 ETL 任务(如租户配置)实际只需 10 天运行一次,员工/助教信息 1 天一次,而订单类任务无此限制。

当前问题:

  • 无法限制任务的最小再次运行间隔,调度到期即执行
  • 无法防止同一任务并发执行(上一次未完成就再次入队)
  • 手动执行(POST /api/schedules/{id}/run)无法区分"尊重间隔"和"强制执行"

需求Requirements

用户故事

  1. 作为管理员,我需要为每个调度任务设置最小运行间隔(如 10 天、1 天),使任务即使调度到期也不会在间隔内重复执行,避免资源浪费。
  2. 作为管理员,我需要在必要时强制执行某个任务(绕过最小间隔限制),以应对紧急数据同步需求。
  3. 作为管理员,我需要在调度任务列表中看到每个任务的最小间隔配置和上次成功执行时间,以便了解任务运行状态。

验收标准

  • AC1scheduled_tasks 表新增 min_run_interval_valueINTEGERmin_run_interval_unitVARCHARlast_success_atTIMESTAMPTZ3 个字段
  • AC2调度器轮询时now() - last_run_at < min_run_interval(换算为秒),跳过本次执行并推进 next_run_at
  • AC3任务执行失败时不更新 last_success_at,允许下次调度到期时立即重试(失败不算有效执行)
  • AC4若任务 last_status = 'running'(上次未完成),跳过本次入队,标记为 skipped_concurrent
  • AC5POST /api/schedules/{id}/run 新增 force 查询参数,force=true 时绕过最小间隔和并发检查
  • AC6Admin Web 创建/编辑调度任务表单新增"最小运行间隔"字段(数字输入 + 单位下拉:分钟/小时/天)
  • AC7Admin Web 任务列表新增"最小间隔"列和"上次成功"列
  • AC8Admin Web "手动执行"按钮增加"强制执行"勾选项
  • AC9现有调度任务 min_run_interval_value 默认 0无限制向后兼容
  • AC10所有变更有对应的 BD 手册更新和审计记录

设计要点

核心概念

  • 最小运行间隔:任务开始执行后的最小等待时间(非完成后),基于 last_run_at 判断
  • 有效执行:仅成功完成的执行才更新 last_success_at;失败不重置间隔计时器,允许立即重试
  • 并发保护:若上一次执行仍在进行中(last_status = 'running'),跳过本次入队
  • 强制执行:通过 API force=true 参数绕过最小间隔和并发检查

DDL 变更(scheduled_tasks 表)

新增 3 个字段:

字段名 类型 默认值 说明
min_run_interval_value INTEGER 0 最小间隔数值0 = 无限制)
min_run_interval_unit VARCHAR(20) 'minutes' 间隔单位:minutes / hours / days
last_success_at TIMESTAMPTZ NULL 最后一次成功执行的时间

min_run_interval_value = 0 表示无限制,与现有行为完全一致,确保向后兼容。

调度器逻辑修改(scheduler.py

check_and_enqueue() 方法的判断流程变更为:

对每个到期任务enabled=true AND next_run_at <= now
  1. 并发检查if last_status == 'running' → 跳过,标记 skipped_concurrent
  2. 间隔检查if min_run_interval_value > 0
     a. 计算 min_interval_seconds = convert(value, unit)
     b. if last_run_at IS NOT NULL AND (now - last_run_at) < min_interval_seconds
        → 跳过,推进 next_run_at标记 skipped_interval
  3. 正常入队执行

SQL 查询扩展(新增读取字段):

SELECT id, site_id, task_config, schedule_config,
       min_run_interval_value, min_run_interval_unit,
       last_run_at, last_status
FROM scheduled_tasks
WHERE enabled = TRUE
  AND next_run_at IS NOT NULL
  AND next_run_at <= NOW()
ORDER BY next_run_at ASC

任务完成回调需区分成功/失败:

  • 成功:last_status = 'completed',同时更新 last_success_at = NOW()
  • 失败:last_status = 'failed',不更新 last_success_at

API 扩展(schedules.py

端点 变更
POST /api/schedules CreateScheduleRequest 新增 min_run_interval_valueint, default=0min_run_interval_unitstr, default='minutes'
PUT /api/schedules/{id} UpdateScheduleRequest 新增同上两个可选字段
GET /api/schedules ScheduleResponse 新增 min_run_interval_valuemin_run_interval_unitlast_success_at
POST /api/schedules/{id}/run 新增查询参数 force: bool = Falseforce=true 时绕过间隔和并发检查

Admin Web 变更(ScheduleTab.tsx

创建/编辑表单

  • 新增"最小运行间隔"行:InputNumber(数值)+ Select(单位:分钟/小时/天)
  • 数值为 0 时显示提示"无限制"
  • 位置:在调度类型配置区域下方

任务列表表格

  • 新增"最小间隔"列:显示格式如"10 天"、"1 小时"、"无限制"
  • 新增"上次成功"列:显示 last_success_at 的相对时间(如"2 小时前"

手动执行

  • 现有"立即执行"按钮点击后弹出确认框
  • 确认框新增"强制执行(忽略最小间隔)"勾选项,默认不勾选
  • 勾选后调用 POST /api/schedules/{id}/run?force=true

ETL 任务注册

ETL 侧 TaskMetaTaskRegistry 不需要修改。最小运行间隔是调度层概念,在 scheduled_tasks 表中配置,与 ETL 任务注册解耦。


数据流向

Admin WebScheduleTab.tsx
  ├─ 创建/编辑表单 → min_run_interval_value + min_run_interval_unit
  └─ 手动执行 → force=true/false

    ↓ APIschedules.py

数据库scheduled_tasks 表
  ├─ min_run_interval_value (INTEGER)
  ├─ min_run_interval_unit (VARCHAR)
  └─ last_success_at (TIMESTAMPTZ)

    ↓ 调度器轮询scheduler.py每 30 秒)

判断逻辑:
  1. 并发检查 → last_status == 'running' → 跳过
  2. 间隔检查 → now - last_run_at < min_interval → 跳过
  3. 正常入队 → task_queue.enqueue()

    ↓ 任务执行完成回调

更新 scheduled_tasks
  - 成功 → last_status='completed', last_success_at=NOW()
  - 失败 → last_status='failed'last_success_at 不变)

边界条件与风险

场景 处理方式
min_run_interval_value = 0 无限制,与现有行为一致
last_run_at IS NULL(从未执行) 跳过间隔检查,正常执行
任务执行失败后立即到期 last_success_at 未更新,但间隔检查基于 last_run_at(开始时间),所以失败后仍需等待间隔。但因为失败不算有效执行,下次到期时 last_run_at 已过间隔,可正常执行
手动执行 + force=false + 间隔未到 返回 409 Conflict提示"最小运行间隔未到,距下次可执行还有 X 分钟"
手动执行 + force=true 绕过所有检查,直接入队
并发:上次仍在 running 跳过入队,last_status 标记为 skipped_concurrent(不覆盖原 running 状态,仅日志记录)
调度器重启后 last_run_atlast_success_at 持久化在数据库,重启无影响

不做什么(明确排除)

  • 不修改 ETL 任务注册机制(TaskMeta/TaskRegistry
  • 不新增独立的 task_run_policy 表(直接在 scheduled_tasks 表扩展)
  • 不提供批量 seed SQL 设定初始间隔值(用户逐个配置)
  • 不修改 schedule_config JSONB 结构(新字段放在表级列)
  • 不涉及 ETL 侧的执行逻辑修改
  • 不涉及小程序或 MCP Server

任务清单

数据库层

  • T1DDL 迁移 — scheduled_tasks 新增 3 个字段
    • 迁移脚本:db/zqyy_app/migrations/2026-03-22__p16_min_run_interval.sql
    • DDL 基线同步:docs/database/ddl/zqyy_app__public.sql

后端层

  • T2Schema 更新 — ScheduleConfigSchema 不变,CreateScheduleRequest/UpdateScheduleRequest/ScheduleResponse 新增字段
    • 文件:apps/backend/app/schemas/schedules.py
  • T3调度器核心逻辑 — check_and_enqueue() 新增并发检查 + 间隔检查
    • 文件:apps/backend/app/services/scheduler.py
    • 新增辅助函数 _convert_interval_to_seconds(value, unit)
  • T4API 路由 — 创建/更新端点支持新字段,手动执行端点支持 force 参数
    • 文件:apps/backend/app/routers/schedules.py
  • T5任务完成回调 — 区分成功/失败,成功时更新 last_success_at
    • 文件:apps/backend/app/services/task_queue.py

前端层

  • T6ScheduleTab 表单 — 新增"最小运行间隔"输入(数字 + 单位下拉)
    • 文件:apps/admin-web/src/components/ScheduleTab.tsx
  • T7ScheduleTab 列表 — 新增"最小间隔"列和"上次成功"列
    • 文件:同 T6
  • T8手动执行确认框 — 新增"强制执行"勾选项
    • 文件:同 T6

文档与验证

  • T9BD 手册更新 — scheduled_tasks 表新增字段说明
    • 文件:docs/database/BD_Manual_scheduled_tasks.md
  • T10验证 — Monorepo 属性测试通过OpenAPI 契约同步;文档同步完成

涉及文件汇总

模块 文件路径 操作
DDL 迁移 db/zqyy_app/migrations/2026-03-22__p16_min_run_interval.sql 新增
DDL 基线 docs/database/ddl/zqyy_app__public.sql 修改
Schema apps/backend/app/schemas/schedules.py 修改
调度器 apps/backend/app/services/scheduler.py 修改
API 路由 apps/backend/app/routers/schedules.py 修改
任务队列 apps/backend/app/services/task_queue.py 可能修改(回调)
Admin Web apps/admin-web/src/components/ScheduleTab.tsx 修改
BD 手册 docs/database/BD_Manual_scheduled_tasks.md 新建/修改