traffic-audit-server/src/main/java/com/trafficaudit/reportexport/controller/ReportExportController.java
@@ -2,6 +2,7 @@
import com.trafficaudit.common.Result;
import com.trafficaudit.reportexport.service.ReportExportService;
import com.trafficaudit.system.service.OperationLogService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
@@ -13,6 +14,7 @@
import javax.annotation.Resource;
import java.net.URLEncoder;
import java.util.List;
import java.nio.charset.StandardCharsets;
@Slf4j
@@ -22,30 +24,254 @@
    @Resource
    private ReportExportService reportService;
    @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(month: 报表期 yyyy-MM;year: 年份 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(H204,全省 + 各市州 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");
    }
    /** 生成_轨道轮渡客运量分市州明细.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:types 逗号分隔(前端"生成选中"多选) */
    @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));
    }
    /** 导出前数据就绪校验:返回每张报表依赖的数据行数/缺失提示(types 逗号分隔) */
    @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));
    }
    /** 报表数据预览(轻量版):就绪 + 月度覆盖 + 核心指标摘要 + 问题清单(types 逗号分隔) */
    @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);
    }
}