From a08c70db67e49bf6cd125df04ebaf68c26f0d8dd Mon Sep 17 00:00:00 2001
From: zhizhijie <zhizhijie@users.noreply.gitee.com>
Date: 星期日, 20 九月 2026 16:19:16 +0800
Subject: [PATCH] feat(投资): 市州月报统一填报模板 + 县(市、区)列解析 + 表头行判定收紧

---
 traffic-audit-server/src/main/java/com/trafficaudit/reportexport/controller/ReportExportController.java |  320 +++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 files changed, 311 insertions(+), 9 deletions(-)

diff --git a/traffic-audit-server/src/main/java/com/trafficaudit/reportexport/controller/ReportExportController.java b/traffic-audit-server/src/main/java/com/trafficaudit/reportexport/controller/ReportExportController.java
index e5f63df..f4e8e8a 100644
--- a/traffic-audit-server/src/main/java/com/trafficaudit/reportexport/controller/ReportExportController.java
+++ b/traffic-audit-server/src/main/java/com/trafficaudit/reportexport/controller/ReportExportController.java
@@ -2,17 +2,21 @@
 
 import com.trafficaudit.common.Result;
 import com.trafficaudit.reportexport.service.ReportExportService;
+import com.trafficaudit.reportexport.service.ReportExportTaskService;
+import com.trafficaudit.system.service.OperationLogService;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.http.HttpHeaders;
 import org.springframework.http.MediaType;
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
 
 import javax.annotation.Resource;
 import java.net.URLEncoder;
+import java.util.List;
 import java.nio.charset.StandardCharsets;
 
 @Slf4j
@@ -22,32 +26,330 @@
 
     @Resource
     private ReportExportService reportService;
+    @Resource
+    private ReportExportTaskService exportTaskService;
+    @Resource
+    private OperationLogService operationLogService;
 
     /** 鐢熸垚_璐ц繍閲忓垎甯傚窞鏄庣粏.xlsx */
     @GetMapping("/export/cityDetail")
-    public ResponseEntity<byte[]> exportCityDetail(@RequestParam("period") String period) throws Exception {
-        return buildResponse(reportService.exportCityDetail(period), "鐢熸垚_璐ц繍閲忓垎甯傚窞鏄庣粏.xlsx");
+    public ResponseEntity<byte[]> exportCityDetail(@RequestParam("period") String period,
+                                                    @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception {
+        logExport("鐢熸垚_璐ц繍閲忓垎甯傚窞鏄庣粏.xlsx", period);
+        return buildResponse(reportService.exportCityDetail(period, mode), "鐢熸垚_璐ц繍閲忓垎甯傚窞鏄庣粏.xlsx");
     }
 
     /** 鐢熸垚_璐ц繍閲忔帓鍚�.xlsx */
     @GetMapping("/export/freightRank")
-    public ResponseEntity<byte[]> exportFreightRank(@RequestParam("period") String period) throws Exception {
-        return buildResponse(reportService.exportFreightRank(period), "鐢熸垚_璐ц繍閲忔帓鍚�.xlsx");
+    public ResponseEntity<byte[]> exportFreightRank(@RequestParam("period") String period,
+                                                     @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception {
+        logExport("鐢熸垚_璐ц繍閲忔帓鍚�.xlsx", period);
+        return buildResponse(reportService.exportFreightRank(period, mode), "鐢熸垚_璐ц繍閲忔帓鍚�.xlsx");
     }
 
     /** 鐢熸垚_鍛ㄨ浆閲忔帓鍚�.xlsx */
     @GetMapping("/export/turnoverRank")
-    public ResponseEntity<byte[]> exportTurnoverRank(@RequestParam("period") String period) throws Exception {
-        return buildResponse(reportService.exportTurnoverRank(period), "鐢熸垚_鍛ㄨ浆閲忔帓鍚�.xlsx");
+    public ResponseEntity<byte[]> exportTurnoverRank(@RequestParam("period") String period,
+                                                      @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception {
+        logExport("鐢熸垚_鍛ㄨ浆閲忔帓鍚�.xlsx", period);
+        return buildResponse(reportService.exportTurnoverRank(period, mode), "鐢熸垚_鍛ㄨ浆閲忔帓鍚�.xlsx");
+    }
+
+    /** 鐢熸垚_鍏矾鏃呭鍒嗗競宸炴槑缁�.xlsx锛坢onth: 鎶ヨ〃鏈� yyyy-MM锛泍ear: 骞翠唤 yyyy锛� */
+    @GetMapping("/export/passengerCityDetail")
+    public ResponseEntity<byte[]> exportPassengerCityDetail(@RequestParam("period") String period,
+                                                            @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception {
+        logExport("鐢熸垚_鍏矾鏃呭鍒嗗競宸炴槑缁�.xlsx", period);
+        return buildResponse(reportService.exportPassengerCityDetail(period, mode), "鐢熸垚_鍏矾鏃呭鍒嗗競宸炴槑缁�.xlsx");
+    }
+
+
+    /** 鐢熸垚_鍏矾鏃呭涓彛寰勬槑缁�.xlsx */
+    @GetMapping("/export/passengerMidDetail")
+    public ResponseEntity<byte[]> exportPassengerMidDetail(@RequestParam("period") String period,
+                                                           @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception {
+        logExport("鐢熸垚_鍏矾鏃呭涓彛寰勬槑缁�.xlsx", period);
+        return buildResponse(reportService.exportPassengerMidDetail(period, mode), "鐢熸垚_鍏矾鏃呭涓彛寰勬槑缁�.xlsx");
+    }
+
+    /** 鐢熸垚_鍏矾鏃呭涓彛寰勬帓鍚�.xlsx */
+    @GetMapping("/export/passengerMidRank")
+    public ResponseEntity<byte[]> exportPassengerMidRank(@RequestParam("period") String period,
+                                                         @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception {
+        logExport("鐢熸垚_鍏矾鏃呭涓彛寰勬帓鍚�.xlsx", period);
+        return buildResponse(reportService.exportPassengerMidRank(period, mode), "鐢熸垚_鍏矾鏃呭涓彛寰勬帓鍚�.xlsx");
+    }
+
+    /** 鐢熸垚_鍏矾鏃呭涓彛寰勫垎鏋�.xlsx */
+    @GetMapping("/export/passengerMidAnalysis")
+    public ResponseEntity<byte[]> exportPassengerMidAnalysis(@RequestParam("period") String period,
+                                                             @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception {
+        logExport("鐢熸垚_鍏矾鏃呭涓彛寰勫垎鏋�.xlsx", period);
+        return buildResponse(reportService.exportPassengerMidAnalysis(period, mode), "鐢熸垚_鍏矾鏃呭涓彛寰勫垎鏋�.xlsx");
+    }
+
+    /** 鐢熸垚_閬撹矾杩愯緭閲忔眹鎬昏〃.xlsx锛堟眹鎬诲伐浣滅翱 P0-P1 楠ㄦ灦锛氭暣鏈鍒舵瘝鐗堬紝椤电/鍏紡/2025 缂撳瓨淇濈暀锛� */
+    @GetMapping("/export/summaryWorkbook")
+    public ResponseEntity<byte[]> exportSummaryWorkbook(@RequestParam("period") String period,
+                                                        @RequestParam(value = "mode", defaultValue = "month") String mode,
+                                                        @RequestParam(value = "toMonth", required = false) Integer toMonth) throws Exception {
+        logExport("閬撹矾杩愯緭閲忔眹鎬昏〃.xlsx", period);
+        return buildResponse(reportService.exportSummaryWorkbook(period, mode, toMonth), String.format("鐢熸垚_閬撹矾杩愯緭閲忔眹鎬昏〃_%s.xlsx", period));
+    }
+
+    /**
+     * 姹囨�诲ぇ琛ㄥ紓姝ュ鍑猴細绔嬪嵆杩斿洖浠诲姟鍙凤紝鍓嶇杞杩涘害銆佸畬鎴愬悗鍐嶄笅杞芥枃浠躲��
+     * 鐢熸垚涓�娆$害 10~20 绉掞紝璧板紓姝ュ彲閬垮厤鐐瑰嚮鍚庨暱鏃堕棿鏃犲搷搴旓紱鍚屾姤琛ㄦ湡 10 鍒嗛挓鍐呯殑缁撴灉鐩存帴澶嶇敤锛堟湡闂存湁瀵煎叆鍒欒嚜鍔ㄥけ鏁堬級锛�
+     * refresh=true 鍙己鍒堕噸鏂扮敓鎴愩��
+     */
+    @GetMapping("/exportTask/summaryWorkbook")
+    public Result<java.util.Map<String, Object>> submitSummaryWorkbookTask(@RequestParam("period") String period,
+                                                                          @RequestParam(value = "mode", defaultValue = "month") String mode,
+                                                                          @RequestParam(value = "toMonth", required = false) Integer toMonth,
+                                                                          @RequestParam(value = "refresh", defaultValue = "false") boolean refresh) {
+        ReportExportTaskService.Task task = exportTaskService.submit(
+                ReportExportTaskService.TYPE_SUMMARY_WORKBOOK, period, mode, toMonth, refresh);
+        if (!task.isFromCache()) {
+            logExport("閬撹矾杩愯緭閲忔眹鎬昏〃.xlsx", period);
+        }
+        return Result.ok(task.toView());
+    }
+
+    /** 鏌ヨ寮傛瀵煎嚭浠诲姟杩涘害锛堢粨鏋滀繚鐣� 30 鍒嗛挓锛� */
+    @GetMapping("/exportTask/{taskId}")
+    public Result<java.util.Map<String, Object>> exportTaskStatus(@PathVariable("taskId") String taskId) {
+        ReportExportTaskService.Task task = exportTaskService.get(taskId);
+        if (task == null) {
+            return Result.error("瀵煎嚭浠诲姟涓嶅瓨鍦ㄦ垨宸茶繃鏈燂紙鐢熸垚缁撴灉淇濈暀 30 鍒嗛挓锛�");
+        }
+        return Result.ok(task.toView());
+    }
+
+    /** 涓嬭浇寮傛瀵煎嚭浠诲姟宸茬敓鎴愮殑鏂囦欢 */
+    @GetMapping("/exportTask/{taskId}/file")
+    public ResponseEntity<byte[]> exportTaskFile(@PathVariable("taskId") String taskId) throws Exception {
+        ReportExportTaskService.Task task = exportTaskService.get(taskId);
+        if (task == null) {
+            throw new RuntimeException("瀵煎嚭浠诲姟涓嶅瓨鍦ㄦ垨宸茶繃鏈燂紙鐢熸垚缁撴灉淇濈暀 30 鍒嗛挓锛�");
+        }
+        if (!task.isDone()) {
+            throw new RuntimeException("瀵煎嚭浠诲姟灏氭湭瀹屾垚锛堝綋鍓嶇姸鎬侊細" + task.getStatus() + "锛夛紝璇风◢鍚庡啀璇�");
+        }
+        return buildResponse(task.getData(), task.getFileName());
+    }
+
+    /** 鐢熸垚_缃戠害杞﹀杩愰噺鎷嗗垎琛�.xlsx锛堟湀搴︼細璁㈠崟闄勪欢1 + 鍏ㄧ渷 pin + 宸℃父鍑虹鏈堟姤锛� */
+    @GetMapping("/export/wycSplit")
+    public ResponseEntity<byte[]> exportWycSplit(@RequestParam("period") String period,
+                                                 @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception {
+        logExport("鐢熸垚_缃戠害杞﹀杩愰噺鎷嗗垎琛�.xlsx", period);
+        return buildResponse(reportService.exportWycSplit(period, mode), String.format("鐢熸垚_缃戠害杞﹀杩愰噺鎷嗗垎琛╛%s.xlsx", period));
+    }
+
+    /** 鐢熸垚_鑳借繍姹囨�昏〃.xlsx锛圚204锛屽叏鐪� + 鍚勫競宸� sheet锛� */
+    @GetMapping("/export/energySummary")
+    public ResponseEntity<byte[]> exportEnergySummary(@RequestParam("period") String period) throws Exception {
+        logExport("鐢熸垚_鑳借繍姹囨�昏〃.xlsx", period);
+        return buildResponse(reportService.exportEnergySummary(period), "鐢熸垚_鑳借繍姹囨�昏〃.xlsx");
+    }
+    /** 鐢熸垚_鍩庡競鍏氦瀹㈣繍閲忓垎甯傚窞鏄庣粏.xlsx锛堝煄甯傚杩愮1姝ワ紝鍏氦锛� */
+    @GetMapping("/export/cityBusDetail")
+    public ResponseEntity<byte[]> exportCityBusDetail(@RequestParam("period") String period,
+                                                       @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception {
+        logExport("鐢熸垚_鍩庡競鍏氦瀹㈣繍閲忓垎甯傚窞鏄庣粏.xlsx", period);
+        return buildResponse(reportService.exportCityBusDetail(period, mode), "鐢熸垚_鍩庡競鍏氦瀹㈣繍閲忓垎甯傚窞鏄庣粏.xlsx");
+    }
+
+    /** 鐢熸垚_宸℃父鍑虹瀹㈣繍閲忓垎甯傚窞鏄庣粏.xlsx锛堝煄甯傚杩愮2姝ワ紝宸℃父鍑虹锛� */
+    @GetMapping("/export/cityTaxiDetail")
+    public ResponseEntity<byte[]> exportCityTaxiDetail(@RequestParam("period") String period,
+                                                        @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception {
+        logExport("鐢熸垚_宸℃父鍑虹瀹㈣繍閲忓垎甯傚窞鏄庣粏.xlsx", period);
+        return buildResponse(reportService.exportCityTaxiDetail(period, mode), "鐢熸垚_宸℃父鍑虹瀹㈣繍閲忓垎甯傚窞鏄庣粏.xlsx");
+    }
+
+    /** 鐢熸垚_2026骞村叏鐪侀瀹夋帓璁″垝杩涘害鎯呭喌姹囨��.xlsx锛堟姇璧勬ā鍧楄〃1锛� */
+    @GetMapping("/export/investPlan")
+    public ResponseEntity<byte[]> exportInvestPlan(@RequestParam("period") String period,
+                                                    @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception {
+        logExport("鐢熸垚_鍏ㄧ渷棰勫畨鎺掕鍒掕繘搴︽儏鍐垫眹鎬�.xls", period);
+        return buildResponse(reportService.investPlan(period, mode), "鐢熸垚_鍏ㄧ渷棰勫畨鎺掕鍒掕繘搴︽儏鍐垫眹鎬�.xls");
+    }
+
+    /** 鐢熸垚_"鍗佷簲浜�"瑙勫垝鐗╂祦椤圭洰杩涘睍鎯呭喌.xlsx锛堟姇璧勬ā鍧楄〃2锛� */
+    @GetMapping("/export/investFiveYearLogistics")
+    public ResponseEntity<byte[]> exportInvestFiveYearLogistics(@RequestParam("period") String period,
+                                                                 @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception {
+        logExport("鐢熸垚_鍗佷簲浜旇鍒掔墿娴侀」鐩繘灞曟儏鍐�.xls", period);
+        return buildResponse(reportService.investFiveYearLogistics(period, mode), "鐢熸垚_鍗佷簲浜旇鍒掔墿娴侀」鐩繘灞曟儏鍐�.xls");
+    }
+
+    /** 鐢熸垚_婀栧寳鐪侊紙瀹㈣揣绔欏満锛変嚎鍏冩姇璧勯」鐩�.xls锛堟姇璧勬ā鍧楄〃3锛屾姇璧勬�昏〃+瑙勪笂椤圭洰琛ㄤ袱sheet锛� */
+    @GetMapping("/export/investBillion")
+    public ResponseEntity<byte[]> exportInvestBillion(@RequestParam("period") String period,
+                                                       @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception {
+        logExport("鐢熸垚_婀栧寳鐪侊紙瀹㈣揣绔欏満锛変嚎鍏冩姇璧勯」鐩�.xls", period);
+        return buildResponse(reportService.investBillion(period, mode), "鐢熸垚_婀栧寳鐪侊紙瀹㈣揣绔欏満锛変嚎鍏冩姇璧勯」鐩�.xls");
+    }
+
+    /** 鐢熸垚_锛堢粡娴庡己鍘块」鐩級浜ら�氱墿娴佸熀纭�璁炬柦鎶曡祫缁熻鎶ヨ〃.xlsx锛堟姇璧勬ā鍧楄〃4锛� */
+    @GetMapping("/export/investCounty")
+    public ResponseEntity<byte[]> exportInvestCounty(@RequestParam("period") String period,
+                                                      @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception {
+        logExport("鐢熸垚_缁忔祹寮哄幙浜ら�氱墿娴佸熀纭�璁炬柦鎶曡祫缁熻鎶ヨ〃.xls", period);
+        return buildResponse(reportService.investCounty(period, mode), "鐢熸垚_缁忔祹寮哄幙浜ら�氱墿娴佸熀纭�璁炬柦鎶曡祫缁熻鎶ヨ〃.xls");
+    }
+
+    /** 鐢熸垚_X鏈堝杩愮珯鎶曡祫鏄庣粏琛�.xlsx锛堟姇璧勬ā鍧楁槑缁嗚〃1锛氬杩愮珯鍦猴級 */
+    @GetMapping("/export/investPassengerDetail")
+    public ResponseEntity<byte[]> exportInvestPassengerDetail(@RequestParam("period") String period,
+                                                              @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception {
+        logExport("鐢熸垚_X鏈堝杩愮珯鎶曡祫鏄庣粏琛�.xlsx", period);
+        return buildResponse(reportService.exportInvestPassengerDetail(period, mode), "鐢熸垚_瀹㈣繍绔欐姇璧勬槑缁嗚〃.xlsx");
+    }
+
+    /** 鐢熸垚_X鏈堢墿娴佺珯鍦烘姇璧勬槑缁�.xls锛堟姇璧勬ā鍧楁槑缁嗚〃2锛氱墿娴佸洯鍖猴級 */
+    @GetMapping("/export/investLogisticsDetail")
+    public ResponseEntity<byte[]> exportInvestLogisticsDetail(@RequestParam("period") String period,
+                                                              @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception {
+        logExport("鐢熸垚_X鏈堢墿娴佺珯鍦烘姇璧勬槑缁�.xls", period);
+        return buildResponse(reportService.exportInvestLogisticsDetail(period, mode), "鐢熸垚_鐗╂祦绔欏満鎶曡祫鏄庣粏.xls");
+    }
+
+    /** 鐢熸垚_杞ㄩ亾杞浮瀹㈣繍閲忓垎甯傚窞鏄庣粏.xlsx锛堝煄甯傚杩愶紝杞ㄩ亾/杞浮鍙栬嚜鍏氦婧愭枃浠讹級 */
+    @GetMapping("/export/cityRailFerryDetail")
+    public ResponseEntity<byte[]> exportCityRailFerryDetail(@RequestParam("period") String period,
+                                                             @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception {
+        logExport("鐢熸垚_杞ㄩ亾杞浮瀹㈣繍閲忓垎甯傚窞鏄庣粏.xlsx", period);
+        return buildResponse(reportService.exportCityRailFerryDetail(period, mode), "鐢熸垚_杞ㄩ亾杞浮瀹㈣繍閲忓垎甯傚窞鏄庣粏.xlsx");
+    }
+
+    /** 鐢熸垚_鍩庡競瀹㈣繍瀹㈣繍閲忓垎甯傚窞绱.xlsx锛堝煄甯傚垎甯傚窞_妯℃澘搴曠锛� */
+    @GetMapping("/export/cityPassengerCitySum")
+    public ResponseEntity<byte[]> exportCityPassengerCitySum(@RequestParam("period") String period,
+                                                              @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception {
+        logExport("鐢熸垚_鍩庡競瀹㈣繍瀹㈣繍閲忓垎甯傚窞绱.xlsx", period);
+        return buildResponse(reportService.exportCityPassengerCitySum("鐢熸垚_鍩庡競瀹㈣繍瀹㈣繍閲忓垎甯傚窞绱.xlsx", period, mode), "鐢熸垚_鍩庡競瀹㈣繍瀹㈣繍閲忓垎甯傚窞绱.xlsx");
+    }
+
+    /** 鐢熸垚_鍩庡競瀹㈣繍鍚勫競宸炴槑缁嗚〃.xlsx锛堝煄甯傚杩愬悇甯傚窞鏄庣粏琛╛妯℃澘搴曠锛� */
+    @GetMapping("/export/cityPassengerDetailSum")
+    public ResponseEntity<byte[]> exportCityPassengerDetailSum(@RequestParam("period") String period,
+                                                                @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception {
+        logExport("鐢熸垚_鍩庡競瀹㈣繍鍚勫競宸炴槑缁嗚〃.xlsx", period);
+        return buildResponse(reportService.exportCityPassengerCitySum("鐢熸垚_鍩庡競瀹㈣繍鍚勫競宸炴槑缁嗚〃.xlsx", period, mode), "鐢熸垚_鍩庡競瀹㈣繍鍚勫競宸炴槑缁嗚〃.xlsx");
+    }
+
+    /** 鐢熸垚_鍩庡競瀹㈣繍姹囨��.xlsx锛堝煄甯傛眹鎬籣妯℃澘搴曠锛� */
+    @GetMapping("/export/cityPassengerSummary")
+    public ResponseEntity<byte[]> exportCityPassengerSummary(@RequestParam("period") String period,
+                                                              @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception {
+        logExport("鐢熸垚_鍩庡競瀹㈣繍姹囨��.xlsx", period);
+        return buildResponse(reportService.exportCityPassengerSummary(period, mode), "鐢熸垚_鍩庡競瀹㈣繍姹囨��.xlsx");
+    }
+
+    /** 鐢熸垚_鍩庡競瀹㈣繍鎶ヨ〃绯诲垪.zip锛堜竴閿墦鍖呭叏閮細鍏氦/鍑虹/杞ㄩ亾杞浮鏄庣粏 + 鍒嗗競宸炵疮璁� + 鍚勫競宸炴槑缁嗚〃 + 姹囨�伙級 */
+    @GetMapping("/export/cityPassengerSeries")
+    public ResponseEntity<byte[]> exportCityPassengerSeries(@RequestParam("period") String period,
+                                                             @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception {
+        logExportZip("鐢熸垚_鍩庡競瀹㈣繍鎶ヨ〃绯诲垪.zip", period);
+        String fileName = String.format("鐢熸垚_鍩庡競瀹㈣繍鎶ヨ〃绯诲垪_%s.zip", period);
+        String encoded = URLEncoder.encode(fileName, "UTF-8").replace("+", "%20");
+        return ResponseEntity.ok()
+            .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8''" + encoded)
+            .contentType(MediaType.parseMediaType("application/zip"))
+            .body(reportService.exportCityPassengerSeries(period, mode));
+    }
+
+    /** 鐢熸垚_璐ц繍鎶ヨ〃绯诲垪.zip锛堜竴閿墦鍖咃細鏄庣粏+鎺掑悕锛� */
+    @GetMapping("/export/freightSeries")
+    public ResponseEntity<byte[]> exportFreightSeries(@RequestParam("period") String period,
+                                                       @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception {
+        logExportZip("鐢熸垚_璐ц繍鎶ヨ〃绯诲垪.zip", period);
+        return zipResponse(reportService.exportFreightSeries(period, mode), String.format("鐢熸垚_璐ц繍鎶ヨ〃绯诲垪_%s.zip", period));
+    }
+
+    /** 鐢熸垚_鍏矾鏃呭鎶ヨ〃绯诲垪.zip锛堜竴閿墦鍖咃細鍒嗗競宸炴槑缁�+涓彛寰勬槑缁�/鎺掑悕/鍒嗘瀽锛� */
+    @GetMapping("/export/passengerSeries")
+    public ResponseEntity<byte[]> exportPassengerSeries(@RequestParam("period") String period,
+                                                        @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception {
+        logExportZip("鐢熸垚_鍏矾鏃呭鎶ヨ〃绯诲垪.zip", period);
+        return zipResponse(reportService.exportPassengerSeries(period, mode), String.format("鐢熸垚_鍏矾鏃呭鎶ヨ〃绯诲垪_%s.zip", period));
+    }
+
+    /** 鐢熸垚_鎶曡祫鎶ヨ〃绯诲垪.zip锛堜竴閿墦鍖咃細棰勫畨鎺�/鍗佷簲浜�/浜垮厓/缁忔祹寮哄幙锛� */
+    @GetMapping("/export/investSeries")
+    public ResponseEntity<byte[]> exportInvestSeries(@RequestParam("period") String period,
+                                                      @RequestParam(value = "mode", defaultValue = "month") String mode) throws Exception {
+        logExportZip("鐢熸垚_鎶曡祫鎶ヨ〃绯诲垪.zip", period);
+        return zipResponse(reportService.exportInvestSeries(period, mode), String.format("鐢熸垚_鎶曡祫鎶ヨ〃绯诲垪_%s.zip", period));
+    }
+
+    /** 鐢熸垚_鎵�閫夋姤琛�.zip锛歵ypes 閫楀彿鍒嗛殧锛堝墠绔�"鐢熸垚閫変腑"澶氶�夛級 */
+    @GetMapping("/export/selected")
+    public ResponseEntity<byte[]> exportSelected(@RequestParam("period") String period,
+                                                 @RequestParam(value = "mode", defaultValue = "month") String mode,
+                                                 @RequestParam("types") String types) throws Exception {
+        List<String> typeList = new java.util.ArrayList<>();
+        for (String t : types.split(",")) {
+            String s = t.trim();
+            if (!s.isEmpty()) typeList.add(s);
+        }
+        logExportZip("鐢熸垚_鎵�閫夋姤琛�.zip", period);
+        return zipResponse(reportService.exportSelectedSeries(period, mode, typeList),
+                String.format("鐢熸垚_鎵�閫夋姤琛╛%s.zip", period));
+    }
+
+    /** 瀵煎嚭鍓嶆暟鎹氨缁牎楠岋細杩斿洖姣忓紶鎶ヨ〃渚濊禆鐨勬暟鎹鏁�/缂哄け鎻愮ず锛坱ypes 閫楀彿鍒嗛殧锛� */
+    @GetMapping("/ready")
+    public Result<java.util.Map<String, Object>> exportReady(@RequestParam("period") String period,
+                                                              @RequestParam(value = "mode", defaultValue = "month") String mode,
+                                                              @RequestParam("types") String types) {
+        List<String> typeList = new java.util.ArrayList<>();
+        for (String t : types.split(",")) {
+            String s = t.trim();
+            if (!s.isEmpty()) typeList.add(s);
+        }
+        return Result.ok(reportService.checkExportReady(period, mode, typeList));
+    }
+
+    /** 鎶ヨ〃鏁版嵁棰勮锛堣交閲忕増锛夛細灏辩华 + 鏈堝害瑕嗙洊 + 鏍稿績鎸囨爣鎽樿 + 闂娓呭崟锛坱ypes 閫楀彿鍒嗛殧锛� */
+    @GetMapping("/preview")
+    public Result<java.util.Map<String, Object>> reportPreview(@RequestParam("period") String period,
+                                                                @RequestParam(value = "mode", defaultValue = "month") String mode,
+                                                                @RequestParam("types") String types) {
+        List<String> typeList = new java.util.ArrayList<>();
+        for (String t : types.split(",")) {
+            String s = t.trim();
+            if (!s.isEmpty()) typeList.add(s);
+        }
+        return Result.ok(reportService.reportPreview(period, mode, typeList));
+    }
+
+    private void logExport(String reportName, String period) {
+        operationLogService.record("鎶ヨ〃鐢熸垚", "瀵煎嚭鎶ヨ〃", reportName + "锛堟姤琛ㄦ湡锛�" + period + "锛�");
+    }
+
+    private void logExportZip(String zipName, String period) {
+        operationLogService.record("鎶ヨ〃鐢熸垚", "瀵煎嚭鎶ヨ〃绯诲垪", zipName + "锛堟姤琛ㄦ湡锛�" + period + "锛�");
+    }
+
+    private ResponseEntity<byte[]> zipResponse(byte[] data, String fileName) throws Exception {
+        String encoded = URLEncoder.encode(fileName, "UTF-8").replace("+", "%20");
+        return ResponseEntity.ok()
+            .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8''" + encoded)
+            .contentType(MediaType.parseMediaType("application/zip"))
+            .body(data);
     }
 
     private ResponseEntity<byte[]> buildResponse(byte[] data, String fileName) throws Exception {
         String encoded = URLEncoder.encode(fileName, "UTF-8").replace("+", "%20");
+        String type = fileName.toLowerCase().endsWith(".xls")
+            ? "application/vnd.ms-excel"
+            : "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
         return ResponseEntity.ok()
             .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8''" + encoded)
-            .contentType(MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"))
+            .contentType(MediaType.parseMediaType(type))
             .body(data);
     }
 }
-
-

--
Gitblit v1.9.1