编辑 | blame | 历史 | 原始文档

功能测试报告 — 2026-07 母版补齐 & 导出件「Excel 打不开」修复

  • 日期:2026-09-16
  • 范围:汇总大表(/api/report/export/summaryWorkbook)2026-07 / 2026-08 导出;docs/生成汇总大表/2026年7月道路运输量汇总表.xlsx 母版补齐
  • 关联:docs/问题汇总/问题与注意事项汇总_2026-09-16.md 第十三节

一、背景

《 货运》页「累计」列口径在 2026-09-16 已在 8 月母版 落地(见第十二节)。用户确认「可以补齐」,要求把同一口径补到 7 月母版,并解决 7 月导出件无法用 Excel 打开的问题。

二、测试环境

项 值
后端 Spring Boot,8090(修复后已重启)
前端 Vue,8080
数据库 MySQL 127.0.0.1:3308 / traffic_audit
Excel 判据 Excel COM Workbooks.Open(每例独立实例,测后强杀孤儿进程)
结构判据 Python zipfile + xml.etree 逐部件校验、逐格 <c> 解析

三、发现的根因(两处,均为本次修复对象)

3.1 导出件打不开:残留 <is> 的非法单元格

坏件里存在 19 个**模式非法**的格子:

<c r="E15" s="111" t="n"><v>67.0</v><is><t>67.00</t></is></c>

<is>(inlineStr)只允许出现在 t="inlineStr" 的格里;t="n" 却带 <is> 属于非法,Excel 直接判定「无法读取的内容」拒绝打开(COM 表现为「不能取得类 Workbooks 的 Open 属性」,连修复模式 CorruptLoad=1 也打不开)。

成因:7 月母版由 openpyxl 写出(docProps/app.xml = Microsoft Excel Compatible / Openpyxl 3.1.5,文本用 <is> 存储)。导出时别的写入路径只改 t/<v>,**不清旧 <is>**;而 8 月母版是 Excel 原生(文本走 sharedStrings),所以同样代码下 8 月件正常、7 月件坏。

为什么只有 7 月坏:7 月存在同期备份母版 _备份_2026年7月道路运输量汇总表_清理前_20260904.xlsx,restoreSummaryMonthlyBlocks 会整格回填;回填写入 inlineStr 母版格时留下旧 <is>。8 月没有该 _备份_* 文件,不走回填,因此不触发。

定位方法(可复用):坏件与好件除 worksheets/styles/sharedStrings 外**全部部件逐字节相同** → 用 zip 杂交 + Excel 逐一开档做 leave-one-out,定位到 sheet9《公交》(17 格) + sheet12《轨道、轮渡》(2 格)。

3.2 母版补齐被备份母版「生成又复活」

补好 7 月母版后导出,货运 页 2022/2021 累计**又变回旧写死常量** —— 因为 restoreSummaryMonthlyBlocks 把备份母版(清理前、仍是旧常量)整格回填覆盖了母版的新公式。

四、修复内容

# 位置 改动
1 ReportExportService.exportSummaryWorkbook 落盘前新增 normalizeStaleInlineStrings(wb):把所有「非 inlineStr 却残留 <is>」的格子清掉(幂等),并打 warn 日志
2 ReportExportService.restoreSummaryMonthlyBlocks 新增 skipFreightHistoryBlocks:《 货运》页 T 列及以后(2025/2024/2023/2022/2021 年度块 + 各年累计/累计同比列)永不从备份母版回填,避免“生成又复活”(与既有「中口径排名」左块保护同一思路)
3 docs/生成汇总大表/2026年7月道路运输量汇总表.xlsx Excel COM 补齐:2025/2024/2023/2022/2021 五个年度块共 360 个块内格(r7/r9/r8/r10 = SUM(17 市州)、r5=r7+r9、r6=r8+r10)+ 324 个累计格(DX=SUM(DK:DV)、EM=SUM(EA:EL)、DY=IFERROR(DX/SUM(EA:EL)-1,"")),CalculateFullRebuild 后保存

备份:docs/生成汇总大表/_bak_2026年7月道路运输量汇总表_补齐前_20260916.xlsx(**故意不用 _备份_ 前缀**,否则会被 resolveSummaryDonor 当成 donor 回填)。

五、用例表

# 用例 输入 预期 实际 结论
1 坏件复现 2026-07 导出(修复前) 可打开 Excel 报「不能取得类 Workbooks 的 Open 属性」,重试 2 次仍失败 复现
2 环境排除 10KB 小件 / 8 月母版 / 7 月母版 / 09-08 旧 7 月导出 — 全部可打开;仅新 7 月导出失败 确认非环境问题
3 路径排除 坏件复制到另一目录改名 仍失败 仍失败 确认是文件内容问题
4 部件级杂交 坏件 + 好件的 styles/sharedStrings/worksheets 定位坏部件 坏件 styles + 坏件 sst + 好件全部 sheets → 可打开 定位到 worksheets
5 leave-one-out 19 个 sheet 逐个换成坏件版 找出坏 sheet 仅 sheet9《公交》/ sheet12《轨道、轮渡》 失败 定位到 2 页
6 负向:非法格计数 修复前导出件 — 19 个 t="n" 带 <is> 命中根因
7 正向:修复后 2026-07 重启后端后导出 可打开、19 页、累计对 OK sheets=19;DX5=144979.2825(=SUM(DK5:DV5))、DY5=-0.101235487797891、EM5=161309.53162、CU5=173045.296428512 通过
8 正向:修复后 2026-08 同上 可打开、累计不变 OK sheets=19;DZ5=144979.2825、EO5=161309.53162、CW5=173045.296428512、S5=130311.892698249 通过
9 跨月一致性 7 月 vs 8 月同口径格子 完全相等 2022 累计 / 2021 累计 / 2023 累计 三个月度 → 三个值**完全相等** 通过
10 非法格复扫 修复后 7 月导出 0 个 0 个 通过
11 母版可打开 补齐后用 Excel 打开 7 月母版 可打开 可打开 通过
12 残留数据复核 2026-09 五张表计数 全 0 全 0(h204 / investment_monthly / h2032 / audit_result / import_batch) 通过

六、发现的问题与处置

  1. 7 月导出件打不开(P0):normalizeStaleInlineStrings 根治;导出件从 941,960 字节(坏)→ 1,310,693 字节(好)。
  2. 备份母版回填让补齐失效(P0):skipFreightHistoryBlocks 保护 货运 T 列及以后。
  3. Excel COM 会留孤儿进程:每例测完必须强杀本次新建的 EXCEL 进程,否则后续 Workbooks.Open 会**间歇性假报**同样的错(本次排查初期被此干扰过)。属排查工具坑,已记入问题汇总。
  4. 注意:本次**未**改动 8 月母版与任何入库数据。

七、结论

  • 2026-07 / 2026-08 两个导出件均可被 Excel 正常打开(各 19 页),且《 货运》页 2021/2022/2023 累计列口径**完全一致**。
  • 7 月母版已按 8 月母版口径补齐(年度块 360 格 + 累计 324 格),并从 openpyxl 血缘转为 Excel 原生。
  • 用户要求的「残留数据全部清除」经复核为 0 残留。

八、遗留

  • 2026-06 / 2026-09 无母版 → 预览返回 ready=False「未找到模板文件」,需新建母版才能出数(用户第 5 问的延伸)。
  • 仓库根目录 _*.* 临时排查文件(含本次 _hyb_* / _loo_* / _v2_* / _final_*)未删,待用户同意。