mcpField
S
spree-dependencies

spree-dependencies

github.com/spree开发10219 次安装4 周前更新
代码仓库

关于 spree-dependencies

介绍通过依赖注入替换 Spree 核心类的开发技能,面向 Spree 应用和扩展开发者。内容涵盖何时使用钩子或类替换,以及工作流、服务和 API 序列化器的约定。

主要功能

  1. 01判断何时适合替换类
  2. 02配置工作流和服务替换
  3. 03通过依赖项扩展 API 序列化器
  4. 04处理旧版依赖键
  5. 05通过 Spree 访问器调用以保留覆盖

适用场景

  1. 01实现自定义购物车或订单工作流
  2. 02为 Store 或 Admin API 序列化器添加字段
  3. 03将旧服务覆盖迁移到工作流
  4. 04让扩展代码遵循宿主应用的覆盖

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/spree/agent-skills --skill spree-dependencies

安装后在 Claude Code 中输入/spree-dependencies确认已加载。

详细介绍

Spree 依赖项(替换整个类)

命令使用 Spree CLI(spree …)。如果是没有 CLI 的经典 Rails 应用,请从应用根目录运行 bin/rails … / bundle exec rake …。路径假设 Rails 应用位于 server/。

Spree.dependencies 可让你用自己的类替换核心类,例如服务、工作流、策略或序列化器。Spree 代码调用的是 Spree.cart_add_item_workflow.call(...),而不是 Spree::Carts::AddItem.call(...),因此你的替换类会在所有地方运行:Store API、Admin API、仪表板、作业和导入流程。

首先:你真的需要替换这个类吗?

替换类意味着你需要自行维护一份副本,并在每次 Spree 发布时重新同步。大多数替换类的原因都可以通过工作流钩子解决,而且这种做法能经受版本升级:

你的需求是……用以下方式代替替换类
阻止加入购物车 / 结账 / 取消 / 退货validate 钩子 — Spree.hooks.register('carts.add_item.validate', 'MyStore::Rule')
为税费或促销计算添加数据上下文钩子(carts.recalculate_totals.set_tax_line_context、carts.recalculate.set_promotion_context)
在变更时原子性地写入相关行生命周期钩子(carts.add_item.after_item_added、carts.complete.after_finalize)
与 ERP 同步 / 发送邮件 / 重新索引事件订阅器(spree-events-webhooks)
添加结账要求Spree::Checkout::Registry.add_requirement(spree-checkout)
为 API 响应添加字段子类化序列化器(见下文)——这是一种小范围且合理的替换方式

请参阅 spree-workflows 技能,查看钩子目录。只有在确实需要替换某项功能的计算方式时,才替换类(例如使用不同的合并策略、自定义购买框排序、B2B 访问策略或佣金模型)。

各扩展接口

Spree::Core::Dependencies::INJECTION_POINTS_WITH_DEFAULTS(位于 spree_core 的 lib/spree/core/dependencies.rb)定义了 167 个核心注入点;其中 106 个是 *_workflow 键。Spree::Api::ApiDependencies::INJECTION_POINTS_WITH_DEFAULTS(spree_api 的 lib/spree/api/dependencies.rb)包含 272 个 API 注入点,且仅限序列化器:Store 有 91 个(product_serializer),Admin 有 139 个(admin_product_serializer),Seller 有 42 个(seller_*_serializer)。(统计数据来自 6.0 源码;运行 spree rake spree:dependencies:list 查看你所用版本的数据。)

命名会告诉你约定:

后缀基类需要重写
*_workflowSpree::Workflow 子类#perform(**keywords) — 绝不能用 #call
*_service使用 prepend Spree::ServiceModule::Base 的普通类#call(**keywords)
*_class、*_finder、*_handler 等各不相同 — 阅读默认实现按默认实现中的说明

常见的替换项:

键默认值说明
cart_add_item_workflowSpree::Carts::AddItemperform(variant:, cart:, quantity:, metadata:, options:, price:)
cart_upsert_items_workflowSpree::Carts::UpsertItems设置数量、移除或批量操作
cart_recalculate_workflowSpree::Carts::Recalculate完整重新计价(配送、促销、总计)
cart_recalculate_totals_workflowSpree::Carts::RecalculateTotals唯一的总计扩展接口(折扣/费用/税费行 → 汇总)
order_recalculate_totals_workflowSpree::Orders::RecalculateTotals订单提交后的重新汇总对应项
cart_merge_workflowSpree::Carts::Merge合并访客与客户购物车
carts_complete_workflowSpree::Carts::Complete结账完成 — 优先使用 carts.complete.* 钩子
order_complete_workflow / order_cancel_workflowSpree::Orders::Complete / ::Cancel
order_update_statuses_serviceSpree::Orders::UpdateStatusespayment_status / fulfillment_status 的唯一写入者
fulfillment_create_workflow / fulfillment_update_workflowSpree::Fulfillments::Create / ::Update
payment_process_workflow、payment_capture_workflow、payment_void_workflow、refund_create_workflowSpree::Payments::*、Spree::Refunds::Create
return_create_workflow … claim_*_workflowSpree::Returns::*、Exchanges::*、Claims::*
storefront_access_policy_classSpree::Storefront::AccessPolicyStore API 记录可见性(所有权 + 访客令牌);B2B 扩展接口
products_for_context_serviceSpree::Products::ForContext为买家缩小商品目录范围
product_buy_box_serviceSpree::Products::SelectBuyBoxMarketplace:决定哪个变体作为主推项
purchase_amount_due_at_checkout_serviceSpree::Purchases::AmountDueAtCheckout定金/净额账期
tax_resolve_exemptions_serviceSpree::Tax::ResolveExemptions提供给税务服务商的免税证明
reporting_adapterSpree::Reporting::Adapters::Live
search_product_presenternil(由搜索 gem 设置)

ability_class(Spree::Ability)负责构建所有员工 CanCanCan 权限,包括 Admin API JWT 员工、Seller API 和导入/导出功能。继承 Spree::Ability,保留 initialize(user, options = {})(options[:store]、options[:resource]),调用 super,然后添加规则。它受权限键门控保护,而机密 API 密钥(Spree::ApiKeyAbility)不会查询它。请参阅 spree-auth-permissions。

替换工作流

继承随附的工作流,并使用相同的关键字参数重写 perform。perform 的签名就是契约(不匹配时 Ruby 会引发 ArgumentError);不带参数的 super 会运行整个父级流程并返回其 Result。

# server/app/workflows/my_store/carts/add_item.rb
module MyStore
  module Carts
    class AddItem < Spree::Carts::AddItem
      # REQUIRED: without this the subclass dispatches 'my_store.carts.add_item.*'
      # and every handler registered on 'carts.add_item.*' silently stops firing.
      workflow_key 'carts.add_item'

      def perform(variant:, cart: nil, **rest)
        result = super                       # full Spree flow; a failure raises through, so code below runs only on success

        external_step :notify_recommendations # network I/O — outside any transaction
        result                                # Result#value is the line item
      end

      private

      def notify_recommendations
        MyStore::Recommendations.item_added(cart_id: cart.prefixed_id, sku: line_item.sku)
      end
    end
  end
end
# server/config/initializers/spree.rb
Spree.cart_add_item_workflow = MyStore::Carts::AddItem

替换工作流的规则:

  • 重写 perform,绝不要重写 call。 Spree::Workflow#call 负责埋点、处理 failure/halt! 以及补偿。
  • 保留 workflow_key,使其指向父级的键(见上文)。这是最常见的静默故障。
  • 若要更改其中一个步骤,可重写私有步骤方法(例如 add_to_line_item)并调用 super——但步骤名称属于内部实现,可能会在不同版本之间变化;使用钩子更安全。
  • 返回 Spree::ServiceModule::Result(super 的返回值,或 success(value));非 Result 返回值会被包装为 success。
  • Spree.cart_add_item_workflow 的调用方预期父级的 Result#value 类型(此处为行项目)。不要更改它。
  • 工作流通过其他衔接点协作(step :recalculate, with: -> { Spree.cart_recalculate_workflow }),因此替换一个衔接点会影响所有委托给它的流程。

替换普通服务

采用相同模式,重写 call:

module MyStore
  class CartUpdate < Spree::Carts::Update
    def call(cart:, params:)
      result = super
      MyStore::ErpSync.enqueue(result.value.prefixed_id) if result.success?
      result
    end
  end
end

Spree.carts_update_service = MyStore::CartUpdate

从默认实现继承,以保留 Spree::ServiceModule::Base 的连接机制(类级 .call、success/failure)。始终返回 Result。

旧版 *_service 键会被暂存,而不会生效

已改为工作流的 Seam 仍接受旧名称,以确保应用能够启动,但赋值不会产生任何效果。值会被存储,并显示一条弃用警告:“Spree 不再读取此值——未应用此覆盖”,Spree 仍会调用工作流。按照旧服务契约(call(order:, …) 和 run 步骤)编写的类,不能与工作流契约互换。请进行迁移:继承工作流,将逻辑移至 perform/钩子中,并赋值给新键。这些名称将在 6.1 中移除。

旧键(仅暂存)改为赋值给默认值
cart_add_item_servicecart_add_item_workflowSpree::Carts::AddItem
cart_recalculate_servicecart_recalculate_workflowSpree::Carts::Recalculate
cart_merge_strategycart_merge_workflowSpree::Carts::Merge
carts_complete_servicecarts_complete_workflowSpree::Carts::Complete
carts_upsert_items_servicecart_upsert_items_workflowSpree::Carts::UpsertItems
order_complete_serviceorder_complete_workflowSpree::Orders::Complete
order_cancel_serviceorder_cancel_workflowSpree::Orders::Cancel
fulfillment_create_servicefulfillment_create_workflowSpree::Fulfillments::Create
shipment_update_service、fulfillment_update_servicefulfillment_update_workflowSpree::Fulfillments::Update
payments_handle_webhook_servicepayments_handle_webhook_workflowSpree::Payments::HandleWebhook
gift_card_apply/remove/redeem_servicegift_card_apply/remove/redeem_workflowSpree::GiftCards::*

纯重命名(会生效,并显示警告):checkout_add_store_credit_service → store_credit_apply_service、checkout_remove_store_credit_service → store_credit_remove_service;API 的 stock_item_serializer → stock_level_serializer、digital_serializer → digital_asset_serializer(以及对应的 admin_ 版本)。

已移除的键(赋值时会在启动时引发 NoMethodError):carts_validate_service(完成验证由 Spree::Checkout::Requirements + carts.complete.validate 处理)、checkout_next_service、checkout_update_service、checkout_complete_service、checkout_select_shipping_method_service、checkout_get_shipping_rates_service、cart_create_service、cart_update_service、cart_estimate_shipping_rates_service、cart_change_currency_service、line_item_*_service、shipment_*_service、account_*_service,以及旧版查找器/排序器(products_finder、taxon_finder、variant_finder,等等)。购物车通过 carts_create_service / carts_update_service 创建和更新;结账流程推进使用 checkout_advance_service。从 5.x 升级?请参阅 spree-upgrade-5-to-6。

API 序列化器

API 依赖项仅限序列化器——没有按 API 区分的服务键;每个端点都会直接调用核心 Spree.<key> Seam。

# server/app/serializers/my_store/product_serializer.rb
module MyStore
  class ProductSerializer < Spree::Api::V3::ProductSerializer   # Alba resource
    typelize brand_name: [:string, nullable: true]
    attribute :brand_name do |product|
      product.brand&.name
    end
  end
end

# config/initializers/spree.rb
Spree.api.product_serializer       = 'MyStore::ProductSerializer'        # Store API
Spree.api.admin_product_serializer = 'MyStore::Admin::ProductSerializer' # Admin API — separate key
  • Store 和 Admin 的键彼此独立。Spree::Api::V3::Admin::ProductSerializer 通过常量继承随附的 Spree::Api::V3::ProductSerializer,因此替换 product_serializer 不会改变 Admin 的输出——如果两个端点都需要该字段,请创建子类并同时替换两者。
  • 嵌套关联通常通过访问器解析(one :product, resource: proc { Spree.api.product_serializer }),因此这里也会使用你的类。
  • 为 @spree/sdk 类型添加带类型的字段:使用 typelize(参见 spree-typescript-sdk)。

按控制器覆盖

若只需更改一个端点,请装饰 API 控制器的具名钩子(serializer_class、scope、collection_includes、model_class、resource_permitted_attributes):

# server/app/controllers/spree/api/v3/store/products_controller_decorator.rb
module Spree::Api::V3::Store
  module ProductsControllerDecorator
    protected

    def serializer_class
      MyStore::DetailedProductSerializer
    end
  end
end

Spree::Api::V3::Store::ProductsController.prepend(Spree::Api::V3::Store::ProductsControllerDecorator)

没有按控制器区分的 *_service 钩子——控制器会调用全局 Seam。请参阅 spree-decorators。

使用自有代码中的接缝

Spree.cart_add_item_workflow.call(cart: cart, variant: variant, quantity: 1)   # resolves overrides
Spree.api.cart_serializer.new(cart, params: { store: current_store }).to_h

Spree::Carts::AddItem.call(...)   # ❌ bypasses any override

扩展尤其必须通过访问器进行配置,才能与宿主应用的覆盖项组合使用。

语法:Spree.<key> = MyClass(推荐——常量拼写错误时会立即报错),多个配置可使用 Spree.dependencies { |d| d.<key> = MyClass },也可使用旧版 Spree::Dependencies.<key> = 'MyClass'(字符串;延迟解析,仍受支持)。

检查覆盖项

spree rake spree:dependencies:list        # every seam with its current value, [OVERRIDDEN] marked
spree rake spree:dependencies:list | grep workflow
spree rake spree:dependencies:overrides   # only overrides: default -> current (file:line that set it)
spree rake spree:dependencies:validate    # constantize every seam; exits 1 on NameError — good CI check

在继承项目中,首先要运行 overrides。对应的编程接口包括:Spree::Dependencies.current_values、.overridden?(:cart_add_item_workflow)、.override_info(:cart_add_item_workflow)、.validate!;API 侧则使用 Spree::Api::Dependencies。仅写入旧版暂存区的内容不会显示为覆盖项——升级后,请在初始化文件中搜索 _service =。

注意事项

  • 加载顺序: 在 config/initializers/spree.rb 中设置覆盖项。如果两个 gem 设置了同一个键,则最后一次赋值生效;可通过 spree:dependencies:overrides 检查。
  • 替换后的工作流失去了钩子 → 缺少 workflow_key(见上文)。
  • 升级后覆盖项“没有效果” → 你赋值的是旧版 *_service 键;请在启动日志中查找“NO LONGER CONSULTED”弃用提示。
  • 丢失行为: 如果替换实现跳过 super,就必须重新实现标准的预留、定价提供方、可用性检查、重新计算和事件。请先完整阅读父级的 perform。
  • 调用时出现 ArgumentError → 你的 perform 签名不接受调用方传入的关键字参数。请接受 **rest 并转发它。

延伸阅读

  • node_modules/@spree/docs/dist/developer/customization/dependencies.md(https://spreecommerce.org/docs/developer/customization/dependencies)——注意:已发布页面的部分内容仍显示 API v2 的 storefront_* 键;应以源代码中的常量为准。
  • node_modules/@spree/docs/dist/developer/customization/workflows.md——钩子,是首选替代方案
  • 源码:spree_core 的 lib/spree/core/dependencies.rb(INJECTION_POINTS_WITH_DEFAULTS、LEGACY_WORKFLOW_KEYS、LEGACY_SERVICE_KEYS、RENAMED_SERVICE_KEYS)、spree_api 的 lib/spree/api/dependencies.rb、lib/tasks/dependencies.rake
  • 相关技能:spree-workflows、spree-customization、spree-decorators、spree-extensions

评论

支持 Markdown