Files
doodle-store/packages/asset-sync/README.md
T
2026-10-20 17:29:10 +08:00

221 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 本地备份同步七牛
独立 Node.js 脚本项目,使用官方 qiniu SDK 7.15.2。入口命令在 doodle-store 根目录执行。只复制本地文件到已存在的七牛空间,不创建空间、不删除/覆盖远端对象、不修改数据库或业务 URL。
## 前置步骤:通过 tcb 下载云存储备份
更新日期:2026-10-20。本节记录日后重新导出资源的操作流程;本次编写文档未登录云环境或执行下载。已有备份位于 `/Users/joey-xd/sites/doodle/doodle-assets/cloud_backup`,无需为了使用同步工具重复下载。
流程为:`原小程序云存储 → tcb 下载到本地 → SOURCE_DIR 指向备份根目录 → asset-sync 上传七牛 → 按需回填数据库`。这里下载的是云存储文件,不是数据库记录、云函数代码或完整环境备份。
当前线上版小程序仍使用旧云开发,原预拉取模式、地址、生成链路和全部旧云函数必须保留。本流程只读取源文件,不删除源文件、不调整存储权限、不停用云函数或定时任务;后续的七牛同步也不会替代旧线上服务。
### 1. 安装并检查 CloudBase CLI
本机已安装 `@cloudbase/cli`,本次通过版本及帮助命令核对的版本为 **3.8.7**。新机器可安装同一版本复现操作;已有 tcb 时先检查版本,不必重复安装:
```bash
# 仅新机器未安装时执行
pnpm add -g @cloudbase/cli@3.8.7
tcb --version
tcb storage download --help
```
使用当前的空格形式 `tcb storage download`;旧文档可能使用冒号形式 `tcb storage:download`。升级 CLI 后先重新查看帮助。参考 [官方安装说明](https://docs.cloudbase.net/cli-v1/install)。
### 2. 登录并确认原云环境
以下 tcb 命令在 `doodle-mini` 目录执行,七牛同步命令则在 `doodle-store` 根目录执行:
```bash
cd /Users/joey-xd/sites/doodle/doodle-mini
# 按终端提示在浏览器中完成授权
tcb login
# 核对账号能访问的环境
tcb env list
# 显式指定原小程序环境,避免误用其他项目的默认环境
tcb storage list -e cloud1-9gifs7a2756e2c87
```
环境 ID 来自 `doodle-mini/cloudbaserc.json`。如果更换源环境,应重新核对并替换下列命令中的 ID;不要直接复制 `cloud://` 文件 ID 作为环境 ID。
授权账号必须能管理原小程序云环境的云存储。微信 AppSecret 和七牛 AK/SK 均不是 tcb 登录凭据。若列表中看不到原环境,先核对账号权限及环境所在地域;必要时按实际地域使用全局 `--region` 参数,不为下载另建环境或改变权限。
### 3. 下载全部资源
每次新备份使用独立目录,保留已有 `cloud_backup`。在同一个终端依次执行:
```bash
DOODLE_CLOUD_BACKUP="/Users/joey-xd/sites/doodle/doodle-assets/cloud_backup-$(date +%Y%m%d-%H%M%S)"
mkdir -p "$DOODLE_CLOUD_BACKUP"
tcb storage download / "$DOODLE_CLOUD_BACKUP" --dir -e cloud1-9gifs7a2756e2c87
```
`/` 表示下载云存储全部文件,`--dir` 表示目录下载。云端路径使用相对对象路径,不使用临时 HTTPS 链接。命令依据 [官方云存储下载说明](https://docs.cloudbase.net/cli-v1/storage)。本项目源端为旧小程序云环境,不因新后端采用 PostgreSQL 就改用 `storage objects` 等 PG 环境命令。
如果只补下载题型预览,可改用目录下载,目标仍保留 `assets/previews` 层级;这是全量下载的替代方式,无需两者都执行:
```bash
mkdir -p "$DOODLE_CLOUD_BACKUP/assets/previews"
tcb storage download assets/previews "$DOODLE_CLOUD_BACKUP/assets/previews" --dir -e cloud1-9gifs7a2756e2c87
```
其他目录按实际云端清单选择,例如 `assets/fonts`、`assets/materials`、`content`、`user-avatars`。云端仍在写入,目录下载不是某个时间点的原子快照;下载失败或重试后都要核对实际文件,不把命令退出成功等同于完整备份,也不将旧目录混入新备份来掩盖缺失。
### 4. 核对目录并连接七牛同步
备份根目录应保留云端相对路径,例如:
```text
cloud_backup-<时间>/
├── assets/
│ ├── fonts/
│ ├── materials/
│ └── previews/
│ ├── chinese/
│ ├── english/
│ ├── math/
│ ├── papers/
│ ├── pinyin/
│ └── puzzle/
├── content/
│ └── page-config.json
└── user-avatars/
```
这只是路径示例,不代表每个目录必有文件;以本次云端列表和下载结果为准。不要多套一层环境 ID、重复的 previews 目录,或直接把文件平铺到备份根目录,否则七牛对象 key 会变化。
抽查图片能打开、字体文件非空、`content/page-config.json` 能解析,并对照云端列表核对数量和大小。历史备份为 62 个文件,其中预览 51 个、头像 7 个、字体/素材/首页配置/demo 各 1 个;这是旧备份基线,不是未来下载必须满足的固定数量。demo 等其他对象也应保留原路径,不限于上方示例目录。
检查完成后,将 `packages/asset-sync/.env` 的 `SOURCE_DIR` 手动改为新备份的实际绝对路径;继续使用已有备份时保持原值:
```dotenv
SOURCE_DIR=/Users/joey-xd/sites/doodle/doodle-assets/cloud_backup
```
不要把包含 `$DOODLE_CLOUD_BACKUP` 的字符串直接写进 `.env`;该配置需要实际路径。`SOURCE_DIR` 指向包含 `assets`、`content`、`user-avatars` 的根目录,不是 `assets/previews` 子目录。备份目录不应存放下载日志、清单或报告,这些普通文件也可能被同步工具扫描上传。
回到 `doodle-store`,先预演并核对报告中的相对路径、目标 key 和文件数量,再正式上传:
```bash
cd /Users/joey-xd/sites/doodle/doodle-store
pnpm assets:sync
pnpm assets:sync --apply
```
本项目当前使用 `QINIU_KEY_PREFIX=doodle-assets/`,补同步已有资源时应保持该值,并与 server 配置一致;下方配置段中的 `doodle/` 是工具默认示例,不能在补同步时误换前缀。同步与数据库回填是独立步骤,详见本文末尾。
## 配置
已生成本机 `packages/asset-sync/.env`,源目录配置为:
```dotenv
SOURCE_DIR=/Users/joey-xd/sites/doodle/doodle-assets/cloud_backup
QINIU_AK=
QINIU_SK=
QINIU_BUCKET=
QINIU_REGION=
QINIU_KEY_PREFIX=doodle/
QINIU_PUBLIC_DOMAIN=
SYNC_CONCURRENCY=3
SYNC_RETRIES=2
SYNC_MAX_FILE_BYTES=67108864
```
填写本地 `.env` 即可,不要将 AK/SK 写到小程序、Git、命令参数或聊天中。该文件被 Git 忽略,并以 0600 权限保存。新机器可从 `.env.example` 复制;相对 SOURCE_DIR 以配置文件目录为基准,运行目录不影响源路径。进程环境变量优先于文件配置。
| 配置 | 说明 |
| ------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| SOURCE_DIR | 本地备份根目录,工具只读 |
| QINIU_AK / QINIU_SK | 七牛服务端 AccessKey / SecretKey,仅正式同步要求 |
| QINIU_BUCKET | 所有资源共用的公开空间:图片、头像、字体、JSON 等 |
| QINIU_REGION | 可留空自动查询;固定区域时使用七牛 region ID,如 z0、z1、z2、na0、as0 |
| QINIU_KEY_PREFIX | 对象前缀;默认 doodle/,空串为原相对路径 |
| QINIU_PUBLIC_DOMAIN | 可选 HTTPS origin,例如 https://cdn.example.com;不含 /doodle 等路径,仅用于生成报告中的公共 URL,不证明域名已经绑定该空间 |
| SYNC_CONCURRENCY | 并发文件数,1~10,默认 3 |
| SYNC_RETRIES | 瞬时错误额外重试次数,0~5,默认 2 |
| SYNC_MAX_FILE_BYTES | 每个文件读取内存的大小限制,默认 64 MiB |
空间必须已存在且有权查询和上传。执行前读取空间设置,核对目标空间实际 private=0,工具不会自动调整 ACL。头像与其他资源使用同一个 QINIU_BUCKET,无需配置私有空间。
## 使用
```bash
# 查看用法
pnpm assets:sync --help
# 默认预演全部文件,不调用七牛接口,也不要求 AK/SK
pnpm assets:sync
# 只预演题型预览图
pnpm assets:sync --only assets/previews
# 填写 .env 后,先同步 51 张预览
pnpm assets:sync --apply --only assets/previews
# 可选择多个目录
pnpm assets:sync --apply --only assets/fonts --only assets/materials
# 全部同步,含头像,均使用 QINIU_BUCKET 公开空间
pnpm assets:sync --apply
# 自定义配置文件/报告;报告文件不能已存在,也不能位于源备份内
pnpm assets:sync --env /绝对路径/.env.sync --report /绝对路径/report.json
```
`--only` 按目录前缀或精确文件路径筛选,不是 glob;`--dry-run` 可显式声明预演,与 `--apply` 互斥。通过默认预演确认清单后,再添加 --apply 上传。文件缺失、损坏 JSON、超限文件、软链接及无法识别的预览/头像图片会停止预检。
## 对象路径和同步行为
默认映射:
```text
assets/previews/math/number-find.jpg
→ 公共 bucket : doodle/assets/previews/math/number-find.jpg
user-avatars/<文件名>.png
→ 同一公共 bucket : doodle/user-avatars/<文件名>.png
```
头像保留 `user-avatars/` 相对路径,与预览等资源共用公开空间。配置 QINIU_PUBLIC_DOMAIN 后,报告也会生成头像的公开地址。工具不根据文件名推断用户身份,用户归属仍需在后续绑定时核实。
1. 扫描源目录,忽略隐藏文件,记录实际 MIME、字节数、SHA-256 与七牛 ETag;不修改源文件。
2. 正式执行时重新读取并核对本地摘要,使用该次读取的 Buffer 上传,避免核对后源文件被修改。
3. 远端不存在时,用指定 bucket:key 的 insertOnly=1 凭证上传,仅允许新增,启用 SDK CRC 校验和 HTTPS。
4. 远端同 key 且 ETag/大小一致:跳过并标记已核对。不同内容:报告 conflict,保留原远端文件;需要新版本时选择新的前缀/key。
5. 上传完成再次 stat,对比远端 ETag/大小;不是仅凭“请求成功”就记完成。
6. 瞬时网络错误、429、5xx 有限重试,鉴权错误和冲突不盲目重试。上传已成功但响应丢失时,重试遇到 614 后回查摘要恢复。
7. 任一文件失败或冲突时命令退出码为 1,其余文件结果仍写入报告。中断后重新运行同一命令,会重新检查远端并跳过已完成对象;未提供分片级断点续传。
校验方式是七牛内容 ETag 和大小,不等同于从 CDN 下载全部文件后比较 SHA-256。SHA-256 是本地备份摘要,报告明确记录 verifiedBy=qiniu-etag-and-size。不会自动刷新 CDN 或替换同名对象。
## 报告与后续接入
默认报告写 `packages/asset-sync/reports/<时间>-<随机ID>.json`,目录被 Git 和格式化工具忽略,文件权限 0600;含源相对路径、目标 bucket/key、MIME、大小、摘要、状态和有限错误码。不包含 AK、SK、上传凭证、签名链接或 SDK 原始异常。中断时报告可能未写完,以重新运行后的远端校验为准。
主要状态:planned(预演)、uploaded(上传并核对)、skipped(已有同内容)、conflict(同名异内容)、failed、not_attempted(全批配置/权限预检未通过)。常见全批错误:CREDENTIALS_REQUIRED、BUCKET_REQUIRED、BUCKET_VISIBILITY_MISMATCH。上传用户需有 bucketInfo、stat、upload 所需权限。
这次只做文件同步。同步成功后仍需按报告映射更新新后端 assets(保持现有 ID 并回填 bucket/key/MIME/摘要、status=ready)、字体配置、用户头像绑定及首页 JSON 内的 cloud 引用。content/page-config.json 当前会原样复制,不会自动转换嵌套链接;原云上传和定时任务也不会自动切换到七牛。
## 验证与当前预演
```bash
pnpm assets:test
pnpm check
```
测试使用临时本地文件和模拟七牛接口,覆盖配置、ETag 边界、头像同空间映射、预演无网络、首次上传、重复跳过、冲突保护、重试、响应丢失恢复、源文件变化、校验失败、凭据脱敏及 CLI 报告权限。没有使用真实 AK/SK 或执行真实上传。
本地备份预演:62 个文件,共 3,568,778 字节;其中题型预览 51、头像 7、字体 1、素材 1、首页配置 1、demo 1。该数量为本地预演结果,不代表已上传。
实现依据:[七牛官方 Node.js SDK](https://developer.qiniu.com/kodo/sdk/nodejs)、[七牛上传策略](https://developer.qiniu.com/kodo/manual/put-policy)。
## 回填服务端资产
上传报告确认成功后,根目录执行 `pnpm assets:map --report /绝对路径/报告.json` 预演,再添加 `--apply` 写入本机 doodle_dev。该独立回填步骤会重新核对七牛对象与备份摘要,事务更新已有题型预览资产,保留资源 ID 并记录审计;不处理头像归属、首页 JSON 嵌套链接或其他资源绑定。配置必须与 server 的域名、前缀及空间一致,详见根 README。