首页
/ Strapi Data Transfer Providers:Source/Destination 双流架构详解与自定义 Provider 实践

Strapi Data Transfer Providers:Source/Destination 双流架构详解与自定义 Provider 实践

2026-09-04 19:34:43作者:董宙帆

本文基于 Strapi 官方文档 docs/docs/docs/01-core/data-transfer/02-providers/00-overview.md 展开,讲清楚 Data Transfer 功能中 Provider(传输提供方)的核心职责、Source 与 Destination 两类接口的完整契约,以及 Strapi 内置的 Strapi File、Local Strapi、Remote Strapi 三套 Provider 的实现结构。读完本文,你将能够理解 Strapi 数据导入/导出/备份/恢复的底层流式架构,并掌握基于 ISourceProvider / IDestinationProvider 接口编写自定义 Provider 的方法。

一、Provider 是什么:数据传输的流式接口层

Strapi 的 @strapi/data-transfer 包(位于 packages/core/data-transfer)负责数据导入、导出、备份与恢复。其核心设计是:传输过程被拆分为若干 stage(阶段),而每个阶段的实际数据读写,都由 Provider(提供方) 以 Node.js 流(Readable / Writable)的形式完成。

文档对 Provider 的定义非常精炼:

Data transfer providers are the interfaces for streaming data during a transfer.(数据传输 Provider 是在传输过程中流式传输数据的接口。)

它分为两个方向:

  • Source Provider(源提供方):为传输的每个 stage 提供 读流(ReadStream),负责从文件、本地数据库或远端实例中"取出"数据;
  • Destination Provider(目标提供方):为传输的每个 stage 提供 写流(WriteStream),负责把读到的数据"写入"目标端。

Strapi 官方为以下三类介质同时提供了 Source 与 Destination Provider:

介质 说明 文档入口
Strapi File 为传输过程设计的标准化文件格式(打包的 .strapi 文件) Strapi File 概览
Local Strapi 连接本地 Strapi 项目,使用其配置的数据库连接来管理数据 Local Strapi 概览
Remote Strapi 对 Local Strapi Provider 的封装,通过 WebSocket 接口对接一个运行中的远端(网络)Strapi 实例 Remote Strapi 概览

一个关键的架构约定是:每种介质(文件、本地、远端)都必须暴露相同的传输接口,但各自可以拥有独特的初始化选项。这正是"同一个接口 + 各自 options"的插件式设计,也意味着你可以用自己的介质(例如对象存储、数据库直连)替换其中任何一环。

从源码结构看,三套内置 Provider 分别位于:

二、传输 Stage:Provider 必须覆盖的阶段

Source 与 Destination Provider 之所以接口对称,是因为传输引擎把整个传输过程切分为固定的 stage 序列。在 engine/index.ts 中,这组 stage 被冻结为一个常量:

export const TRANSFER_STAGES: ReadonlyArray<TransferStage> = Object.freeze([
  'entities',     // 实体(内容条目)
  'links',        // 关联关系(relations)
  'assets',       // 媒体文件
  'schemas',      // 内容类型结构定义
  'configuration',// 配置实体
]);

即:实体 → 关联 → 媒体 → 结构 → 配置。Source Provider 的每个 create{stage}ReadStream() 必须产出一段可读流,Destination Provider 的对应 create{stage}WriteStream() 接收同一段数据。引擎还会提供一组预设过滤器(见 engine/index.ts 中的 TransferGroupPresets),允许用户按 content(entities + links)、files(assets)、config(configuration)的粒度选择传输范围——Provider 只要完整实现五个 stage 的流,引擎就能按用户选择裁剪。

三、Provider 公共契约:IProvider 接口

在深入两类 Provider 之前,先看它们的公共基座。所有接口定义都在 src/types/providers.ts(文档中引用的 types/providers.d.ts 即此文件构建后产出的类型声明)中:

export type ProviderType = 'source' | 'destination';

export interface IProvider {
  type: ProviderType;
  name: string; // a unique name for this provider
  results?: IProviderTransferResults; // 供引擎外部跟踪的可选结果对象

  /**
   * bootstrap() 在传输引擎 bootstrap 时被调用。
   * 用于初始化操作:建立数据库连接、打开文件、检查授权等。
   */
  bootstrap?(diagnostics?: IDiagnosticReporter): MaybePromise<void>;
  close?(): MaybePromise<void>; // 在传输引擎 close 时被调用

  getMetadata(): MaybePromise<IMetadata | null>; // 返回用于版本校验的传输元数据
  getSchemas?(): MaybePromise<Record<string, Struct.Schema> | null>; // 返回用于结构校验的 schema

  beforeTransfer?(): MaybePromise<void>; // 在 stage 执行前立即调用
}

几个值得注意的生命周期语义:

  1. bootstrap() 是资源获取点。Source 与 Destination 都可能在此处打开文件、建立数据库连接或校验鉴权。本地 Provider 通常会在此处拿到 Strapi 实例——@strapi/data-transfer 提供了工具函数 utils/providers.ts 中的 assertValidStrapi(),在实例缺失时抛出 ProviderInitializationError,保证 Provider 不会带着空实例静默运行。
  2. getMetadata() 用于版本校验。元数据会随传输流动,目标端 Provider 通过 setMetadata()(见下文)接收源端元数据,实现跨实例版本兼容检查。
  3. getSchemas() 用于结构校验。引擎在传输前对比源/目标 schema(DEFAULT_SCHEMA_STRATEGY = 'strict'),可以推断出:schema 校验失败时引擎会默认拒绝传输,除非用户显式放宽策略。
  4. close() 是资源释放点,与 bootstrap() 成对出现。

引擎侧对 Provider 的接入并非"信任后使用"。在 TransferEngine 的构造函数中(engine/index.ts):

constructor(sourceProvider: S, destinationProvider: D, options: ITransferEngineOptions) {
  this.diagnostics = createDiagnosticReporter();

  validateProvider('source', sourceProvider);
  validateProvider('destination', destinationProvider);
  // ...
}

两个 Provider 会先经过 engine/validation/provider.ts 中的 validateProvider() 校验,接口不合规会在引擎启动前就报错,而不是在传输中途失败。

四、Source Provider:逐 Stage 的读流接口

Source Provider 必须实现 ISourceProvider 接口,其文档定义是(见 01-source-providers.md):

它为每个 stage 提供一组 create{stage}ReadStream() 方法,返回一个 Readable 流;该流负责获取自己的数据,然后对每个实体、关联、媒体文件、配置实体或内容类型 schema 执行 stream.write(entity)(具体写什么取决于当前 stage)。

当某个 stage 的流发完所有数据后,在传输引擎进入下一个 stage 之前,流必须被关闭

对应源码接口(src/types/providers.ts):

export interface ISourceProvider extends IProvider {
  results?: ISourceProviderTransferResults;

  /**
   * 可选的 stage 总量估计。
   * 引擎在 stage 读流创建后、stage::start 之前调用。
   * 用于 CLI 进度显示(剩余字节/条数、ETA)。
   * 未知时可省略或返回 null(旧版远端、文件 Provider 即如此)。
   */
  getStageTotals?(stage: TransferStage): MaybePromise<StageTotalsEstimate | null | undefined>;

  createEntitiesReadStream?(): MaybePromise<Readable>;
  createLinksReadStream?(): MaybePromise<Readable>;
  createAssetsReadStream?(): MaybePromise<Readable>;
  createConfigurationReadStream?(): MaybePromise<Readable>;
  createSchemasReadStream?(): MaybePromise<Readable>;
}

实现要点:

  • 每个 stage 一个独立方法,方法名与 stage 一一对应(entities / links / assets / schemas / configuration),返回 MaybePromise<Readable>——既可以是同步的流,也可以是异步构造后 resolve 的流(例如本地 Provider 需要先查询数据库再开始流式吐出)。
  • getStageTotals() 是进度体验的关键。CLI 的进度条依赖它来计算"剩余 X 条 / 剩余 X MB / 预计 X 分钟"。返回 null 时进度条退化为不定态显示。
  • "流必须关闭才能进入下一 stage" 这一约束意味着 Provider 的读流承担了 stage 边界的职责——流没关完,引擎就不会推进,这既保证了背压语义,也让"部分传输"成为不可能出现的中间状态。

以本地 Strapi Provider 为例,strapi/providers/local-source/ 目录下按 stage 拆分了实现文件:entities.tslinks.tsassets.tsconfiguration.ts,以及 estimate-asset-totals.ts(专门实现 getStageTotals 的媒体总量估计)。

五、Destination Provider:写流、回滚与警告通道

Destination Provider 必须实现 IDestinationProvider 接口(见 02-destination-providers.md):

它为每个 stage 提供一组 create{stage}WriteStream() 方法,返回一个 Writable 流;该流会接收到由 Source Provider 读流 pipe 过来的每个实体、关联、媒体文件、配置实体或内容类型 schema。

源码接口(src/types/providers.ts):

export interface IDestinationProvider extends IProvider {
  results?: IDestinationProviderTransferResults;

  /**
   * 可选的回滚实现。
   * 当传输过程中抛出错误时调用,以执行回滚操作。
   */
  rollback?<T extends Error = Error>(e: T): MaybePromise<void>;

  setMetadata?(target: ProviderType, metadata: IMetadata): IDestinationProvider;
  onWarning?: (message: string) => void;

  createEntitiesWriteStream?(): MaybePromise<Writable>;
  createLinksWriteStream?(): MaybePromise<Writable>;
  createAssetsWriteStream?(): MaybePromise<Writable>;
  createConfigurationWriteStream?(): MaybePromise<Writable>;
  createSchemasWriteStream?(): MaybePromise<Writable>;
}

相比 Source 侧,Destination 侧多了三个与"写数据安全"相关的钩子:

  1. rollback(e):传输过程中出现错误时调用,允许 Provider 撤销已写入的数据(例如回滚数据库事务、清理已写入的媒体文件)。这是 Destination 接口独有的能力——只有"写端"才需要回滚。
  2. setMetadata(target, metadata):目标端接收源端元数据的通道,供版本校验使用(与 IProvider.getMetadata() 呼应)。
  3. onWarning:非致命警告的回调通道。例如恢复过程中遇到无法解析的引用,Provider 可以通过它把警告上报给引擎,由引擎统一通过 reportWarning()(见 engine/index.ts)汇入诊断报告,而不中断传输。

本地 Destination Provider 的恢复策略代码组织也印证了这种"分 stage 写入"的设计:strapi/providers/local-destination/strategies/restore/ 下按 stage 拆分了 entities.tslinks.tsconfiguration.tsresolve-link-ref.ts(关联引用解析),另有一个 assets-destination-writable.ts 专门处理媒体写流的特殊语义(媒体写入需要处理文件落盘而非单纯的数据库记录)。

六、内置 Provider 的实现解剖

6.1 Strapi File Provider:文件介质的标准化格式

Strapi File 是"把整个 Strapi 项目状态打包成一个文件"的介质。Source 侧实现位于 file/providers/source/index.ts,其初始化选项完整展示了"各介质拥有自己独特 options"的设计:

export interface ILocalFileSourceProviderOptions {
  file: {
    path: string; // 要读取的文件路径
  };
  encryption: {
    enabled: boolean; // 文件是否加密(需要解密)
    key?: string;    // 解密密钥
  };
  compression: {
    enabled: boolean; // 文件是否压缩(需要解压缩)
  };
}

export const createLocalFileSourceProvider = (options: ILocalFileSourceProviderOptions) => {
  return new LocalFileSourceProvider(options);
};

从源码结构看,文件 Provider 内部按 "tar 打包 → 解压(zlib)→ JSONL 解析" 的管道组装读流(依赖 tartar-streamstream-json,见 package.json 的 dependencies),元数据固定存放在包内的 metadata.json(源码常量 METADATA_FILE_PATH = 'metadata.json')。构造器还包含一个典型的前置校验:encryption.enabled 为 true 但 key 缺失时立即抛出 'Missing encryption key',避免传输中途才暴露配置错误。Destination 侧(file/providers/destination/index.ts)则负责反向操作:把各 stage 的写流数据序列化、压缩、加密后写回标准文件。文件结构的细节可参考 01-file-structure.md

6.2 Local Strapi Provider:直连本地项目数据库

Local Provider 直接操作一个本地 Strapi 项目的数据。Source 侧(strapi/providers/local-source/)通过 Strapi 实例的数据库服务按 stage 流式查询实体、关联、配置,并单独实现媒体资产的读取与总量估计(estimate-asset-totals.ts)。Destination 侧(strapi/providers/local-destination/index.ts)实现的是"恢复(restore)"语义:把流式数据还原为目标项目的实体、关联与配置,且具备 rollback 能力。官方文档明确建议:编写自定义 Provider 时,参考 Local Strapi Provider 的实现——它是最接近"完整实现"的范例。

6.3 Remote Strapi Provider:在 Local 之上封装 WebSocket

Remote Provider 并不是独立重写的第二套实现。文档定义为:

a wrapper of local Strapi provider that adds a websocket interface to a running remote (network) instance of Strapi(对 Local Strapi Provider 的封装,为运行中的远端 Strapi 实例增加 WebSocket 接口)。

源码结构印证了这一点:strapi/remote/ 下包含 flows/(传输流程)与 handlers/pull.ts / push.ts 两端处理器、abstract.ts 抽象基类),而协议类型定义在 types/remote/protocol/(client / server / auth 三层)。也就是说:网络传输只关心字节怎么过 WebSocket,业务语义仍然由 Local Provider 的读写流承担。这种"本地逻辑 + 网络壳"的分层,使得远端导入导出可以复用本地恢复的全部测试覆盖。

七、编写自定义 Provider

官方文档给出的路径非常直接:

  1. 实现 ISourceProviderIDestinationProvider 中你需要的接口(定义于 src/types/providers.ts);
  2. 不必两者都实现——只实现你的使用场景所需的那一部分即可。例如:只想支持"从某对象存储导出到 Strapi",就只需实现 Source 侧(读流 + getMetadata + bootstrap/close);
  3. 参考现有 Provider(推荐 Local Strapi Provider);
  4. 每个 stage 的读/写流遵循第四、五节的契约:读流逐条 write()在数据发完后关闭;写流消费从对端 pipe 过来的数据,且 Destination 侧应考虑实现 rollback

一个最小 Source Provider 的骨架(按接口契约整理,非仓库内代码):

import type { Readable } from 'stream';
import type { IMetadata, ISourceProvider, ProviderType } from '@strapi/data-transfer';

class MySourceProvider implements ISourceProvider {
  type: ProviderType = 'source';
  name = 'source::my-custom'; // 唯一名称,引擎与日志都会使用它

  async bootstrap() { /* 建立连接 / 打开资源 / 鉴权 */ }
  async close() { /* 释放资源 */ }

  async getMetadata(): Promise<IMetadata | null> {
    // 返回源端元数据,供引擎做版本校验
    return { strapiVersion: '5.x.x', // ...按 IMetadata 结构填充
             createdAt: new Date().toISOString() } as IMetadata;
  }

  async createEntitiesReadStream(): Promise<Readable> {
    // 从自定义介质逐条读取实体并 write(entity),发完后 end() 关闭
    throw new Error('not implemented');
  }
  // 按需实现 createLinksReadStream / createAssetsReadStream /
  // createConfigurationReadStream / createSchemasReadStream
}

注意:引擎在构造 TransferEngine 时会通过 validateProvider() 校验接口合规性,因此接口缺失会在传输启动前被捕获,而不是运行到某个 stage 才失败。

八、资产(Asset)传输的现状与限制

文档最后专门强调了当前版本的一项限制,使用自定义 Provider 处理媒体时务必知晓:

目前,所有 data-transfer Provider 只处理本地媒体资产/upload 文件夹)。Provider 级媒体(Provider media,如对象存储直传)仍在开发中。因此,一切与资产传输相关的内容——包括 Strapi 文件结构、恢复策略、资产回滚——当前均被视为 unstable(不稳定),近期可能发生变化。

这一点在源码中也有对应:本地 Destination 的媒体写流由独立的 assets-destination-writable.ts 处理,且其测试单独成组(local-destination/tests/assets.test.tsassets-destination-writable.test.ts)。换言之:基于本地 /upload 目录的资产传输是可用的,但面向外部存储 Provider 的媒体链路接口可能调整,依赖该能力的自定义 Provider 应做好随版本迭代的准备。

九、验证路径:从源码到测试

本文的关键论断均可在仓库中复核:

小结

Strapi Data Transfer 的 Provider 体系用"统一的 stage 化流接口 + 各介质自定义 options"的方式,把导入、导出、备份、恢复统一到了同一套传输引擎之下:Source Provider 负责按 stage 产出读流并在发完数据后关闭,Destination Provider 负责消费写流并具备回滚能力,而 Strapi File、Local Strapi、Remote Strapi 三套内置实现恰好覆盖了"文件 ↔ 本地项目 ↔ 远端实例"三条最常用的传输链路。文档标注该功能属于 experimental,其中资产(Provider media)相关链路仍处于 unstable 状态——在评估生产可用性时,应把媒体传输的接口稳定性作为重点观察项。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
889
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
982
502
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384