资讯动态

Medusa 自定义订阅者(Subscribers)开发指南:从事件订阅声明到事件处理实战

发布时间:2026/9/11 13:29:39 来源:尧图企业网站定制
Medusa 自定义订阅者Subscribers开发指南从事件订阅声明到事件处理实战【免费下载链接】medusaThe worlds most flexible commerce platform for agents and developers项目地址: https://gitcode.com/GitHub_Trending/me/medusa导读本文以 packages/plugins/loyalty/src/subscribers/README.md 为骨架系统讲解 Medusa 自定义订阅者Custom Subscribers的完整开发方法如何通过src/subscribers目录下的一个文件同时声明事件订阅关系与事件处理函数如何消费SubscriberArgs中的事件载荷与容器依赖并结合 loyalty 插件中真实的订单礼品卡订阅者深入源码剖析订阅者的加载、校验与注册机制。读完本文你将能够在自己的 Medusa 项目中编写出可落地、可复用的自定义订阅者。订阅者是什么Medusa 事件驱动扩展机制在 Medusa 中订阅者Subscriber负责处理应用中发出emit的事件。它的工作方式非常直接应用内部如工作流、模块服务通过事件总线Event Bus发布事件订阅者声明自己关心哪些事件事件被发布时事件总线找到对应的订阅者并执行其处理函数。这种解耦设计让开发者可以在不修改核心代码的前提下对产品被创建订单已下单等业务节点做出响应是 Medusa 插件与自定义项目中最常用的扩展点之一。从仓库实现看订阅者的核心类型定义在 packages/core/framework/src/subscribers/types.ts而实际加载逻辑位于 packages/core/framework/src/subscribers/subscriber-loader.ts本文后续会逐一展开。第一个订阅者文件位置、导出约定与最小示例订阅者文件创建在项目或插件的src/subscribers目录下使用 TypeScript 或 JavaScript 编写。例如创建文件src/subscribers/product-created.ts内容如下import { type SubscriberConfig, } from medusajs/framework // subscriber function export default async function productCreateHandler() { console.log(A product was created) } // subscriber config export const config: SubscriberConfig { event: product.created, }一个订阅者文件必须导出两部分内容导出类型作用default订阅者函数异步函数关联事件触发时被执行的处理器config配置对象SubscriberConfig声明该订阅者监听的事件值得强调的是这里遵循了约定优于配置的设计文件放在src/subscribers下、default导出处理函数、config导出配置三者缺一不可。加载器正是据此自动发现并注册订阅者。SubscriberConfig 的完整定义从 packages/core/framework/src/subscribers/types.ts 可以看到SubscriberConfig的完整结构export type SubscriberConfig { event: string | string[] context?: SubscriberContext }event订阅的事件名既可以是单个字符串也可以是字符串数组——用数组即可让一个订阅者同时监听多个事件。context可选的订阅上下文其中最重要的字段是subscriberId用于显式指定订阅者的唯一标识后文在注册机制部分会详细说明其优先级。订阅者参数 SubscriberArgs事件载荷与容器订阅者函数会收到一个参数对象包含以下属性属性类型说明eventEvent事件详情对象其data属性即事件的载荷数据containerMedusaContainerMedusa 容器用于解析模块的主服务及其他已注册资源pluginOptionsRecordstring, unknown插件选项当订阅者来自插件时可用其中event的完整结构定义在 packages/core/types/src/event-bus/common.tsexport type EventTData unknown { name: string // 事件名称例如 user.created metadata?: EventMetadata data: TData // 订阅者收到的数据载荷例如 { id: 123 } }下面的示例订阅者在product.created事件触发后从事件载荷中取出产品 ID通过容器解析product模块服务并查询产品信息import type { SubscriberArgs, SubscriberConfig, } from medusajs/framework export default async function productCreateHandler({ event: { data }, container, }: SubscriberArgs{ id: string }) { const productId data.id const productModuleService container.resolve(product) const product await productModuleService.retrieveProduct(productId) console.log(The product ${product.title} was created) } export const config: SubscriberConfig { event: product.created, }这里的核心技巧有两个类型化载荷通过泛型SubscriberArgs{ id: string }为事件data声明类型让解构出的data.id获得完整的类型检查与编辑器补全容器解析依赖container.resolve(...)是获取模块服务、查询引擎等资源的统一入口不需要手动实例化任何东西。实战loyalty 插件中的订单礼品卡订阅者理论之外仓库中的 packages/plugins/loyalty/src/subscribers/create-gift-card.ts 是一个完全可运行的真实订阅者。它监听订单下单事件自动为订单中的礼品卡商品生成对应礼品卡是理解事件订阅 容器 工作流三者如何协作的最佳范本。声明订阅监听订单下单事件export const config: SubscriberConfig { event: OrderWorkflowEvents.PLACED, };其中OrderWorkflowEvents.PLACED定义于 packages/core/utils/src/core-flows/events.ts其值为字符串order.placed事件载荷为{ id }订单 ID。该对象还包含order.updated、order.canceled、order.completed等一组订单工作流事件常量订阅订单相关事件时优先使用这些常量可避免手写字符串拼写错误。处理函数查询订单并过滤礼品卡商品export default async function createGiftCardHandler({ event: { data }, container, }: SubscriberArgs{ id: string }) { const orderId data.id; const query container.resolve(ContainerRegistrationKeys.QUERY); const { data: [order], } await query.graph({ entity: order, filters: { id: orderId }, fields: [ id, currency_code, items.id, items.subtotal, items.quantity, items.*, items.product.is_giftcard, total, subtotal, tax_total, ], }); ... }这里使用container.resolve(ContainerRegistrationKeys.QUERY)解析查询引擎通过query.graph一次性取回订单及其行项目、行项目商品是否礼品卡等字段随后用item.product?.is_giftcard过滤出礼品卡行项目若订单不含礼品卡则直接return提前结束。调用工作流创建礼品卡for (const giftCardLineItem of giftCardLineItems) { // For each gift card line item quantity, create a gift card for (let i 0; i giftCardLineItem.quantity; i) { const giftCardValue giftCardLineItem.subtotal / giftCardLineItem.quantity; const { result: [giftCard], } await createGiftCardsWorkflow.run({ input: [{ value: giftCardValue, currency_code: order.currency_code, line_item_id: giftCardLineItem.id, reference: order, reference_id: order.id, metadata: {}, }], container, }); } }对每个礼品卡行项目按其quantity循环创建礼品卡单张面值为subtotal / quantity并以订单作为引用来源reference: order、reference_id: order.id。createGiftCardsWorkflow定义于 packages/plugins/loyalty/src/workflows/gift-cards/workflows/create-gift-cards.ts它依次执行创建礼品卡未提供code时自动生成编码见 create-gift-cards.ts 步骤、创建匿名储值账户并建立关联、按礼品卡面值入账、最后将礼品卡标记为已赎回等步骤全部在一个可追踪、可回滚的工作流中完成。订阅者如何被加载与注册源码级原理要真正理解订阅者约定需要看 packages/core/framework/src/subscribers/subscriber-loader.ts 中的SubscriberLoader。它通过discoverResources扫描目录对每个文件依次执行文件级校验onFileLoaded→validateSubscriberdefault导出必须是函数否则跳过并告警必须存在config否则跳过并告警必须声明event——注意这里有个环境差异生产环境下缺失event会直接抛错非生产环境则仅告警并跳过event必须是字符串或字符串数组否则跳过并告警。订阅者 ID 推断inferIdentifier优先级从高到低优先使用config.context.subscriberId否则使用处理函数名经 kebab-case 转换匿名函数则回退到文件名。注册到事件总线createSubscriber将event统一展开为数组逐个调用eventBusService.subscribe(event, subscriber, { ...config.context, subscriberId })完成订阅同时通过registerDevServerResource注册开发服务器资源便于开发模式下热更新与调试。处理函数在执行时会被包装接收{ event: { name: e, ...data }, container, pluginOptions }作为参数——这正是SubscriberArgs三个属性的来源。总结Medusa 的自定义订阅者机制可以归纳为三条简单约定文件放在src/subscribers、default导出异步处理函数、config导出订阅声明。在此基础上通过SubscriberArgs的类型化泛型安全消费事件载荷通过container.resolve获取模块服务与查询引擎再配合OrderWorkflowEvents等事件常量和现成的工作流即可在完全不侵入核心代码的前提下把订单、产品、客户等业务节点串联成自己的自动化逻辑。以 loyalty 插件的 create-gift-card.ts 为参照你可以在自己的项目中直接复刻这套模式。【免费下载链接】medusaThe worlds most flexible commerce platform for agents and developers项目地址: https://gitcode.com/GitHub_Trending/me/medusa创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价