【免费下载链接】agendaLightweight job scheduling for Node.js项目地址https://gitcode.com/gh_mirrors/ag/agenda点击查看免费下载导读Agenda 是轻量级 Node.js 作业调度器其重复任务every()/repeatEvery()支持startDate、endDate、skipDays三类日期约束。本篇文章以仓库中 .changeset/pg-redis-date-constraints.md 记录的修复为核心剖析约束字段被静默丢弃、带 endDate 的重复任务永远运行下去这一缺陷的成因与修复链路完整讲解日期约束的计算语义、PostgreSQL 与 Redis 两个后端的持久化实现、自动迁移机制以及可复制的 API 用法与测试验证方法。一、缺陷背景被静默丢弃的日期约束在修复之前startDate、endDate、skipDays三个字段虽然在核心模型与计算逻辑中已经存在但在 PostgreSQL 与 Redis 后端的持久化环节缺失导致以下连锁问题任务每次调度时nextRunAt计算会用到这些约束但任务执行完毕、重新从数据库加载时约束字段已经丢失于是一个设置了endDate的重复任务在过期之后仍然被不断重新调度——会永远运行下去。变更记录原文明确指出Persist and loadstartDate,endDateandskipDaysso date constraints on repeating jobs are honored (previously these fields were silently dropped — a repeating job with anendDatewould keep running forever). The PostgreSQL backend adds thestart_date,end_dateandskip_dayscolumns automatically on connect for existing installations.即修复的核心工作是持久化 读取两端补齐同时为 PostgreSQL 老安装提供开箱即用的自动加列迁移。同类问题在 MongoDB 后端也以独立变更记录 .changeset/mongo-date-constraint-fields.md 修复将文档字段映射回 Job 对象说明这是跨后端的共性问题。二、三个日期约束的语义与计算原理2.1 字段定义核心模型约束字段定义于 JobParameters 接口startDate?: Date任务在此日期之前不会运行endDate?: Date任务在此日期之后停止运行nextRunAt会被置为nullskipDays?: number[]每周跳过的日子0 周日、1 周一……6 周六任务在这些天不运行。同时startDate、endDate与lastRunAt、nextRunAt等一起被列入datefields日期字段清单见 JobParameters.tsJob.toJSON等序列化路径会将其统一按日期处理。2.2 计算逻辑applyAllDateConstraints约束的真正裁决发生在 packages/agenda/src/utils/dateConstraints.ts这是理解整个修复价值的核心文件applyDateRangeConstraints(nextRunAt, startDate, endDate)若nextRunAt早于startDate则把结果抬升到startDate若晚于endDate直接返回null任务不应再运行applySkipDays(date, skipDays, timezone)从原日期起逐日向后查找第一个合法工作日保留原时刻时分秒不变若所有 7 天都被跳过则返回null为避免死循环设有MAX_SKIP_ITERATIONS 8的上限shouldSkipDay(date, skipDays, timezone)判断某一天是否应被跳过内部将 Luxon 的星期表示周一 1、周日 7转换为 JS 的星期表示周日 0applyAllDateConstraints(nextRunAt, options)组合入口执行顺序为范围约束仅 startDate→ 跳过日 → 再次校验 endDate因为跳过日可能导致日期后移越过endDateisWithinDateRange(date, startDate, endDate)范围判断工具。注意applySkipDays与applyDateRangeConstraints都支持timezone参数实际调用时传入repeatTimezone保证跳过哪一天按任务自身时区而非服务器时区判定。2.3 接入点nextRunAt 的两条计算路径computeFromInterval间隔 / cron / 人类可读间隔与computeFromRepeatAt定点重复都在 packages/agenda/src/utils/nextRunAt.ts 中且都在算出裸nextRunAt后统一调用applyAllDateConstraints// computeFromInterval 中nextRunAt.ts if (attrs.startDate || attrs.endDate || attrs.skipDays) { nextRunAt applyAllDateConstraints(nextRunAt, { startDate: attrs.startDate, endDate: attrs.endDate, skipDays: attrs.skipDays, timezone: attrs.repeatTimezone }); if (nextRunAt null) { log([%s:%s] nextRunAt is null after applying date constraints, attrs.name, attrs._id); } }当约束把nextRunAt压成null时任务便不再进入调度队列——这正是endDate 到期后停止的语义落点。也正因为这一裁决依赖attrs上的约束字段持久化一旦丢字段裁决便形同虚设这正是本次修复要解决的根因。三、PostgreSQL 后端schema、自动迁移与读写映射3.1 表结构与自动迁移packages/postgres-backend/src/schema.ts 定义了建表 SQL、迁移 SQL、索引 SQL 与updated_at触发器新建表的CREATE TABLE IF NOT EXISTS中直接包含三列start_date TIMESTAMPTZ, end_date TIMESTAMPTZ, skip_days JSONB,getMigrationSQL(tableName)针对存量表提供三条幂等迁移语句ADD COLUMN IF NOT EXISTS因此可以反复执行、对老安装安全ALTER TABLE agenda_jobs ADD COLUMN IF NOT EXISTS start_date TIMESTAMPTZ; ALTER TABLE agenda_jobs ADD COLUMN IF NOT EXISTS end_date TIMESTAMPTZ; ALTER TABLE agenda_jobs ADD COLUMN IF NOT EXISTS skip_days JSONB;3.2 连接时自动加列迁移并非手工步骤而是在PostgresJobRepository.connect()中自动触发连接成功后若配置ensureSchema默认truecreateSchema会依次执行建表、迁移、索引、触发器见 PostgresJobRepository.ts。也就是说旧库只要重启应用并连上数据库三列便会自动补齐无需人工 DDL。3.3 读写映射读rowToJob将数据库行转换为JobParameters三列被映射回驼峰字段startDate: row.start_date ?? undefined、endDate: row.end_date ?? undefined、skipDays: row.skip_days ?? undefined见 PostgresJobRepository.ts写start_date / end_date / skip_days同时进入字段白名单列表第 58–60 行、UPDATE语句参数第 681–683 行、INSERT ... ON CONFLICT的列与EXCLUDED赋值第 729–746 行以及 upsert 分支第 859–936 行确保普通保存与single类型every()使用的 upsert 语义两条写入路径都不会再丢字段。四、Redis 后端Hash 的序列化与反序列化Redis 后端没有 DDL 概念任务以 Hash 结构存储因此修复集中在序列化两端见 packages/redis-backend/src/RedisJobRepository.ts写jobToHash第 177–179 行日期类型统一转 ISO 字符串空值用字符串null占位skipDays用JSON.stringify序列化为字符串startDate: job.startDate?.toISOString() || null, endDate: job.endDate?.toISOString() || null, skipDays: job.skipDays ? JSON.stringify(job.skipDays) : null,读hashToJob第 137–143 行反向解析null还原为undefinedskipDays通过JSON.parse还原为number[]startDate: data.startDate data.startDate ! null ? new Date(data.startDate) : undefined, endDate: data.endDate data.endDate ! null ? new Date(data.endDate) : undefined, skipDays: data.skipDays data.skipDays ! null ? (JSON.parse(data.skipDays) as number[]) : undefined,两条路径普通保存与 upsert 分支都覆盖了这三个字段保证从 Redis 重载任务后约束依然完整nextRunAt计算可以正确裁决。五、如何设置日期约束API 用法5.1 声明式agenda.every()选项every()的签名packages/agenda/src/index.ts原生支持三类约束配合timezone、skipImmediate、forkMode一起使用await agenda.every(5 minutes, cleanup-job, undefined, { timezone: Asia/Shanghai, startDate: new Date(2026-10-01T00:00:0008:00), // 10 月 1 日起生效 endDate: new Date(2026-10-31T23:59:5908:00), // 10 月 31 日之后停止 skipDays: [0, 6], // 跳过周六、周日 skipImmediate: false });内部实现第 896–917 行会先把startDate / endDate / skipDays应用到Job上再调用repeatEvery(interval, options)计算nextRunAt最后job.save()落库——约束在首次计算时即参与裁决。5.2 命令式Job 链式方法Job.ts 提供三个可链式调用的方法均带输入校验job.startDate(date)/job.endDate(date)第 166–187 行接受Date或可解析字符串非法日期直接抛错job.skipDays(days)第 194–203 行要求0–6的整数自动去重[0, 6]即跳过周末。典型组合用法const job agenda.create(report, {}); job.repeatEvery(1 day, { timezone: Asia/Shanghai }); job.startDate(2026-11-01T00:00:0008:00); job.endDate(2026-11-30T23:59:5908:00); job.skipDays([0]); await job.save();搭配skipImmediate: true时repeatEvery会以当前nextRunAt为基准计算下一次运行避免立即先跑一次见 Job.ts。六、验证与测试修复的正确性有测试兜底核心用例集中在 packages/agenda/test/date-constraints.test.ts覆盖shouldSkipDay空数组 / 未定义返回false周一1、周日0命中正确applyDateRangeConstraints早于startDate时抬升到startDate晚于endDate时返回null区间内原样返回组合场景startDate落在周六且skipDays: [0, 6]时结果被推到下一个周一跳过日推移后越过endDate时返回nullisWithinDateRange的边界判定。后端持久化的回归验证可参考各后端测试目录如 packages/postgres-backend/test 与 packages/redis-backend/test执行创建带约束的重复任务 → 重启/重新加载 → 断言约束字段与nextRunAt裁决正确即可验证不再静默丢弃的修复目标。七、升级与注意事项PostgreSQL 存量库无需手工迁移只要ensureSchema为默认的true连接时自动执行ADD COLUMN IF NOT EXISTS若显式关闭了ensureSchema则需要自行执行 schema.ts 中的三条迁移语句。Redis 无迁移负担null占位字符串与 JSON 序列化对旧数据完全兼容旧任务重写一次即带上约束字段。语义边界endDate的终止依据是计算出的下一次运行时间晚于 endDate 则不再调度并非任务执行瞬间的实时判断skipDays与startDate组合时若起跑日恰为跳过日会顺延到下一个合法工作日受MAX_SKIP_ITERATIONS 8保护。时区一致性建议同时设置repeatTimezone保证跳过日与起止日期按任务时区裁决避免服务器 UTC 与业务时区错位导致跳错天。至此从缺陷根因持久化缺环到计算裁决dateConstraints.ts、双后端实现PostgreSQL 自动迁移列 Redis Hash 序列化、API 用法与测试验证startDate/endDate/skipDays三条日期约束在 Agenda 中的完整闭环已经清晰可见——这正是本次 patch 变更记录所要传达的全部技术价值。赞分享【免费下载链接】agendaLightweight job scheduling for Node.js项目地址https://gitcode.com/gh_mirrors/ag/agenda点击查看免费下载相关推荐Prefect Worker 源码架构指南基于工作池Work Pool的基础设施执行层深入剖析Prefect Worker 源码架构指南基于工作池Work Pool的基础设施执行层深入剖析 Worker工作器是 Prefect 工作池WorkAgenda 修复重复 Cron 任务跳过合法触发点computeFromInterval 调度逻辑深度解析Agenda 修复重复 Cron 任务跳过合法触发点computeFromInterval 调度逻辑深度解析 导读 本文基于 Agenda 仓库中 fix c彻底解决日期选择边界问题bootstrap-datepicker startDate与endDate全解析彻底解决日期选择边界问题bootstrap datepicker startDate与endDate全解析 引言你还在为日期选择器的边界限制烦恼吗 在We前端UI组件上一篇Mask2Former 通用图像分割模型在 MMDetection 中的配置与源码解析下一篇YgoMaster终极指南免费离线畅玩完整游戏王体验创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
