Files
doodle-store/packages/asset-sync

本地备份同步七牛

独立 Node.js 脚本项目,使用官方 qiniu SDK 7.15.2。入口命令在 doodle-store 根目录执行。只复制本地文件到已存在的七牛空间,不创建空间、不删除/覆盖远端对象、不修改数据库或业务 URL。

配置

已生成本机 packages/asset-sync/.env,源目录配置为:

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,无需配置私有空间。

使用

# 查看用法
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、超限文件、软链接及无法识别的预览/头像图片会停止预检。

对象路径和同步行为

默认映射:

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 当前会原样复制,不会自动转换嵌套链接;原云上传和定时任务也不会自动切换到七牛。

验证与当前预演

pnpm assets:test
pnpm check

测试使用临时本地文件和模拟七牛接口,覆盖配置、ETag 边界、头像同空间映射、预演无网络、首次上传、重复跳过、冲突保护、重试、响应丢失恢复、源文件变化、校验失败、凭据脱敏及 CLI 报告权限。没有使用真实 AK/SK 或执行真实上传。

本地备份预演:62 个文件,共 3,568,778 字节;其中题型预览 51、头像 7、字体 1、素材 1、首页配置 1、demo 1。该数量为本地预演结果,不代表已上传。

实现依据:七牛官方 Node.js SDK、七牛上传策略。