ARTICLE · 1040872
AIDBM支持对象存储备份恢复了!
坚持持续分享各类技术干货,大家有想要学习的内容,欢迎评论区留言。恪守 “开源知识来源于互联网,回归于互联网” 的初心,持续输出工作中实用的技术内容。不足之处恳请大家多多指正,彼此交流共同成长,筑牢技术根基。
关注公众号,技术道路不迷路

AIDBM 平台更新说明(2026-09-18 ~ 09-19)
本轮工作覆盖 09-18 至 09-19 两天,对应两次发布:v1.4.11(commit 775a06b)与 v1.4.12(commit 68eacbd)。
说明:09-18 做的内容当时未单独提交,随
ff434ff("同步入库累积改动")一并入库,因此两天的成果全部落在v1.4.10..v1.4.12这段区间:91 个文件、+23524 / -466 行。
两天做的事可以概括成两句话:
补了 5 项能力:对象存储备份(第 11 类保护对象)、「无 Agent」能力体系(可取证、可对外承诺)、对外 API 契约与离线文档、代码图谱生成器、质量门禁工程化(含 CI 与 pre-commit)。
修了 4 组缺陷:数据同步/迁移页数据库类型下拉失效、对象存储「恢复自动建桶」是假开关、文件备份单文件源失败且原因空白、MySQL 全实例写满磁盘报错不可读;以及用静态检查一次性扫出的 9 组"引用了但从未导入/定义"的崩溃隐患。
版本:v1.4.12。镜像已构建并推送:ghcr.io/zhh9126/backup-platform:v1.4.12(含 v1.4.12-20260919、latest 别名,image id 2db8923b6386,digest sha256:e8bc332422805ad93b7d0edc0905054a7ce22a6688cfe3134b3023e51e8521dd)。
更早版本的完整说明见 readme_20260916.md 及其之前的归档,本文不重复。
一、新增能力
1.1 对象存储备份(MinIO / 阿里云 OSS / 腾讯云 COS / S3 兼容)
新增代码:core/engines/object_storage.py(601 行)、core/objectstore/(client.py 517 / providers.py 239 / signers.py 235)、api/object_storage.py(254 行)、static/js/object_storage.js(909 行)、templates/object_storage.html(329 行)。菜单位置:备份管理 → 对象存储备份。
机制设计对齐 AWS Backup / Veeam / Rubrik 的业界共识:
| 机制 | 说明 |
|---|---|
| 元索引优先 | 产物不是"把桶抄一份",而是 manifest.json(对象级索引:key / size / ETag / LastModified / VersionId / StorageClass / SHA256)+ 对象本体。有索引才谈得上对象级细粒度恢复与缺失检测 |
| 永远增量 | 每次只传输 ETag / LastModified / Size 变化的对象;每个包带 parent 指针,恢复按 full → inc → … 回放,删除事件同样记录并回放 |
| 版本控制感知 | os_include_versions=true 捕获全部历史版本,与桶 Versioning 配合形成时间点保护;以删除标记为主体的 key 视为已删除 |
| 并行 + 流式落盘 | 并发拉取、下载直写本地文件,内存占用与对象大小解耦(本机曾因整包进内存触发 MemoryError,这里从设计上规避) |
| 桶侧零改造 | 协议会话由平台侧发起,桶侧零安装 |
传输层是自己实现的 S3 客户端(SigV4 签名),零第三方依赖,符合离线自足要求。任务字段复用现有 backup_tasks 表结构(不改表、不影响既有功能):host=端点、username/password=AccessKey/SecretKey(沿用平台加密存储)、db_name=桶名,其余走 extra_options;因此调度、记录、保留策略、三级存储/复制等既有链路零改动复用。
真实端到端验证(本机 Docker MinIO,scripts/object_storage_e2e.py,报告 docs/object_storage_e2e_report_20260919.md):32 项通过 / 0 失败,覆盖全量备份、逐字节一致性、增量只传变化对象、增量链回放、恢复后桶状态一致、前缀重映射、always/if_newer/never 覆盖策略、多版本捕获与还原、对象级细粒度恢复、深度校验(损坏包与缺失产物如实判失败)、桶不存在/密钥错误/配置缺失的失败原因。
未验证边界(如实标注):阿里云 OSS(V4 签名)、腾讯云 COS、华为云 OBS 本机无资源未做真实验证;分片上传(Multipart)尚未实现,超限对象会拿到服务端明文错误并如实失败,不会静默截断。
1.2「无 Agent」能力体系:从口号变成可判定等级
新增代码:core/agentless/(plan.py 195 / audit.py 191 / channels.py 130)、api/agentless.py(98 行)、templates/agentless.html(218 行)、scripts/agentless_forensics.py(324 行)、scripts/no_agent_audit.py(82 行)。菜单位置:备份管理 → 无 Agent。设计说明见 docs/agentless_architecture_20260919.md(借鉴 Apache HertzBeat 的"协议 + 数据化模板消灭目标端部署",但不吸收其通用监控面)。
核心产出是把口头承诺改成五档侵入度,每个任务/每次执行都能标出落在哪一档:
| 等级 | 含义 | 典型场景 |
|---|---|---|
| A0 | 零安装、零文件、零改配,只用账号 + 标准协议 | SSH 执行目标端自带 mysqldump/pg_dump;协议直连拉库列、数据对比 |
| A1 | 零安装、零文件,但调用目标端自带客户端 | 逻辑备份(expdp/impdp/rman/dexp/sqlcmd 都是数据库自带工具) |
| A2 | 临时文件落地,执行完立即清理 | xtrabackup/mariabackup 推送执行、恢复时临时解包 |
| A3 | 需客户侧改配置(不是装软件) | MySQL log-bin+ROW、PG wal_level+archive_command、Oracle ARCHIVELOG、达梦 RLOG_APPEND_LOGIC、金仓复制槽授权 |
| X(禁止) | 常驻进程 / 开机自启 / crontab / systemd unit / 常驻监听端口 / 写入系统目录 | 平台一律不支持——这是区别于"业界都要装 agent"的红线 |
平台当前真实通路共 6 条(每条都能在代码里定位):SSH/SFTP(paramiko)、原生数据库协议(core/native_conn.py)、JDBC 桥接(JVM 与 jar 都在平台侧)、HTTP/REST 控制面(PVE 等)、临时二进制推送(用完即删)、恢复时临时落地(用完即删)。
真实取证(docs/agentless_forensics_report_20260919.md):23 项通过 / 0 失败,备份前后两次取证结论均 PASS(ps / find / crontab / systemd / 包管理五个分节全空)。关键是投放了负向对照——在目标端放一个 /tmp/bk_agentless_canary 后取证立刻报 WARN 并指认到该文件,清除后回到 PASS,证明这套判定不是"只能报 PASS"。执行期临时二进制(bk_pushed_xb_1789800914、bk_pushed_xb_libs)在备份结束后确认已消失。
1.3 对外 API 契约与离线文档
新增代码:api/contract.py(228 行)、api/openapi.py(220 行)、core/error_codes.py(129 行)、templates/api_docs.html(176 行)、scripts/api_contract_check.py(229 行)。规范文档 docs/api_conventions.md + 存量债务清单 docs/api_naming_baseline.md。
| 规则 | 落点 |
|---|---|
版本前缀 /api/v1 | api/contract.py:V1_PREFIX(同一蓝图双注册,/api 兼容前缀与规范路径不会两套实现漂移) |
| 三段式错误响应 | core/error_codes.py + api/contract.py:normalize_response |
| 统一分页 | ?page=1&size=50 为主,兼容 ?limit/offset,size ≤ 500 自动收敛而非报错 |
| 命名规范(kebab-case / 集合复数) | scripts/api_contract_check.py,只拦新增违规,历史路径用规范别名收敛(如 /api/db-migrate → /api/v1/migration-plans) |
| OpenAPI 3.0 | api/openapi.py → /api/v1/openapi.json;文档页 /api/docs 本地自绘(内联 CSS/JS,不引 CDN 版 Swagger UI,离线可看) |
生成方式是从 Flask url_map + docstring 静态分析生成,不引入 apispec/flask-smorest 等新依赖,零构建、零联网。
1.4 代码图谱生成器(替代"改一个功能先通读几万行代码")
scripts/gen_code_graph.py(1900 行,纯标准库、零第三方依赖、只 ast.parse 不执行被分析代码),产出 docs/code_graph.md(3691 行,人读)+ docs/code_graph.json(6154 行,机器读)。
覆盖:分层架构与模块依赖、枢纽模块、循环/越层依赖体检、332 条 REST 路由索引(METHOD+URL+handler)、页面→JS→接口链路、元数据库表访问热点、关键业务链路、以及"想改 X 先看哪些文件"。人工领域注解每次生成都做存在性校验,失效即标 ⚠,防止图谱随代码演进腐化。
python scripts/gen_code_graph.py # 重新生成python scripts/gen_code_graph.py --diff# 看结构漂移python scripts/gen_code_graph.py --strict# CI 卡口(当前 ERROR 0)
当前统计:Python 文件 172 个 / 后端 72965 行 / 模板 33 个(7972 行)/ 前端 JS 12 个(11913 行)。
1.5 质量门禁工程化
新增 scripts/test_gate.sh、scripts/gate_baseline.py、scripts/test_baseline.json、scripts/frontend_smoke.py(414 行,HTTP 模式前端冒烟,无需浏览器)、scripts/hooks/pre-commit、scripts/install_git_hooks.sh、pytest.ini、requirements-dev.txt、.github/workflows/ci.yml(121 行)。
./scripts/test_gate.sh # 快速门禁:确定性用例 + 覆盖率门槛(提交前/CI 阻断)./scripts/test_gate.sh --full# 全量回归:与基线比对,禁止新增失败./scripts/test_gate.sh --ui# 前端冒烟./scripts/test_gate.sh --all# 发布前全跑./scripts/test_gate.sh --update-baseline
用例集与基线是显式的(不静默跳过):快速集只含当前 100% 通过的确定性模块;全量模式允许存量失败(环境依赖),但失败/错误数不得高于基线、通过数不得低于基线。覆盖率门槛当前取存量基线 17%(core+api 约 3.9 万行),只用于拦下降,每版上调目标 +5pt。
1.6 AI 智能助手增强
core/ai_agent/agent.py(+399)、core/ai_agent/tools.py(+400)、新增 static/js/agent-render.js(331 行)+ static/css/agent-render.css(209 行)。
工具执行改本地直调:工具的 executor 直接调用本地 Python API(models / scheduler / inspection / db),不再走内部 HTTP,规避 session/cookie 认证问题并显著降低延迟。
风险分级与条件确认:
Tool新增risk_level(low/medium/high,用于步骤卡着色与审计)与confirm_if(args) -> bool,支持"同一工具不同参数风险不同"(如巡检 quick 免确认、full 需确认)。渲染层:
agent-render.js零依赖离线可用——Markdown 安全渲染(先 HTML 转义再放行自生成标签)、状态词彩色徽章、工具调用轨迹渲染为可展开的"执行步骤卡"(中文名 + 参数 + 结果摘要 + 耗时 + 原始 JSON)。意图消歧补齐(v1.4.12):
_apply_intent_tiebreak此前在全项目从未定义(调用点已在用,属必崩路径),已按_INTENT_TOKENS既有设计补齐——命中"物理/逻辑/增量/全量/实时"等强意图词的任务整体前移,命中唯一时可直接执行,命中多个或只有"备份"这类弱词时交回用户澄清。新增用例 6/6 通过。
二、缺陷修复
2.1 数据同步/迁移页「数据库类型」下拉失效(用户截图反馈)
参见 docs/fix_ui_db_types_20260919.md。两个页面两种根因:
| 页面 | 现象 | 根因 |
|---|---|---|
| 数据同步 → 新建同步任务 | 下拉完全是空的 | sync.js 在 DOMContentLoaded 里同步调用填充,而类型清单要等 /api/meta 异步返回后才存在——永远抢在前面,拿到空数组 |
| 数据迁移 → 新建迁移计划 | 下拉只有 2 项 | templates/migration.html 两个 `` 里硬编码了 mysql 与 postgresql,MariaDB/金仓/达梦/Oracle/SQL Server 这些已支持的类型根本不出现 |
修复要点:api/system.py 抽出 build_meta()(/api/meta 与页面注入共用,避免两处漂移)并新增 sync_types(= 同步插件注册表,替代备份引擎全集,不再列出 redis/mongodb/neo4j 等不能同步的类型);base.html 在 bkp-core.js之前内联 window.__BKP_META__(首屏即有清单,消除竞态);bkp-core.js 新增 BKP.ensureMeta()(并发去重、失败不抛),fillDbTypeSelect 在 META 未就绪时自愈;迁移页两个 select 改为 JS 动态填充;默认端口改用 META.default_ports(PG 5432 / Oracle 1521 / 达梦 5236 / 金仓 54321…)并做类型↔端口联动(仅替换未被用户改过的端口)。
连带修掉的同类问题:_source_stats 此前只硬编码 MySQL 与 PostgreSQL,导致 Oracle/达梦/SQL Server 作源时被统计成 0 张表,预检查直接判"源库没有可迁移的业务表"、迁移根本进不去;创建接口不校验类型,伪造类型会落库跑到一半才失败。新增 tests/test_ui_db_types.py(300 行)。
竞态修复用 dukpy 真实执行了修复前后的源码做对照:修复前下拉 option 数 0,修复后 7。
2.2 对象存储「恢复时自动创建目标桶」是假开关
前端有开关、API 没透传。修复:api/object_storage.py 透传 create_bucket,引擎优先取弹窗开关值、缺省回退任务级 os_restore_create_bucket。
2.3 文件备份:单个文件源「备份失败 + 原因空白」(用户反馈问题 1)
根因:源路径填单个文件时引擎仍按目录处理(cd + tar -C 目录),且远端打包的 stderr 被 2>/dev/null 吞掉,失败消息成了「源打包失败: 」——冒号后面什么都没有。
修复:新增源路径真实探测(一次 SSH 用 -d/-f/-e 判定目录/文件/其它/不存在);单文件源改用 -C <父目录> <文件名> 打包并在归档内独立存根;打包失败必带 rc + 真实 stderr + 实际执行的命令(stderr 为空时显式说明"命令未输出任何错误信息"并打印命令原文)。顺带修掉三个同类隐患:源不存在/不支持时提前失败(不再产出"成功但空包"的假备份)、exclude_patterns 排除规则首次贯通(此前界面有、代码从未生效,现覆盖本地/远端四种传输组合,且快照记录当次规则,避免"规则变了"被误判成"文件被删除")、远端打包改流式落盘(大目录会 MemoryError,而 MemoryError 的报错文本是空的)。
2.4 MySQL 全实例:mysqldump: Got errno 28 on write 且失败文案误导(用户反馈问题 2)
磁盘写满不再只报一句天书:识别 ENOSPC,直接给出哪个路径满了、各自还剩多少、怎么处置;关键错误行自动从 mysqldump 无关警告(如
column statistics not supported)中挑出来。不再默认用
/tmp当中转:临时工作目录改到产物所在分区的.bp_work(config.FULL_INSTANCE_WORK_DIR,envBP_WORK_DIR)。备份前空间预检:估算口径为「逻辑统计 vs InnoDB 物理文件」逐库取大者——只信
information_schema.DATA_LENGTH会严重低估(实测刚灌 4MB 的表只报 16KB);物理大小按版本兼容查询(MySQL 8.0+ 用innodb_tablespaces,5.6/5.7 与 MariaDB 用innodb_sys_tablespaces,PG 用pg_database_size())。第二道防线:逐库 dump 之间检查余量,放不下下一个库就地终止并说明进度(已完成 n/N),不把设备写满才炸。
失败文案按原因分流:磁盘满给清空间指引、客户端缺失给工具安装/
tool_path指引、认证失败给地址端口账号口令核对指引,其余才提 SSH——不再一律写"请纳管 SSH 备份机"。
2.5 一批"引用了但从未导入/定义"的崩溃(v1.4.12)
用户连续反馈两处 NameError(AI 助手页 needs_confirm、操作日志页 contract),说明同类隐患不止一处。本轮改为写基于 symtable 的静态检查器一次性全仓扫描(186 文件),扫出 9 组 11 处并全部修复,检查器复跑 0 处残留:
| 位置 | 问题 |
|---|---|
api/logs.py、api/ai_alert.py | contract 未导入(操作日志页 500;告警页同一隐患尚未爆出) |
core/ai_agent/tools.py | json 未导入;_apply_intent_tiebreak全项目从未定义 |
core/cross_host.py | 跨主机全实例恢复脚本里 {db} 被当成 Python 变量(构造命令即崩),改 $db |
core/remote_dump.py | dumpall 签名缺 tool_path(调用方已传 → TypeError,函数体又引用 → NameError);MySQL 全实例 tar 用未定义的 task,改用新增 db_type 参数(值取真实探测的服务端 flavor,不信 task.db_type) |
core/probe.py | 金仓 JDBC 探测兜底参数名笔误 |
core/sync/engine.py | 缺 os / traceback |
core/engines/mongodb.py | 缺 subprocess |
core/db_adapters.py | shlex_quote → shlex.quote |
其中跨主机全实例恢复、MySQL 全实例 tar GTID 跳过、PG 系远端 dumpall 三条属此前 100% 失败的功能级致命路径。
三、可重复执行的验证资产
两天新增的脚本都能直接重跑,结果可复核(全部真实执行,不仿真):
| 脚本 | 用途 | 本次结果 |
|---|---|---|
scripts/object_storage_e2e.py | 对象存储备份/恢复/增量/版本/校验全链路(真实 MinIO) | 32/32,报告 docs/object_storage_e2e_report_20260919.md |
scripts/agentless_forensics.py | 目标端无 Agent / 零残留取证(含负向对照) | 23/23,报告 docs/agentless_forensics_report_20260919.md |
scripts/no_agent_audit.py | 仓库级"是否混入 agent 常驻物"审计 | 通过 |
python tests/e2e_v1411_fixes.py | §2.3 / §2.4 两个用户问题的端到端回归(10 项断言) | 10/10;容器内用 --tiny-dir /bpwork(docker run --tmpfs /bpwork:size=2m)造真实 ENOSPC |
python scripts/gen_code_graph.py | 代码图谱生成 / --diff / --strict | ERROR 0 |
python scripts/api_contract_check.py | API 命名与契约门禁(只拦新增) | 通过 |
./scripts/test_gate.sh [--full | --ui | --all] | 质量门禁 | 见 §1.5 |
python scripts/frontend_smoke.py | 前端冒烟(HTTP 模式) | 通过 |
回归结论:v1.4.11 用 git archive HEAD 导出干净快照跑同命令对比,全量 pytest 与基线一致(170 failed / 292 passed / 1 skipped / 30 errors,零回归;failed 项均为既有用例间共享临时库导致的存量冲突)。v1.4.12 跑 test_ai_agent/test_ai_alert/test_ai_alert_taskdetail/test_api_contract/test_custom_backup 为 145 passed / 8 failed,8 项失败与改动前完全同一批旧断言,零回归。
跑 pytest 必须带
CODEBUDDY_SAFE_DELETE_ENABLED=0,否则 safe-delete 钩子会让 pytest 直接 INTERNALERROR。
四、新增文件清单
api/ agentless.py contract.py object_storage.py openapi.pycore/ agentless/{__init__,plan,channels,audit}.py engines/object_storage.py objectstore/{__init__,client,providers,signers}.py error_codes.pytemplates/ object_storage.html agentless.html api_docs.htmlstatic/ js/object_storage.js js/agent-render.js css/agent-render.cssscripts/ gen_code_graph.py object_storage_e2e.py agentless_forensics.py no_agent_audit.py api_contract_check.py api_contract_baseline.json frontend_smoke.py test_gate.sh gate_baseline.py test_baseline.json hooks/pre-commit install_git_hooks.shdocs/ agentless_architecture_20260919.md agentless_forensics_report_20260919.md object_storage_e2e_report_20260919.md fix_ui_db_types_20260919.md api_conventions.md api_naming_baseline.md code_graph.md code_graph.jsontests/ e2e_v1411_fixes.py test_agentless_audit.py test_agentless_plan.py test_api_contract.py test_ui_db_types.py工程配置 .github/workflows/ci.yml pytest.ini requirements-dev.txt
五、已知边界与遗留(如实标注)
| 项 | 状态 |
|---|---|
| 阿里云 OSS / 腾讯云 COS / 华为云 OBS | 按公开文档实现,本机无资源未做真实验证 |
| 分片上传(Multipart) | 未实现;单 PUT 上限取决于服务端(AWS S3 5GB),超限会拿到服务端明文错误并如实失败,不会静默截断 |
| 8 个 AI 助手旧断言用例 | 仍失败,属过期断言(schema 加了 task_name、备份改为免确认策略),待确认是否同步更新 |
| 覆盖率门槛 | 当前仅取存量基线 17%,只拦下降,需逐版上调 |
| 本机平台进程 | 未重启,本机页面尚未加载新代码;镜像内已验证 /login=200 且含本轮全部改动 |
镜像拉取与运行:
docker pull ghcr.io/zhh9126/backup-platform:v1.4.12docker run -d-p8080:8080 -v /data/backup-platform:/data ghcr.io/zhh9126/backup-platform:v1.4.12
各类实用工具全部开源在GitHub,欢迎Star⭐收藏!
项目地址:https://github.com/Zhh9126
文章部分内容参考网络资料,如有侵权,请私信联系我删除。
公众号「小周的数据库进阶之路」持续分享 AI、数据库、中间件、云计算干货。看完觉得有所收获,不妨转发给身边技术好友,一起交流提升。想和业内大佬深度交流的朋友,可添加我的微信,拉你进专属技术交流群!