【免费下载链接】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),仅供参考
企业数字化 ERP 产品动态
相关推荐
打造AI安全审计技能:SKILL.md设计与落地实践 最近我把团队内部用了两个月的security-audit-skill整理成了独立仓库。起因很直接:团队里几个主力开发都开始用 Claude Code 和 Codex 写业务代码,AI 产出速度确实快,但安全上总是漏风。更尴尬的是,你让模型直接做代码审计&#x… · 2026/9/24 22:25:14
Python基础知识与三种程序结构:变量、判断、循环实战详解 做Python编程笔记整理这事,我其实拖延了挺久。之前一直觉得“基础知识”和“三种结构”这种内容,网上一搜一大把,再写一遍好像没什么意思。但真当我自己系统梳理学习路线时才发现,很多人(包括以前的我)卡住… · 2026/9/24 22:25:14
Qwen Coder本地部署实战:Mac上运行AI代码生成模型的完整指南 把"coder"丢进搜索引擎,你会发现这个单词养活了好几条完全不相干的搜索路径:有人想知道"coder咋下载",有人盯着"qwen coder mac 部署"研究半天,有人在讨论"ai coder 代码生成现状"&#… · 2026/9/24 22:25:14
网页视频下载实战:从开发者工具定位地址到HLS切片与防盗链处理 不知道你有没有遇到过这种场景:想从某个网页上保存一段视频到本地,但页面里既没有下载按钮,也没有分享链接,右键菜单里只有脏兮兮的一段“视频另存为”结果点完直接变成假死,或者干脆转圈。我经常收到类似“下载页面上… · 2026/9/24 23:01:33
38岁被裁、N+3赔偿、房贷压顶:用工程思维重构职场安全边界 1. 被叫去谈话之前,其实早有预兆这事发生在一个关系还挺好的前同事身上。他今年38岁,在某家互联网公司做运营总监,月薪两万八,每月房贷一万二。周三下午被HR约谈,周四上午签完字,周五就收拾东西走人了。过程… · 2026/9/24 23:01:33
GitHub日榜观察:如何筛选高质量开源项目并快速上手落地 这段时间打开 GitHub 的 Trending 页面已经成了我的一个固定动作,每天抽几分钟扫一眼日榜,看看社区里又冒出了哪些新东西。2026 年 9 月 20 日这天也不例外,榜单上依然是 AI 工具链、开发者效率工具和学习型仓库占大头,但仔细翻下… · 2026/9/24 23:01:26
星辰Xing4.0-29B本地部署实测:MoE架构下的表格与财报助手 1. 项目概述:为什么我盯上了星辰 Xing4.0-29B星辰 Xing4.0-29B 这个名字,最近在本地部署圈子里出现的频率明显高了。它是中国电信星辰系列开源出来的一枚 29B MoE 模型,权重公开、授权商用,我在第一时间拉下来跑了一周,… · 2026/9/24 23:01:26
Android Init启动流程详解:从内核到Zygote的完整链路 Android Init 启动流程,说实话,很多做上层应用开发的朋友可能一辈子都用不到它。但只要你接触过上层的系统稳定性问题、开机流程优化、或者是做过 BSP 适配,你早晚要回来啃这一块。作为一个被 Init 折腾过无数回的过来人,我觉得有… · 2026/9/24 23:01:26
Modbus转MQTT网关实战:老旧设备数据上云选型部署与踩坑指南 开头部分:做工业数据采集这行快十年了,这两年被问得最多的一个问题就是:现场有台老设备,没网口也没串口,数据怎么上云?或者更常见的情况——设备有RS485口,但PLC型号太老,厂里没人会… · 2026/9/24 23:01:26
基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程 简介:这是一套面向计算机、人工智能、自动化等专业学生与教师的毕业设计级项目资源,围绕YOLOv8实现渔船作业监控系统,可用于毕设、课程设计、大作业或项目立项演示。压缩包共97个文件,约24.21MB,以70个Python源码文件为… · 2026/9/24 0:00:13
1D-CNN时间序列建模实战:从Conv1d原理到工业落地 简介:面向时间序列数据建模的一维卷积神经网络完整实现,适合深度学习入门者及需要快速验证时序模型的研究者,能够从音频、文本、传感器或股价等序列中挖掘局部特征与时间依赖。压缩包体积很小,只有3KB,内含3个Python脚… · 2026/9/24 0:00:26
柔软的L:汉语语流中被忽视的舌肌张力控制 1. 这个“L”不是字母表里的L,而是舌尖上的L最近在几个方言群和语音教学社群里,反复看到有人发一句:“也说字母L:柔软的长舌”。初看以为是英语发音课笔记,点开才发现全是方言爱好者、播音系学生、语言康复师甚至戏曲演… · 2026/9/24 0:00:44