Spree 6.0 Integrations Admin:用统一注册表、Admin API 与仪表盘打通服务商凭据管理
发布时间:2026/9/14 17:43:23来源:尧图网络
Spree 6.0 Integrations Admin用统一注册表、Admin API 与仪表盘打通服务商凭据管理【免费下载链接】spreeOpen Source eCommerce Platform for B2B, Marketplace, and Enterprise. REST API, TypeScript SDK, and production-ready Next.js storefront. Self-host it. Own your stack. No vendor lock-in. Zero platform fees.项目地址: https://gitcode.com/GitHub_Trending/sp/spree本文基于 Spree 仓库中的规划文档 docs/plans/6.0-integrations-admin.md状态已于 2026-08-06 随 6.0 交付全部测试套件通过撰写并结合当前仓库的实际源码展开。读完后你将理解 Spree 6.0 如何把Spree::Integration打造成所有服务商接缝运费计算、税务、履约、自提点等的唯一凭据入口从Spree.integrations显式注册表、PreferenceSchema驱动的表单渲染、Admin API v3 的完整 CRUD 与verify-before-activate校验到仪表盘/settings/integrations集成画廊的实现链路以及 EasyPost 作为第一个真实消费者如何完成注册。背景5.x 已埋下的模型缺的是另一半Spree 5.x 已经交付了spree_integrations数据表按 store 归属、STI 分型、preferences文本字段、active布尔位与can_connect?钩子建表迁移见 20250407085228_create_spree_integrations.rbcreate_table :spree_integrations, if_not_exists: true do |t| t.references :store, null: false, index: true t.string :type, null: false, index: true t.text :preferences t.boolean :active, default: false, null: false, index: true t.timestamps end但按规划文档的表述核心代码中没有任何子类、没有注册表、没有 API、没有 UI——模型上的全部能力在 5.x 从未被真正使用过。6.0 的 Integrations Admin 方案就是补齐这缺失的一半其定位是服务商provider保持无状态策略对象Integration 记录持有凭据仪表盘负责可见性——商家在一个页面看到当前 store 连接了所有什么。关键设计决策规划文档明确标注未经讨论不得偏离唯一的凭据归宿One credential home。每个 provider gem 携带一个Spree::Integration子类把凭据放在模型 preferences 中再附带一个或多个无状态 provider 类通过store.integrations.active.find_by(type: ...)解析凭据。Provider 自身绝不存储凭据也不允许新的 provider 接缝自创凭据存储禁止 env 变量约定、禁止在宿主模型上放 preferences。现有例外Meilisearch 搜索 provider 在 6.0 仍走MEILISEARCH_URL环境变量——它是跨 store 共享的基础设施而非单店商务凭据迁移不在范围内。显式注册表Spree.integrations。一个以类名字符串为元素的数组gem 在 initializer 里追加Spree.integrations SpreeEasyPost::Integration形态与Spree.fulfillment_providers、Spree.delivery_method_rules一致。规划文档明确拒绝 descendants 扫描式的隐式发现理由是依赖 eager-load 的魔法会掩盖意图。Preference schema 驱动 UI。Spree::Integration引入既有的Spree::PreferenceSchemaconcern类型发现端点直接下发serialized_preference_schema仪表盘用现成的PreferencesForm组件渲染表单。秘密值使用:password类型的 preference走现有的Spree::Preferences::Masking机制——读取时掩码•••• 后 4 位写入时有掩码回写保护与今天支付方式的凭据处理完全一致。每 (store, type) 一条 Integration保持不变模型校验早已存在本方案按仓库惯例补上支撑唯一约束的数据库唯一索引多账号支持明确排除在外。纯 Admin 面。永远没有 store 序列化器、没有前台暴露API Key 访问受新增的read_integrations/write_integrations两个 scope 门控JWT 访问由 CanCanCan 对Spree::Integration授权。先验证后激活Verify before activate。保存凭据本身从不发起网络调用——半成品配置可以保存、但保持非激活状态把active翻成true时才会运行can_connect?失败则以 422 拒绝错误信息进入connection_error_message。接受的代价服务商宕机会临时阻止上线但不阻止编辑显式的POST /:id/test端点随时可用于诊断。画廊品牌化采用logo_url 服务端本地化description。Integration gem 声明类级logo_url托管品牌资源的绝对 URL或自包含 gem 的data:URI和descriptionRails 翻译键spree.integrations.api_type.description优先因此 gem 可在服务端本地化——Ruby gem 无法扩展 dashboard 的 locale 文件。文档刻意不使用 asset-pipeline 路径在 6.0 的拆分架构里 Rails 侧是纯数据 API若 integration gem 要携带浏览器图片就会迫使无头部署主机引入 Propshaft、预编译提示与 host-URL 解析。接受的代价托管 logo 是礼遇而非保证vendor CDN 会失效、隔离环境不可达仪表盘始终有字母头像兜底。连接状态是瞬时的ephemeral。不设last_checked_at/last_error列——test 端点返回实时结果画廊展示已存储状态active加按需测试的结果。接受的代价没有2 小时前验证过这类展示必须点击测试。模型层实现spree/core规划文档给出的核心代码骨架class Spree::Integration Spree.base_class include Spree::PreferenceSchema # preference_schema, serialized_preferences (masked) validate :type_must_be_registered, if: :type_changed? end对照仓库中已落地的 integration.rb实现比骨架更完整值得逐项看注册表接线与注册校验registers_subclasses_via { registered_classes } def self.registered_classes Spree.integrations.map { |entry| entry.is_a?(Class) ? entry : entry.to_s.constantize } endregisters_subclasses_via是 preference_schema.rb 里子类注册机制的入口该 concern 同时被支付方式的/payment_methods/types等发现端点复用。注册项允许类名字符串文档化的扩展形式或类对象两种形态。类型校验按仅在 type 变更时生效实现并有一个关键的逃逸分支validate :type_must_be_registered, if: :type_changed? def type_must_be_registered return if type.blank? # 注册表为空 没有安装任何 integration gem没有可校验的对象 return if Spree.integrations.empty? return if Spree.integrations.map(:to_s).include?(type) errors.add(:type, :integration_type_not_registered, ...) end规划文档的 Implementation status 专门解释了这一点核心不携带任何 integration注册表为空时跳过注册校验保证裸核心安装与测试 factory 继续工作而 Admin API 无论如何都走注册表解析类型未注册的类型根本到不了模型层。同时仅在变更时校验也保证了 gem 被卸载后存量数据行不会立刻失效。verify-before-activate 在模型上的落点validate :must_connect_when_activating, if: - { active? will_save_change_to_active? } def must_connect_when_activating return if can_connect? errors.add(:base, connection_error_message.presence || Spree.t(errors.messages.integration_connection_failed)) end注意错误挂在:base而不是:active上——源码注释解释服务商返回的消息This api key is no longer active…是记录级失败挂成属性错误会在渲染时被加上人类可读的属性前缀Active This api key…。can_connect?的基类实现恒为true由子类覆盖连接错误文案存放在非持久化的attr_accessor :connection_error_message上每次连接尝试都会重置。此外模型还声明了两个 webhook 入口基类接受一切、parse_webhook_event返回nil表示非本集成处理的事件以及after_commit钩子把Spree::Current.integrations请求级快照置空——因为 activate-and-verify 流程会在单次请求内连接/停用集成不能让后续读取继续吃旧快照。api_type线协议简写的推导def self.api_type return super unless name.demodulize Integration outer name.deconstantize.delete_prefix(Spree) return super if outer.blank? outer.underscore end这是规划文档 Two notes beyond the design 中的第 (2) 条gem 约定导致SpreeEasyPost::Integration、SpreeAvalara::Integration等所有类 demodulize 后都撞车在Integration上因此对恰好命名为Integration的类从外层模块推导简写SpreeEasyPost::Integration→easy_post。发现端点的数据源与唯一索引discovery_entries为 Admin types 端点产出稳定按 name 排序的条目字段包括type简写、name、group、description经human_description取 Rails 翻译或类级回退、logo_url与preference_schema即serialized_preference_schema其中:password类型的 default 被置空——发现端点是无认证的公开面绝不能泄露 gem 携带的默认秘密值。支撑每 (store, type) 一条的数据库唯一索引迁移为 20260806130001_add_unique_store_type_index_to_spree_integrations.rbadd_index :spree_integrations, [:store_id, :type], unique: true给已有的模型唯一性校验补上 DB 层背书。Spree.integrations访问器与默认值注册表访问器直接桥接到 Rails 配置见 core.rbdef self.integrations Rails.application.config.spree.integrations end def self.integrations(value) Rails.application.config.spree.integrations value end默认值在核心引擎中初始化为空数组engine.rb 中Rails.application.config.spree.integrations []。测试 core_environment_spec.rb 校验了两者始终相等且为Array类型。PreferenceSchemaschema 驱动 UI 的底座Integration 引入的 Spree::PreferenceSchema 是整个表单不需要硬编码字段机制的核心要点preference_schema返回[{ key:, type:, default:, choices: }]从静态的preference :name, :type声明推导类加载时记忆化compute_preference_schema会跳过preference_deprecated与preference_internal的字段——内部字段是 Spree 在注册到服务商后由服务商回写的例如用于签名验证的密钥暴露成表单字段会诱导有人覆写掉它。serialized_preference_schema是线协议安全变体:password的 default 被置nil并剥离服务端专用的:key_string缓存输出冻结的{ key, type, default, choices? }数组。serialized_preferences实例方法委托给Spree::Preferences::Masking.serialize把:password值掩码成•••• 后 4 位。规划文档强调这条安全性放在 concern 里而不是序列化器里目的是让任何持有 Preferable 实例的调用方都获得同样的保证。find_by_api_type按注册表解析线简写未知简写返回nil查找是注册表驱动的被移除的或外来的子类无法被夹带进来。若类未通过registers_subclasses_via声明注册表registered_subclasses会抛出UndeclaredRegistryError而不是静默返回空列表——源码注释解释了动机过去的静默返回曾导致带类型的记录被无声地从 payload 中丢掉、admin 选择器渲染为空。模型层的类型与校验行为由 integration_spec.rb 覆盖含注册表为空时的逃逸路径、未注册类型的拒绝。Admin API v3/api/v3/admin/integrations控制器 integrations_controller.rb 继承ResourceController并混入共享的SubclassedResourceconcern规划文档中的端点表格全部落地端点用途GET /integrations当前 store 已连接的集成Ransack 支持type、active过滤GET /integrations/types注册类型发现typeapi_type简写、name、group、description、logo_url、preference_schemapassword 默认值置空——仪表盘画廊的数据源POST /integrationsparams.permit(:type, :active, preferences: {})扁平参数type 只通过注册表从线简写解析绝不裸constantizePATCH /integrations/:id合并 preferences 并带掩码回写保护提交的值若携带掩码 token则保留已存储的秘密。激活创建或更新时active: true会运行can_connect?失败 422DELETE /integrations/:id断开连接引用它的 provider 按各自的available_for_store?约定降级POST /integrations/:id/test运行can_connect?返回{ connected:, error_message: }取自connection_error_message永不持久化控制器实现中有几个值得注意的细节subclassed_via - { Spree::Integration.registered_classes }, unknown_type_error: unknown_integration_type def types authorize! :create, model_class render json: { data: Spree::Integration.discovery_entries } end def test resource find_resource authorize_resource!(resource, :update) connected resource.can_connect? render json: { connected: connected, error_message: connected ? nil : resource.connection_error_message } end def build_subclassed_resource(klass, attrs) current_store.integrations.build(attrs.merge(type: klass.sti_name)) end源码注释明确types是纯注册表发现刻意不携带per-store 连接状态——客户端会把这类响应做长期缓存实时连接状态应从 integrations 列表读取build_subclassed_resource把注册表解析出的类的 STI 全名写入type列线简写与数据库 STI 名在此解耦作用域限定为current_store.integrations跨 store 的 id 一律 404控制器与集成测试 integrations_spec.rb 均覆盖了跨 store 404、掩码回写往返、未注册类型拒绝这三条路径。序列化器 integration_serializer.rb 只输出 Admin 可见字段id、type简写取自integration.class.api_type、name、group、active、preferences经serialized_preferences掩码、preference_schema经serialized_preference_schema、时间戳。类注释把红线写得很直白credentials never have a storefront surface凭据永远没有前台面。密钥访问的 scope 门控由配套的5.5-admin-api-key-scopes方案定义的read_integrations/write_integrations实现控制器通过read_actions把types计入只读动作。仪表盘/settings/integrations与 SDK规划文档定义了管理端的完整形态且标注为已随本波次交付设置导航入口挂进现有 settings 注册表用Can对 integrations 权限门控画廊页注册类型按integration_groupshipping、tax、…经 i18n 键翻译分组每张卡片显示图标、名称与 connected/active 状态——这正是一个页面看到连接了什么的目标连接/配置 SheetPreferencesForm依据preference_schema渲染带 active 开关、调用 test 端点并内联展示error_message的 Test connection 按钮以及需破坏性确认的 disconnectSDKspree/admin-sdk中的adminClient.integrations资源含手写 type/params 定义与生成的Integration类型i18n六个 locale 全部补齐。规划文档同时说明 OpenAPI 与 CLI 规范已随之重新生成rswag 覆盖对应的 API 规范文档可参考 docs/api-reference/admin-api 系列。Provider 联动Integration 是凭据枢纽Provider 是选择器每个 provider 类声明self.integration_class与self.available_for_store?(store)后者在 docs/plans/6.0-delivery-rate-provider.md 中先行定义。管理端的选择器delivery-method 的 provider 下拉、tax provider 下拉按available_for_store?过滤并在所需集成尚未连接时深链到/settings/integrations——集成页是这些流程的落地枢纽这也是不要为单个 provider 建凭据 UI约束的由来。第一个真实消费者 EasyPost 的注册代码可以见 engine.rb它精确呈现了 gem 侧约定config.after_initialize do Spree.integrations SpreeEasyPost::Integration Spree.delivery_rate_providers SpreeEasyPost::DeliveryRateProvider Spree.fulfillment_providers SpreeEasyPost::FulfillmentProvider end一个 gem 同时挂上 Integration凭据与多个无状态 provider策略后者运行时经store.integrations.active.find_by(type: ...)取回凭据。同仓的 integration_spec.rb 验证了该 gem 加载后注册表确实包含SpreeEasyPost::Integration。迁移路径与开发约束规划文档给出的落地顺序已全部完成核心注册表 PreferenceSchema引入 类型包含校验 唯一索引迁移Admin API控制器、序列化器、scope集成规格快乐路径 控制器规格掩码回写、跨 store 404、未注册类型拒绝SDK 资源 生成类型仪表盘画廊 sheet 设置导航入口 i18n首个消费者随各自方案落地SpreeEasyPost::Integrationdelivery-rate Phase 5与 Avalaratax自提点网络集成最早 6.1见 docs/plans/decisions.md 2026-08-06 修订。无数据迁移——表在实践中是空的核心零子类。对后续开发者的硬约束Constraints on Current Work新 provider gem必须携带 Integration 子类并在Spree.integrations注册单店 provider 禁止 env 变量凭据约定秘密必须是:password类型的 preference——其他类型会绕过掩码从序列化器泄露明文绝不constantize客户端提供的 integration type——一律经注册表解析不要为单个 provider 建凭据 UI例如 delivery-method sheet 上的凭据卡片——配置统一放在 integrations 页provider 选择器只做深链。小结与延伸参考Integrations Admin 用一条清晰的职责切分收敛了 6.0 所有服务商接缝的凭据问题Spree::Integration子类持有秘密Spree/core/app/models/spree/integration.rbSpree.integrations注册表负责装了什么PreferenceSchema负责长什么表单Admin API v3 负责谁能改仪表盘负责看得见。它与 docs/plans/6.0-delivery-rate-provider.md、docs/plans/6.0-tax-provider.md、docs/plans/6.0-fulfillment-and-delivery.md 互相咬合scope 门控则依赖 docs/plans/5.5-admin-api-key-scopes.md。仓库内可直接追溯的关键文件方案全文与实现状态docs/plans/6.0-integrations-admin.md模型与注册校验spree/core/app/models/spree/integration.rbSchema/掩码底座spree/core/app/models/concerns/spree/preference_schema.rb控制器与序列化器spree/api/app/controllers/spree/api/v3/admin/integrations_controller.rb、spree/api/app/serializers/spree/api/v3/admin/integration_serializer.rb迁移spree/core/db/migrate/20250407085228_create_spree_integrations.rb、spree/core/db/migrate/20260806130001_add_unique_store_type_index_to_spree_integrations.rb首个消费者示例spree/providers/easypost/lib/spree_easypost/engine.rb测试spree/core/spec/models/spree/integration_spec.rb、spree/api/spec/controllers/spree/api/v3/admin/integrations_controller_spec.rb、spree/api/spec/integration/spree/api/v3/admin/integrations_spec.rb【免费下载链接】spreeOpen Source eCommerce Platform for B2B, Marketplace, and Enterprise. REST API, TypeScript SDK, and production-ready Next.js storefront. Self-host it. Own your stack. No vendor lock-in. Zero platform fees.项目地址: https://gitcode.com/GitHub_Trending/sp/spree创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网站建设高端定制企业官网