首页
/ Strapi Data Transfer 之 Local Strapi Source Provider 全解:从初始化实例读取实体、关系、配置与资产的传输源

Strapi Data Transfer 之 Local Strapi Source Provider 全解:从初始化实例读取实体、关系、配置与资产的传输源

2026-09-04 22:01:52作者:卓炯娓

本文围绕 Strapi monorepo 中 @strapi/data-transfer 包的 Local Strapi Source Provider(本地 Strapi 数据源提供者)展开:它直接连接一个已初始化的 Strapi 实例,借助数据库查询引擎(Query Engine / queryBuilder)以流式方式读取实体、关系链接、应用配置、Schema 与媒体资产,是 strapi export 命令与远程 Pull 流程的数据出口。读完本文,你将掌握该 Provider 的两个选项 getStrapi / autoDestroy 的确切语义、bootstrap 与 close 的生命周期行为,以及各数据流(entities / links / configuration / schemas / assets)的底层实现路径与边界条件。

文档定位:Source Provider 是什么

官方文档条目 Local Strapi Source 对该 Provider 的一句话定义是:

This provider will retrieve data from an initialized strapi instance using its Entity Service and Query Engine.

即:该 Provider 不从文件、也不是从远端 API 取数,而是直接驱动一个已在当前进程中初始化的 Strapi 实例,通过其数据库查询能力把数据"泵"进 Transfer Engine 的读取流。源码入口位于 LocalStrapiSourceProvider 实现,工厂函数为 createLocalStrapiSourceProvider,Provider 的注册名为 source::local-strapi

它属于 data-transfer 模块的 Strapi 层 Provider 之一,与 Destination 侧的 local-destination、以及远端 remote-source / remote-destination 对称组织,统一从 providers 出口 导出。该模块在文档中带有 experimental 标签,属于实验性能力,API 在后续版本中可能调整,适用前提是 Strapi v5 体系下的 monorepo 源码结构。

Provider 选项:getStrapi 与 autoDestroy

文档中给出的选项接口(实际源码中的准确类型名为 ILocalStrapiSourceProviderOptions,文档正文沿用了旧名 ILocalFileSourceProviderOptions,两者字段一致)只有两个成员,定义见 ILocalStrapiSourceProviderOptions

export interface ILocalStrapiSourceProviderOptions {
  getStrapi(): Core.Strapi | Promise<Core.Strapi>; // return an initialized instance of Strapi
  autoDestroy?: boolean; // shut down the instance returned by getStrapi() at the end of the transfer
}

逐项说明:

  • getStrapi(必填):一个返回已初始化 Strapi 实例的函数(允许异步)。Provider 本身不负责启动 Strapi——实例的创建、配置加载、数据库连接都由调用方完成,Provider 只消费它。这与"谁拥有实例,谁负责实例化"的所有权模型一致。
  • autoDestroy(可选,默认 true):控制传输结束时是否调用 strapi.destroy()。源码在 close() 方法 中的判定是:
async close(): Promise<void> {
  const { autoDestroy } = this.options;
  assertValidStrapi(this.strapi);
  this.strapi.db.lifecycles.enable();
  // Basically `!== false` but more deterministic
  if (autoDestroy === undefined || autoDestroy === true) {
    await this.strapi?.destroy();
  }
}

也就是说,只有显式传 autoDestroy: false 才不销毁实例。单元用例 index.test.ts 的 Close 组 验证了三种取值:undefinedtruedestroy 恰好被调用一次,false 时从未调用。

注意所有权陷阱:如果实例是由你的进程(而非 Provider)创建的,通常应保持同一实例的生命周期由自己管理,此时考虑 autoDestroy: false;而 strapi export CLI 场景下实例由命令本身创建、用完即弃,交由 Provider 自动销毁即可。

生命周期:bootstrap 阶段的关键动作

bootstrap() 是 Transfer Engine 启动传输前调用 Provider 的入口,源码见 bootstrap

async bootstrap(diagnostics?: IDiagnosticReporter): Promise<void> {
  this.#diagnostics = diagnostics;
  this.strapi = await this.options.getStrapi();
  this.strapi.db.lifecycles.disable();
}

两件事值得关注:

  1. 延迟获取实例:只有调用 bootstrapthis.strapi 才被赋值(测试用例 Bootstrap 组 确认了"bootstrap 之前 provider.strapi 未定义")。
  2. 禁用数据库生命周期钩子:读取过程中调用 this.strapi.db.lifecycles.disable(),避免读取触发的查询意外激活业务方定义的 lifecycle 回调;close() 时对称地 enable() 恢复。

Provider 还内置了诊断上报:#reportInfo / #reportWarning / #reportError 会把事件写入 IDiagnosticReporter(origin 标记为 local-source-provider),CLI 侧通过 engine.diagnostics.onDiagnostic(...) 订阅并打印。流读取过程中的错误会经由 #handleStreamError 同时写入 strapi.log.error 和诊断通道,错误消息统一带 [Data transfer] 前缀,便于在混合日志中定位。

五类读取流:数据到底怎么被读出来

Provider 实现了 ISourceProvider 接口,对外暴露五类产出能力,全部基于 strapi.db.queryBuilder(...).stream()流式查询(而非一次性 findAll),这是它能在不撑爆内存的前提下处理大体量数据的关键:

1. 实体流 createEntitiesReadStream

实现位于 entities.ts。逻辑分两层:

  • createEntitiesStream 遍历 strapi.contentTypes 的每一个 UID,逐个构建查询:queryBuilder(uid).select('*').populate(...) 并取 .stream()。其中的 populate 参数由 Entity 查询工具 生成的 query.deepPopulateComponentLikeQuery 提供,用于把"组件形态"(component-like)关联也展开;
  • 任一内容类型的流读取失败时,不会静默丢弃:源码注释明确说明"每一个被跳过的实体都会留下悬空链接",因此通过 options.onWarning 上报 Failed to read all entities of type "<uid>" from the source, the remaining entities of this type were skipped: ... 后继续处理其余类型;
  • createEntitiesTransformStream 再把原始行 { id, ...attributes } 归一为传输格式 { type: <uid>, id, data: attributes }

最终 createEntitiesReadStreamstream-chain 把"多内容类型原始流"与"格式转换流"串联(见 index.ts 中 createEntitiesReadStream)。

2. 关系流 createLinksReadStream

位于 links.ts。它遍历所有内容类型与组件的 UID(strapi.contentTypes + strapi.components),对每个 UID 调用 createLinkQuerygenerateAll 生成器,产出 ILink(左右两端引用)。一个值得注意的健壮性设计:悬空链接(指向已不存在实体的关系)会被跳过并计数,警告使用 createCappedWarningReporter 限量输出,结束后汇总上报 Links export omitted N relation(s) pointing at missing entities...,提示用户导入后核对关系完整性。

3. 配置流 createConfigurationReadStream

位于 configuration.ts。它把两类"应用级配置"打包为 { type, value } 项:

  • Core StorequeryBuilder('strapi::core-store').stream(),并将 JSON 字符串列 value 解析为对象;
  • WebhookqueryBuilder('strapi::webhook').stream()

其中 Core Store 项在导出前还会经过 enrichProjectSettingsForExport 处理(见 project-settings-logos.ts),把项目设置中的 logo 等资产信息一并补全,保证导入端可以还原。

4. Schema 读取 getSchemas / createSchemasReadStream

getSchemas()strapi.contentTypesstrapi.components 合并后,经 schemasToValidJSONmapSchemasValues 处理成合法的 JSON Schema 结构;createSchemasReadStream() 则直接把各 Schema 作为可迭代流输出(见 index.ts)。

5. 资产流 createAssetsReadStream

实现位于 assets.ts,是五类流中边界条件最多的一个:

  • 数据源为 queryBuilder('plugin::upload.file').select('*').stream(),逐条处理上传文件记录;
  • Provider 分支:若 file.provider === 'local',文件路径拼接为 join(strapi.dirs.static.public, file.url) 并用 fs-extracreateReadStream 直读;否则(如 S3、Cloudinary 等)走 signUploadFileForTransfer——当对应上传 Provider 返回私有资源(provider.isPrivate() 为真)时,调用 provider.getSignedUrl 生成签名 URL,再通过 strapi.fetch 流式下载;
  • 文件缺失:统计大小时若捕获 ENOENT,不抛错而是 warnMissingAssetcontinue,警告消息形如 [Data transfer] Media item <id> (hash: <hash>) exists in database but no corresponding file was found to transfer. Path: ...——数据库有记录但磁盘文件丢失的情况被降级为可观察的告警;
  • 格式图(formats):主文件之后逐个遍历 file.formats,每项以 { ...fileFormat, type: format, id: file.id, mainHash: file.hash } 作为元数据单独 yield,从而保留"同一媒体多个变换产物"的从属关系;
  • 产出统一封装为 { metadata, filepath, filename: hash + ext, stream, stats: { size } }IAsset 流(Duplex)。

6. 阶段总量 getStageTotals

getStageTotals(stage) 仅对 assets 阶段返回估算值(委托 estimateAssetTotals),其他阶段返回 null,供引擎计算进度百分比。

谁在使用它:strapi export 与远程 Pull

在 CLI 侧,strapi export 命令是该 Provider 最典型的消费方,见 export 命令 action

const createSourceProvider = (strapi: Core.Strapi) => {
  return createLocalStrapiSourceProvider({
    async getStrapi() {
      return strapi; // 命令先 createStrapiInstance() 启动实例,再闭包返回
    },
  });
};

完整链路为:createStrapiInstance() 启动实例 → 构造 source(本文主角)与 destination(tar/dir 文件 Provider)→ createTransferEngine(source, destination, { versionStrategy: 'ignore', schemaStrategy: 'ignore', exclude/only/throttle/transforms, ... })engine.transfer() → 校验产物并打印结果表。由于导出的目标端没有可比对版本,两个 strategy 均固定为 ignore

此外,远程 Pull 流程的本地端同样复用它:pull.tsthis.provider = createLocalStrapiSourceProvider({...}),即"从远端拉取到本地"时,本地实例既是被读的数据源,也参与流程协商。

手动组合的最小可运行示例

以下示例演示脱离 CLI、在脚本中直接使用该 Provider 并搭配文件目标端,展示选项的正确用法:

import fs from 'fs';
import { engine, file, strapi } from '@strapi/data-transfer';
import { createStrapiInstance } from './bootstrap-strapi'; // 自行封装:加载 config/ 并 await strapi()

const { createTransferEngine } = engine;
const { providers: { createLocalFileDestinationProvider } } = file;
const { providers: { createLocalStrapiSourceProvider } } = strapi;

const source = createLocalStrapiSourceProvider({
  async getStrapi() {
    // 返回一个已初始化(数据库已连接)的 Strapi 实例
    return createStrapiInstance();
  },
  // autoDestroy 省略即默认 true:传输结束后自动销毁上面创建的实例
});

const destination = createLocalFileDestinationProvider({
  file: { path: 'backup.tar', maxSizeJsonl: 100 * 1024 * 1024 }, // 单个 jsonl 文件上限 100MB
  compression: { enabled: false },
  encryption: { enabled: false },
});

const transferEngine = createTransferEngine(source, destination);
const results = await transferEngine.transfer();
console.log(results);

要点提示:

  • getStrapi 的返回值必须是完成初始化的实例(含数据库连接),否则 bootstrap 后首次查询即失败;
  • 若实例由外部常驻进程管理(例如在长驻服务内做增量导出),应显式传 autoDestroy: false,避免 Provider 提前 destroy()
  • 警告(缺失文件、悬空链接等)会进入诊断通道,生产脚本建议订阅 transferEngine.diagnostics 落盘,与 export 命令中 engine.diagnostics.onDiagnostic(formatDiagnostic(...)) 的用法一致。

小结

Local Strapi Source Provider 是 Strapi 数据迁移体系的"本地数据出口":

  1. 所有权清晰——getStrapi 只要求"给我一个已初始化的实例",实例生命周期默认随传输结束销毁(autoDestroy 显式为 false 除外),并有单测钉死三种取值的行为;
  2. 全流式读取——实体、关系、配置、Schema、资产全部通过 queryBuilder(...).stream() 增量产出,配合 deepPopulateComponentLikeQuery 与 formats 从属关系处理,覆盖 Strapi 数据模型的主要面;
  3. 失败可观察——读流错误、悬空链接、磁盘文件缺失都有带 [Data transfer] 前缀的告警路径,而非静默失败;
  4. 多入口复用——同一 Provider 同时服务于 strapi export 命令行与远程 Pull 流程的本地侧。

相关源码入口:Provider 主体实体流关系流配置流资产流Provider 测试export 命令

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

项目优选

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