数据库图片迁移到阿里云 OSS
纯 JavaScript / Node.js 工具。默认自动读取项目当前环境的主数据库连接,从数据库识别旧数据库存储和默认阿里云 OSS,直接读取原图上传并校验,然后更新文件表和业务字段中的旧地址。
通常不需要填写配置,不需要设置数据库密码环境变量,也不需要启动本地后端。 需要 Node.js 20.19+、MySQL 5.7/8.x 和项目已设置好的默认阿里云 OSS。
直接使用
在本目录执行:
npm ci --ignore-scripts
node migrate.mjs init
这会读取代码里的数据库连接,查询存储配置和已有迁移批次,自动生成 config.local.json。不上传图片、不更新数据库、不创建迁移表。数据库密码仍从项目 YAML 读取,OSS 密钥仍从存储配置表读取,不重复写入生成文件。
接着只读预检,或开始迁移:
node migrate.mjs
node migrate.mjs apply --limit 50
node migrate.mjs apply
第一条没有参数,默认只读预检;第二条实际迁移少量图片;第三条继续完成剩余迁移。可跳过 init,预检时也会自动生成配置。中断或失败后重跑相同 apply 命令即可。
预检只读文件元数据,不读取图片二进制内容或计算 SHA-256。图片分组、文件记录和路径拼写由三次批量查询完成,不再逐张反复访问远程数据库。输出包含当前阶段、扫描表名和行数;等待较长查询时每 5 秒显示耗时。estimatedBytes 是文件表记录的容量估算,sizePending 是缺少容量信息的文件数,实际长度和哈希在 apply 时校验。
上传时的源文件复核也分两步:先只查最新记录编号,再按主键读取该图片的长度和哈希,避免在没有路径索引的排序查询中读取图片大字段。每张图片完成后会显示源读取/复核、OSS 上传/校验、最终地址校验及总耗时,便于区分数据库等待与网络传输。
“更新已校验文件记录”是数据库事务阶段,不是再次上传。脚本每批最多处理 50 张:按主键范围加锁,批量复核源文件最新版本和哈希,再批量更新文件记录、写入逐条回滚日志及迁移状态,一起提交。保留原值比较和事务保护,不需要新增配置或给业务表加索引。该阶段重新计时,并显示每批源校验、记录及日志、提交的耗时。已知源变化或原值冲突会回滚整批,再逐项定位;连接或提交异常则停止,避免重复提交。
按 Ctrl+C 中断后,保留 config.local.json 和同一个 runId,重新执行 node migrate.mjs apply 即可续跑。已提交记录保留,未提交事务由数据库回滚。处于 verified 的已上传文件采用最多 4 张并发,重新读取 OSS 内容和最终访问地址核对哈希,不重新下载数据库图片,也不重新上传;切换时仍在事务里复核源内容。已切换文件也会批量核对文件记录,并并发检查访问地址。校验失败的图片不会用于更新业务引用。更新脚本后,需要停止旧进程再执行,新代码才会生效。
文件总大小不等于网络总传输量:脚本在运行电脑上从数据库读取、上传 OSS,并读取 OSS 及最终访问地址进行校验。macOS 系统代理开关也不代表 Node 默认会使用代理;需要结合进程代理变量、实际连接及系统路由判断。此脚本不修改系统代理设置。
如需回滚:
node migrate.mjs rollback
也可以使用 npm run init、npm run plan、npm run apply、npm run rollback。生成的配置和报告已忽略,不进入 Git。
自动识别的内容
- 环境:显式
--profile优先,其次SPRING_PROFILES_ACTIVE,否则按项目application.yaml。当前源码默认是 dev;生成文件会固定已选环境,避免下一次切换环境后误续跑。 - 数据库:合并基础 YAML 和对应环境 YAML,读取 dynamic.primary 指定的主数据源,包括地址、端口、数据库、用户名、密码。按需要解析环境变量占位符。
- 源配置:从仍有未删除 BLOB 的数据库存储配置中识别,不依赖迁移过程中会变化的文件表 config_id。
- 目标:唯一的默认存储配置,且必须是阿里云 OSS。不能确定时停止,不自动猜选。
- 历史域名:从旧文件地址提取;OSS Bucket、访问域名和密钥直接使用已有配置。
- 批次:已有批次优先续跑;首次生成稳定批次名称。保留服务器身份、存储编号和扫描范围,避免换库或换目标后误续跑。
- 下载接口:根据项目端口和 API 前缀推导。当前源码端口是 48083;默认直接读数据库,不依赖这个接口。
项目 dev/local 的数据库可能是远程地址,不能把环境名 local 当成本机数据库。每次执行会显示实际连接地址和库名。工具读取仓库配置及支持的环境变量,无法知道部署服务器额外的 Docker/命令行覆盖。
多个未完成批次、默认 OSS 改变、数据库服务器改变等情况会明确提示。若固定批次建立后又出现新 BLOB,可显式新建批次:
node migrate.mjs apply --run-id db-images-next
新批次名称会保存到自动配置。完成回滚的批次不会自动重新迁移。
仅在需要时覆盖
正常不需要复制示例或逐项编辑。config.example.json 为 {},表示全部自动识别;config.manual.example.json 保留完全手工配置方式,兼容旧用法。
例如切换项目环境:
node migrate.mjs init --profile local
同一生成配置绑定原数据库;如果切换后指向不同数据库,会停止,不能把原批次用于另一台服务器。
其他可选设置仍可写入配置文件:referenceSchemas 添加同一 MySQL 实例的 IM 库,imagesOnly: false 包含非图片,sourceMode: "http" 使用原下载接口。手动连接另一数据库时可用 --config config.manual.local.json,其中 mysql.passwordEnv 指定的环境变量需要自行提供。
迁移前备份数据库,保持原图片和目标配置稳定。回滚只恢复本批次写入且没有被后来修改的值,不删除原 BLOB 或 OSS 对象。最终清理仍是独立步骤。
实际处理流程
- 按数据库自身的路径比较规则,选取每个
config_id + path最大的未删除内容 ID,与DBFileClient.getContent一致。大整数 ID 全程使用字符串。 - 默认处理 MIME 为
image/*或常见图片扩展名的内容;没有文件表记录但可以识别为图片的 BLOB 也会处理。其他文件计入跳过数量;如确需迁移全部数据库文件,设置imagesOnly: false。无法识别的无扩展名内容需要单独核查。 - 建立固定迁移清单,保存原文件记录和旧地址。默认直接读取数据库原始字节;HTTP 模式通过旧下载接口读取。两种方式均与数据库计算的长度、SHA-256 比较。
- 使用独立目录
objectPrefix/旧配置编号/路径哈希/内容哈希.扩展名上传;保留原始文件字节,不压缩图片。上传前后均检查对象,不覆盖不同内容。 - 从 OSS 重新读取校验,并匿名访问最终将写入数据库的 URL 再次校验。SDK 上传成功但浏览器地址不可访问时,不切换数据库记录。
- 本次选中文件全部通过上传校验后,每批最多 50 张用一个事务更新
infra_file.config_id/path/url/type/size,保留文件记录 ID 和自动更新时间。逐条回滚日志与文件记录、迁移状态同时提交。 - 扫描指定数据库的字符、文本、JSON 字段,按精确地址映射替换单 URL、逗号列表、JSON 值及可识别的富文本地址。每行更新都检查原值,避免覆盖用户刚修改的数据。原生 JSON 的日志保存数据库实际持久化结果,便于正确回滚。
上传图片是逐个进行的;正式迁移按主键分批计算内容哈希,不会一次加载整个 BLOB 表。文件元数据和 URL 映射保存在内存;引用读取每页至少 1000 行,更新仍逐行执行。MySQL 当前会话的单条 SELECT 默认最多执行 60 秒,沿用 timeoutMs 配置,不修改服务器全局设置。
OSS 访问方式
- 默认
targetUrlMode: "direct",业务字段保存 OSS/CDN 稳定地址。地址优先使用targetPublicDomain,其次使用存储配置的domain,最后根据 Bucket 和 OSS 节点生成公网域名。Bucket/CDN 应允许这些图片按该地址读取;脚本不会修改 Bucket 权限。 - 私有 Bucket 可使用
targetUrlMode: "backend",业务字段保存现有后端下载入口加目标配置编号的地址,后端通过自己的 OSS 凭据读取。downloadBaseUrl此时必须是用户设备能访问的稳定地址,不能填仅运行脚本机器可访问的 localhost。现有接口是@PermitAll,此模式沿用项目已有访问规则,不新增鉴权。 - 默认
sourceMode: "database",无需启动旧后端。也可设置sourceMode: "http"使用原下载接口。使用 backend 目标模式时,后端仍须在线。
旧地址与迁移范围
修改文件表不会自动改变旧接口的行为:它仍按 URL 中的配置编号直接读取存储。因此脚本始终保留 infra_file_content 和原数据库配置,让旧客户端缓存及未替换引用仍有机会继续读取。
过渡期通过文件管理删除 OSS 文件,不会自动删除旧数据库副本。历史副本的删除和最终清理需要关联处理;本脚本不承担清理工作。续跑会重新检查已迁移对象和文件记录,目标已被删除或改动时停止复用对应映射。
旧接口会解码路径一次,并将裸 + 当空格。脚本下载时逐段编码;旧 URL 只有在其实际解码结果对应当前文件时才加入映射。裸 +、已有 %HH、?/# 等存在歧义的旧地址不会被静默指向另一张图片,未解决的引用会进入复核统计。
未知查询参数、片段、相对地址和无法识别的文本组合不做猜测替换。JSON 属性名不改。历史域名别名通过 oldDomains 补充。默认扫描主数据库,额外数据库只扫描同一个 MySQL 实例且当前账号有权限的 referenceSchemas;不同实例的 IM 库需要单独处理。
IM 自己上传到 MinIO 的文件不属于本次 BLOB 搬迁,但 IM 中保存的主库旧图片 URL 可以随引用扫描替换。Redis、客户端本地缓存、其他数据库实例和外部系统不在扫描范围,旧 BLOB 不能在脚本结束后立即清理。
业务引用表需要单列主键;无主键、复合主键表会列入待复核。更新要求 InnoDB。视图、生成列、文件基础表和迁移日志表不参与业务引用替换。excludeReferenceTables 可以填写明确不需要扫描的表名或 库名.表名;这些主动排除项会在报告中列出。
接口访问日志、接口错误日志、定时任务日志、系统操作日志和登录日志也默认排除:它们不是页面图片引用,迁移不应改写历史记录。具体为 infra_api_access_log、infra_api_error_log、infra_job_log、system_operate_log、system_login_log,不会泛化排除其他名字包含 log 的业务表。
结果、恢复与权限
每次命令在配置文件旁的 reports/ 写 JSON 报告:
| 状态 | 含义 |
|---|---|
planned |
数据库预检通过,尚未校验 HTTP/OSS 连通性 |
complete |
本批文件已切换,已扫描范围未检测到剩余旧路由引用 |
partial |
限量试迁移、上传失败、文件记录冲突或业务更新冲突,需要续跑/处理 |
needs-review |
预检/迁移扫描仍发现旧地址或不支持的表;可能包含有意跳过的非图片、未知 URL 变体,不能据此清理源数据 |
rolled_back / rollback_partial |
回滚完成 / 仍有并发修改冲突 |
退出码:0 为本次命令成功;2 为部分完成或需要复核;1 为执行错误。 失败时只输出简短错误,不输出配置密钥;详细原值、映射及逐项状态保存在主库的三个表:
infra_file_oss_migration_runs
infra_file_oss_migration_items
infra_file_oss_migration_changes
plan 不创建这些表、不上传、不更新业务数据;apply 首次执行会创建日志表。更新与日志在同一事务中提交,回滚不依赖本地报告。保留这些表及数据库备份直到验收结束。
同一主库的 apply/rollback 使用 MySQL 命名锁避免重复运行。同一 runId 绑定原数据库服务器、存储目标和扫描范围,不能换库或换 Bucket 后继续执行。需要 SELECT/UPDATE、主库创建和写入日志表的权限,以及 OSS 对指定对象的读取/写入权限;不需要 OSS 删除权限。
脚本会开启当前数据库连接的严格模式,并在 SQL 出现截断等警告时回滚,防止较短的业务字段保存损坏 URL。apply 将当前连接的隔离级别设为 REPEATABLE READ,要求源内容表、文件表和日志表为 InnoDB;批次源校验的主键范围锁覆盖可能新增或恢复的较新版本,防止校验期间源内容变化。不修改数据库全局隔离级别。配置文件、依赖目录和报告已通过此目录的 .gitignore 排除;回滚日志包含原业务值,应按业务数据库的权限保护。
验证
npm test
默认运行本地 HTTP、模拟 OSS 和事务接口的测试。真实 MySQL 集成测试默认跳过;仅可指向专用临时 MySQL 容器的端口:
MIGRATION_TEST_DB_PORT=临时容器端口 node --test test/integration.test.mjs
集成测试会创建并删除固定的 codex_oss_migration_test 测试库,不可指向业务数据库服务器。其 OSS 是内存模拟,HTTP 下载接口由本地测试服务模拟;真实阿里云连接和实际业务页面仍需通过少量试迁移验收。