🏁 迁移已完成,本工具包大部分已退役(2026-07-25 标注)
MySQL→PG 迁移于 2026-07-21 全部完成(数据落在腾讯云 CVM 共享 PG
YOUR_DB_HOST:15432,
开发与测试共用一库)。本目录内脚本的现状分三类:
脚本 现状 backup.sh✅ 仍是主力——CVM 库每日 03:00 四库备份即由它执行 create-app-roles.sh✅ 仍需要——新环境(含将来的正式环境)restore 前必须先建角色,否则 owner 落不对 install.sh/assemble-*.sh⚠️ 仅"全新空库"备用——上线已改为整库 dump/restore(见 docs/release-checklist.md§1),不再现场重建 schemamigrate-data.sh/migrate-nacos-config.sh/schema-drift-check.sh⛔ 已退役,勿再执行——它们的前提是"腾讯云 MySQL 是事实源",该前提已消失(MySQL 数据停在 07-16,仅作冷备回退底牌保留) 误跑
migrate-data.sh的后果是真实的数据事故:它会用 MySQL 旧快照 truncate 重灌目标库。
脚本已加执行护栏(启动即拒绝,须显式设I_KNOW_MIGRATION_IS_RETIRED=1才放行)。当前拓扑与连接纪律:
docs/db-connection-pool-guide.md。定位(2026-07-17,Backlog B 方案 (c) 合流后):本目录是**一次性 MySQL→PG 数据迁移工具包**,
只覆盖「schema 初始化 + 停机窗口迁数据 + 备份」;现场割接完成后即退役。
PG 容器的部署统一走仓库根docker-compose.yml(Backlog A 加固基线:端口收敛 +${VAR:?}
硬约束凭据),旧docker-compose.prod.yml单机部署面已删除。
整栈离线交付见docs/docker-compose-guide.md§14(make images-offline);本目录会被收入
交付包的migration/子目录。方案背景见docs/archive/pg-migration-plan.md,踩坑见tasks/lessons.md。
| 文件 | 说明 |
|---|---|
install.sh |
schema 初始化(全新空库):建四库(jnpf_init/jnpf_flow/jnpf_xxljob/nacos_config)+ 建表 |
migrate-data.sh |
停机窗口一次性数据迁移(现场 MySQL → PG) |
backup.sh |
pg_dump 定时备份 + 保留策略(配 cron),默认含 nacos_config |
create-app-roles.sh + create_app_roles.sql |
建业务专用连库角色(三库三角色,读 .env,幂等,L030) |
schema-drift-check.sh |
表级+列级漂移检查(远程 MySQL 事实源 vs 本地 PG);合并 main 或数据刷新**之前**先跑,阻断项直接给类型明细(SOP 见 docs/docker-compose-guide.md §18,L035) |
assemble-package.sh |
构建机专用,把 repo 里的 SQL 组装进 sql/(现场不用) |
sql/ |
全部初始化/迁移 SQL(构建机 assemble 生成,不进 git) |
cd docker/postgres/migration
./assemble-package.sh # 把 sql/ 填满(含官方 init + 自建表 DDL + pgloader 模板)
组装结果随 make images-offline 一并进整栈离线交付包,不再单独打 tar。
docker compose version 可用)。/data 分区建议 ≥ 20GB(PG 数据 + 备份 + 临时 dump)。docker load(含 mysql:8.0 与dimitri/pgloader 迁移辅助镜像,在交付包 images/jnpf-migration-tools-*.tar.gz)。PG 由根 compose(仓库根或交付包根)拉起——凭据经 .env 注入、基线不对外发布端口:
# 在仓库根 / 交付包根:
cp .env.example .env && vi .env # 全部密码改现场值(角色名/用户名保持默认,SQL 已硬编码)
docker volume create postgres_pgdata # 全新机器
docker compose up -d cx-postgres # 只起 PG(healthcheck 过了即可)
混合形态(JNPF 服务暂留宿主进程、仅 PG 容器化):基线不发布 PG 端口,需临时 override——
yaml # docker-compose.override.yml(现场临时,切全容器后删除) services: cx-postgres: ports: ["127.0.0.1:5432:5432"]
全容器形态无需此步(业务经 compose 网络服务名cx-postgres:5432直连,见 release-checklist §0.10)。
然后建 schema(**只用于全新空库**;库里已有表会拒绝执行,防止 init 里的 DROP TABLE 清数据):
cd migration/ # 交付包内;仓库内则 cd docker/postgres/migration
./install.sh # 建四库 → 官方 init → 自建表 → 数据接口修正 → Nacos 存储库 schema
结束会打印表数核对,应为: jnpf_init BASE TABLE = 366 (官方293 + 自建73) jnpf_flow BASE TABLE = 70 jnpf_xxljob BASE TABLE = 11 nacos_config BASE TABLE = 12
此刻库是**空 schema**,业务数据在下一步停机窗口迁入。
schema 就绪后按docs/release-checklist.md§0.3 跑./create-app-roles.sh建业务角色
(顺序铁律 L030:先定稿 .env → 跑脚本 → 同步 Nacos datasource.yaml → 重启消费方)。
迁移期间需**停止对源 MySQL 的写入**(停 JNPF 业务服务),保证数据一致。
① 停 JNPF 业务服务(停止对源 MySQL 写入)
② cd migration/ && SRC_MYSQL_HOST=... SRC_MYSQL_PWD='***' ./migrate-data.sh all
③ 核对 migrate-data.sh 末尾抽样校验 + 跑全量 rowcount 校验(§3.3)
④ 切 Nacos datasource.yaml 到 PG(§4)
⑤ 重启 JNPF 服务,冒烟验证
⑥ 清理临时资源:./migrate-data.sh cleanup
migrate-data.sh all 做了什么mysqldump(--single-transaction --hex-blob,跳过 base_sys_log 大日志表)导出 jnpf_init / jnpf_flow。mysql:8.0 中转容器(mysql_native_password),灌入 dump。pgloader 灌 jnpf_init(排除 base_sys_log、blade_visual_map)与 jnpf_flow(大写 ACT/FLW 表)。blade_visual_map(巨行表,pgloader 会堆爆)走 mysqldump --tab → PG COPY(NULL=\N 天然兼容)。data_interface_pg_fixes.sql(4 条 UPDATE)。lims_biz_log.log_seq 序列 setval 到当前最大值。源库地址与密码**必须显式传入**(不设软默认,codex review 2026-07-17);目标 PG 密码默认读
仓库根/交付包根 .env 的 JNPF_PG_PASSWORD:bash SRC_MYSQL_HOST=x.x.x.x SRC_MYSQL_PORT=3306 SRC_MYSQL_USER=root SRC_MYSQL_PWD='***' \ ./migrate-data.sh all
迁移产生的
dump/目录含完整业务数据,验收后务必./migrate-data.sh cleanup清除。
# mirror 仍在时,逐表对比 MySQL 源 vs PG(应输出「不一致 0」)
MYSQL_HOST=cx-mysql-mirror MYSQL_PORT=3306 MYSQL_USER=root MYSQL_PWD_V=mirror_2026 \
MYSQL_DOCKER_NET=$(docker inspect -f '{{range $k,$v := .NetworkSettings.Networks}}{{$k}}{{end}}' cx-postgres) \
PG_CONTAINER=cx-postgres \
bash sql/rowcount_check.sh jnpf_init
# jnpf_flow 同理:
... bash sql/rowcount_check.sh jnpf_flow
datasource.yaml 改为 PG(示例值,现场按实际填):yaml jnpf: datasource: db-type: PostgreSQL host: 127.0.0.1 # PG 容器宿主 port: 5432 db-name: jnpf_init username: postgres password: 改成强密码 # 与 install 时 PG_PASSWORD 一致
改法二选一:
- 控制台:Nacos → 配置管理 → datasource.yaml(group=DEFAULT_GROUP、对应命名空间)→ 编辑发布。
- API:
bash TOKEN=$(curl -s -X POST 'http://<nacos>:8848/nacos/v1/auth/login' \ -d 'username=nacos&password=nacos' | sed 's/.*"accessToken":"\([^"]*\)".*/\1/') curl -X POST "http://<nacos>:8848/nacos/v1/cs/configs?accessToken=$TOKEN" \ --data-urlencode 'dataId=datasource.yaml' \ --data-urlencode 'group=DEFAULT_GROUP' \ --data-urlencode 'tenant=<命名空间id>' \ --data-urlencode 'type=yaml' \ --data-urlencode 'content@datasource.yaml'
⚠️
ConfigValueUtil没有@RefreshScope:改完 Nacos 必须重启 JNPF 服务才生效,不会热更新。
重启后冒烟:登录 → 菜单 → 打开若干 LIMS 台账列表(数据/中文正常)→ 文件上传预览(注意 L015:Nacos config.ApiDomain 要指现场网关域名)。
# 手动跑一次确认可用(产物落 volume 外)
BACKUP_DIR=/data/pg-backup ./backup.sh
# crontab -e,每天 02:00 备份,保留 14 天(路径按现场实际解包位置调整)
0 2 * * * BACKUP_DIR=/data/pg-backup RETENTION_DAYS=14 /opt/jnpf/offline/migration/backup.sh >> /var/log/jnpf-pg-backup.log 2>&1
还原(custom 格式 -Fc,可选择性还原):bash docker exec -i cx-postgres pg_restore -U postgres -d jnpf_init --clean --if-exists < /data/pg-backup/jnpf_init_YYYYMMDD_HHMMSS.dump
本地已验证(2026-07-16):JNPF 定制 Nacos 与 xxl-job-admin 均原生支持 PG,切换后 MySQL 可完全下线。
install.sh 已把 nacos_config 作为第四库一并建好(含 nacos/nacos 登录种子——bash docker exec cx-postgres psql -U postgres -c "CREATE DATABASE nacos_config ENCODING 'UTF8'" docker exec -i cx-postgres psql -U postgres -d nacos_config -v ON_ERROR_STOP=1 -f - < sql/nacos_config_pg.sql nacos/conf/application.properties(先备份;文件里自带 Postgre 注释示例段,照抄改连接串):properties spring.datasource.convert=postgre # platform 保持 mysql 不动 db.url.0=jdbc:postgresql://<PG宿主>:5432/nacos_config?currentSchema=public db.user=postgres db.password=<PG密码> logs/start.out 出现 SwitchDatabase:postgre + use external storage 即成功;登录(nacos/nacos,尽快改密)→ 同 id 重建 namespace(customNamespaceId 参数)→ 重发配置 → 逐条 GET 回来与导出内容 diff。建库跑 schema → 导出旧配置 → 停全部 JNPF 业务服务 → 改配置重启 Nacos → 导入配置并 diff → 按序拉起业务服务。**不要让业务服务带着旧连接硬扛 Nacos 重启**——nacos-client 在服务端不可达窗口会泄漏 grpc 重连线程(实测 1.5h 后打满每进程 4096 上限,服务假活、注册表逐个掉线,见 tasks/lessons.md L022)。application-dev.yml 里注释 MySQL 段、启用自带的 PostgreSQL 注释段:yaml datasource: url: jdbc:postgresql://<PG宿主>:5432/jnpf_xxljob username: postgres password: <PG密码> driver-class-name: org.postgresql.Driver # 驱动 jar 已内置 postgresql-42.7.8 server.sh restart,登录 /xxl-job-admin(admin/123456 种子)验证列表/日志页正常。jnpf.license.licensePath(默认 /data/jnpfsoft/license.lic)存在且有效(绑定硬件),缺失即退出——本地开发机无 license 未验启动,**此步必须在现场有 license 的机器上做**。datasource.yaml(已是 PG),datasource-scheduletask.yaml 里只有 admin 地址无 DB 连接,无需改。PG 侧数据是**新写入**,源 MySQL 在停机窗口内**未被改动** → 回滚零数据损失:
datasource.yaml 切回 MySQL 原连接:yaml db-type: MySQL host: <现场源 MySQL 地址> port: 3306 db-name: jnpf_init username: <原用户> password: <原密码> @RefreshScope)。回滚判据:切 PG 后核心链路冒烟失败、或 §3.3 校验发现数据缺失且无法快速修复。
database "jnpf_init" does not exist 但 docker exec psql 能看到表(L016):宿主机另有 PG 占了 5432,服务连错实例。lsof -iTCP:5432 -sTCP:LISTEN 查占用;现场应确保只有容器监听 5432。form-urlencoded + AES(MD5(密码)),不能用 JSON 裸调。base_visual_release.f_web_type,webType=1 纯表单无列表属正常,非数据丢失。system-config.yaml 的 config.ApiDomain 要指现场网关;改后重启 jnpf-file。| 目的 | 命令 |
|---|---|
| 起 PG 容器(根 compose) | docker volume create postgres_pgdata && docker compose up -d cx-postgres |
| 建 schema(全新空库) | ./install.sh |
| 建业务角色(.env 定稿后) | ./create-app-roles.sh |
| 停机窗口迁数据 | SRC_MYSQL_HOST=... SRC_MYSQL_PWD='***' ./migrate-data.sh all |
| 只跑 PG 侧(mirror 已就绪) | ./migrate-data.sh load |
| 全量行数校验 | 见 §3.3 |
| 清理临时 mirror/dump | ./migrate-data.sh cleanup |
| 备份 | BACKUP_DIR=/data/pg-backup ./backup.sh |
| 进 PG 命令行 | docker exec -it cx-postgres psql -U postgres -d jnpf_init |