存量文件迁移指引
本文介绍如何把旧版 SDK 上传的文件迁入云存储桶,让它 们恢复存储桶归属。
在 PG 环境中使用过旧版 SDK(JS SDK V2 / 旧 Node SDK / 旧 wx-server-sdk)上传文件,且控制台「云存储 - 存储管理」页顶部提示「旧版 SDK 上传的文件不归属于任何存储桶」。
开始之前
什么是「存量文件」:使用旧版 SDK 上传、目前未归属任何存储桶的文件。它们的字节已经在环境关联的 COS 桶里,只是缺了「归属」这一步,因此在控制台按存储桶查看时看不到,也不受存储桶的权限与策略管控。
迁移这件事本身,你可能会关心:
| 你可能想问 | 回答 |
|---|---|
| 会影响线上业务吗 | 不会。迁移只读取源端文件、向目标存储桶写入新对象,不改动也不删除已有文件(源文件默认保留) |
| 需要停服吗 | 不需要,可在线执行 |
| 大概要多久 | 取决于文件数量与总大小。脚本默认并发 4(可用 --concurrency 调整),支持断点续跑,中断后重跑会接着传 |
| 会产生额外费用吗 | 迁移期间同一份数据会短暂存在两份(源端原文件 + 桶内副本)。确认无误后执行 cleanup 清理源端即可恢复单份 |
| 业务代码要改路径吗 | 不需要。迁移不改变文件的对象名(key),只是补上存储桶归属 |
| 升级 SDK 后还需要迁移吗 | 需要。升级只让新上传的文件走存储桶链路,已上传的文件不会自动迁移 |
先在测试环境用小范围数据(--source-prefix)跑通全流程,再对生产环境全量迁移。
你需要做的两件事
| 你要做的 | 解决什么 | 现在不做会怎样 |
|---|---|---|
| 升级 SDK | 让「新上传的文件」走存储桶链路 | 直传链路封禁后,上传请求会被直接拒绝 |
| 迁移存量文件(本指引) | 让「已经传上去的文件」恢复归属 | 文件在控制台按桶不可见、不受桶策略管控 |
升级路径:JS SDK V3(推荐) | Node SDK V4.1 | wx-server-sdk(小程序)
迁移总览
体检 ──► 识别 ──► 迁移 ──► 校验 ──► 收口
工具/凭证/ 列出待迁移 写入目标桶 确认全部归入 确认后可清理
源端连通 的文件 且策略生效 源端、关掉提示
迁移的本质:文件的字节已经在环境的 COS 桶里,缺的是「归属」。迁移就是按官方网关把它们重新写入指定存储桶,让字节落位与元数据登记同时成立。
本指引配套迁移脚本,请先下载:下载 pg-bucket-migration.zip(含迁移脚本与配置模板,解压即用,Node.js ≥ 18,零 npm 依赖)。
下文命令均在解压后的 pg-bucket-migration 目录中执行。
前置准备
-
确认目标存储桶已存在。在控制台「云存储 - 存储管理」创建或选定目标桶(下文以
bucket1为例):tcb storage buckets list -e <envId> -
登录 tcb CLI(迁移默认走 CLI 后端):
tcb login # 推荐:交互式授权登录,密钥不落到命令行注意如需非交互登录(CI 场景),可用
tcb login --apiKeyId <SecretId> --apiKey <SecretKey>。这种方式会让密钥出现在 shell 历史与进程列表中,建议改用临时密钥,并在迁移结束后及时禁用。 -
安装 rclone(用于枚举与拉取源端文件):
# macOSbrew install rclone# Linuxcurl https://rclone.org/install.sh | sudo bash# Windows: scoop install rclone -
填写配置:
unzip pg-bucket-migration.zip && cd pg-bucket-migrationcp .env.example .envvim .env # 填 CB_ENV_ID / CB_TARGET_BUCKET / COS 凭证或 CB_SOURCE
权限要求:源端 COS 桶的只读权限、目标存储桶的读写权限。
.env 含明文密钥,已加入 .gitignore,请勿提交到仓库或外发。
步骤 1:迁移前体检
先确认工具、凭证、源端连通性、接口都正常,避免跑到一半才发现问题:
node migrate-orphaned-to-bucket.mjs check
── 体检结果 ───────────────────────────────
rclone : 可用
源端可访问 : cbstorage-src:<bucket-APPID>(1,247 个对象,2.31 GB)
envId : my-env-id
目标存储桶 : bucket1
存储后端 : tcb CLI(可用,桶:bucket1)
桶内对象列表 : 正常(当前 402 个对象)
───────────────────────────────────────────
任一项异常就先按提示修好,再进入下一步。
步骤 2:识别待迁移文件
脚本用「源端文件全集」减去「已归属的文件」,得到待迁移清单。这一步只读,不写入任何数据,可以反复执行。
node migrate-orphaned-to-bucket.mjs plan
→ 枚举源端存量文件(COS)…
源端文件:1,247 个,合计 2.31 GB
→ 列出已归桶对象(pg 存储桶「bucket1」)…
已归桶:402 个
── 迁移预览 ───────────────────────────────────
待迁移(未归桶):845 个,1.86 GB
已归桶(跳过) :402 个
已归桶物理对象 :402 个(已归属其他存储桶,自动跳过)
清单已写入 :.pg-bucket-migration/manifest.json
───────────────────────────────────────────────
「已归桶物理对象」是已归属存储桶的对象(在 COS 中的路径带桶 ID 前缀)。脚本会自动识别并跳过,无需人 工处理。
先看清单,再决定怎么迁。 常用参数:
| 情况 | 处理方式 |
|---|---|
| 只想迁某个目录 | --source-prefix uploads/ |
| 想排除某些文件 | --exclude "tmp/**" |
| 源端路径带了多余前缀,想剥掉 | --strip-prefix legacy/ |
| 个别大文件想单独处理 | --max-file-size 104857600(跳过 >100MB 的) |
步骤 3:执行迁移
node migrate-orphaned-to-bucket.mjs migrate --yes
脚本逐个处理:拉取源端文件 → 通过官方网关写入目标桶 → 记录结果 → 继续下一个。
将把 845 个文件(1.86 GB)迁移进存 储桶「bucket1」
策略:reupload
✓ [1/845] uploads/avatar-001.jpg (1.24 MB)
✓ [2/845] docs/manual.pdf (3.80 MB)
✓ [3/845] images/banner.png (2.10 MB)
…
── 迁移结果 ───────────────────────────────
成功:845 / 845 失败:0
───────────────────────────────────────────
特性:
- 可断点续跑:中断或部分失败后重跑同一条命令,自动跳过已成功的文件
- 幂等:目标桶已存在同名对象时自动覆盖,重跑不会报错
- 默认并发 4,可用
--concurrency 8调整 - 默认保留源端文件(见可选:回收源端空间)
迁移不会改变文件的对象名(key)。原来叫
uploads/avatar-001.jpg,归入存储桶后仍是这个名字,业务代码无需改路径。
步骤 4:校验结果
node migrate-orphaned-to-bucket.mjs verify
── 校验结果 ───────────────────────────────
清单文件数 :845
桶内对象总数 :1,247
缺失(未归桶) :0
大小不一致 :0
───────────────────────────────────────────
✅ 全部待迁移文件已归入存储桶。
校验通过后,再到控制台「云存储 - 存储管理」做最后确认:
- 在目标存储桶下能看到这批文件
- 文件大小、路径与迁移前一致
- 存储桶的访问权限、策略、文件大小限制、允许文件类型对它们已生效(可按策略试读/试写验证)
- 在测试环境验证一遍业务读写
若还有缺失或大小不一致,重跑 migrate(自动跳过已成功项)后再 verify。
步骤 5:关掉控制台提示
待迁移文件全部归入存储桶后,把控制台「存储管理」页顶部的提示条收口:
node migrate-orphaned-to-bucket.mjs close-banner
脚本会打印具体操作方式,按提示将环境标记 postgresql_orphaned_storage 置为 false 即可。
只有当环境确实不再存在未归属文件时才执行,否则会误关掉其他使用者的提示。可先跑一次 plan,确认「待迁移」为 0。
可选:回收源端空间
迁移默认保留源端文件,便于随时回退。但同一份数据会占两份空间,确认业务正常后建议清理:
# 先预览会删哪些文件
node migrate-orphaned-to-bucket.mjs cleanup --dry-run
# 确认后执行
node migrate-orphaned-to-bucket.mjs cleanup
── 源端清理预览 ───────────────────────────
可清理(桶内已存在同名对象):845 个,1.86 GB
- uploads/avatar-001.jpg
- docs/manual.pdf
…
跳过(桶内未找到同名对象,保持不动):0 个
───────────────────────────────────────────
安全性:cleanup 只删除已记录为迁移成功、且目标桶内确实存在同名对 象的源端文件,其余一律跳过。逐条执行并即时记录,中断后可安全重跑。
也可以在迁移时顺带清理:node migrate-orphaned-to-bucket.mjs migrate --delete-source。
常见问题
迁移相关
| 问题 | 回答 |
|---|---|
| 升级了 SDK,还需要迁移吗 | 需要。升级只管新上传的文件,已上传的不会自动迁移 |
| 迁移后业务要改代码吗 | 不需要。对象名不变,只是补上存储桶归属 |
| 文件很多(>10GB)怎么办 | 可按目录分批(--source-prefix),或参考控制台的大批量导入工具 |
| 迁移中途中断了怎么办 | 直接重跑同一条 migrate 命令,已成功的文件会被跳过 |
| 迁移后能回退吗 | 能。源端文件默认保留,删除目标桶内已迁入的对象即可 |
报错处理
| 现象 | 原因 | 处理 |
|---|---|---|
未找到 rclone | 未安装或不在 PATH | brew install rclone,或用 RCLONE_BIN 指定路径 |
403 PathStyleDomainForbidden | COS 只支持 Virtual Hosted-Style 寻址 | 确认 rclone 配置含 force_path_style=false |
STORAGE_KEY_ALREADY_EXISTS | 目标桶已有同名对象,接口默认拒绝覆盖 | 脚本已自动处理;手工操作时加 --upsert |
STORAGE_BUCKET_NOT_FOUND | 目标存储桶不存在 | 先在控制台创建存储桶 |
STORAGE_PERMISSION_DENIED | 存储桶的 RLS 策略未放行 | 配置存储桶 RLS 策略后重试 |
| 待迁移清单明显偏多 | 源端混着非业务文件 | 用 --exclude 排除 |
| 部分文件上传失败 | 网络抖动或单文件过大 | 重跑 migrate 自动补传;或调小并发、调大 --retries |
安全与回滚
- 先小范围验证:用
--source-prefix选一个子目录跑通全流程,再全量迁移。 - 默认不删源文件:源端文件保持原样,随时可回退。
- 权限最小化:只授予源端桶只读、目标桶读写权限;迁移结束后及时禁用或删除所用密钥。
- 回滚方式:迁移不破坏源数据。如需回退,删除目标桶内已迁入的对象即可(
tcb storage objects rm <key> --bucket <桶>)。
附录 A:脚本包目录说明
下载的 pg-bucket-migration.zip 解压后是一个目录,构成如下:
pg-bucket-migration/
├── migrate-orphaned-to-bucket.mjs
├── .env.example
├── README.md
├── .gitignore
└── 存量文件迁移指引.md
| 文件 | 作用 | 是否必需 |
|---|---|---|
migrate-orphaned-to-bucket.mjs | 迁移脚本主程序,包含体检 / 识别 / 迁移 / 校验 / 收口全部逻辑,零 npm 依赖 | 必需 |
.env.example | 配置模板,复制为 .env 后填写环境与凭证,避免手拼环境变量 | 建议 |
README.md | 目录说明、依赖要求与命令一览,解压后第一眼就能看到 | 建议 |
.gitignore | 忽略 .env 与状态目录,防止密钥被提交进你的仓库 | 建议 |
存量文件迁移指引.md | 本指引,离线可查 | 可选 |
运行环境要求:Node.js ≥ 18、rclone;使用默认的 CLI 后端时还需要 tcb CLI。环境准备见前置准备。
首次运行后,脚本会在同目录生成状态目录 .pg-bucket-migration/(可用 --state-dir 或 CB_MIGRATION_DIR 改名):
.pg-bucket-migration/
├── manifest.json
├── state.json
├── report.json
└── verify.json
| 文件 | 由哪个命令产生 | 作用 |
|---|---|---|
manifest.json | plan | 待迁移文件清单与跳过统计 |
state.json | migrate | 逐文件结果,断点续跑的依据 |
report.json | migrate | 本轮成功 / 失败汇总与失败明细 |
verify.json | verify | 校验结论:缺失数、大小一致性、是否通过 |
状态目录只记录迁移结果,不含密钥,可随时删除。删除后下次运行会重新生成,代价是丢失断点续跑能力与迁移留痕。
附录 B:不使用脚本的手工迁移
文件很少(个位数)、或不方便运行脚本时,也可以纯手工完成。
# 1. 查看目标存储桶与桶内对象
tcb storage buckets list -e <envId>
tcb storage objects list --bucket bucket1 -e <envId>
# 2. 查看源端文件(rclone 直连 环境 COS 桶,注意 force_path_style = false)
rclone ls cbstorage-src:<bucket-APPID>
# 3. 逐个写入目标存储桶(--upsert 允许覆盖同名对象)
rclone copyto "cbstorage-src:<bucket-APPID>/uploads/avatar-001.jpg" ./tmp.jpg
tcb storage objects upload ./tmp.jpg "uploads/avatar-001.jpg" --bucket bucket1 -e <envId> --upsert
手工方式没有差集识别、断点续跑与校验,文件一多就容易出错,不建议用于批量迁移。