首页
/ Strapi 数据迁移:Local Strapi Destination Provider 的配置选项、Restore 策略与回滚机制详解

Strapi 数据迁移:Local Strapi Destination Provider 的配置选项、Restore 策略与回滚机制详解

2026-09-04 17:30:37作者:傅爽业Veleda

本文基于 Strapi 官方文档 Local Strapi Destination Provider 及其底层源码,详解 data-transfer 引擎中“本地 Strapi 目标提供者”的完整用法:它如何通过 Entity Service 与 Query Engine 向一个已初始化的 Strapi 实例写入数据、restore 冲突策略的每个选项如何工作,以及出错时数据库事务回滚与媒体文件备份恢复是如何协同保障数据安全的。读完后你能够独立编写迁移脚本、正确配置 restore 选项,并理解迁移失败时备份目录 uploads_backup_{timestamp} 的处置方式。

定位:把本地 Strapi 实例作为迁移目标

Local Strapi Destination Provider 是 Strapi data-transfer(数据迁移)引擎中的一个目标端(destination)提供者,其核心职责正如文档所述:

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

即通过 Entity Service(实体服务)与 Query Engine(查询引擎)把源端数据逐条插入到一个已经初始化完成的 Strapi 实例中。结合本地提供者的总览文档 00-overview.md 可知:创建本地 Strapi 数据提供者必须传入一个可用的 strapi 服务端对象,交互所依赖的就是该实例的 Entity Service 与 Query Engine。因此有一个硬性前提——如果本地 Strapi 项目无法启动(例如启动报错),该提供者就无法使用

从源码结构看,该提供者的完整实现位于 local-destination/index.ts,注册名为 destination::local-strapiindex.ts#L32-L33)。

Provider Options:四个核心选项

文档指出接受选项的类型定义为 ILocalFileSourceProviderOptions,对应的源码实现是 ILocalStrapiDestinationProviderOptionsindex.ts#L22-L30):

getStrapi(): Strapi.Strapi | Promise<Strapi.Strapi>; // return an initialized instance of Strapi
autoDestroy?: boolean; // shut down the instance returned by getStrapi() at the end of the transfer
restore?: restore.IRestoreOptions; // the options to use when strategy is 'restore'
strategy: 'restore'; // conflict management strategy; only the restore strategy is available at this time

逐项说明如下:

选项 必填 说明
getStrapi 返回一个已初始化的 Strapi 实例(可同步、可异步)。提供者 bootstrap 时会调用它获取实例(index.ts#L60-L69),取不到则抛出 ProviderInitializationError('Could not access local strapi')
autoDestroy 迁移结束后是否自动关闭(destroy)getStrapi() 返回的实例。默认行为是关闭:源码中判断为 `autoDestroy === undefined
restore 策略为 restore 时必填 冲突为 restore 时在迁移前清空目标数据所使用的选项,详见下文
strategy 冲突管理策略。当前只支持 "restore",源码中 VALID_CONFLICT_STRATEGIES = ['restore']DEFAULT_CONFLICT_STRATEGY = 'restore'index.ts#L19-L20

两点使用注意事项,来自文档与源码的共同印证:

  1. strategyrestore 的强校验bootstrap 阶段的 #validateOptions() 会做两件事:策略不在 VALID_CONFLICT_STRATEGIES 中时抛出 ProviderValidationError;策略为 restore 但未提供 restore 选项时抛出 Missing restore optionsindex.ts#L109-L123)。
  2. autoDestroy 的场景化取值。总览文档给出的建议是:通过外部脚本跑迁移时,建议 autoDestroy: true(或不设置)以保证实例被正确关闭;但如果你在正在运行的 Strapi 实例内部执行迁移,必须设置 autoDestroy: false,否则迁移结束时你的 Strapi 实例会被一并销毁。

此外源码中还暴露了一个文档未提及的可选回调 onTransferPhase?: (message: string) => voidindex.ts#L28-L29),用于在 beforeTransfer(restore 准备阶段)向 CLI/UI 上报人类可读的进度,例如 Local: backing up existing upload folder…Local: deleting existing media files from disk… 等。

Restore 策略:先清空、再写入

strategy: 'restore' 的含义在文档中非常明确:

A conflict management strategy of "restore" deletes all existing Strapi data before a transfer to avoid any conflicts.

在迁移开始前删除目标端已有的 Strapi 数据,从根本上避免主键/关联冲突。restore 选项的完整接口(源码定义见 strategies/restore/index.ts#L6-L18):

export interface IRestoreOptions {
  assets?: boolean; // delete media library files before transfer
  configuration?: {
    webhook?: boolean; // delete webhooks before transfer
    coreStore?: boolean; // delete core store before transfer
  };
  entities?: {
    include?: string[]; // only delete these stage entities before transfer
    exclude?: string[]; // exclude these stage entities from deletion
    filters?: ((contentType: ContentTypeSchema) => boolean)[]; // custom filters to exclude a content type from deletion
    params?: { [uid: string]: unknown }; // params object passed to deleteMany before transfer for custom deletions
  };
}

assets:是否清理媒体库文件

assets 同时控制三件事,源码中统一由 #areAssetsIncluded() 读取 this.options.restore?.assets 来判断(index.ts#L72-L74):

  • 是否把现有 public/uploads 目录备份到 uploads_backup_{timestamp}(见下文回滚一节);
  • 是否通过查询 plugin::upload.file 记录、并调用 strapi.plugin('upload').provider.delete(file) 逐个删除磁盘上的媒体文件(包括 formats 衍生图,见 #deleteAllAssetsindex.ts#L134-L164);
  • 是否允许资产写入流工作。若未开启 assets 却尝试传输资产,createAssetsWriteStream() 会直接抛出 ProviderTransferError: Attempting to transfer assets when 'assets' is not set in restore optionsindex.ts#L316-L323)。

从源码结构看,只有当 upload 插件的 provider 配置为 local 时,备份/清理逻辑才会真正执行(index.ts#L261index.ts#L303)。

configuration.webhook / configuration.coreStore:清理全局配置数据

这两个布尔值控制是否删除 Webhook 与核心存储(core store)中的既有记录。对应实现 deleteConfigurationRecords 有一个值得注意的细节:两者默认值均为 truestrategies/restore/index.ts#L114-L143):

const { coreStore = true, webhook = true } = options?.configuration ?? {};

也就是说,如果你完全不传 configuration,Webhook 与 core-store 仍会被清空;若想保留,必须显式传 configuration: { webhook: false, coreStore: false }

entities.include / exclude / filters / params:精细化控制实体删除范围

实体清理逻辑在 deleteEntitiesRecords 中实现(strategies/restore/index.ts#L36-L112),源码注释与逻辑共同界定了各字段语义:

  • include(白名单):注释写明 "include means 'only include these types'"——一旦设置了 include只有列出的内容类型(及模型)会被删除,其余全部保留。
  • exclude(黑名单):被列出的 uid 免于删除;"not being excluded doesn't mean it's kept",即未设置 exclude 时默认删除所有。
  • filters(自定义过滤器):一组 (contentType: ContentTypeSchema) => boolean 函数,所有过滤器都返回 true 时该内容类型才会被删除(源码用 entities.filters.every((filter) => filter(contentType)) 实现),适合按 schema 特征批量排除某类内容类型。
  • params(按 uid 的自定义删除参数){ [uid: string]: unknown } 对象,会在 deleteMany 调用时透传,用于对特定内容类型施加自定义删除条件。

执行细节上,内容类型走 Entity Service 的 contentTypeQuery(uid).deleteMany(entities?.params),而模型(models)直接走 strapi.db.query(uid).deleteMany({}),所有类型并行删除(Promise.all)并汇总每个 uid 的删除计数。

数据写入流:实体、链接、配置与资产

清空之后,引擎会创建四类写入流(Writable stream)把源端数据逐条灌入目标实例。以实体写入流为例,restore 策略的实现位于 strategies/restore/entities.ts

const created = await create({
  data,
  populate: getDeepPopulateComponentLikeQuery(contentType, { select: 'id' }),
  select: 'id',
});
updateMappingTable(type, id, created.id);

每条实体在创建后会调用 updateMappingTable(type, oldID, newID),把源端 ID 映射到目标端新生成的 ID,并递归登记组件实例的 ID 映射(collectComponentIdMappings)。这张映射表随后被链接写入流(createLinksWriteStream 中的 mapID)用于重写实体间引用,保证关联指向新库中的真实记录。这是迁移后外键与组件引用不出现悬空 ID 的关键机制(index.ts#L352-L370)。

配置写入流同理,restoreConfigs 会把 core-store 与 webhook 记录重新创建,且 core-store 的恢复会经过 restoreProjectSettingsRow 特殊处理项目设置行(logo 等)(configuration.ts#L31-L39)。

整个 beforeTransfer 阶段的执行顺序在 index.ts#L172-L197 中一目了然:

  1. 事务挂载(transaction.attach);
  2. assets 开启:备份 public/uploads 到备份目录;
  3. assets 开启:流式读取 plugin::upload.file 记录并删除磁盘上的媒体文件;
  4. restore 选项删除实体与配置数据。

回滚机制:数据库事务 + 媒体文件备份

这是文档中技术含量最高的一节。Local Strapi Destination Provider 在出错时自动提供回滚机制,文档将其分为两条路径,源码均可一一对应验证:

数据库回滚:包裹 restore 与插入的事务

"对 Strapi 数据,是通过一个包裹 restore 和数据插入的数据库事务实现的,成功则提交,失败则回滚。" 源码实现分两层:

  • 事务抽象层:bootstrap 中通过 utils.transaction.createTransaction(this.strapi) 创建一个事务对象(index.ts#L68)。该实现基于 strapi.db.transaction 开启一个长事务,内部用 EventEmitter 维护一个回调队列——任何阶段(restore、实体写入、链接写入、配置写入)都通过 transaction.attach(cb) 把自己的操作挂入同一事务;end() 关闭队列后事务正常提交,rollback() 则触发底层 rollback(utils/transaction.ts#L7-L98)。
  • 提供者层:rollback() 方法直接执行 this.transaction?.rollback()index.ts#L166-L170)。

此外,bootstrap 时还会先 strapi.db.lifecycles.disable()closeenable()index.ts#L67index.ts#L102),确保批量迁移期间不被生命周期钩子干扰。

媒体文件回滚:uploads_backup_{timestamp} 备份目录

媒体文件不在数据库事务的保护范围内,文档给出的方案在源码中完整可见:

  • 备份#handleAssetsBackup() 仅在 assets 开启且 upload provider 为 local 时执行。它先检查 public/uploads 及其父目录的读/写/存在权限,然后把整个 uploads 目录移动到 public/uploads_backup_{timestamp}(目录名在构造函数中用 uploads_backup_${Date.now()} 生成,index.ts#L57),并重新创建空 uploads 目录、写入 .gitkeep 占位(index.ts#L253-L294)。权限不足时会抛出 ASSETS_DIRECTORY_ERR 错误。
  • 成功:迁移成功后 #removeAssetsBackup()fse.rm(backupDirectory, { recursive: true, force: true }) 删除备份目录(index.ts#L296-L313)。
  • 失败:删除本次导入的文件并把备份目录还原回 uploads。文档同时明确警告:某些失败情况下备份可能无法自动还原,需要你手动把备份中的资产文件恢复回去——这是运维时需要记住的兜底手段。

只读 uploads 环境的限制

文档末尾的注意事项值得所有部署者留意:

Because of the need for write access, environments without filesystem permissions to move the assets folder (common for virtual environments where /uploads is mounted as a read-only drive) will be unable to include assets in a transfer and the asset stage must be excluded in order to run the transfer.

翻译过来即:由于备份机制需要移动整个 uploads 目录的写权限,/uploads 以只读方式挂载的环境(常见的虚拟化部署)中,无法将资产纳入迁移,此时必须在迁移配置中排除 asset 阶段(也就是不启用 restore.assets)才能跑通迁移。

实践要点小结

  1. 使用本地目标提供者前,确保目标 Strapi 项目能正常启动并提供 getStrapi 回调;这是所有操作的硬依赖。
  2. strategy 只能是 'restore',且必须同时提供 restore 选项,否则 bootstrap 校验阶段即会失败。
  3. entities.include / exclude / filters / params 精确控制清理范围;记住 include 一旦设置就是“只删这些”的白名单语义。
  4. configuration 不传时 webhook 与 core-store 默认都会被清空,需要保留时显式设为 false
  5. 是否在 Strapi 实例内部执行迁移,决定 autoDestroy 的取值:外部脚本用 true,实例内部必须 false
  6. 失败后先查数据库是否已整体回滚;若 assets 开启且备份还原失败,检查 public/uploads_backup_{timestamp} 目录并手动恢复。
  7. 只读 uploads 挂载的环境必须排除资产阶段。

相关测试用例可进一步印证上述行为,例如 restore.test.tsrestore-entities.test.tsassets.test.tsassets-destination-writable.test.ts,覆盖了实体清理范围、资产备份与写入流等关键路径。

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

项目优选

收起
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
980
502
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384