状态:已定版(2026-09-06 实施,2026-09-07 定版,契约 1.22.0;2026-09-09 数据库导出/恢复改为自有格式数据脚本,零外部工具,接口形状不变)
实施摘要:
com.dms.backup / Python dms/services/backup_service.py 双版对齐):链式全量+增量备份引擎(存储扫描→数据库数据脚本导出→差异拷贝→rsync 推送→链清理)、JSON 配置存储(运行目录 dms-backup-config.json,snake_case,双端互读互通)、定时触发器、备份历史与实时进度快照。paths/backup.yaml):GET/PUT /api/v1/backup/config、POST /api/v1/backup/jobs(202 受理)、GET /api/v1/backup/jobs/current、POST /api/v1/backup/jobs/current/cancel、GET /api/v1/backup/jobs;仅 ADMIN 可用;审计动作 BACKUP_CREATE(target=BACKUP,字典已含)。dms-server restore-backup [--backup-id dms-bak-xxx] [--yes],Java RestoreBackupCommand(--restore-backup);存储文件按清单恢复、数据库脚本驱动直连导入(禁用外键→清空业务表→导入→恢复外键)与 .env 副本落地提示(.env 存为 .env.restored,不覆盖当前配置)。features/backup/(类型守卫 backupTypes、backupApi、BackupDashboard)+ 管理员专属本地页签「备份管理」(配置表单、全量/增量触发、取消确认、2 秒进度轮询、历史清单表)。.env 配置副本;rsync://user@host:873/module/);本机目录模式作为开发/单机回退;dms-bak-yyyyMMdd-HHmmss-F(全量)/ dms-bak-yyyyMMdd-HHmmss-I(增量);manifest.json;目标根维护 index.json(全部备份索引);files 为存储全量清单;增量 manifest 的 files 为合并到链尾的累计清单(= 前驱清单 + 本次差异),恢复任一链尾即可得到完整文件视图;manifest.json 最后原子写入——目标端存在 manifest 才视该备份完成,中断的残缺目录在历史/恢复中不可见;阶段(BackupPhase):SCANNING(扫描存储)→ EXPORTING_DB(数据脚本导出)→ COPYING(拷贝文件)→ PUSHING(推送目标,仅 RSYNC)→ CLEANING(清理旧链)→ SUCCESS / FAILED。
database/dms-data.sql(驱动直连导出,零外部工具;实现于 Python sql_dump / Java SqlDataDumper,详见 2.6);.env:随包备份一份副本;yyyy-MM-dd HH:mm:ss(非 UTC)。LOCAL:直接写入本机目录(开发/单机);RSYNC:组包到本地临时目录后 rsync 推送异机(rsync 守护进程协议),整体进度取 rsync 输出百分比(pushPercent);密码存独立文件不回显(接口仅返回 rsync_password_set 布尔);BACKUP_TARGET_UNAVAILABLE(HTTP 503);已有任务在跑报 BACKUP_RUNNING(HTTP 409)。dms-backup-config.json(运行目录,snake_case):target_mode / local_path / rsync_url / schedule_enabled / schedule_time / full_interval_days / retain_full_count + 密码独立保存;保存时全量替换,rsync_password 为 null/空串表示保持已存密码;@Scheduled(fixedDelay=30s) / Python 后台线程轮询,到达 schedule_time(业务时区 HH:MM)且当日未触发时启动;类型决策 decideScheduledType:距最近全量 ≥ full_interval_days 天(或无可用链)→ FULL,否则 INCREMENTAL;retain_full_count,从最旧链开始整链删除(链 = 全量 + 其后所有增量)。dms-server restore-backup [--backup-id dms-bak-xxx] [--yes];Java:RestoreBackupCommand;.env 副本落为 .env.restored,不覆盖当前 .env(防止恢复机数据库地址不同导致服务起不来)。背景:达梦客户端工具 dexp/dimp 不随环境安装导致备份失败,且外部子进程难以诊断。改为自有格式数据脚本(参考 MySQL→DM 迁移工作区 temp/sql 的导入器与拓扑排序脚本设计):
database/dms-data.sql UTF-8 纯数据 SQL 脚本(无 DDL),头部注释元信息(格式版本/生成引擎/时间/源模式/表数量),逐表块 = 行数注释 + 可选 SET IDENTITY_INSERT 包裹 + 带列清单 INSERT(列清单保证备份旧/程序新时新列取默认值);manifest.database_file 字段值同步变更,接口形状不变;SqlDataDumper / Python sql_dump,驱动直连零外部工具):表清单来自 ALL_TABLES(含 alembic_version / flyway_schema_history 迁移簿记表,版本戳随备份走);外键拓扑序(Kahn,父表在前、同层字典序、环兜底字母序);单列主键表按 PK 升序取行(自引用表父行必先于子行);IDENTITY 以 SET ON/OFF 探测决定是否包裹;字面量渲染覆盖 NULL/布尔/数值/二进制(0x hex)/datetime(微秒)/date/字符串(单引号转义);run_upgrade_silent),Java 版报错引导先建库(Java 无完整 DM 迁移链);| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/v1/backup/config |
读取配置视图(8 字段,密码永不回显) |
| PUT | /api/v1/backup/config |
保存配置(全量替换;rsync_password null=保持) |
| POST | /api/v1/backup/jobs |
触发备份 {type: FULL\|INCREMENTAL},202 + 初始进度快照 |
| GET | /api/v1/backup/jobs/current |
当前进度快照 + running 布尔 |
| POST | /api/v1/backup/jobs/current/cancel |
请求取消,返回 {canceled} |
| GET | /api/v1/backup/jobs |
历史清单 {entries}(按备份 ID 升序,含链尾累计口径的清单统计) |
权限:全部仅 ADMIN;触发成功审计 BACKUP_CREATE;查询/进度/取消不产生审计。
NavigationItem.module = "BACKUP" 本地锚点,不走 allowedModules 下发;普通用户不可见,直接访问渲染「模块权限异常」);BackupDashboard 三区:任务区(全量/增量触发、取消确认弹窗、进度条与阶段/文件/字节统计、失败原因)、配置区(模式/路径/rsync 地址与密码/定时开关与时刻/全量间隔/保留链数)、历史区(ID/类型/前驱/起止时间/本次传输/清单累计/数据库导出大小);running 由真变假时自动刷新历史;backupTypes.ts 按实际 JSON 形状逐字段校验(配置/历史 snake_case,进度 camelCase)。pytest(干净 dms_test 库)289 通过 / 470 跳过(2026-09-07 定版复验,含修复 dmPython 驱动环境后恢复的 20 项),含 test_backup.py(引擎级 7 项真实执行:全量→增量链路、目标目录布局、链尾累计清单、无链升级全量、dexp 缺失 FAILED、单任务互斥、保留链清理与残留清扫、配置 JSON 往返与校验、进度快照形状;接口级 7 项沿用 B2 门控,提供 DMS_TEST_* 环境变量时执行);test_openapi_storage(1.22.0 版本、4 个备份路径白名单、Flask 路由与 OpenAPI 方法集精确一致)及 q1b/q2b/q3b 契约版本守卫;mvn compile / mvn test 通过(既有测试无回归);tsc --noEmit 通过;vitest run 66 文件 424 项全部通过;vite build 成功;dms-backup-config.json 与备份目录结构,Java 版与 Python 版互为可恢复(备份链清单格式一致)。DmBackupTool(dexp/dimp 子进程封装)与 Python 端同类封装;新增 Java SqlDataDumper、Python sql_dump 模块(双版逐条对齐);BackupEngine / RestoreBackupCommand / backup_service 全部改走驱动直连;manifest database_file 值 database/dms.dmp → database/dms-data.sql(字段与接口形状不变,无需升契约版本)。test_sql_dump.py 新增 19 项(切分/字面量/拓扑/导出块/恢复序列/空库与缺表守卫/外键失败回补),test_backup.py 改造为伪导出全绿,全套 317 通过 / 501 跳过;Java SqlDataDumperTest 新增 11 项(对齐 Python 用例),全套 49 项通过;mvn compile 通过。database/dms.dmp 不再被恢复命令识别(恢复 CLI 引导重新生成备份);DMS_DEXP_EXECUTABLE/DMS_DM_HOME 相关配置与探测逻辑一并移除(.env 未曾默认携带)。