关于 spree-upgrade-5-to-6
将 Spree 5.x 升级到 6.0 的迁移指南,供开发团队参考。内容包括升级前提、迁移与数据回填步骤、破坏性变更及升级后的检查。
主要功能
- 01Spree 6.0 升级前提
- 02有序迁移与数据回填步骤
- 03破坏性变更概览
- 04代码搜索与修复指引
- 05升级后数据检查
适用场景
- 01规划从 Spree 5.6 升级到 6.0
- 02准备 Rails 与扩展依赖
- 03检查迁移后的订单和客户数据
- 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_core6.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 storeread_<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类内部):
如果密钥仅保存在 credentials 中而未进行此设置,webhook 密钥和网关客户 ID 将保持明文状态(它们的%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 endencrypts会检查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 | 作用 |
|---|---|---|
| 1 | consolidate_metadata | 安全兜底:将所有遗留的 public_metadata/private_metadata 对合并到单个 metadata 列 |
| 2 | migrate_media_class_names | 将存储的 Spree::Asset/Spree::Image 类名改写为 Spree::Media |
| 3 | migrate_master_images_to_product_media | 将固定到变体的图片移至商品图库(会加入队列的任务——等待任务处理完毕) |
| 4 | backfill_media_store_ids | 为每条媒体记录添加 store_id |
| 5 | remove_master_variant | 回填 default_variant_id,停用主变体 |
| 6 | migrate_taxons_to_categories_and_collections | 自动分类节点 → Spree::Collection;其余保留为 Spree::Category;改写 Spree::Taxon* 字符串 |
| 7 | backfill_library_media_placements | 将类别/集合图片放入媒体库 |
| 8 | migrate_users_to_customers | spree_users → spree_customers,重新指向 user_type 列(参见 Devise pepper 前置条件) |
| 9 | migrate_rich_text_to_columns | 将 Action Text 正文迁移到文本列,并进行清理 |
| 10 | migrate_shipping_to_delivery | 将 Shipment 字符串/状态迁移为 fulfillment 字符串/状态、配送服务提供方,并将 display_on → storefront_visible |
| 11 | migrate_delivery_profiles | 将配送类别迁移为归商店所有的配送档案 |
| 12 | migrate_fulfillment_statuses | 重新映射为 unfulfilled / fulfilled / delivered |
| 13 | migrate_deliveries | 将追踪号码迁移到 spree_deliveries 记录 |
| 14 | migrate_zones_to_delivery_zones | 将配送引用的区域迁移为 Spree::DeliveryZone |
| 15 | migrate_country_state_codes | 填充 ISO country_code/state_code 列(国家/州现在是参考数据) |
| 16 | migrate_promotion_option_value_rules | 将选项值促销规则迁移为稳定的选项值 ID |
| 17 | migrate_adjustments_to_typed_rows | 将 spree_adjustments → TaxLine / Discount / Fee;标记无法对账的订单 |
| 18 | backfill_reason_store_ids | 将退货/退款原因归商店所有 |
| 19 | migrate_returns | 将 ReturnAuthorization 迁移为 Spree::Return / Spree::Exchange(失败的记录会导致中止;SKIP_FAILED_ROWS=true) |
| 20 | migrate_stock_movements_to_typed_rows | 为库存移动添加类型;重新分配未完成的履约(库存现在在发货时扣减) |
| 21 | backfill_delivery_and_stock_store_ids | 将配送方式和库存地点归商店所有 |
| 22 | migrate_tax_zones | 将税率区域迁移为税率上的国家/州信息 |
| 23 | backfill_tax_store_ids | 将税率/类别归商店所有 |
| 24 | backfill_order_coupon_codes | 为历史已下单订单填充新的 spree_orders.coupon_code |
| 25 | backfill_order_markets | 为订单分配默认市场 |
| 26 | migrate_incomplete_orders_to_carts | 将未完成订单迁移为 Spree::Cart(令牌保持不变) |
| 27 | migrate_calculator_bounds_to_delivery_method_rules | 将 FlatRate 最小/最大限额迁移为 DeliveryMethodRule |
| 28 | store_settings_backfill_from_config | 将已迁移的 Spree::Config 设置复制到每个商店 |
| 29 | migrate_capture_methods | 将支付方式上的 auto_capture → capture_method |
| 30 | product_types_backfill | 将商品类型归商店所有 |
| 31 | search_reindex | 重建索引搜索提供方(Meilisearch);数据库提供方不执行任何操作 |
| 32 | fold_store_credit_categories | 将商店信用类别迁移为备注 |
| 33 | migrate_stripe_webhook_keys | (可选,仅限 spree_stripe)将 webhook 密钥迁移到网关 |
| 34 | backfill_import_export_tenancy | 为导入记录添加 store_id(以及 seller_id) |
| 35 | backfill_custom_field_definition_stores | 将自定义字段定义归商店所有 |
| 36 | package_types | 将商店默认包装盒迁移为包装类型记录 |
| 37 | migrate_external_receives_to_purchase_orders | 将 5.x 供应商收货记录迁移为采购订单;为调拨添加商店/状态 |
| 38 | backfill_actor_types | 填充新的 actor *_type 列(canceler、approver、created_by 等),使用管理员用户类;运行此步骤之前,旧记录解析时会发出弃用警告 |
| 39 | recount_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”(请修正断言以及解析 422details的客户端)。 - 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钩子中。 - 国家和州是参考数据(来自
countriesgem 的普通对象,不支持.where/.find)。记录通过 ISOcountry_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,而不是强行继续。- 现在覆盖
*_serviceDI 键不会产生任何效果。 像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