首页
/ Strapi 文件目标 Provider 详解:导出 Strapi Data File 的选项、加密压缩与底层实现原理

Strapi 文件目标 Provider 详解:导出 Strapi Data File 的选项、加密压缩与底层实现原理

2026-09-04 19:32:43作者:吴年前Myrtle

本文围绕 Strapi 数据迁移(Data Transfer)体系中的 Strapi File Destination Provider 展开:它负责把源端数据写成标准的 Strapi Data File(tar 归档),并支持可选的 gzip 压缩与 AES 加密。通过本文,你将完整掌握 ILocalFileDestinationProviderOptions 中每一个选项的含义与默认行为,理解文件命名、JSONL 分片、加密管道等底层实现机制,并能结合 strapi export CLI 将该 Provider 落地到实际的数据导出场景中。

一、Strapi File Destination Provider 是做什么的

在 Strapi 的数据传输引擎中,Destination Provider(目标端 Provider)是数据流向的终点。Strapi File Destination Provider(源码类名 LocalFileDestinationProvider,Provider 名称为 destination::local-file)的职责是:输出一个 Strapi Data File——即一个可选地经过 gzip 压缩、AES 加密的 .tar 归档文件,归档内部使用 POSIX 风格路径存放配置、实体、链接、Schema 与资产数据。

归档的具体内部结构(metadata.jsonconfigurationentitieslinksschemas 等目录下的顺序编号 .jsonl 文件)在 Strapi File Structure 文档 中有完整描述;读取这类文件的 Source 端对应 Strapi File Source Provider 文档

实现入口位于 目标 Provider 源码

export const createLocalFileDestinationProvider = (
  options: ILocalFileDestinationProviderOptions
) => {
  return new LocalFileDestinationProvider(options);
};

class LocalFileDestinationProvider implements IDestinationProvider {
  name = 'destination::local-file';
  type: ProviderType = 'destination';
  ...
}

关键特性:不校验 Schema 与版本

原文档明确指出:该 Destination Provider 不提供 schema 或 metadata 的校验能力,因此永远不会报告 schema 匹配错误(schema match error)或版本校验错误(version validation error)

这一点可以从源码中得到双重印证:

  1. LocalFileDestinationProvidergetMetadata() 直接返回 null(见 源码 L168-L170),即它不会向引擎提供自身用于校验的元数据;
  2. 在 CLI 层,导出命令创建传输引擎时显式传入 versionStrategy: 'ignore'schemaStrategy: 'ignore',注释写明“导出到文件时,versionStrategy 与 schemaStrategy 总是被跳过”(见 export 命令实现):
const engine = engineDataTransfer.createTransferEngine(source, destination, {
  versionStrategy: 'ignore', // for an export to file, versionStrategy will always be skipped
  schemaStrategy: 'ignore', // for an export to file, schemaStrategy will always be skipped
  ...
});

这意味着:文件目标端只负责“原样落盘”,数据的结构兼容性判断交给导入侧(Source Provider 与目标系统)处理。这是理解该 Provider 行为边界的重要前提。

二、Provider Options 完整说明

ILocalFileDestinationProviderOptions 定义了该 Provider 接受的全部选项,接口定义见 源码 L22-L37

export interface ILocalFileDestinationProviderOptions {
  encryption: {
    enabled: boolean; // if the file should be encrypted
    key?: string; // the key to use when encryption.enabled is true
  };

  compression: {
    enabled: boolean; // if the file should be compressed with gzip
  };

  file: {
    path: string; // the filename to create
    maxSize?: number; // the max size of a single backup file
    maxSizeJsonl?: number; // the max lines of each jsonl file before creating the next file
  };
}

各选项的作用与实现行为如下:

选项 类型 默认行为 作用
file.path string(必填) 无默认值 目标文件名(基础路径)。最终产物会在其后自动追加 .tar.gz.enc 后缀
file.maxSize number? 可选 单个备份文件的最大尺寸(字节)
file.maxSizeJsonl number? 无显式值时回落到分片器默认值 单个 .jsonl 文件达到该字节数后滚动创建下一个文件
compression.enabled boolean 由调用方决定 是否用 gzip 压缩整个归档
encryption.enabled boolean 由调用方决定 是否对归档加密
encryption.key string? encryption.enabledtrue 时必填 加密口令;缺失时 bootstrap 阶段直接抛错

最终文件名的生成规则

file.path 只是基础名,真正写盘的归档路径由 #archivePath getter 按固定规则拼接(见 源码 L82-L96):

get #archivePath() {
  const { encryption, compression, file } = this.options;

  let filePath = `${file.path}.tar`;

  if (compression.enabled) {
    filePath += '.gz';
  }

  if (encryption.enabled) {
    filePath += '.enc';
  }

  return filePath;
}

后缀追加顺序是 .tar.gz.enc,即压缩开启且加密开启时,最终产物形如 backup.tar.gz.enc。这与 Source 侧“根据文件扩展名(.gz / .enc)推断是否需要解压/解密”的约定(见 Source 文档)正好互为镜像。

三、bootstrap 管道:tar、gzip 与加密如何串联

Provider 初始化发生在 bootstrap(diagnostics) 中(见 源码 L109-L143),它完成了四件事:

  1. 加密前置校验encryption.enabledtrue 但未提供 key 时立即抛出 Can't encrypt without a key,避免运行到一半才失败;
  2. 创建 tar 打包流:使用 tar-streamtar.pack() 作为归档核心流;
  3. 创建磁盘输出流fs-extracreateWriteStream 写向 #archivePath,并对 ENOSPC(磁盘空间不足)错误做了专门的语义转换,抛出 ProviderTransferError("Your server doesn't have space to proceed with the import."),让上层能给出更明确的错误提示;
  4. 按序串联转换管道:用 stream-chain 组装 tar → gzip? → cipher? → 磁盘 的管道。
const archiveTransforms: Stream[] = [];

if (compression.enabled) {
  archiveTransforms.push(this.createGzip());
}

if (encryption.enabled && encryption.key) {
  archiveTransforms.push(createEncryptionCipher(encryption.key));
}

this.#archive.pipeline = chain([this.#archive.stream, ...archiveTransforms, outStream]);

从管道顺序可以看出数据流方向:先 tar 打包,再 gzip 压缩,最后加密(加密作用于已压缩的字节流)。这一顺序与 #archivePath.gz 先于 .enc 的后缀顺序一致,也意味着导入侧必须按相反顺序(先解密、再解压)还原数据。

同时 bootstrap 会把即将生成的归档路径写入 results.file.path,供引擎在转移结束后回传结果。

四、加密与压缩的实现细节

加密:scrypt 派生密钥 + AES-128-ECB

加密转换由 createEncryptionCipher 提供(见 encryption 工具)。Destination Provider 调用它时只传了密钥,未指定算法,因此走默认分支 'aes-128-ecb'(与 Strapi File Structure 文档 中“文件可选使用 'aes-128-ecb' 加密”的表述一致)。

策略实现(见 源码 L8-L37):

'aes-128-ecb'(key: string): Cipheriv {
  const hashedKey = scryptSync(key, '', 16);
  const initVector: BinaryLike | null = null;
  const securityKey: CipherKey = hashedKey;
  return createCipheriv(algorithm, securityKey, initVector);
},

可以注意到两点实现事实:

  • 用户提供的 key(本质是口令)不会直接用作密钥,而是先经过 scryptSync 哈希派生出 16 字节的安全密钥;
  • aes-128-ecb 分支使用空 IV,同一密钥下相同明文块会产生相同密文块——这是该算法模式的固有特性,由源码结构看属于 Strapi 为保证不同版本/实现间加密结果可互读而做出的选择。工具函数还支持 aes128/aes192/aes256(CBC 模式,IV 取自派生密钥的后半段),但文件 Provider 默认未启用这些分支。

压缩:Node.js 原生 zlib

压缩分支仅一行 zlib.createGzip()(见 源码 L104-L107),即标准 gzip 流,无额外参数。

五、数据如何写入归档:各阶段流与 JSONL 分片

引擎在转移过程中会通过 Provider 的方法获取各阶段的写入流。该 Provider 提供了五类写入流,全部以 POSIX 路径写入 tar(“always write tar files with posix paths”,保证跨系统路径一致性):

方法 归档内目标路径 用途
createSchemasWriteStream() schemas/schemas_NNNNN.jsonl Schema 数据
createEntitiesWriteStream() entities/entities_NNNNN.jsonl 实体记录
createLinksWriteStream() links/links_NNNNN.jsonl 关联链接
createConfigurationWriteStream() configuration/configuration_NNNNN.jsonl 配置数据
createAssetsWriteStream() assets/uploads/<filename> + assets/metadata/<filename>.json 资产二进制与其元数据

前四者的实现完全同构(以 entities 为例,见 源码 L212-L226):

createEntitiesWriteStream(): Writable {
  if (!this.#archive.stream) {
    throw new Error('Archive stream is unavailable');
  }
  this.#reportInfo('creating entities write stream');
  const filePathFactory = createFilePathFactory('entities');

  const entryStream = createTarEntryStream(
    this.#archive.stream,
    filePathFactory,
    this.options.file.maxSizeJsonl
  );

  return chain([stringer(), entryStream]);
}

管道由两段组成:

  1. stringer()(来自 stream-json/jsonl/Stringer):把逐条写入的 JSON 对象序列化为 JSON Lines(每行一个对象),避免把整文件载入内存,这是大体积传输时控制 RAM 占用的关键;
  2. createTarEntryStream:把 JSONL 字节流切分成顺序编号的 tar entry,并在达到 maxSizeJsonl 时滚动到下一个文件。

分片机制:maxSizeJsonl 的实际行为

分片逻辑在 utils.ts 中,两个要点值得注意:

export const createTarEntryStream = (
  archive: tar.Pack,
  pathFactory: (index?: number) => string,
  maxSize = 2.56e8
) => { ... }
  • 默认分片阈值maxSize 未显式传入时默认为 2.56e8(约 256 MB)。也就是说,即使不设置 file.maxSizeJsonl,单个 JSONL 文件写满 256 MB 左右也会自动滚动出下一个文件;
  • 分片计数:缓冲累积长度超过 maxSize 时触发 flush()fileIndex += 1 后由 pathFactory 生成新文件名。文件名由 createFilePathFactory 生成,格式为 {type}/{type}_{5位序号}.jsonl,序号用 padStart(5, '0') 补齐,例如 entities/entities_00001.jsonlentities/entities_00002.jsonl——这正是 File Structure 文档 中“任意数量文件、只要序号连续即可”约定的写入侧实现;
  • 单块保护:若单个写入块本身就超过 maxSize,会直接回调 payload too large 错误,防止无意义地循环分片。

此外,destroy 钩子里的最后一次 flush() 保证流关闭时残留缓冲也会落盘为最后一个 JSONL 文件。

资产流(createAssetsWriteStream)则不走 JSONL 分片:它以 objectMode 接收 IAsset,为每个资产写入 assets/uploads/<filename> 二进制 entry 和 assets/metadata/<filename>.json 元数据 entry(见 源码 L260-L305)。

metadata.json:来自源端的信息

归档关闭时(close() 先于 stream.finalize() 调用)会执行 #writeMetadata()(见 源码 L172-L194):它把引擎在转移前通过 setMetadata('source', metadata) 注入的源端元数据(例如来源 Strapi 版本、创建时间)以 metadata.json 写入归档。这解释了 File Structure 文档 中 metadata.json 的用途——它记录“数据的原始来源”,供导入方做兼容性检查。注意这与第一节的“该 Provider 自身不报告 schema/版本错误”并不矛盾:Destination 只负责忠实抄录源端元数据,不做任何校验。

rollback:失败即清理

rollback() 先执行 close() 收尾管道,然后 rm(this.#archivePath, { force: true }) 删除已写入的半成品归档(见 源码 L162-L166)。即:转移失败回滚后不会留下残损的备份文件,需要重新导出。

六、实战:在 strapi export 命令中使用该 Provider

CLI 的 strapi export 是该 Destination Provider 的主要消费方,实现见 export action。命令行参数到 Provider 选项的映射关系如下:

return createLocalFileDestinationProvider({
  file: {
    path: filepath,              // --file 指定,未指定时取默认导出名
    maxSizeJsonl: maxSizeJsonlInMb,
  },
  encryption: {
    enabled: encrypt ?? false,   // --encrypt
    key: encrypt ? key : undefined, // --key 仅在 --encrypt 时生效
  },
  compression: {
    enabled: compress ?? false,  // --compress
  },
});

对应关系一览:

CLI 选项 映射的 Provider 选项 说明
--file file.path 省略时使用 getDefaultExportName() 生成的默认文件名
--compress compression.enabled 默认 false
--encrypt encryption.enabled 默认 false
--key encryption.key 仅在 --encrypt 时传入
--max-size-jsonl file.maxSizeJsonl 以 MB 为单位,内部乘以 1024 * 1024 换算为字节(BYTES_IN_MB,见 源码 L40、L179-L181

注意 --max-size-jsonl 的单位差异:CLI 接收 MB,Provider 接口 maxSizeJsonl 的语义是字节,CLI 在构造 Provider 前完成了换算。若程序化调用 Provider 时直接传入字节数(如 256 * 1024 * 1024)即可,无需换算。

导出流程的其余环节在 action.ts 中:源端由 createLocalStrapiSourceProvider 提供当前 Strapi 实例的数据;engine.transfer() 完成后,命令校验 results.destination?.file?.path 指向的产物确实存在(tar 模式检查文件、dir 模式检查目录下存在 metadata.json),失败时通过 abortTransfer/回滚清理半成品,并打印 Export archive is in <path> 结果信息。

七、相关文档与延伸阅读

八、小结

Strapi File Destination Provider 是整个数据迁移链路中“落盘”环节的执行者:它以 destination::local-file 之名接入传输引擎,通过 tar → gzip → AES-128-ECB → 磁盘 的可配置管道输出标准 Strapi Data File;file.maxSizeJsonl 控制 JSONL 分片(默认约 256 MB 滚动)、encryption.key 经 scrypt 派生后参与加密、失败时 rollback 清理半成品文件;并且它按设计不做 schema/版本校验,将兼容性判断完全留给导入侧。理解上述机制后,无论是通过 strapi export 命令还是程序化调用 createLocalFileDestinationProvider,都能精确预期产物文件的命名、内部结构与大小。

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

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
docsdocs
暂无描述
Markdown
889
5.78 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384
flutter_flutterflutter_flutter
本仓库是 Flutter SDK 与 Flutter Engine 的 OpenHarmony 适配版本,由 CPF-Flutter 团队维护。开发者可使用熟悉的 Flutter 技术栈开发 OpenHarmony 应用,3.35.7 及以后的适配版本可基于本仓库源码构建支持 OpenHarmony 的 Flutter Engine。
Dart
1.17 K
341