# 数据库图片迁移到阿里云 OSS 纯 JavaScript / Node.js 工具。默认自动读取项目当前环境的主数据库连接,从数据库识别旧数据库存储和默认阿里云 OSS,直接读取原图上传并校验,然后更新文件表和业务字段中的旧地址。 **通常不需要填写配置,不需要设置数据库密码环境变量,也不需要启动本地后端。** 需要 Node.js 20.19+、MySQL 5.7/8.x 和项目已设置好的默认阿里云 OSS。 ## 直接使用 在本目录执行: ```sh npm ci --ignore-scripts node migrate.mjs init ``` 这会读取代码里的数据库连接,查询存储配置和已有迁移批次,自动生成 `config.local.json`。不上传图片、不更新数据库、不创建迁移表。数据库密码仍从项目 YAML 读取,OSS 密钥仍从存储配置表读取,不重复写入生成文件。 接着只读预检,或开始迁移: ```sh 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 默认会使用代理;需要结合进程代理变量、实际连接及系统路由判断。此脚本不修改系统代理设置。 如需回滚: ```sh 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,可显式新建批次: ```sh node migrate.mjs apply --run-id db-images-next ``` 新批次名称会保存到自动配置。完成回滚的批次不会自动重新迁移。 ## 仅在需要时覆盖 正常不需要复制示例或逐项编辑。`config.example.json` 为 `{}`,表示全部自动识别;`config.manual.example.json` 保留完全手工配置方式,兼容旧用法。 例如切换项目环境: ```sh node migrate.mjs init --profile local ``` 同一生成配置绑定原数据库;如果切换后指向不同数据库,会停止,不能把原批次用于另一台服务器。 其他可选设置仍可写入配置文件:`referenceSchemas` 添加同一 MySQL 实例的 IM 库,`imagesOnly: false` 包含非图片,`sourceMode: "http"` 使用原下载接口。手动连接另一数据库时可用 `--config config.manual.local.json`,其中 mysql.passwordEnv 指定的环境变量需要自行提供。 迁移前备份数据库,保持原图片和目标配置稳定。回滚只恢复本批次写入且没有被后来修改的值,不删除原 BLOB 或 OSS 对象。最终清理仍是独立步骤。 ## 实际处理流程 1. 按数据库自身的路径比较规则,选取每个 `config_id + path` 最大的未删除内容 ID,与 `DBFileClient.getContent` 一致。大整数 ID 全程使用字符串。 2. 默认处理 MIME 为 `image/*` 或常见图片扩展名的内容;没有文件表记录但可以识别为图片的 BLOB 也会处理。其他文件计入跳过数量;如确需迁移全部数据库文件,设置 `imagesOnly: false`。无法识别的无扩展名内容需要单独核查。 3. 建立固定迁移清单,保存原文件记录和旧地址。默认直接读取数据库原始字节;HTTP 模式通过旧下载接口读取。两种方式均与数据库计算的长度、SHA-256 比较。 4. 使用独立目录 `objectPrefix/旧配置编号/路径哈希/内容哈希.扩展名` 上传;保留原始文件字节,不压缩图片。上传前后均检查对象,不覆盖不同内容。 5. 从 OSS 重新读取校验,并匿名访问最终将写入数据库的 URL 再次校验。SDK 上传成功但浏览器地址不可访问时,不切换数据库记录。 6. 本次选中文件全部通过上传校验后,每批最多 50 张用一个事务更新 `infra_file.config_id/path/url/type/size`,保留文件记录 ID 和自动更新时间。逐条回滚日志与文件记录、迁移状态同时提交。 7. 扫描指定数据库的字符、文本、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 为执行错误。 失败时只输出简短错误,不输出配置密钥;详细原值、映射及逐项状态保存在主库的三个表: ```text 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` 排除;回滚日志包含原业务值,应按业务数据库的权限保护。 ## 验证 ```sh npm test ``` 默认运行本地 HTTP、模拟 OSS 和事务接口的测试。真实 MySQL 集成测试默认跳过;仅可指向专用临时 MySQL 容器的端口: ```sh MIGRATION_TEST_DB_PORT=临时容器端口 node --test test/integration.test.mjs ``` 集成测试会创建并删除固定的 `codex_oss_migration_test` 测试库,不可指向业务数据库服务器。其 OSS 是内存模拟,HTTP 下载接口由本地测试服务模拟;真实阿里云连接和实际业务页面仍需通过少量试迁移验收。