| docs/功能测试报告/功能测试报告_汇总工作簿月度块扩列还原试点_2026-09-04.md | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| traffic-audit-server/src/main/java/com/trafficaudit/reportexport/service/ReportExportService.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
docs/¹¦ÄܲâÊÔ±¨¸æ/¹¦ÄܲâÊÔ±¨¸æ_»ã×ܹ¤×÷²¾Ô¶ȿéÀ©Áл¹ÔÊÔµã_2026-09-04.md
New file @@ -0,0 +1,30 @@ # åè½æµè¯æ¥åï¼æ±æ»å·¥ä½ç°¿æåº¦åæ©å/è¿åè¯ç¹ï¼2026-07ï¼ï¼2026-09-04ï¼ ## åè½è¯´æ ç¨æ·ææ¿ï¼ä»¥ã2026å¹´7æéè·¯è¿è¾éæ±æ»è¡¨.xlsxãï¼å·²æ¸ ç为â1-4 æéª¨æ¶âï¼ä¸ºæ¯çï¼ç±ç¨åºæ 5-7 ææåº¦åæåæå¤ä»½æ¯çè¿åï¼å®ç°âéææ©åçæ 1-N ææ±æ»è¡¨âçç¬¬ä¸æ¥è¯ç¹ï¼M1ï¼ç®æ æ 2026-07ï¼ãå£å¾ï¼1-4 æä¿çæ¯çæ°æ®ï¼ç´¯è®¡å ¬å¼æ¢å¤ä¸º 1-7 æï¼åæ¯ R åæ¯ç¨ 2025 å¹´ 1-7 æåï¼2025/2024 åèåä¿æä¸å¨ï¼è¾åºå¦åæ°æä»¶ï¼ä¸æ¹æ¯çã ä»£ç æ¹å¨ï¼ - traffic-audit-server/.../reportexport/service/ReportExportService.java - exportSummaryWorkbook å¢å ï¼å®ä½åæå¤ä»½æ¯çï¼_å¤ä»½_*_æ¸ çå_*.xlsxï¼æ {å¹´}å¹´{æ}æ å¹é ï¼æç´¢é ç½®ç®å½/user.dir/ä¸çº§ç®å½ä¸ä¸ªåéæ ¹ï¼â éæ ¼æâå¤ä»½æå 容è骨æ¶ç¼ºå¤±æå 容ä¸åâçåå æ ¼åå¡«ï¼å« 5-7 æè¡¨å¤´ãæ°æ®ãå ¬å¼ä¸ç´¯è®¡/åæ¯å ¬å¼ï¼ï¼ - æ°å¢ resolveSummaryDonor / restoreSummaryMonthlyBlocks / summaryCellText ä¸ä¸ªç§ææ¹æ³ï¼ - å¯¼åºæ¶ç½® fullCalcOnLoad=trueï¼ä¿è¯ Excel/WPS æå¼å³éç®è¢«æ¹å/è¿åçå ¬å¼ã ## æµè¯ç¯å¢ - å端 8090ï¼å¤æ¬¡éå¯åææ°ä»£ç 就绪ï¼ï¼MySQL 127.0.0.1:3308ã - æ¯çï¼docs/çææ±æ»å¤§è¡¨/2026å¹´7æéè·¯è¿è¾éæ±æ»è¡¨.xlsxï¼1-4 æéª¨æ¶ï¼19 页ç¾ï¼ï¼å¤ä»½ï¼docs/çææ±æ»å¤§è¡¨/_å¤ä»½_2026å¹´7æéè·¯è¿è¾éæ±æ»è¡¨_æ¸ çå_20260904.xlsxã - æ¹æ³ï¼è°ç¨ GET /api/report/export/summaryWorkbook?period=2026-07&mode=monthï¼ä¿å为 docs/çææ±æ»å¤§è¡¨/è¾åº/éè·¯è¿è¾éæ±æ»è¡¨_2026-07.xlsxï¼ç¨ openpyxl ä¸å¤ä»½æ¯çéæ ¼æ¯å¯¹ï¼å 容/å ¬å¼ææ¬ï¼éæ ·å¼ï¼ã ## ç¨ä¾ä¸ç»æ 1. è¿åå¹¿åº¦ï¼æ£åï¼ï¼æ¸ çæ¶è¢«è£ç 7 个å°è´¦/åæé¡µï¼è´§è¿/ç线å 车/åå¸å®¢è¿/å ¬äº¤/åºç§/ç½çº¦è½¦/轨é轮渡/ä¸å£å¾æç»ï¼ç¼ºå¤±åå æ ¼å ¨é¨è¡¥é½ï¼missing=0ï¼å ¶ä½é¡µï¼æå页/éèé¡µï¼æ¬æ å·®å¼ï¼æªæ¹å¨ãç»è®º PASSã 2. éæ ¼å¯¹ç §ï¼æ£åï¼ï¼è¾åºä¸å¤ä»½æ¯ç 19 é¡µå ¨éæ¯å¯¹ï¼real-diff=0ï¼ä» 31 æ ¼ä¸ºâæ´æ° vs 46143.0âçæ°å¼ç价表示差å¼ï¼Excel æ°å¼ç¸åï¼ãç»è®º PASSã 3. 累计/åæ¯å ¬å¼ï¼æ£åï¼ï¼æ½æ¥ ç线å 车/å ¬äº¤/åºç§/è´§è¿ çè¡ Q æ¢å¤ä¸º 1-7 æåæå¼æ±åï¼å¦ =G4+E4+C4+I4+K4+M4+O4ï¼ï¼R æ¢å¤ä¸º =Q/SUM(2025 å¹´ 1-7 æå)-1ï¼ä¸å¤ä»½ä¸è´ãç»è®º PASSã 4. æå¼å³éç®ï¼æ£åï¼ï¼äº§ç© xl/workbook.xml å« <calcPr fullCalcOnLoad="true"/>ãç»è®º PASSã 5. æ¯çæªè¢«æ¹å¨ï¼æ£åï¼ï¼çæå忝çæä»¶åèæªåï¼åªè¯»å¤å¶ï¼ãç»è®º PASSã 6. ç¼è¯ä¸å¯å¨ï¼mvn compile EXIT=0ï¼å端éå¯å°±ç»ªï¼æ¥å£ 200 è¿å xlsxãç»è®º PASSã ## åç°çé®é¢ä¸å¤ç½® 1. åç donor å®ä½åªæ¥é ç½®ç®å½ç¸å¯¹è·¯å¾ï¼è spring-boot:run çå·¥ä½ç®å½æ¯æ¨¡åç®å½ï¼traffic-audit-serverï¼ï¼å¯¼è´âæªæ¾å°å¤ä»½æ¯çâãè¾åºä»ä¸ºéª¨æ¶ï¼å·²æ¹ä¸ºä¸ resolveAnyTemplate ç¸åç 3 åéæ ¹ç®å½æ«æï¼å¤æµè¿åæåã 2. POI setForceFormulaRecalculation æªå calcPrï¼æ¹ä¸ºç´æ¥æä½ XSSFWorkbook.getCTWorkbook() ç calcPr.fullCalcOnLoad=trueï¼å·²éªè¯åå ¥ã ## ç»è®º M1 è¯ç¹éè¿ï¼ä» 1-4 æéª¨æ¶æ¯çå¯ç¨åºåè¿ååºä¸äººå·¥ 7 ææ¯çå 容é¶å·®å¼ç宿´å·¥ä½ç°¿ï¼å« 5-7 ææåº¦åä¸ 1-7 累计/åæ¯å ¬å¼ï¼ï¼è¾åºæä»¶ docs/çææ±æ»å¤§è¡¨/è¾åº/éè·¯è¿è¾éæ±æ»è¡¨_2026-07.xlsxãä¸ä¸æ¥ M1bï¼æä»½>7ï¼8-12 æï¼æ¶æâä¸ä¸æåå é+åå¼ç¨å¹³ç§»âèªå¨æå ¥æ°åï¼å¤ä»½æ å¯¹åºæä»½æ¶ç¨ï¼ï¼åæ¥å ¥ M2 å°è´¦æ°æ®ä»åºå å¡«å ã traffic-audit-server/src/main/java/com/trafficaudit/reportexport/service/ReportExportService.java
@@ -3207,9 +3207,20 @@ } try (InputStream in = new FileInputStream(template); XSSFWorkbook wb = new XSSFWorkbook(in)) { File donor = resolveSummaryDonor(year, month); if (donor != null) { try (InputStream din = new FileInputStream(donor); XSSFWorkbook dwb = new XSSFWorkbook(din)) { int restored = restoreSummaryMonthlyBlocks(wb, dwb); log.info("æ±æ»å·¥ä½ç°¿æåº¦åè¿å宿ï¼è¡¥é½/è¦çåå æ ¼æ°={}ï¼æ¯çï¼{}ï¼å¤ä»½æ¯çï¼{}ï¼", restored, template.getName(), donor.getName()); } } else { log.warn("æ±æ»å·¥ä½ç°¿æªæ¾å°åæå¤ä»½æ¯çï¼_å¤ä»½_{}å¹´{}æâ¦ï¼ï¼è¾åºå°ä¿ææ¯çç°ç¶ï¼å¯è½ä¸º 1-4 æéª¨æ¶ï¼", year, month); } dynamicSummaryYear(wb, year); applyTwoDecimalFormat(wb); autoFitContentColumns(wb); // æ±æ»è¡¨å¯¼åºåæå 容å 宽å宽ï¼åªå 宽ä¸ç¼©çªï¼ if (!wb.getCTWorkbook().isSetCalcPr()) wb.getCTWorkbook().addNewCalcPr(); wb.getCTWorkbook().getCalcPr().setFullCalcOnLoad(true); // è¿å/æ¹åå ¬å¼åæå¼å³éç® return toBytes(wb); } } @@ -3476,6 +3487,86 @@ return map; } /** å®ä½åæå¤ä»½æ¯çï¼æ¸ çåç宿´ææ¯çï¼ï¼ç¨äºæéª¨æ¶æ¯ç缺失ç 5-7 ææåº¦åæå¤ä»½è¿åï¼ * ä¸ resolveAnyTemplate ç¸åçç¥ï¼å¨é ç½®ç®å½ãuser.dir ç¸å¯¹ç®å½ãä¸çº§ç®å½ä¸ä¸ªåéæ ¹ä¸æ«æ */ private File resolveSummaryDonor(int year, int month) { String rel = summaryTemplateDir; while (rel.startsWith("./")) rel = rel.substring(2); String[] roots = { summaryTemplateDir, System.getProperty("user.dir") + "/" + rel, System.getProperty("user.dir") + "/../" + rel }; for (String root : roots) { if (root == null || root.trim().isEmpty()) continue; File dir = new File(root); File[] files = dir.listFiles((d, name) -> name.startsWith("_å¤ä»½_") && name.contains(year + "å¹´" + month + "æ") && name.contains("éè·¯è¿è¾éæ±æ»è¡¨")); if (files != null && files.length > 0) return files[0]; } return null; } /** æåæå¤ä»½æ¯çè¿åæåº¦åï¼å¡å¤ä»½æå å®¹èæ¯çåæ ¼ç¼ºå¤±/å 容ä¸åï¼æ¸ çæ¶è¢«è£æç 5-7 æå¼ä¸ç´¯è®¡å ¬å¼ï¼çåå æ ¼ï¼æ´æ ¼åå¡«å¹¶è¦ç */ private int restoreSummaryMonthlyBlocks(XSSFWorkbook wb, XSSFWorkbook donor) { int restored = 0; for (int i = 0; i < donor.getNumberOfSheets(); i++) { Sheet ds = donor.getSheetAt(i); if (ds == null) continue; Sheet bs = wb.getSheet(ds.getSheetName()); if (bs == null) continue; for (int r = ds.getFirstRowNum(); r <= ds.getLastRowNum(); r++) { Row dr = ds.getRow(r); if (dr == null) continue; Row br = bs.getRow(r); for (Cell dc : dr) { if (dc == null) continue; CellType t = dc.getCellType(); if (t == CellType.BLANK) continue; String dtx = summaryCellText(dc); if (dtx == null || dtx.isEmpty()) continue; Cell bc = (br == null) ? null : br.getCell(dc.getColumnIndex()); if (bc != null && dtx.equals(summaryCellText(bc))) continue; Row tr = (bc != null) ? br : bs.getRow(r); if (tr == null) tr = bs.createRow(r); Cell tc = (bc != null) ? bc : tr.getCell(dc.getColumnIndex()); if (tc == null) tc = tr.createCell(dc.getColumnIndex()); tc.setBlank(); if (t == CellType.FORMULA) { String f = dc.getCellFormula(); if (f != null) tc.setCellFormula(f); } else if (t == CellType.STRING) { tc.setCellValue(dc.getStringCellValue()); } else if (t == CellType.NUMERIC) { tc.setCellValue(dc.getNumericCellValue()); } else if (t == CellType.BOOLEAN) { tc.setCellValue(dc.getBooleanCellValue()); } restored++; } } } return restored; } /** åå æ ¼å å®¹ææ¬åï¼å ¬å¼å¸¦ = åç¼ï¼ï¼ç¨äºè·¨ç°¿æ¯å¯¹ */ private String summaryCellText(Cell c) { if (c == null) return null; switch (c.getCellType()) { case FORMULA: { String f = c.getCellFormula(); return f == null ? "" : "=" + f; } case STRING: return c.getStringCellValue(); case NUMERIC: return Double.toString(c.getNumericCellValue()); case BOOLEAN: return Boolean.toString(c.getBooleanCellValue()); default: return ""; } } /** æ±æ»å·¥ä½ç°¿å¹´ä»½å¨æåï¼ä» æ 2026å¹´ åç¼æ¿æ¢ä¸ºç®æ å¹´ï¼2025/2024 åèåä¸å¨ï¼ */ private void dynamicSummaryYear(XSSFWorkbook wb, int year) { String oldPrefix = "2026å¹´";