mcpField
S
spree-upgrade-5-to-6

spree-upgrade-5-to-6

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

关于 spree-upgrade-5-to-6

将 Spree 5.x 升级到 6.0 的迁移指南,供开发团队参考。内容包括升级前提、迁移与数据回填步骤、破坏性变更及升级后的检查。

主要功能

  1. 01Spree 6.0 升级前提
  2. 02有序迁移与数据回填步骤
  3. 03破坏性变更概览
  4. 04代码搜索与修复指引
  5. 05升级后数据检查

适用场景

  1. 01规划从 Spree 5.6 升级到 6.0
  2. 02准备 Rails 与扩展依赖
  3. 03检查迁移后的订单和客户数据
  4. 04查找应用代码中的旧 API

安装

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

$npx skills add https://github.com/spree/agent-skills --skill spree-upgrade-5-to-6

安装后在 Claude Code 中输入/spree-upgrade-5-to-6确认已加载。

附带文件references/grep-recipes.md

详细介绍

将 Spree 5.x 升级至 6.0

Spree 6.0 是一个包含破坏性变更的版本。购物车和订单现在是独立模型;状态机已移除(改用状态 + 工作流);调整项变为带类型的行(TaxLine / Discount / Fee);主变体已移除;配送改为履约/交付;分类项改为类别/合集;用户改为 Spree::Customer;员工权限改为数据;Rails 管理后台、Rails 商店前台和 API v2 已移除,改用 React 仪表板(spree_dashboard)和 API v3。

升级步骤为:前置条件 → gems → 迁移 → 回填 → 代码修复 → 行为审查。 请为最后两项安排充足时间;rake 部分很简单。

通用操作(spree upgrade 标志、STEP=、生产发布阶段)请参阅 spree-upgrade 技能。本技能是针对 6.0 的操作指南。

1. 前置条件 — 修改 Gemfile 前先完成这些步骤

  • 使用 5.6.x,并完成全部升级。 6.0 清单假定使用 5.6 架构。请先升级到 5.6(spree upgrade --to 5.6,或将版本提升到 ~> 5.6 并运行 bundle exec rake spree:upgrade)。6.0 升级也会重新执行 5.4→5.5 和 5.5→5.6 的回填,但这不能替代先升级到 5.6。
  • Rails 8.1。 spree_core 6.0 要求 rails >= 8.1, < 8.2;Ruby ≥ 3.2。尽可能将 Rails 升级(及其弃用项处理)作为单独的提交。
  • 备份数据库,并在恢复的副本上演练。多项回填会重写金额、库存和客户数据行;请测量所需时间。
  • 替换 Gemfile 依赖:
    • 移除 spree_admin 和 spree_storefront(已弃用,分别由 React 仪表板和 Next.js 店面取代);
    • 添加 spree_dashboard(在 /dashboard 提供管理后台)——仅 API 部署可跳过;
    • 如果 Spree.search_provider 指向 Meilisearch,则添加 spree_meilisearch;
    • 其他所有 spree_* 扩展(支付、i18n、自有扩展等)都需要兼容 6.0 的版本——请在开始前确认,否则 bundle update 将无法解析依赖。spree_stripe、spree_easypost 和 spree_meilisearch 现在都从 Spree 单体仓库发布,并使用同步版本。
  • Action Text / Action Cable。 spree_core 不再依赖它们。如果你的代码使用 has_rich_text、Action Text 辅助方法或 Action Cable,请在 config/application.rb 中添加 require 'action_text/engine' / require 'action_cable/engine'。
  • 如有需要,先扩充富文本允许列表。 描述/备注会重新清理为仪表板编辑器生成的内容(段落、标题、行内格式、代码、引用块、列表、链接、图片)。表格、div/span、行内 style 和任意类名都会被移除。如果需要这些内容,请在初始化器中设置 Spree::RichTextSanitizer.allowed_tags += %w[…] / allowed_attributes += %w[…],并确保在运行 migrate_rich_text_to_columns 之前以及在 6.0 下保存任何内容之前完成。
  • 将员工角色重新创建为数据。 权限集已移除,没有迁移桥接。已有角色行会以无权限状态出现——持有该角色的员工将无法访问(默认拒绝)。请在部署前为其填充数据:
    store.roles.find_or_create_by!(name: 'support').update!(permissions: %w[read_orders read_customers])  # roles belong to a store
    
    键名为目录中的 read_<resource> / write_<resource>(Spree::ApiKey.known_scopes 会列出这些键)。
  • 删除 PermissionSets 初始化器。 Spree::PermissionSets::* 会引发 NameError,Spree.permissions.assign 会在启动时引发错误。扩展可通过 Spree.permissions.register_scope(:reviews, group: :catalog, resources: -> { [SpreeReviews::Review] }) 注册资源。
  • 客户迁移检查(Devise 应用)。 spree:upgrade:migrate_users_to_customers 会将 spree_users 复制到 spree_customers,并将 Devise 的 encrypted_password 映射到 password_digest。仅当未配置 Devise pepper 时才会正常工作;由于不再加载 Devise,必须传入 CONFIRM_NO_PEPPER=true 才能继续。包含空白/重复电子邮件的行也会导致迁移中止(可使用 SKIP_INVALID_ROWS=true 跳过)。使用 pepper、SSO 或非 bcrypt 的部署应继续使用自定义 Spree.customer_class。
  • 已迁移到店铺的初始化器设置(track_inventory_levels、auto_capture、company 等)由 store_settings_backfill_from_config 复制——请保留它们,直到该步骤运行完毕,然后再移除。
  • 启用 Active Record 加密。 Spree 6 会加密 webhook 签名密钥、网关客户 ID 和 OAuth 身份令牌——前提是已配置密钥。请让 config/application.rb 读取这些密钥(放在 Application 类内部):
    %i[primary_key deterministic_key key_derivation_salt].each do |key|
      value = ENV["ACTIVE_RECORD_ENCRYPTION_#{key.upcase}"].presence ||
        credentials.dig(:active_record_encryption, key).presence
      config.active_record.encryption[key] = value if value
    end
    
    如果密钥仅保存在 credentials 中而未进行此设置,webhook 密钥和网关客户 ID 将保持明文状态(它们的 encrypts 会检查 config.active_record.encryption)。为每个环境生成一套独立密钥(开发环境 .env 使用 spree encryption init,生产环境使用 spree encryption init --print / bin/rails db:encryption:init),在部署之前设置到主机上,做好备份,切勿更改。启用加密后,现有的明文 webhook 密钥和网关客户 ID 将无法读取:先使用 config.active_record.encryption.support_unencrypted_data = true 和 extend_queries = true 部署,然后运行 [Spree::WebhookEndpoint, Spree::GatewayCustomer, Spree::UserIdentity].each { |m| m.find_each(&:encrypt) },之后移除这两项设置。详见 spree-security。
  • 使用 settings 创建的密钥 API 密钥将失去 /admin_users、/invitations、/roles 的访问权限——现在需要 read_staff / write_staff。请重新创建用于管理员工的集成密钥。

2. 运行升级

# create-spree-app (ejected dev stack; Rails app in server/, older projects backend/)
spree upgrade
spree upgrade --plan              # after the gem bump: list every step

# classic Rails app / no CLI
bundle update
bundle exec rake spree:install:migrations && bin/rails db:migrate
DRY_RUN=1 bundle exec rake spree:upgrade
CONFIRM_NO_PEPPER=true bundle exec rake spree:upgrade

生产环境:部署时运行 db:migrate,然后在发布阶段运行 bundle exec rake spree:upgrade(如适用,也要在此处传入 CONFIRM_NO_PEPPER=true)。请安排维护窗口——回填操作会将正在进行的结账转换为购物车。

3. 5.6 → 6.0 清单的运行顺序

位于 spree_core/lib/spree/upgrades/5_6_to_6_0/manifest.yml。所有操作都具有幂等性;使用 spree upgrade --step <id> 重新运行其中一个操作。

#步骤 ID作用
1consolidate_metadata安全兜底:将所有遗留的 public_metadata/private_metadata 对合并到单个 metadata 列
2migrate_media_class_names将存储的 Spree::Asset/Spree::Image 类名改写为 Spree::Media
3migrate_master_images_to_product_media将固定到变体的图片移至商品图库(会加入队列的任务——等待任务处理完毕)
4backfill_media_store_ids为每条媒体记录添加 store_id
5remove_master_variant回填 default_variant_id,停用主变体
6migrate_taxons_to_categories_and_collections自动分类节点 → Spree::Collection;其余保留为 Spree::Category;改写 Spree::Taxon* 字符串
7backfill_library_media_placements将类别/集合图片放入媒体库
8migrate_users_to_customersspree_users → spree_customers,重新指向 user_type 列(参见 Devise pepper 前置条件)
9migrate_rich_text_to_columns将 Action Text 正文迁移到文本列,并进行清理
10migrate_shipping_to_delivery将 Shipment 字符串/状态迁移为 fulfillment 字符串/状态、配送服务提供方,并将 display_on → storefront_visible
11migrate_delivery_profiles将配送类别迁移为归商店所有的配送档案
12migrate_fulfillment_statuses重新映射为 unfulfilled / fulfilled / delivered
13migrate_deliveries将追踪号码迁移到 spree_deliveries 记录
14migrate_zones_to_delivery_zones将配送引用的区域迁移为 Spree::DeliveryZone
15migrate_country_state_codes填充 ISO country_code/state_code 列(国家/州现在是参考数据)
16migrate_promotion_option_value_rules将选项值促销规则迁移为稳定的选项值 ID
17migrate_adjustments_to_typed_rows将 spree_adjustments → TaxLine / Discount / Fee;标记无法对账的订单
18backfill_reason_store_ids将退货/退款原因归商店所有
19migrate_returns将 ReturnAuthorization 迁移为 Spree::Return / Spree::Exchange(失败的记录会导致中止;SKIP_FAILED_ROWS=true)
20migrate_stock_movements_to_typed_rows为库存移动添加类型;重新分配未完成的履约(库存现在在发货时扣减)
21backfill_delivery_and_stock_store_ids将配送方式和库存地点归商店所有
22migrate_tax_zones将税率区域迁移为税率上的国家/州信息
23backfill_tax_store_ids将税率/类别归商店所有
24backfill_order_coupon_codes为历史已下单订单填充新的 spree_orders.coupon_code
25backfill_order_markets为订单分配默认市场
26migrate_incomplete_orders_to_carts将未完成订单迁移为 Spree::Cart(令牌保持不变)
27migrate_calculator_bounds_to_delivery_method_rules将 FlatRate 最小/最大限额迁移为 DeliveryMethodRule
28store_settings_backfill_from_config将已迁移的 Spree::Config 设置复制到每个商店
29migrate_capture_methods将支付方式上的 auto_capture → capture_method
30product_types_backfill将商品类型归商店所有
31search_reindex重建索引搜索提供方(Meilisearch);数据库提供方不执行任何操作
32fold_store_credit_categories将商店信用类别迁移为备注
33migrate_stripe_webhook_keys(可选,仅限 spree_stripe)将 webhook 密钥迁移到网关
34backfill_import_export_tenancy为导入记录添加 store_id(以及 seller_id)
35backfill_custom_field_definition_stores将自定义字段定义归商店所有
36package_types将商店默认包装盒迁移为包装类型记录
37migrate_external_receives_to_purchase_orders将 5.x 供应商收货记录迁移为采购订单;为调拨添加商店/状态
38backfill_actor_types填充新的 actor *_type 列(canceler、approver、created_by 等),使用管理员用户类;运行此步骤之前,旧记录解析时会发出弃用警告
39recount_stock_levels填充 reserved_count / incoming_count
始终以 spree upgrade --plan(升级 gem 后运行)的结果为准,而非此表——你安装的 gem 才是事实来源。
后续检查:冻结的订单

migrate_adjustments_to_typed_rows 不会更改金额。如果订单的类型化金额总和与存储的总额不一致,则不会对其进行更改,并会在 metadata 中标记(指南中写的是 private_metadata——该列现在是 metadata)。请逐一检查:

# spree console
Spree::Order.where.not(completed_at: nil).find_each.select { |o| o.metadata['typed_adjustments_frozen'].present? }
  .map { |o| [o.number, o.metadata['typed_adjustments_frozen']] }

原因包括 totals_do_not_reconcile 和退货授权冻结(由 migrate_returns 处理)。

4. 修复代码——grep 配方

在 server/app server/config server/lib server/spec(经典应用:app config lib spec)以及 apps/storefront、apps/dashboard 和任何集成代码中运行这些配方。完整的模式 → 替换表见 references/grep-recipes.md。以下是最先会导致启动或结账失败的匹配项:

cd server   # or the app root
grep -rnE "checkout_flow|go_to_state|insert_checkout_step|remove_checkout_step|state_machine|before_transition|after_transition" app config lib
grep -rnE "PermittedAttributes|permitted_[[:alnum:]_]+_attributes" app config lib
grep -rnE "PermissionSets|permissions\.assign|ApiKey::SCOPES" app config lib
grep -rnE "Spree::Adjustment|\.adjustments\b|promo_total|ship_total|item_count" app config lib
grep -rnE "\.master\b|is_master|default_price|DefaultPrice|variant\.price\b|\.price =" app config lib
grep -rnE "finalize!|order\.completed|OrderUpdater|register_update_hook|\bresume!?\b" app config lib
grep -rnE "ReturnAuthorization|CustomerReturn|Reimbursement|ReturnItem" app config lib
grep -rnE "_service\s*=" config

已弃用但仍可用的名称(Shipment、ShippingMethod、Taxon、metafields、firstname、Spree.user_class 等)会记录 Spree::Deprecation 警告——现在就修复;它们将在 6.1 中移除。运行测试套件时显示弃用警告,并将每条警告都视为待办事项。

5. 行为审查清单

这些不会引发异常——它们会改变实际行为。逐项检查。

  • 已完成的购物车为只读。 结账后的编辑应针对 Spree::Order。任何通过购物车 API 在完成后修改订单的操作,都必须移至订单端的管理服务。
  • line_item.order 可能为 nil。 LineItem、Fulfillment、Payment、TaxLine、Discount、Fee 和 StockReservation 都有 cart_id 和 order_id(两者恰有一个有值)。请使用 #owner。
  • 状态为派生值。 payment_status / fulfillment_status 仅由 Spree::Orders::UpdateStatuses 写入;直接赋值会被覆盖。payment_state/shipment_state/state 在 6.1 之前是读取别名。订单筛选:q[status_eq]=placed。
  • 已下单的订单金额被冻结。 重新计算会重新汇总类型化行项目,不会重新生成它们。下单后的金额编辑应通过 Spree::Orders::Discounts::* / Orders::Fees::* 进行。
  • 5.x 扩展会在购物车上调用 Order 名称。 当购物车还是未完成订单时编写的支付网关 gem,会在 Spree::Cart 上调用 promo_total、item_count、shipments、user、available_payment_methods 等方法。这些已弃用名称现在在 Cart 和 Order 上都会发出警告并转发,直至 6.1(参见 grep 方案)。请留意弃用日志并更新 gem。
  • 赠品不计入门槛。 通过 create_line_items 创建的赠品不再计入 item_total 规则或订单百分比折扣的基数,也不计入其所属促销的门槛。“消费满 $100 即赠礼”会在付费商品金额降至 $100 时收回赠品。
  • 取消不可撤销。 订单或 Fulfillment 都不再支持 resume;order.resumed / fulfillment.resumed 事件已移除。全部召回的订单会显示 fulfillment_status: unfulfilled。
  • Spree::Store.default 可能为 nil。 在请求之外运行的作业、rake 任务、种子和测试中设置 Spree::Current.store;创建商店范围的记录前,确保存在默认商店。
  • 继承 Spree::Base 的所有内容默认都要求 belongs_to,包括你自己的模型。在允许留空的地方添加 optional: true。错误信息为“must exist”,而非“can't be blank”(请修正断言以及解析 422 details 的客户端)。
  • Storefront 约定。 requirements[] 条目包含 { step, field, code, message };配送要求为 field: 'delivery_method'、code: 'delivery_method_required'。富文本读取会返回纯文本及 *_html(description_html、internal_note_html)——请使用 *_html 初始化编辑器。一个版本期间,购物车 number 与带前缀的 cart_ ID 相同。
  • Webhooks/事件。 订阅 order.placed(order.completed 在 6.1 之前是已弃用的别名)。report.*、order.resumed、fulfillment.resumed 已移除。新增:return.*、exchange.*、claim.*、digital_asset.*、digital_link.downloaded。
  • 富文本允许列表已缩小范围(参见前置条件)。内容会在下次保存时重新清理。
  • 数字下载会对短时有效的签名 URL 返回 302(最长 1 小时,digital_asset_link_expire_time)。客户端必须跟随重定向。现在客户会收到来自 spree_emails 的下载链接邮件——如果你已发送邮件,请停用自己的邮件。
  • 自定义字段定义按商店划分范围。 多商店安装会将所有定义放在默认商店;请为每个商店重新创建定义。产品 CSV 列使用 custom_field. 前缀(原为 metafield.)和 product_type(原为 shipping_category)。
  • 员工 API 权限。 JWT 员工与密钥通过相同的控制器级权限检查(403,包含 details.required_permission)。员工端点需要 read_staff / write_staff。
  • Store API 授权不再使用 CanCanCan——客户访问权限通过所有权范围限定及 Spree::Storefront::AccessPolicy 控制。请将 Storefront 的 can 规则移至访问策略子类或工作流 validate 钩子中。
  • 国家和州是参考数据(来自 countries gem 的普通对象,不支持 .where/.find)。记录通过 ISO country_code / state_code 标识它们。
  • 库存会在发货时扣减,而非下单时扣减(未完成的 Fulfillment 会占用分配库存)。仅基于 count_on_hand 构建的库存报告会显示不同结果。
  • 作业。 新项目会在 Puma 中运行 Solid Queue(Mission Control 位于 /jobs)。升级后的应用会保留自己的 config.active_job.queue_adapter——Sidekiq 仍可正常工作;只需将 Spree 定期作业移至你使用的调度器中。
  • SDK 主版本。 在 Storefront 中将 @spree/sdk 升级至 2.x,在集成中将 @spree/admin-sdk 升级至 1.x;将 Rails 管理后台自定义项替换为 apps/dashboard 插件(spree add dashboard)。

注意事项

  • 不要重新排列回填步骤。 多个步骤依赖前面的步骤(先处理媒体,再移除主变体;先处理分类和客户,再处理富文本;先处理调整项,再处理购物车和退货;先处理退货,再处理库存变动)。清单顺序至关重要。
  • migrate_users_to_customers 会按设计中止:Devise 移除后,如果未设置 CONFIRM_NO_PEPPER=true,就会中止。请先确认没有使用 pepper,而不是强行继续。
  • 现在覆盖 *_service DI 键不会产生任何效果。 像 Spree::Dependencies.carts_complete_service = 'MyComplete' 这样的旧写法只会被暂存,不会应用。请改用 *_workflow 键(继承已提供的工作流),或者更推荐使用 Spree.hooks.register 处理程序——参见 spree-dependencies 和 spree-workflows。
  • Spree::Product.additional_permitted_attributes << :x 会引发 FrozenError。 请使用 += [:x]。
  • 旧表会保留到 6.1(spree_adjustments、退货授权表、spree_state_changes、spree_log_entries、spree_users、Action Text 行),用作回滚数据源。请在 6.1 之前导出所有需要保留的数据。

延伸阅读

  • node_modules/@spree/docs/dist/developer/upgrades/5.6-to-6.0.md — 官方指南(https://spreecommerce.org/docs/developer/upgrades/5.6-to-6.0)
  • references/grep-recipes.md — 完整的模式 → 替换对照表
  • 相关技能:spree-upgrade、spree-workflows、spree-checkout、spree-order-totals、spree-fulfillment、spree-returns、spree-auth-permissions、spree-catalog、spree-pricing、spree-dashboard-plugins、spree-typescript-sdk

评论

支持 Markdown