ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Strapi Local Strapi 数据迁移 Provider 深度解析:autoDestroy 生命周期、restore 冲突策略与回滚机制

2026/9/7 14:37:15 拓冰建站 浏览量
Strapi Local Strapi 数据迁移 Provider 深度解析:autoDestroy 生命周期、restore 冲突策略与回滚机制 Strapi Local Strapi 数据迁移 Provider 深度解析autoDestroy 生命周期、restore 冲突策略与回滚机制【免费下载链接】strapi Strapi is the leading open-source headless CMS. It’s 100% JavaScript/TypeScript, fully customizable, and developer-first.项目地址: https://gitcode.com/GitHub_Trending/st/strapiStrapi 的数据传输引擎data-transfer支持多种 Provider 类型其中Local Strapi Provider允许直接将运行数据传输引擎的当前 Strapi 实例本身作为数据源Source或写入目标Destination通过该实例的 Entity Service 与 Query Engine 完成实体的读取与写入。本文基于仓库中 Local Strapi Providers 概览文档 及其配套的 Source 文档、Destination 文档结合packages/core/data-transfer中的源码实现讲解 Provider 的适用前提、autoDestroy生命周期控制、restore 冲突策略的完整参数以及数据与媒体文件的回滚机制。Local Strapi Provider 的定位与适用前提在 数据迁移 Provider 总览 中Strapi 为数据迁移提供了三类内置 ProviderStrapi file标准化文件格式用于跨环境传输Local Strapi连接本地 Strapi 项目直接使用其配置的数据库连接管理数据Remote Strapi对 Local Provider 的封装为远程网络运行的 Strapi 实例附加 WebSocket 接口。Local Strapi Provider 的核心约束是必须传入一个已经初始化完成的strapi服务对象。因为 Provider 需要与该实例的 Entity Service 和 Query Engine 交互来管理数据所以如果本地 Strapi 项目本身无法启动例如存在配置错误或数据库连接失败Local Provider 也就无法使用。从源码结构看这一前提在 strapi/providers/index.ts 的工厂函数出口得到体现createLocalStrapiSourceProvider与createLocalStrapiDestinationProvider分别位于 local-source/index.ts 和 local-destination/index.ts两者都要求通过getStrapi()回调获取实例并在每次流操作前调用assertValidStrapi校验实例有效性。此外需要留意Providers 总览 指出目前所有 Provider 的资产assets传输仅处理本地媒体资产/upload文件夹Provider 媒体传输仍在开发中因此与资产传输相关的一切——包括 restore 策略、资产回滚——目前都被标记为unstable近期可能发生变化。autoDestroy决定实例生命周期的关键选项概览文档中最重要的注意事项是autoDestroy选项当一次 transfer 完成时传入的strapi对象会根据autoDestroy选项被自动关闭shutdown。如果是通过外部脚本运行迁移建议使用autoDestroy: true以确保实例被正确关闭如果迁移是在一个正在运行的 Strapi 实例内部发起的则应设置autoDestroy: false否则你的 Strapi 实例会在迁移结束时被终止。源码实现印证了这一点。Source Provider 的close()方法见 local-source/index.ts逻辑如下async close(): Promisevoid { 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(); // 销毁 Strapi 实例 } }两个关键细节默认行为是销毁判断条件是autoDestroy undefined || autoDestroy true即只有显式传入autoDestroy: false才能阻止实例被销毁。这意味着外部脚本不传该参数时实例会被自动关闭而在运行中的实例内部发起迁移例如由 Admin 面板或自定义控制器触发时必须显式传入autoDestroy: false否则迁移结束后整个 Strapi 进程会被destroy()。数据库生命周期钩子成对恢复Provider 在bootstrap()阶段调用this.strapi.db.lifecycles.disable()禁用数据库生命周期避免迁移期间触发自定义钩子并在close()中先调用this.strapi.db.lifecycles.enable()恢复再根据autoDestroy决定是否销毁实例。Destination Provider 的close()还额外执行了this.transaction?.end()来结束数据库事务见 local-destination/index.ts。Source Provider 与 Destination Provider 共享同一套选项定义模式getStrapiautoDestroy但 Destination 额外提供了restore与strategy选项。Source Provider从本地实例读取四个阶段的流Source 文档 说明该 Provider 通过已初始化实例的 Entity Service 与 Query Engine 获取数据其选项接口为源码中命名为ILocalStrapiSourceProviderOptionsgetStrapi(): Core.Strapi | PromiseCore.Strapi; // 返回一个已初始化的 Strapi 实例 autoDestroy?: boolean; // transfer 结束时关闭 getStrapi() 返回的实例从 local-source/index.ts 的完整实现看该 Provider 实现了ISourceProvider接口name为source::local-strapi为数据传输的每个阶段stage提供对应的可读流方法用途实现getMetadata()返回元数据创建时间、Strapi 版本版本取自config.get(info.strapi)直接读取实例配置createEntitiesReadStream()实体数据读取流createEntitiesStreamcreateEntitiesTransformStream通过stream-chain链式组合createLinksReadStream()关联关系links读取流createLinksStreamcreateConfigurationReadStream()配置数据读取流createConfigurationStreamgetSchemas()/createSchemasReadStream()内容类型与组件的 Schema合并strapi.contentTypes与strapi.components经schemasToValidJSON清洗createAssetsReadStream()媒体文件读取流createAssetsStream并绑定error事件上报诊断信息getStageTotals(stage)仅对assets阶段返回数量/大小估算estimateAssetTotals(strapi)值得注意的是Source Provider 内建了诊断上报机制bootstrap可接收IDiagnosticReporter流错误会通过#handleStreamError同时写入strapi.log.error和诊断报告kind 为info/warning/error便于外部脚本或 CLI 收集迁移过程中的告警信息。实体读取流的具体查询实现位于 local-source/entities.ts媒体文件流在 local-source/assets.ts配置流在 local-source/configuration.ts。该 Provider 也是 Strapi 内置 CLI 迁移命令的底层依赖export 命令、import 命令 和 transfer 命令 都通过工厂函数创建 Local Provider 来对接本地实例。Destination Providerrestore 冲突策略与完整参数说明Destination 文档 说明该 Provider 通过 Entity Service 与 Query Engine 向已初始化实例插入数据其选项如下getStrapi(): Strapi.Strapi | PromiseStrapi.Strapi; // 返回一个已初始化的 Strapi 实例 autoDestroy?: boolean; // transfer 结束时关闭 getStrapi() 返回的实例 restore?: restore.IRestoreOptions; // strategy 为 restore 时使用的选项 strategy: restore; // 冲突管理策略目前仅提供 restore 策略源码中的实际接口ILocalStrapiDestinationProviderOptionslocal-destination/index.ts还包含一个供 CLI / UI 显示进度的回调onTransferPhase?: (message: string) void。strategy 校验目前只支持 restorestrategy定义冲突管理策略。当前可用值只有一个——restore这在源码中被显式约束export const VALID_CONFLICT_STRATEGIES [restore]; export const DEFAULT_CONFLICT_STRATEGY restore;bootstrap()会先执行#validateOptions()若strategy不在白名单内抛出ProviderValidationError当strategy restore而未提供restore选项时也会抛出 Missing restore options 校验错误。也就是说destination 侧的restore配置对象在实践中是必填的。IRestoreOptions 完整参数restore 策略的语义是在迁移开始前删除目标实例中的现有 Strapi 数据以避免冲突。可用的 restore 选项定义在 restore/index.tsexport interface IRestoreOptions { assets?: boolean; // 迁移前删除媒体库文件 configuration?: { webhook?: boolean; // 迁移前删除 webhooks coreStore?: boolean; // 迁移前删除 core store }; entities?: { include?: string[]; // 仅删除这些 stage 实体 exclude?: string[]; // 将这些 stage 实体排除在删除之外 filters?: ((contentType: Struct.ContentTypeSchema) boolean)[]; // 自定义过滤器排除某内容类型 params?: { [uid: string]: unknown }; // 传给 deleteMany 的自定义删除参数 }; }各参数的源码行为assets开启后迁移前会先备份public/uploads目录再通过strapi.db.queryBuilder(plugin::upload.file)流式遍历所有文件记录逐个调用strapi.plugin(upload).provider.delete(file)删除磁盘上的媒体文件包括formats中记录的每种格式变体见 local-destination/index.ts 的#deleteAllAssets。configuration.coreStore/configuration.webhook对应删除strapi::core-store与strapi::webhook两个模型下的全部记录。从源码结构看这两项默认为trueconst { coreStore true, webhook true } options?.configuration ?? {}即不传configuration时会清空 core store 与 webhooks。entities.include语义是只删除清单内的类型——不在include列表中的内容类型/模型不会被删除。entities.exclude将指定类型排除在删除范围外。entities.filters对每个内容类型执行所有过滤器函数必须全部返回true才会被删除源码使用filters.every((filter) filter(contentType))适合按 Schema 特征如是否属于某个插件批量排除。entities.params按 uid 传入deleteMany的查询参数实现带条件的自定义删除例如只删除某个时间之前的记录。删除逻辑的完整实现见 deleteRecords它先根据 include/exclude/filters 筛出待清理的内容类型通过 Entity Service 的contentTypeQuery(uid).deleteMany(entities?.params)删除再筛出插件模型如strapi::core-store之外的model.uid通过strapi.db.query(uid).deleteMany({})直接走 Query Engine 删除并对每个 uid 聚合删除数量返回{ count, entities, configuration }统计结果。写入流与 ID 映射Destination Provider 的createEntitiesWriteStream()在 restore 策略下调用restore.createEntitiesWriteStream({ strapi, updateMappingTable, transaction })并在实体写入过程中维护一张实体 ID 映射表#entitiesMapper旧 ID → 新 ID。由于 restore 会先清空数据、重建后自增 ID 可能与源实例不同这张映射表随后被用于createLinksWriteStream()通过mapID (uid, id) this.#entitiesMapper[uid]?.[id]把源端的关联 ID 换算为目标端新 IDcreateAssetsWriteStream()通过resolveUploadFileId: (metadata) fileEntitiesMapper?.[metadata.id]解析plugin::upload.file记录的新 ID把媒体实体与内容实体正确关联起来。相关写流实现位于 strategies/restore/entities.ts、strategies/restore/links.ts 和 strategies/restore/configuration.ts。回滚机制数据库事务 媒体文件备份目录Destination 文档 指出该 Provider 在出错时自动提供回滚机制其实现分为两条线数据库数据事务回滚bootstrap()阶段通过utils.transaction.createTransaction(this.strapi)创建一个事务句柄见 utils/transaction.ts。beforeTransfer()把整个 restore 准备流程包在transaction.attach(async (trx) {...})中备份资产 → 删除媒体记录 → 按 restore 选项清空数据库内容随后实体插入也挂在同一事务上。成功时提交失败时调用rollback()执行transaction.rollback()见 local-destination/index.ts保证清空旧数据 写入新数据是原子操作。媒体文件uploads_backup_{timestamp} 目录数据库事务管不到磁盘文件因此媒体文件采用临时搬移策略实现见#handleAssetsBackuplocal-destination/index.ts仅当restore.assets为真且 upload 插件的 provider 为local时才执行S3、Cloudinary 等远程 provider 不涉及本地文件先通过fs.access检查uploads目录及父目录的读写权限再执行fse.move(uploads, uploads_backup_{timestamp})然后重建空的uploads目录并写入.gitkeep迁移成功后调用#removeAssetsBackup()删除备份目录迁移失败时删除本次导入的文件并把备份目录恢复原位。文档同时警告在某些失败场景下可能无法自动把备份文件移回原位例如进程中途崩溃此时需要人工把uploads_backup_{timestamp}目录中的文件恢复到public/uploads。文件系统权限限制Destination 文档特别注明回滚备份需要写权限。对于/uploads以只读驱动器挂载的虚拟环境较常见的部署形态Provider 无法搬移资产目录此时必须把 asset stage 从迁移中排除才能执行迁移。源码中这一点体现为备份目录创建失败时会抛出带错误码ASSETS_DIRECTORY_ERR的ProviderTransferError提示请确认 Strapi 对 public 目录具有写权限而createAssetsWriteStream()在restore.assets未设置时会直接抛出 Attempting to transfer assets whenassetsis not set in restore options。一个典型的本地迁移选项组合综合上述文档与源码外部脚本中初始化 destination 侧 Local Provider 时的选项形态大致如下具体调用以 CLI transfer 命令实现 为参考import { createLocalStrapiSourceProvider, createLocalStrapiDestinationProvider, } from strapi/data-transfer; // 外部脚本场景脚本自己启动目标实例结束后自动销毁 const destination createLocalStrapiDestinationProvider({ getStrapi: () startMyStrapiInstance(), autoDestroy: true, // 外部脚本推荐 true strategy: restore, restore: { assets: true, // 允许迁移媒体文件需要 public/uploads 可写 configuration: { webhook: true, // 迁移前清空 webhooks源码默认即 true coreStore: true, // 迁移前清空 core store源码默认即 true }, entities: { exclude: [admin::user], // 示例保留管理员账号不随 restore 删除 }, }, }); // 运行中的实例内部发起迁移必须显式 false避免实例被 destroy() const source createLocalStrapiSourceProvider({ getStrapi: () this.app, // 当前运行实例 autoDestroy: false, });要点回顾strategy只能是restore且restore选项必填否则bootstrap阶段抛ProviderValidationErrorentities.include是白名单只删语义entities.exclude是从删除中排除语义二者可组合filters用于基于 Schema 的动态排除外部脚本传autoDestroy: true或省略运行中实例内部发起迁移必须传autoDestroy: false涉及资产迁移的环境必须保证public/uploads可写且要理解备份目录的命名与手动恢复路径资产迁移含备份与回滚目前属于unstable能力升级 Strapi 版本后应重新核对行为。相关文档与源码入口概览与前提docs/docs/docs/01-core/data-transfer/02-providers/04-local-strapi/00-overview.mdSource Provider 选项docs/docs/docs/01-core/data-transfer/02-providers/04-local-strapi/01-source.mdDestination Provider 与 restore 选项docs/docs/docs/01-core/data-transfer/02-providers/04-local-strapi/02-destination.mdProvider 体系介绍含自建 Provider 的接口位置docs/docs/docs/01-core/data-transfer/02-providers/00-overview.mdSource 实现packages/core/data-transfer/src/strapi/providers/local-source/index.tsDestination 实现packages/core/data-transfer/src/strapi/providers/local-destination/index.tsrestore 策略实现含 IRestoreOptions 与 deleteRecordspackages/core/data-transfer/src/strapi/providers/local-destination/strategies/restore/index.ts事务工具packages/core/data-transfer/src/utils/transaction.ts单元测试local-source 测试、local-destination 测试、restore 行为测试、资产回滚相关测试【免费下载链接】strapi Strapi is the leading open-source headless CMS. It’s 100% JavaScript/TypeScript, fully customizable, and developer-first.项目地址: https://gitcode.com/GitHub_Trending/st/strapi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考