拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Medusa 定时任务(Scheduled Jobs)编写与自动加载机制全解析

Medusa 定时任务Scheduled Jobs编写与自动加载机制全解析【免费下载链接】medusaThe worlds most flexible commerce platform for agents and developers项目地址: https://gitcode.com/GitHub_Trending/me/medusa导读Medusa 框架内置了一套基于工作流Workflow的定时任务体系开发者只需在任意模块、插件或自定义目录中放置一个导出了handler与config的 JS/TS 文件框架启动时便会自动发现该文件、校验配置、注册为名为job-name的工作流并按 Cron 表达式周期性执行。本文以框架 jobs 目录下的示例任务order-summary见 fixtures 目录为核心骨架结合 JobLoader 实现 与其 单元测试完整讲解任务文件格式、config 参数语义、加载过滤规则与调度执行原理帮助你写出规范、可调试、可测试的 Medusa 定时任务。一、一个最小定时任务长什么样Medusa 中一个定时任务就是一个模块文件默认导出异步处理函数handler同时具名导出config配置对象。以框架自带的测试示例 order-summary.ts 为例import { MedusaContainer } from medusajs/types export default async function handler(container: MedusaContainer) { console.log(You have received 5 orders today) } export const config { name: summarize-orders, schedule: * * * * * *, numberOfExecutions: 2, }这个文件只有两个关键导出defaulthandler任务的实际执行逻辑接收containerMedusaContainer类型的依赖注入容器可以通过container.resolve(...)获取数据库连接、模块服务、Logger 等任意注册资源并支持异步。config任务元信息由name、schedule、numberOfExecutions等字段组成决定任务注册名与调度策略。值得注意的是在框架的fixtures目录 中还放置了扩展名为.md、.txt的同名文件其内容虽然是合法的任务代码但加载器会将其静默忽略——这正是下一节要讲的扩展名过滤规则。因此实际项目中的定时任务必须使用.js或.ts扩展名。二、config 配置项详解根据 job-loader.ts 中的 CronJobConfig 类型定义 与加载器校验逻辑config支持以下字段字段类型是否必填说明namestring必填任务名称。注册时会自动拼接为工作流名job-${name}例如summarize-orders会生成工作流job-summarize-ordersschedulestring \| SchedulerOptions必填Cron 表达式字符串如* * * * * *六段式也可以直接传入一个包含cron、numberOfExecutions等字段的调度器选项对象numberOfExecutionsnumber可选限定任务的总执行次数。示例中的2表示该任务只会被调度执行 2 次之后不再触发用于一次性/限期任务的场景这三个字段并非可有可无——加载器在注册前会执行严格的配置校验validateConfigif (!config) { throw new MedusaError( MedusaError.Types.INVALID_ARGUMENT, Config is required for scheduled jobs. ) } if (!config.schedule) { throw new MedusaError( MedusaError.Types.INVALID_ARGUMENT, Cron schedule definition is required for scheduled jobs. ) } if (!config.name) { throw new MedusaError( MedusaError.Types.INVALID_ARGUMENT, Job name is required for scheduled jobs. ) }从 源码 可以看到config、schedule、name三者缺一不可否则会抛出INVALID_ARGUMENT类型的MedusaError且任一配置缺失都会导致任务无法注册。因此编写任务时请确保三个字段同时存在。schedule 的两种传法schedule既可以是字符串也可以是对象。加载器在注册时会进行归一化处理const workflowConfig { name: workflowName, schedule: isObject(config.schedule) ? config.schedule : { cron: config.schedule, numberOfExecutions: config.numberOfExecutions, }, }即当schedule传入的是对象时直接透传当传入字符串时自动包装为{ cron, numberOfExecutions }。上面的示例任务经过转换后其调度配置等价于{ cron: * * * * * *, numberOfExecutions: 2, }测试 register-jobs.spec.ts 恰好验证了这一结果——注册完成后通过WorkflowManager.getWorkflow(job-summarize-orders)能取到工作流且其options.schedule精确等于{ cron: * * * * * *, numberOfExecutions: 2 }。三、任务是如何被自动发现的JobLoader 加载链路3.1 资源发现与文件过滤定时任务的自动发现由 JobLoader 完成它继承自 ResourceLoader 基类。JobLoader.load()会调用基类的discoverResources()递归扫描传入的sourceDir并对每个候选文件执行自定义过滤customFiltering (entry: Dirent) { const parsedName parse(entry.name) return ( !entry.isDirectory() (allowIndex || parsedName.name ! index) !parsedName.base.endsWith(.d.ts) !entry.path.includes(__tests__) [.js, .ts].includes(parsedName.ext) !this.#excludes.some((exclude) exclude.test(parsedName.base)) !exclude.some((exclude) exclude.test(parsedName.base)) ) }也就是说只有同时满足以下条件的文件才会被当作任务加载扩展名必须是.js或.ts——这是 fixtures 中的order-summary.md、order-summary.txt不会被加载的根本原因文件名不能是index不以.d.ts结尾路径中不能包含__tests__目录不以_开头/^_[^/\\]*(\.[^/\\])?$/也不以.spec.[jt]s、.test.[jt]s结尾。这套规则保证了 README、测试文件、辅助模块等不会误注册为定时任务。对应的行为在测试中得到了直接验证it(should not load non js/ts files, async () { const jobLoader: JobLoader new JobLoader( join(__dirname, ../__fixtures__/plugin/jobs-with-other-files), container ) await jobLoader.load() const workflow WorkflowManager.getWorkflow(job-summarize-orders) expect(workflow).toBeUndefined() })由于jobs-with-other-files目录下只有.md与.txt文件加载后工作流job-summarize-orders未被注册toBeUndefined()从侧面印证了扩展名过滤的确定性。3.2 校验、注册与工作流化通过过滤的文件会进入onFileLoaded流程其类型签名要求导出defaulthandler与config两个成员protected async onFileLoaded( path: string, fileExports: { default: CronJobHandler config: CronJobConfig } ) { if (isFileSkipped(fileExports)) { return } this.validateConfig(fileExports.config) this.logger.debug(Registering job from ${path}.) this.register({ path, config: fileExports.config, handler: fileExports.default, }) }其中isFileSkipped来自 define-file-config.ts它会检查导出中是否带有框架约定的跳过标记MEDUSA_SKIP_FILE——若任务文件显式导出该标记则会被跳过不注册为开发者提供了一种临时停用任务而不删除文件的手段。通过校验后进入register方法加载器会将 handler 包装为一个Step再用createWorkflow注册为工作流const workflowName job-${config.name} const step createStep( ${config.name}-as-step, async (input: ScheduledJobWorkflowInput | undefined, stepContext) { const { container } stepContext const context: ScheduledJobContext { scheduledFor: input?.scheduledFor ? new Date(input.scheduledFor) : new Date(), } try { const res await handler(container, context) return new StepResponse(res, res) } catch (error) { this.logger.error( Scheduled job ${config.name} failed with error: ${error.message} ) throw error } } )这段代码揭示了几个关键点每个定时任务最终都被注册为名为job-name的工作流Workflow复用 Medusa 的工作流调度与执行基础设施handler 的执行发生在 step 内部container由工作流上下文注入因此任务内可以直接使用容器解析任何服务任务抛出的异常会被捕获并记录为Scheduled job name failed with error: ...随后重新抛出便于在日志中定位失败任务。3.3 scheduledFor任务触发时间上下文从 types.ts 可以看到handler 的完整签名是export type ScheduledJobHandler ( container: MedusaContainer, context?: ScheduledJobContext ) Promiseunknown export type ScheduledJobContext { scheduledFor: Date } export type ScheduledJobWorkflowInput { scheduledFor: string }handler 的第二个参数context.scheduledFor表示本次任务计划执行的时间点。加载器在调度执行时会从工作流输入中取出scheduledFor并转换为Date若未提供则默认取当前时间new Date()。这一机制的语义验证可见 fixtures 中的 scheduled-for.ts它的 handler 直接把context.scheduledFor写入全局变量对应的测试通过LocalWorkflow.run(ulid(), { scheduledFor }, { __type: MedusaContextType })手动触发工作流job-capture-scheduled-for最终断言global.__medusaScheduledForTest等于传入时间戳转换后的Date对象见 register-jobs.spec.ts。如果你的业务需要按计划时间戳而非实际运行时刻来统计数据例如补跑某天的订单汇总务必使用context.scheduledFor。四、从插件加载sourceDir 的传参方式JobLoader的构造函数接受sourceDir: string | string[]支持同时从多个目录发现任务constructor(sourceDir: string | string[], container: MedusaContainer) { super(sourceDir, container) }基类 ResourceLoader.discoverResources 会把目录统一规范化为数组并对每个目录执行access(sourcePath)存在性检查——若目录不存在会打印No job to load from path. skipped.并跳过而不是报错。这意味着插件可以把任务文件放在自己的src/jobs目录下框架启动时即可自动发现即使某个插件没有 jobs 目录加载流程也不会中断多个 sourceDir 之间互不影响各自独立发现与注册。测试should registers jobs from plugins即演示了以join(__dirname, ../__fixtures__/plugin/jobs)作为 sourceDir从插件形态的目录中加载order-summary与scheduled-for两个任务并成功注册工作流job-summarize-orders的过程。五、任务编写与调试实战建议结合上述机制编写一个规范的 Medusa 定时任务可以参考以下实践1. 保持文件结构清晰建议将任务统一放在项目的src/jobs或插件的同名字目录下一个文件一个任务文件名与config.name保持对应关系如order-summary.ts对应summarize-orders便于排查与维护。2. 使用完整的六段 Cron 表达式示例中使用的* * * * * *是六段式 Cron秒 分 时 日 月 周开发调试时可用它让任务每秒触发生产环境请根据实际频率改写为合理的表达式避免高频空转。3. 善用 numberOfExecutions 控制执行次数对于一次性数据修复、迁移类任务可以通过numberOfExecutions: 1让任务只执行一次配合schedule即可实现启动后立即执行且仅执行一次的语义无需额外的状态标记。4. 通过容器获取服务而非直接 importhandler 的第一个参数是MedusaContainer应当通过container.resolve(...)获取订单服务、库存服务或 Logger 等资源这样既能享受依赖注入与测试 mock 的便利也符合框架的解耦设计。5. 遵守扩展名与命名约束文件必须为.js/.ts不要命名为index、.d.ts不要放入__tests__目录不要以_开头或以.spec.ts/.test.ts结尾否则会被自动过滤若希望临时停用某个任务可以导出MEDUSA_SKIP_FILE标记详见 define-file-config.ts而不是删除文件或破坏扩展名。6. 利用日志定位失败任务当 handler 抛出异常时加载器会输出Scheduled job name failed with error: ...建议在 handler 内部也使用注入的 Logger 记录关键业务步骤便于区分调度失败与业务失败。六、机制总结回顾整条链路一个 Medusa 定时任务的生命周期是发现JobLoader递归扫描 sourceDir按扩展名与命名规则过滤出合法的.js/.ts任务文件非 JS/TS 文件如.md、.txt被忽略校验validateConfig强制要求config、schedule、name三者齐全缺失即抛INVALID_ARGUMENT注册handler 被包装为 step与归一化后的调度配置一起通过createWorkflow注册为job-name工作流执行工作流调度器按 Cron 触发 step向 handler 注入container与包含scheduledFor的上下文numberOfExecutions控制累计执行次数失败处理异常被记录日志后重新抛出等待调度器与监控体系进一步处理。这套设计让定时任务与 Medusa 的工作流引擎深度统一开发者无需关心底层的调度实现只需遵循default handler config的约定编写任务文件即可获得自动发现、自动注册、可单测通过WorkflowManager.getWorkflow断言注册结果的完整能力。【免费下载链接】medusaThe worlds most flexible commerce platform for agents and developers项目地址: https://gitcode.com/GitHub_Trending/me/medusa创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门