尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Egg 定时任务调度实战指南:基于 Schedule 装饰器的 Worker/All 双模式实现
Egg 定时任务调度实战指南基于 Schedule 装饰器的 Worker/All 双模式实现【免费下载链接】egg Born to build better enterprise frameworks and apps with Node.js Koa. https://307.run/eggcode项目地址: https://gitcode.com/gh_mirrors/eg/egg导读本文面向使用 Egg tegg 技术栈的开发者完整讲解基于Schedule装饰器实现定时任务的完整流程从插件启用、interval间隔模式与cron表达式模式的配置语法到worker/all两种执行模式的行为差异再到immediate、disable、env等任务运行参数的使用方法。读完本文你将能够用装饰器方式在 Egg 应用中快速注册定时任务并理解任务在 agent 与 worker 之间的调度与分发原理。核心概念与适用场景Egg 的定时任务Schedule用于支持定期执行的自动化逻辑例如数据清洗、缓存预热、报表生成、定时推送等。文档明确指出其核心使用场景是Supports regular scheduled tasks that will execute on every deployed machine.即常规定时任务会在每一台部署的机器上执行。例如生产环境通常至少部署 2 台机器那么该定时任务会在两台机器上都运行。这一特性与后文要讲到的worker/all模式共同决定了任务的实际执行次数若按“每台机器上仅一个进程执行”适合worker模式与单机执行次数无关每台机器各执行一次若按“每台机器上所有 worker 进程都执行”适合all模式每台机器上 N 个 worker 就执行 N 次。与 tegg 体系内的其他装饰器如Inject、SingletonProto一致Schedule装饰器同样遵循“类即控制器”的约定被装饰的类必须包含一个名为subscribe的方法框架在调度任务时会调用该类的subscribe方法。使用前提切勿把代码放到 app/schedule 目录文档给出了一个高优先级警告⚠️ Do not place your code in theapp/schedulepath, because Egg scans this path by default to register scheduled tasks, which will conflict with the decorator-based approach.即不要把使用装饰器的定时任务代码放在app/schedule目录下。原因在于 Egg 框架默认会扫描app/schedule路径并自动注册定时任务这与 tegg 装饰器方式注册会产生冲突。从源码可以印证这一点load_schedule.ts 中的loadSchedule函数会把app.loader.getLoadUnits().map((unit) path.join(unit.path, app/schedule))作为默认扫描目录之一同时叠加app.config.schedule.directory配置的额外目录。因此本文示例统一将定时任务类放在app/port/schedule/目录下如app/port/schedule/Demo.ts这也是 tegg 推荐的端口port目录约定。启用插件schedule是 Egg 内置插件默认启用。在config/plugin.ts中显式声明即可export default { teggSchedule: true, };从插件入口 index.ts 可以看到eggjs/schedule插件通过definePluginFactory定义enable: true无需额外安装export default definePluginFactory({ name: schedule, enable: true, path: import.meta.dirname, }) as EggPluginFactory;插件同时导出了ScheduleWorker与Scheduler两个核心类分别用于 worker 侧的任务注册与 agent 侧的调度管理。两种触发模式Interval 间隔模式interval模式表示每个机器按固定间隔执行一次任务间隔通过Schedule装饰器第一个参数的scheduleData.interval设置number 类型单位为毫秒例如100表示每 100ms 执行一次string 类型会使用 ms将其转换为毫秒例如5s表示每 5 秒执行一次同样支持1m、2h等写法。示例代码// app/port/schedule/Demo.ts import { Inject, Logger } from egg; import { IntervalParams, Schedule, ScheduleType } from egg/schedule; ScheduleIntervalParams({ type: ScheduleType.WORKER, scheduleData: { interval: 100, // Execute every 100ms // interval: 5s, // Execute every 5s }, }) export class IntervalScheduler { Inject() private logger: Logger; async subscribe() { this.logger.info(schedule called); } }在底层实现中TimerStrategy.getNextTick()见 timer.ts对 interval 模式直接调用ms(this.scheduleConfig.interval)换算成下一次执行的毫秒数随后通过safeTimeout触发下一次调度形成固定频率的执行循环。Cron 表达式模式cron模式按照 cron 表达式规则执行任务表达式语法遵循 cron-parser 中通过cronParser.parseExpression(cron, cronOptions)解析。Egg 的 cron 表达式支持6 位字段包含秒各字段含义如下* * * * * * ┬ ┬ ┬ ┬ ┬ ┬ │ │ │ │ │ | │ │ │ │ │ └ day of week (0 - 7) (0 or 7 is Sun) │ │ │ │ └───── month (1 - 12) │ │ │ └────────── day of month (1 - 31) │ │ └─────────────── hour (0 - 23) │ └──────────────────── minute (0 - 59) └───────────────────────── second (0 - 59, optional)以下示例在每台机器上每天凌晨 3 点执行一次// app/port/schedule/CronDemo.ts import { Inject, Logger } from egg; import { CronParams, Schedule, ScheduleType } from egg/schedule; ScheduleCronParams({ type: ScheduleType.WORKER, scheduleData: { // Execute once daily at 3 AM cron: 0 0 3 * * *, // Execute every 5 seconds // cron: */5 * * * * *, }, }) export class CronSubscriber { Inject() private logger: Logger; async subscribe() { this.logger.info(schedule called); } }从类型定义schedule.ts可以看到CronParams还支持cronOptions字段用于向 cron-parser 传递currentDate、startDate、endDate、tz时区等解析选项。在 timer.ts 的 cron 分支中框架会循环调用cronInstance.next()不断向后推进时间直到找到一个晚于当前时刻的触发点再据此计算nextTick若表达式超出endDate时间范围则打印日志并停止调度。两种执行模式worker 与 all常规定时任务一般使用worker模式即每台机器上只有一个 worker 进程会执行该任务框架同时提供all模式用于需要每台机器上所有 worker 都执行的场景worker模式每台机器上仅有一个 worker 执行每次执行时由 agent随机选择哪个 workerall模式每台机器上的每个 worker 都会执行该任务。import { Inject, Logger } from egg; import { IntervalParams, Schedule, ScheduleType } from egg/schedule; ScheduleIntervalParams({ type: ScheduleType.ALL, // All workers will execute scheduleData: { interval: 100, }, }) export class AllScheduler { Inject() private logger: Logger; async subscribe() { this.logger.info(schedule called); } }这一行为差异在底层由两套策略类实现见 agent.tsWorkerStrategyworker.tshandler()调用this.sendOne()在 base.ts 中通过this.agent.messenger.sendRandom(egg-schedule, info)将任务随机发送给某一个 workerAllStrategyall.tshandler()调用this.sendAll()在 base.ts 中通过this.agent.messenger.send(egg-schedule, info)广播给所有 worker。每次触发时agent 都会为任务生成一个唯一 job idgetSeqId()由时间戳、高精度计时与计数拼接而成并记录[Job#${id}] ${key} triggered, send random/all by agent日志。worker 侧收到egg-schedule消息后见 app.ts会等待应用 ready、创建匿名 Context 并执行schedule.task(ctx, ...info.args)随后把执行结果成功与否、耗时rt、错误信息通过messenger.sendToAgent回传给 agent 完成闭环。任务运行参数ScheduleOptionsSchedule装饰器还支持第二个参数用于指定任务运行参数对应类型定义中的ScheduleOptions见 schedule.ts参数类型说明immediateboolean为true时应用启动并 ready 后立即执行一次任务默认falsedisableboolean为true时定时任务不启动默认falseenvstring[]指定只在特定环境如devserver、test、prod下才启动该任务import { Inject, Logger } from egg; import { IntervalParams, Schedule, ScheduleType } from egg/schedule; ScheduleIntervalParams( { type: ScheduleType.WORKER, scheduleData: { interval: 100, }, }, { immediate: true, // Execute once immediately after app starts and becomes ready // disable: true, // When true, the scheduled task will not start env: [devserver, test], // Only run in offline environments }, ) export class ParamScheduler { Inject() private logger: Logger; async subscribe() { this.logger.info(schedule called); } }这些参数在框架各阶段生效env过滤发生在加载阶段load_schedule.ts 中若配置了env数组且当前app.config.env不在其中则直接忽略该任务并打印ignore schedule ... due to schedule.env not match日志。tegg 侧对应 ScheduleMetadata.shouldRegister() 的实现disable过滤发生在注册阶段schedule.ts 的registerSchedule中schedule.disable为true的任务不会创建对应策略实例worker 侧收到消息后也会再次校验并打印disable日志immediate立即执行发生在启动阶段timer.ts 的start()中若开启immediate则通过setImmediate(() this.handler())立即触发一次否则进入scheduleNext()等待下一次触发点。另外TimerStrategy构造函数timer.ts会断言interval、cron、immediate三者至少存在其一否则抛出错误提示避免写出无法触发的空任务。装饰器到插件执行链的完整打通作为补充可以看看装饰器侧如何与eggjs/schedule插件衔接。Schedule装饰器本身Schedule.ts所做的工作包括通过ScheduleInfoUtil将isSchedule标记、调度参数ScheduleParams与运行选项ScheduleOptions写入类元数据将该类注册为SingletonProto单例 BeanaccessLevel: AccessLevel.PUBLIC通过PrototypeUtil.setFilePath记录类的源文件路径供后续importResolve归一化 key。tegg 插件侧tegg/plugin/schedule的SchedulePrototypeHook与ScheduleWorkerLoadUnitHook会在 Bean 原型加载阶段感知到带isSchedule标记的类由ScheduleManagerScheduleManager.ts将其包装为{ schedule, task, key }结构并注册进 worker 侧的scheduleWorker.registerSchedule()最终落入与文件扫描方式相同的ScheduleWorker注册表schedule_worker.ts。这正是装饰器任务与app/schedule文件任务能在同一套 agent 调度链路中共存的原因也再次印证了“两条注册路径必须避免重复声明”的警告。配置参考与日志eggjs/schedule插件的默认配置config.default.ts如下注册了一个独立的scheduleLogger自定义日志器consoleLevel: NONE控制台不输出日志写入egg-schedule.log文件schedule.directory默认为空数组可配置为自定义额外扫描目录的完整路径与默认的app/schedule目录叠加。因此排障时可以重点关注egg-schedule.log中的以下关键日志register schedule key任务成功注册[Job#id] key triggered, send random/all by agentagent 已触发任务[Job#id] key executing by appworker 开始执行[Job#id] key execute succeed/failed, used rtms任务执行结果与耗时。借助这些日志可以快速确认任务是未注册env/disable过滤、未触发cron 表达式问题还是执行失败业务异常从而完成从配置到运行的全链路排障。小结本文完整覆盖了 Egg tegg 定时任务的装饰器使用方式开启teggSchedule插件 → 在app/port/schedule/目录编写带Schedule装饰器与subscribe方法的类 → 按需选择interval毫秒/时间字符串或cron6 段表达式触发模式 → 通过type决定worker每机单 worker 随机执行或all每机全 worker 执行→ 通过第二参数immediate、disable、env控制任务运行时机。在此基础上本文结合 plugins/schedule 与 tegg/core/schedule-decorator、tegg/core/types 的源码说明了 agent 调度、worker 分发、参数过滤与日志埋点等底层机制帮助你既会写、也能排查。【免费下载链接】egg Born to build better enterprise frameworks and apps with Node.js Koa. https://307.run/eggcode项目地址: https://gitcode.com/gh_mirrors/eg/egg创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED

相关推荐

深入 Draggable Examples 示例沙箱:项目结构、构建流程与拖拽交互源码实战

深入 Draggable Examples 示例沙箱:项目结构、构建流程与拖拽交互源码实战

深入 Draggable Examples 示例沙箱:项目结构、构建流程与拖拽交互源码实战 【免费下载链接】draggable The JavaScript Drag & Drop library your grandparents warned you about. 项目地址: https://gitcode.com/gh_mirrors/dr/draggable 本文以官方示例…

📅 2026/9/21 14:08:02
CVE-2026-70426 Jenkins Remoting反序列化RCE实战:原理、复现、检测脚本与加固方案

CVE-2026-70426 Jenkins Remoting反序列化RCE实战:原理、复现、检测脚本与加固方案

前言 Java反序列化漏洞发展到今天,单纯的原生无过滤反序列化漏洞已经极少。各类中间件、框架都引入类白名单机制,把危险类拦截在反序列化入口。JEP-200就是Jenkins为了封堵Remoting通道反序列化攻击引入的防护,多年来被视作Jenkins控制器最重…

📅 2026/9/21 14:08:02
网络安全行业五大实战误区:从攻防本质纠正企业安全建设偏差

网络安全行业五大实战误区:从攻防本质纠正企业安全建设偏差

做网络安全久了,我发现绝大多数企业的安全问题,从来都不是缺设备、缺预算、缺技术人员,而是从根上走错了方向。 很多团队常年陷入无效安全建设:年年采购新安全设备、月月做漏洞扫描、次次开展安全培训,但是真正的入侵风…

📅 2026/9/21 14:08:02
MORE NEWS

更多资讯

📰

Altium Designer交互式BOM插件安装与版本兼容指南

做PCB这行久了,你会越来越觉得,BOM这东西做得好不好,直接决定贴片厂对你的态度。以前我导出的BOM要么是Excel、要么是PDF,一大串位号、封装、料号堆在一起,工人得对着板子上的丝印一个个找,找错了就焊错&am…

📰

OrCAD Capture报错警告本质解析与工程化应对策略

1. 为什么“ERROR”和“Warning”不是报错,而是设计意图的翻译错误OrCAD Capture里弹出的红色ERROR和黄色Warning,绝大多数人第一反应是“软件出问题了”,立刻去搜“OrCAD报错怎么解决”,结果翻遍论坛、看十篇教程,发现…

📰

Feathers 接入 Discord OAuth 登录:从应用配置到自定义策略的完整实战

Feathers 接入 Discord OAuth 登录:从应用配置到自定义策略的完整实战 【免费下载链接】feathers The API and real-time application framework 项目地址: https://gitcode.com/gh_mirrors/fe/feathers 本指南基于 Feathers 官方 Cookbook 中的 Discord 认证…

📰

Neovide 完全指南:用 Rust 打造的 Neovim 图形界面客户端,从安装到配置与源码架构

Neovide 完全指南:用 Rust 打造的 Neovim 图形界面客户端,从安装到配置与源码架构 【免费下载链接】neovide No Nonsense Neovim Client in Rust 项目地址: https://gitcode.com/gh_mirrors/ne/neovide Neovide 是一个用 Rust 编写的简单图形用户…

📰

把 Cursor 的自定义模型改到 TaoToken 之后,.cursor/rules 里的 .mdc 规则照样被 Agent 自动应用

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

react-admin 记录锁实时查询指南:useGetLockLive Hook 从入门到实战

前端UI组件 【免费下载链接】react-admin A frontend Framework for single-page applications on top of REST/GraphQL APIs, using TypeScript, React and Material Design 项目地址: https://gitcode.com/gh_mirrors/re/react-admin 点击查看 免费下载 useGetLo…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬