mcpField
S
spree-extensions

spree-extensions

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

关于 spree-extensions

指导开发者构建 Spree 6 扩展的技能,扩展可采用 Rails engine gem,并可选配仪表板插件。内容包括扩展结构、API 资源、注册机制、提供方、测试和常见集成问题。

主要功能

  1. 01创建 Rails engine 结构的扩展 gem
  2. 02添加 Store 和 Admin API 资源
  3. 03注册钩子、提供方和订阅器
  4. 04构建可选仪表板插件
  5. 05使用 RSpec 和 Factory Bot 测试扩展

适用场景

  1. 01将定制功能打包并在多个 Spree 应用间共享
  2. 02添加店铺范围的资源和 API 端点
  3. 03集成支付或履约提供方
  4. 04通过仪表板插件添加 Admin 页面

安装

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

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

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

详细介绍

Spree 扩展

命令使用 spree CLI 形式(Rails 应用位于 server/)。在经典 Rails 应用中,从应用根目录运行 bundle … / bin/rails … — 参见 spree-project。

Spree 6 扩展分为两部分:

部分内容包含
Gem(Rails 引擎)spree_<name> Ruby gem模型、迁移、Store/Admin API 端点、序列化器、权限范围、工作流钩子、订阅器、提供程序类、集成
Dashboard 插件(可选)使用 defineDashboardPlugin 的 npm 包管理界面、导航项、商品页组件 — 与 gem 的 Admin API 端点通信

无需添加视图:Spree 6 没有 Rails 管理后台,也没有 Rails 店面。店面页面位于你的店面应用中,通过 @spree/sdk 实现。

Gem 是合适的形式吗? 只有当定制内容需要在多个应用间共享或发布时才合适。对于单个应用,将相同代码放在 server/app/ 中,并在 server/config/initializers/spree.rb 中注册 — 参见 spree-customization。

安装扩展 gem

spree bundle add spree_reviews          # or edit server/Gemfile, then `spree bundle install`
spree generate spree_reviews:install    # convention: every extension ships <gem>:install
spree migrate
spree restart                           # Gemfile changes need a full restart (Ctrl+C `spree dev` if running)

对于使用预构建镜像的新建 create-spree-app 项目,运行一次 spree eject,以便容器从 server/ Gemfile 构建。

安装生成器通常会复制迁移文件(db/migrate/<ts>_<name>.<engine_name>.rb),并可能添加初始化程序。如果扩展包含 Dashboard 插件,还需在 apps/dashboard/ 中运行 pnpm add @scope/plugin — 插件会通过其 package.json 中的 spree.dashboard.plugin 标记自动发现,无需编辑宿主代码。

脚手架项目中的 Gems

spree-starter 服务端 Gemfile(create-spree-app 克隆到 server/ 的文件)包含:

Gem提供功能
spree元 gem → spree_core + spree_api
spree_dashboard在 /dashboard 提供 React 管理界面
spree_emails事务性电子邮件
spree_stripeStripe 支付 + Stripe Connect 付款提供程序
spree_easypostEasyPost 承运商费率、面单、追踪(配送费率 + 履约提供程序)
spree_meilisearchMeilisearch 搜索提供程序(SpreeMeilisearch::SearchProvider)
spree_opentelemetryOpenTelemetry 跟踪(设置 OTEL_* 环境变量后才会启用)
spree_i18nUI 翻译

spree_adyen 和 spree_paypal_checkout 已存在,但处于注释状态 — 启用前,请检查各 gem 的 gemspec/CHANGELOG,确认是否有兼容 Spree 6 的版本。Spree 4/5 时代的社区 gem(spree_reviews、spree_avatax_official 等)也同样需要检查:先确认 spree_core 约束;大多数都需要移植(没有 spree_admin、Spree::Adjustment 或状态机)。集成目录:https://spreecommerce.org/docs/integrations。

多店铺共享(在多个店铺之间共享一个商品/促销/支付方式)并非核心功能 — 核心记录只属于一个店铺。请使用 spree_multi_store 扩展。

构建扩展

gem install spree_extension -v '>= 2.0'   # 1.x generates a Spree 5 scaffold (spree_admin, importmap) — don't use it
spree-extension create reviews            # → ./spree_reviews
cd spree_reviews

2.x 脚手架依赖 spree_core + spree_api(>= 6.0.0.beta),并提供:

  • lib/spree_reviews/engine.rb — 使用 config.to_prepare 加载所有 app/**/*_decorator*.rb(启动时及每次开发环境重新加载时),并附有注释掉的 additional_permitted_attributes += 示例。
  • config/initializers/spree.rb — 扩展的注册文件,与其他初始化程序一样,由宿主在启动时加载,并附有注释掉的 register_scope、Spree.hooks.register 示例,以及用于订阅器和注册表的 after_initialize 块示例。
  • config/routes.rb — Spree::Core::Engine.add_routes 钩子、lib/spree_reviews/factories.rb,以及用于复制迁移文件的 install 生成器。
模型
# db/migrate/20260901000000_create_spree_reviews.rb
create_table :spree_reviews do |t|
  t.references :store, null: false, index: true, foreign_key: false
  t.references :product, null: false, index: true, foreign_key: false
  t.integer :rating, null: false
  t.text :body
  t.boolean :approved, null: false, default: false
  t.timestamps
end
# app/models/spree/review.rb
module Spree
  class Review < Spree.base_class
    include Spree::SingleStoreResource     # store filled in; API scopes to current store

    has_prefix_id :review                  # review_k5nR8xLq on the wire
    publishes_lifecycle_events             # review.created / .updated / .deleted

    belongs_to :product, class_name: 'Spree::Product'   # required by default ("must exist")

    validates :rating, presence: true, inclusion: { in: 1..5 }
    scope :approved, -> { where(approved: true) }

    self.whitelisted_ransackable_attributes = %w[rating approved]
  end
end

约定:使用 Spree.base_class,不设外键约束,关联中使用 class_name,数据按店铺划分作用域,ID 加前缀,使用事件而非回调。

API

Store API 控制器继承 Spree::Api::V3::Store::ResourceController;Admin API 控制器继承 Spree::Api::V3::Admin::ResourceController,并且必须声明 scoped_resource :reviews(权限门控)和 resource_permitted_attributes。序列化器继承 Spree::Api::V3::BaseSerializer(admin 序列化器扩展 store 序列化器)。路由:

# config/routes.rb
Spree::Core::Engine.add_routes do
  namespace :api, defaults: { format: 'json' } do
    namespace :v3 do
      namespace(:store) { resources :reviews, only: [:index, :show] }
      namespace(:admin) { resources :reviews }
    end
  end
end

完整的控制器/序列化器模板:spree-resource(spree:api_resource 生成器会生成这些模板)和 spree-api-v3。为插件拥有的资源添加命名空间路径,以避免与未来的核心资源冲突。

注册 — 在何处以及何时

注册项放在扩展的 config/initializers/spree.rb 中;触及模型的代码放在引擎的 config.to_prepare 中。时机很重要,因为核心会在自己的 config.after_initialize 中使用 = 赋值某些注册表,从而清除之前追加的内容:

注册项放在何处
Spree.payment_methods <<、Spree.fulfillment_providers <<、Spree.integrations <<、Spree.stock_splitters <<、Spree.tracking_carriers[...] =、Spree.adjusters <<、Spree.promotions.actions <<、Spree.*_authentication_strategies.addRails.application.config.after_initialize do … end — 核心会在自己的 after_initialize 中赋值这些注册表,并且会先运行
Spree.subscribers、Spree.delivery_rate_providers、tax_providers、payout_providers、digital_asset_providers、pricing_providers、inventory_providers、order_routing.rules/strategies、promotions.rules、Spree.reporting在应用初始化器运行前完成预置,核心会拼接内容 — 可在任意时机注册;统一放在 after_initialize 是安全的选择(也可避免在启动过程中自动加载类)
Spree.hooks.register(...)、Spree.permissions.register_scope(...)、Spree.search_provider =初始化器的顶层(基于字符串/注册表,可安全重载)
Spree::Product.additional_permitted_attributes += [...]、装饰器加载,以及任何触及模型类的内容引擎的 config.to_prepare(在开发环境重新加载时会再次运行)
# config/initializers/spree.rb (inside the gem)
Spree.permissions.register_scope(:reviews, group: :catalog, resources: -> { [Spree::Review] })
Spree.hooks.register('products.activate.validate', 'SpreeReviews::RequireDescription')

Rails.application.config.after_initialize do
  Spree.subscribers << SpreeReviews::ReviewRequestSubscriber   # `bin/rails g spree:subscriber` adds these; pass the class — a String crashed production boot through 6.0.0.beta4
  Spree.integrations << 'SpreeReviews::Integration'            # if the gem needs per-store credentials
end
# lib/spree_reviews/engine.rb
module SpreeReviews
  class Engine < Rails::Engine
    require 'spree/core'
    isolate_namespace Spree
    engine_name 'spree_reviews'

    config.to_prepare do
      Dir.glob(SpreeReviews::Engine.root.join('app/**/*_decorator*.rb')) do |decorator|
        Rails.configuration.cache_classes ? require(decorator) : load(decorator)
      end
      Spree::Product.additional_permitted_attributes += [:reviews_enabled]  # += never << (frozen array)
    end
  end
end

宿主应用也以相同方式在 server/config/initializers/spree.rb 中注册相同内容。对于核心赋值的注册表,仅使用 to_prepare 是不够的 — 启动时它会在 after_initialize 之前运行,因此核心会覆盖它。

各注册项的作用:

  • register_scope 会生成 read_reviews / write_reviews,显示在员工角色编辑器中,也可授予密钥 API key。请在 config/locales/en.yml 的 en.spree.permissions_catalog.resources.reviews.{label,description} 下为其添加标签。选项:write: false、audiences:、read_only_for:。参见 spree-auth-permissions。
  • 工作流钩子在核心流程中运行,并可中止流程(workflow.reject!('…'))。启用急切加载时,启动阶段会验证钩子键 — 拼写错误会导致启动失败。参见 spree-workflows。
  • 订阅器(Spree::Subscriber、subscribes_to 'order.placed')会在事件发生后作出响应。参见 spree-events-webhooks。
  • 提供方可接入税费、配送费率、履约、支付、搜索、付款、数字资产和身份验证。所有基类和注册表请参见 spree-providers。
  • Spree.dependencies 可替换核心服务/工作流(*_workflow 键)。参见 spree-dependencies。优先使用钩子 — 替换的工作流必须与核心保持同步。

最后再考虑使用装饰器(例如向 Spree::Product 添加 has_many :reviews)— 参见 spree-decorators。

Dashboard 插件部分
npx @spree/cli plugin new reviews       # or `spree plugin new reviews`

插件会调用 defineDashboardPlugin(...) 来添加导航项、路由和插槽小组件,并通过 @spree/admin-sdk 的 client.request 逃生接口读写你的端点。将它作为带有 spree.dashboard.plugin 标记的 npm 包发布;使用者通过 pnpm add 添加后,它会自动注册。请为导航键、本地化键和 API 路径添加命名空间(使用 acme-brands,而不是 brands)— 重复的键会导致启动失败。参见 spree-dashboard-plugins。

测试扩展
bundle exec rake test_app     # generate spec/dummy (re-run after adding migrations)
bundle exec rspec

通过 spree_dev_tools 使用 RSpec + Factory Bot。将工厂放在 lib/spree_reviews/factories.rb 中。API 规格使用 spree/api/testing_support/v3/base 中的共享上下文——'API v3 Store'、'API v3 Admin authenticated'、'API v3 Admin with custom permissions'、'API v3 Seller authenticated'。请参阅 spree-testing。

注意事项

  • 在错误的阶段注册 = 悄然失效。 在 after_initialize 外追加支付方式、履约服务提供商或集成,会在启动时消失(核心会重新分配这些数组)。表现为:无法在控制面板中选择 / 出现“type must be registered” 422 错误。
  • additional_permitted_attributes << :x 会引发 FrozenError。务必使用 +=。
  • Admin 控制器没有 scoped_resource 会在请求时引发错误——每个 Admin API 控制器都必须指定其权限作用域。
  • 迁移不会自动应用。 升级扩展后,重新运行其安装生成器(或运行 spree rails railties:install:migrations),然后运行 spree migrate。单独运行 spree migrate 只会安装核心迁移。
  • 不要重命名复制过来的迁移——.<engine_name>.rb 后缀用于让 Rails 跳过已复制的迁移,也用于让 Spree 的启动检查发现缺失的迁移。
  • 凭据应存放在 Spree::Integration 中(按店铺区分,:password 偏好设置会被遮蔽),而不是 ENV 中——多店铺应用会使用不同账户付款 / 发货。
  • 同一方法上有两个装饰器——最后加载的会生效。优先使用钩子 / 事件,以便扩展能够组合。
  • 订阅者代码会热重载;更改 注册项(初始化器)则需要重启。

延伸阅读

  • 扩展教程:node_modules/@spree/docs/dist/developer/contributing/creating-an-extension.md
  • 服务提供商与集成:node_modules/@spree/docs/dist/developer/providers/overview.md
  • 控制面板插件:node_modules/@spree/docs/dist/developer/dashboard/plugins/{overview,scaffolding,distributing}.md
  • 定制快速入门:https://spreecommerce.org/docs/developer/customization/quickstart
  • 相关技能:spree-customization、spree-providers、spree-workflows、spree-resource、spree-auth-permissions、spree-dashboard-plugins、spree-testing

评论

支持 Markdown