mcpField
S
spree-customization

spree-customization

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

关于 spree-customization

spree-customization 是面向 Spree 6 开发者的技能,帮助选择扩展点而不分叉核心代码。它将常见改动对应到设置、钩子、事件、提供程序、API 资源等定制方式。

主要功能

  1. 01为改动选择扩展点
  2. 02用工作流钩子校验或调整流程
  3. 03通过订阅器或 Webhook 响应事件
  4. 04配置提供程序和认证策略
  5. 05扩展资源、仪表盘界面和模型

适用场景

  1. 01在结账时执行策略检查
  2. 02将订单事件发送到外部系统
  3. 03添加 API 资源或仪表盘功能
  4. 04用自定义行为替换核心工作流

安装

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

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

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

详细介绍

Spree 自定义——我的代码该放在哪里?

Spree 6 的设计允许你在不分叉代码库的情况下扩展功能。难点在于选择合适的模式。按照表格从上往下查看:越靠前的选项越偏向数据或配置,编写成本更低,也不会在升级时受到影响;越靠后的选项与 Spree 内部实现的耦合越紧密。

命令使用 spree CLI 格式。在经典 Rails 应用中,请在应用根目录运行 bin/rails …,并去掉 server/ 路径前缀——参见 spree-project。

决策树

你想……使用技能
更改货币、市场、语言、配送区域/方式、税率、支付方式、订单编号商店设置 — 仪表盘 → Settings,或 Admin API。数据更改,无需部署spree-dashboard、spree-api-v3
调整全站安装级别的限制(密码长度、JWT 过期时间、速率限制、购物车过期时间、Webhook 开关)SPREE_* 环境变量(启动时进行类型检查和验证);Spree.config { |c| … } 在 config/initializers/spree.rb 中仍然有效—(文档:customization/configuration)
为某种记录类型设置专属的类型化设置(例如 Brand 的“featured”标志、计算器的费率)模型偏好设置 — 在带有 preferences 列的模型上使用 preference :featured, :boolean, default: falsespree-resource
否决某项操作(购买限制、地区政策、B2B 资格)工作流 validate 钩子 — Spree.hooks.register('carts.add_item.validate', 'MyApp::CheckLimit'),处理器调用 workflow.reject!spree-workflows
在核心流程中运行代码(完成后、取消前,提供定价/服务商上下文)工作流生命周期/上下文钩子 — carts.complete.after_finalize、orders.cancel.before_cancel、set_*_contextspree-workflows
在自有应用中某件事发生后作出响应(索引、同步、发送邮件、审计)事件订阅者 — 监听 order.placed、product.updated 等事件的 Spree::Subscriberspree-events-webhooks
无需 Ruby 代码即可告知外部系统发生了某件事Webhook 端点 — 仪表盘 → Settings → Webhooks,或 Admin APIspree-events-webhooks
要求结账完成前提供某些信息(PO 编号、电话号码、年龄验证),或添加结账步骤Spree::Checkout::Registry.add_requirement / register_step — 会显示在 Cart API 的 requirements[] 中spree-checkout
替换税费、配送费率、履约、搜索、定价、库存、付款结算、数字商品配送或支付的工作方式服务商 — 注册一个类(Spree.tax_providers、Spree.delivery_rate_providers、Spree.search_provider = …、Spree.payment_methods 等),并在商店/市场/方式上选择该类spree-providers、spree-taxes、spree-payments
使用自有 IdP 或令牌为顾客/员工登录身份验证策略 — Spree.store_authentication_strategies.add(:idp, MyApp::Auth::IdpStrategy)(也适用于 admin_/seller_)spree-auth-permissions
存储面向商家的额外数据(材质、护理说明、礼品留言)自定义字段 — CustomFieldDefinition + record.set_custom_field('custom.material', 'Cotton');类型化、可筛选,并支持 storefront_visiblespree-catalog
存储集成/机器数据(ERP ID、UTM 来源、同步状态)metadata — 无模式 JSON,采用合并语义;Store API 中只写入,Admin API 中可读取spree-api-v3
使某个属性可通过 q[...] 筛选/排序Spree.ransack.add_attribute / add_association / add_scopespree-api-v3
添加或移除地址规则(PO 盒、电话号码格式)Spree.validators.addresses.register / unregister(在 config.to_prepare 中调用)spree-customization(见下文)
允许通过 Admin API 写入新列Spree::Product.additional_permitted_attributes += [:brand_id]spree-resource
将新资源限制为特定员工角色/API 密钥权限范围可用Spree.permissions.register_scope(:brands, group: :catalog, resources: -> { [Spree::Brand] }) → read_brands / write_brandsspree-auth-permissions
添加全新的模型和 Admin/Store 端点spree generate api_resource Brand …spree-resource
为核心模型添加状态Spree::Return.add_status('on_hold', after: 'approved') + 自有工作流,用于将记录转换为该状态spree-workflows
从根本上更改核心流程的行为依赖项替换 — Spree::Dependencies.cart_add_item_workflow = 'MyApp::Carts::AddItem'spree-dependencies
为核心模型添加关联、作用域或辅助方法装饰器(spree generate model_decorator Spree::Product)— 最后手段spree-decorators
添加管理界面、字段、列、导航项在 apps/dashboard/src/plugins.ts 中使用 Dashboard 插件(defineDashboardPlugin)spree-dashboard、spree-dashboard-plugins
在多个应用之间共享上述任意扩展扩展 gem(Rails 引擎)+ 可选的 Dashboard 插件(spree plugin new)spree-extensions
Hook、事件还是依赖项?
  1. 必须阻止流程或改变结果吗? → 工作流 Hook。只有 validate(和 before_cancel)Hook 可以停止流程,而且它们会在资金流动前运行。
  2. 只需要知道某件事已经发生吗? → 事件订阅器(应用内)或 Webhook(外部)。彼此解耦,默认异步处理,升级时无需改动。
  3. 流程必须发生根本性变化吗? → 替换依赖项,并接受升级时你需要自行维护这一类代码。

实例

阻止特定地区的结账 — validate Hook
# server/config/initializers/spree.rb
Spree.hooks.register('carts.complete.validate', 'MyApp::RegionPolicy')

# server/app/services/my_app/region_policy.rb
module MyApp
  class RegionPolicy
    def call(workflow)
      return unless workflow.cart.ship_address&.country_code == 'KP'

      workflow.reject!('We cannot deliver to this region')
    end
  end
end

启动时会验证 Hook 键(拼写错误会触发 Spree::Hooks::UnknownHookError)。处理器以类名字符串的形式存储,因此注册可安全用于重新加载。多个处理器可以共享一个键——第一个调用 reject! 的处理器生效。

将已下单订单推送到 ERP — 事件订阅器
spree generate subscriber ErpOrderSync order.placed   # creates the class AND registers it
# server/app/subscribers/erp_order_sync_subscriber.rb
class ErpOrderSyncSubscriber < Spree::Subscriber
  subscribes_to 'order.placed'

  def handle(event)
    order = Spree::Order.find_by_prefix_id(event.payload['id'])
    MyApp::Erp.push(order) if order
  end
end

订阅器不会自动发现——如果不使用生成器,请自行添加 Spree.subscribers << ErpOrderSyncSubscriber。

要求 B2B 购物车填写 PO 编号 — 结账要求
Spree::Checkout::Registry.add_requirement(
  step: :payment,
  field: :po_number,
  message: 'PO number is required',
  satisfied: ->(cart) { cart.metadata['po_number'].present? },
  applicable: ->(cart) { cart.company.present? }
)

此要求会显示在 Cart API 的 requirements 数组中,因此店面无需重复规则即可将其呈现出来,而 Carts::Complete 会在要求得到满足前拒绝完成结账。

为商品添加 brand_id — 资源 + 允许的属性 + 装饰器
spree generate api_resource Brand name:string
spree generate migration AddBrandIdToSpreeProducts brand_id:bigint:index
spree migrate
# server/config/initializers/spree.rb
Rails.application.config.to_prepare do
  Spree::Product.additional_permitted_attributes += [:brand_id]   # += — the default is frozen
  Spree::Product.storefront_ransackable_associations |= %w[brand]  # Store API filters follow only this list
end
Spree.ransack.add_association(Spree::Product, :brand)            # Admin API filters
Spree.permissions.register_scope(:brands, group: :catalog, resources: -> { [Spree::Brand] })

# server/app/models/spree/product_decorator.rb — structural addition, the one legit decorator job
module Spree
  module ProductDecorator
    def self.prepended(base)
      base.belongs_to :brand, class_name: 'Spree::Brand', optional: true
    end
  end
  Product.prepend(ProductDecorator)
end

如果数据可由商家编辑,且不需要单独的表,那么使用自定义字段比上述所有做法都省事。

放宽已发布的地址规则 — 验证器注册表
# server/config/initializers/spree.rb
Rails.application.config.to_prepare do
  Spree.validators.addresses.register(MyApp::PostBoxValidator)          # an ActiveModel::Validator
  Spree.validators.addresses.unregister(Spree::Addresses::PhoneValidator)
end

有几条规则也是普通的商店偏好设置(address_requires_phone、company_field_enabled、disable_sku_validation)——请先检查这些设置。其他模型没有通用的验证器注册表:通过装饰器添加模型验证;使用 validate Hook 否决操作。

替换购物车添加商品逻辑 — 替换依赖项
Spree::Dependencies.cart_add_item_workflow = 'MyApp::Carts::AddItem'   # subclass Spree::Carts::AddItem

工作流的键以 _workflow 结尾(carts_complete_workflow、order_cancel_workflow、cart_recalculate_totals_workflow、fulfillment_update_workflow,……);普通服务的键以 _service 结尾(order_update_statuses_service)。如果只需添加行为,优先使用 Hook。

反模式

  • 在装饰器中重写 finalize!、after_finalize、取消操作或其他生命周期方法 → 注册 Hook(carts.complete.after_finalize、orders.cancel.after_cancel)。已没有可挂接的状态机——state_machine 块、after_transition 和 checkout_flow 均已不存在。
  • 为副作用向 Spree 模型添加 after_save / after_commit 回调 → 使用事件订阅器。
  • clear_validators! → 清除模型上的所有验证,包括其他扩展添加的验证。请使用商店偏好设置、Spree.validators.addresses.unregister 或针对性装饰器。
  • Spree::Product.additional_permitted_attributes << :brand_id → 会引发 FrozenError。始终使用 +=。
  • 写入旧版 *_service 依赖键(cart_add_item_service、carts_complete_service)→ 写入会被暂存并忽略,同时显示警告;请使用 *_workflow 键。
  • 直接设置 payment_status / fulfillment_status → 这些状态由 Spree::Orders::UpdateStatuses 派生;请改为操作付款/履行记录。
  • 为新状态字段使用枚举或状态机 → include Spree::HasStatus; has_status :a, :b, default: :a,并在工作流中定义状态转换。
  • 通过 Rails 视图/局部模板修改管理后台 → 管理后台是 React 仪表板;请使用仪表板插件。
  • 为单个应用创建私有扩展 gem → 将代码放在 server/app/;gem 用于在多个应用之间共享。
  • Fork Spree → 先查阅此表;几乎可以肯定已有合适的扩展点。

延伸阅读

  • node_modules/@spree/docs/dist/developer/customization/quickstart.md — 官方推荐的规范阅读顺序
  • …/customization/{configuration,workflows,dependencies,decorators,validations,metadata,model-preferences,permissions,checkout,api}.md
  • …/providers/overview.md 和每种提供方契约对应的 …/how-to/custom-*.md
  • …/dashboard/customization/quickstart.md — 管理界面
  • 在线文档:https://spreecommerce.org/docs/developer/customization/quickstart

评论

支持 Markdown