From 18e90dacbfff3f9239bf3978fb0b1f633884def5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A9=AC=E5=AE=9D=E9=BE=99?= Date: Sat, 27 Jun 2026 12:30:33 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cms/constant/enums/ExportModuleEnum.java | 32 + .../controller/AnalysisCommonController.java | 19 + .../com/ruoyi/cms/domain/dto/ExcelRow.java | 93 +++ .../ruoyi/cms/domain/dto/QueryParamDto.java | 5 + .../cms/service/AnalysisExportService.java | 24 + .../impl/AnalysisExportServiceImpl.java | 134 +++++ .../ruoyi/cms/util/document/ExcelUtil.java | 66 +++ .../util/document/MultiSheetExcelUtil.java | 558 ++++++++++++++++++ 8 files changed, 931 insertions(+) create mode 100644 ruoyi-bussiness/src/main/java/com/ruoyi/cms/constant/enums/ExportModuleEnum.java create mode 100644 ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/dto/ExcelRow.java create mode 100644 ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/AnalysisExportService.java create mode 100644 ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/AnalysisExportServiceImpl.java create mode 100644 ruoyi-bussiness/src/main/java/com/ruoyi/cms/util/document/ExcelUtil.java create mode 100644 ruoyi-bussiness/src/main/java/com/ruoyi/cms/util/document/MultiSheetExcelUtil.java diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/constant/enums/ExportModuleEnum.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/constant/enums/ExportModuleEnum.java new file mode 100644 index 0000000..5552926 --- /dev/null +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/constant/enums/ExportModuleEnum.java @@ -0,0 +1,32 @@ +package com.ruoyi.cms.constant.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 导出模块枚举 + * + * @author 马宝龙 + * @date 2026/6/27 + */ +@Getter +@AllArgsConstructor +public enum ExportModuleEnum { + + EMPLOYMENT_INDUSTRY_EMPLOYMENT_INFO("就业大数据专题分析平台-行业就业情况信息分析"), + EMPLOYMENT_GRADUATE_EMPLOYMENT_INFO("就业大数据专题分析平台-高校毕业生就业信息分析"), + EMPLOYMENT_MAJOR_INDUSTRY_EMPLOYMENT_MONITOR("就业大数据专题分析平台-重点行业就失业监测分析"), + EMPLOYMENT_SECTOR_TALENT("就业大数据专题分析平台-产业发展与人才供需分析"), + EMPLOYMENT_SECTOR_OCCUPATION_SKILL("就业大数据专题分析平台-产业发展与职业技能提升分析"), + EMPLOYMENT_JOB_DEMAND_TREAD("就业大数据专题分析平台-岗位需求趋势变化分析"), + EMPLOYMENT_KEY_GROUP_SERVICE_SITUATION("就业大数据专题分析平台-重点人群跟踪服务情况分析"), + + WISDOM_EMPLOYMENT_INDUSTRY_EMPLOYMENT_INFO("智慧就业大数据分析展示系统-行业就业情况信息分析"), + WISDOM_EMPLOYMENT_GRADUATE_EMPLOYMENT_INFO("智慧就业大数据分析展示系统-高校毕业生就业信息分析"), + WISDOM_EMPLOYMENT_MAJOR_INDUSTRY_EMPLOYMENT_MONITOR("智慧就业大数据分析展示系统-重点行业就失业监测分析"), + WISDOM_EMPLOYMENT_SECTOR_TALENT_SKILL("智慧就业大数据分析展示系统-产业与人才供需技能提升分析"), + WISDOM_EMPLOYMENT_KEY_GROUP_SERVICE_SITUATION("智慧就业大数据分析展示系统-重点人群跟踪服务情况分析"), + WISDOM_EMPLOYMENT_LABOR_FORCE_EMPLOYMENT("智慧就业大数据分析展示系统-劳动力就业失业数据分析"), + ; + private final String desc; +} diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/AnalysisCommonController.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/AnalysisCommonController.java index 3d484c4..cd15ddf 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/AnalysisCommonController.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/controller/AnalysisCommonController.java @@ -1,12 +1,18 @@ package com.ruoyi.cms.controller; +import com.ruoyi.cms.domain.dto.QueryParamDto; import com.ruoyi.cms.service.AnalysisCommonService; +import com.ruoyi.cms.service.AnalysisExportService; import com.ruoyi.common.core.domain.AjaxResult; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import javax.servlet.http.HttpServletResponse; + /** * 分析通用 * @@ -19,6 +25,7 @@ import org.springframework.web.bind.annotation.RestController; public class AnalysisCommonController { private final AnalysisCommonService analysisCommonService; + private final AnalysisExportService analysisExportService; /** * 查询团场列表 @@ -74,4 +81,16 @@ public class AnalysisCommonController { public AjaxResult sectorList() { return AjaxResult.success(analysisCommonService.sectorList()); } + + /** + * 数据导出 + * + * @param response 响应 + * @param dto 查询参数 + */ + @PostMapping("/exportData") +// @Log(title = "导出", businessType = BusinessType.EXPORT) + public void exportData(HttpServletResponse response, @RequestBody QueryParamDto dto) { + analysisExportService.exportData(response, dto); + } } diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/dto/ExcelRow.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/dto/ExcelRow.java new file mode 100644 index 0000000..c4b1c6d --- /dev/null +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/dto/ExcelRow.java @@ -0,0 +1,93 @@ +package com.ruoyi.cms.domain.dto; + +import com.ruoyi.common.annotation.Excel; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; + +@Data +@NoArgsConstructor +public class ExcelRow { + @Excel(name = "A") + private String A = ""; + @Excel(name = "B") + private String B = ""; + @Excel(name = "C") + private String C = ""; + @Excel(name = "D") + private String D = ""; + @Excel(name = "E") + private String E = ""; + @Excel(name = "F") + private String F = ""; + @Excel(name = "G") + private String G = ""; + @Excel(name = "H") + private String H = ""; + @Excel(name = "I") + private String I = ""; + @Excel(name = "J") + private String J = ""; + @Excel(name = "K") + private String K = ""; + @Excel(name = "L") + private String L = ""; + @Excel(name = "M") + private String M = ""; + @Excel(name = "N") + private String N = ""; + @Excel(name = "O") + private String O = ""; + @Excel(name = "P") + private String P = ""; + @Excel(name = "Q") + private String Q = ""; + @Excel(name = "R") + private String R = ""; + @Excel(name = "S") + private String S = ""; + @Excel(name = "T") + private String T = ""; + @Excel(name = "U") + private String U = ""; + + // 列索引到字段的映射(静态初始化,只加载一次) + private static final Map COLUMN_FIELD_MAP; + + static { + COLUMN_FIELD_MAP = new HashMap<>(); + Field[] fields = ExcelRow.class.getDeclaredFields(); + + for (Field field : fields) { + Excel excelAnnotation = field.getAnnotation(Excel.class); + if (excelAnnotation != null) { + String columnName = excelAnnotation.name(); + // 计算列名对应的索引(A=0, B=1, ..., U=20) + int columnIndex = columnName.charAt(0) - 'A'; + // 允许访问私有字段 + field.setAccessible(true); + COLUMN_FIELD_MAP.put(columnIndex, field); + } + } + } + + public ExcelRow(String... values) { + if (values == null) return; + // 遍历传入的参数,给对应列赋值(最多处理到 U 列) + for (int i = 0; i < Math.min(values.length, COLUMN_FIELD_MAP.size()); i++) { + Field field = COLUMN_FIELD_MAP.get(i); + if (field != null) { + try { + // 赋值(空参数时保持默认空字符串) + field.set(this, values[i] != null ? values[i] : ""); + } catch (IllegalAccessException e) { + // 反射访问异常(理论上不会发生,因为已设置 setAccessible(true)) + throw new RuntimeException("赋值 Excel 列 " + (char) ('A' + i) + " 失败", e); + } + } + } + } +} diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/dto/QueryParamDto.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/dto/QueryParamDto.java index ca02c5b..b880be0 100644 --- a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/dto/QueryParamDto.java +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/domain/dto/QueryParamDto.java @@ -1,5 +1,6 @@ package com.ruoyi.cms.domain.dto; +import com.ruoyi.cms.constant.enums.ExportModuleEnum; import com.ruoyi.cms.constant.enums.QueryTimeTypeEnum; import lombok.AllArgsConstructor; import lombok.Builder; @@ -58,6 +59,10 @@ public class QueryParamDto implements Serializable { * 职业 */ private String occupation; + /** + * 导出模块 + */ + private ExportModuleEnum exportModule; /** * 开始时间 */ diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/AnalysisExportService.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/AnalysisExportService.java new file mode 100644 index 0000000..b0a023f --- /dev/null +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/AnalysisExportService.java @@ -0,0 +1,24 @@ +package com.ruoyi.cms.service; + +import com.ruoyi.cms.domain.dto.QueryParamDto; +import com.ruoyi.common.core.domain.AjaxResult; + +import javax.servlet.http.HttpServletResponse; + +/** + * 分析导出 + * + * @author 马宝龙 + * @date 2026/6/27 + */ +public interface AnalysisExportService { + + + /** + * 数据导出 + * + * @param response 响应 + * @param dto 查询参数 + */ + void exportData(HttpServletResponse response, QueryParamDto dto); +} diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/AnalysisExportServiceImpl.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/AnalysisExportServiceImpl.java new file mode 100644 index 0000000..e0ac4d0 --- /dev/null +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/service/impl/AnalysisExportServiceImpl.java @@ -0,0 +1,134 @@ +package com.ruoyi.cms.service.impl; + +import com.ruoyi.cms.domain.dto.ExcelRow; +import com.ruoyi.cms.domain.dto.QueryParamDto; +import com.ruoyi.cms.service.AnalysisExportService; +import com.ruoyi.cms.util.document.ExcelUtil; +import com.ruoyi.cms.util.document.MultiSheetExcelUtil; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.DateUtils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * 分析导出 + * + * @author 马宝龙 + * @date 2026/6/27 + */ +@Service +@Validated +@Slf4j +public class AnalysisExportServiceImpl implements AnalysisExportService { + @Override + public void exportData(HttpServletResponse response, QueryParamDto dto) { + if (Objects.isNull(dto.getExportModule())) { + throw new ServiceException("请选择导出模块"); + } + try { + switch (dto.getExportModule()) { + // 就业大数据专题分析平台-行业就业情况信息分析 + case EMPLOYMENT_INDUSTRY_EMPLOYMENT_INFO: + test(response, dto); + break; + // 就业大数据专题分析平台-高校毕业生就业信息分析 + case EMPLOYMENT_GRADUATE_EMPLOYMENT_INFO: + test(response, dto); + break; + // 就业大数据专题分析平台-重点行业就失业监测分析 + case EMPLOYMENT_MAJOR_INDUSTRY_EMPLOYMENT_MONITOR: + test(response, dto); + break; + // 就业大数据专题分析平台-产业发展与人才供需分析 + case EMPLOYMENT_SECTOR_TALENT: + test(response, dto); + break; + // 就业大数据专题分析平台-产业发展与职业技能提升分析 + case EMPLOYMENT_SECTOR_OCCUPATION_SKILL: + test(response, dto); + break; + // 就业大数据专题分析平台-岗位需求趋势变化分析 + case EMPLOYMENT_JOB_DEMAND_TREAD: + test(response, dto); + break; + // 就业大数据专题分析平台-重点人群跟踪服务情况分析 + case EMPLOYMENT_KEY_GROUP_SERVICE_SITUATION: + test(response, dto); + break; + // 智慧就业大数据分析展示系统-行业就业情况信息分析 + case WISDOM_EMPLOYMENT_INDUSTRY_EMPLOYMENT_INFO: + test(response, dto); + break; + // 智慧就业大数据分析展示系统-高校毕业生就业信息分析 + case WISDOM_EMPLOYMENT_GRADUATE_EMPLOYMENT_INFO: + test(response, dto); + break; + // 智慧就业大数据分析展示系统-重点行业就失业监测分析 + case WISDOM_EMPLOYMENT_MAJOR_INDUSTRY_EMPLOYMENT_MONITOR: + test(response, dto); + break; + // 智慧就业大数据分析展示系统-产业与人才供需技能提升分析 + case WISDOM_EMPLOYMENT_SECTOR_TALENT_SKILL: + test(response, dto); + break; + // 智慧就业大数据分析展示系统-重点人群跟踪服务情况分析 + case WISDOM_EMPLOYMENT_KEY_GROUP_SERVICE_SITUATION: + test(response, dto); + break; + // 智慧就业大数据分析展示系统-劳动力就业失业数据分析 + case WISDOM_EMPLOYMENT_LABOR_FORCE_EMPLOYMENT: + test(response, dto); + break; + // 可以添加更多导出格式的处理 + default: + throw new ServiceException("暂不支持该模块"); + } + } catch (Exception e) { + log.error("导出失败:", e); + throw new ServiceException("导出失败,请联系管理员"); + } + } + + private void test(HttpServletResponse response, QueryParamDto dto) throws IOException { + MultiSheetExcelUtil multiSheetExcelUtil = test(dto); + multiSheetExcelUtil.setFileName(dto.getExportModule().getDesc()); + multiSheetExcelUtil.exportExcel(response); + + } + + private MultiSheetExcelUtil test(QueryParamDto dto) { + // 1. 创建多工作表Excel工具 + MultiSheetExcelUtil multiSheetExcelUtil = new MultiSheetExcelUtil(); + LocalDateTime now = LocalDateTime.now(); + String exportTime = DateUtils.dateTimeNow(DateUtils.YYYY_MM_DD_HH_MM_SS); + List basicExportList = new ArrayList<>(); + basicExportList.add(new ExcelRow("导出时间", exportTime)); + basicExportList.add(new ExcelRow("数据总览")); + basicExportList.add(new ExcelRow("测试1", "测试2", "测试3", "测试4", "测试5")); + basicExportList.add(new ExcelRow( + "1", "1", "1", "1", "1" + )); + basicExportList.add(new ExcelRow( + "2", "2", "2", "2", "2" + )); + basicExportList.add(new ExcelRow( + "3", "3", "3", "3", "3" + )); + + // 创建Excel工具类并添加工作表 + ExcelUtil basicSituationExcelUtil = new ExcelUtil<>(ExcelRow.class); + basicSituationExcelUtil.setDataList(basicExportList); + basicSituationExcelUtil.setSheetName("测试数据"); + multiSheetExcelUtil.addSheet(basicSituationExcelUtil); + + return multiSheetExcelUtil; + } +} diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/util/document/ExcelUtil.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/util/document/ExcelUtil.java new file mode 100644 index 0000000..27cabcb --- /dev/null +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/util/document/ExcelUtil.java @@ -0,0 +1,66 @@ +package com.ruoyi.cms.util.document; + +import java.lang.reflect.Method; +import java.util.List; + +/** + * 就业服务Excel工具类,直接使用原始的ExcelUtil类 + * 提供便捷的方法设置数据列表和工作表名称 + */ +public class ExcelUtil extends com.ruoyi.common.utils.poi.ExcelUtil { + + public ExcelUtil(Class pojoClass) { + super(pojoClass); + // 不在这里调用init方法,而是在setDataList之后调用 + } + + /** + * 设置数据列表 + * @param dataList 数据列表 + * @return 当前实例,支持链式调用 + */ + public ExcelUtil setDataList(List dataList) { + try { + // 直接使用反射设置父类的list字段 + java.lang.reflect.Field listField = getClass().getSuperclass().getDeclaredField("list"); + listField.setAccessible(true); + listField.set(this, dataList); + // 在设置数据列表后调用init方法 + this.init(); + return this; + } catch (Exception e) { + throw new RuntimeException("设置数据列表失败", e); + } + } + + /** + * 设置工作表名称 + * @param sheetName 工作表名称 + * @return 当前实例,支持链式调用 + */ + public ExcelUtil setSheetName(String sheetName) { + try { + // 直接使用反射设置父类的sheetName字段 + java.lang.reflect.Field sheetNameField = getClass().getSuperclass().getDeclaredField("sheetName"); + sheetNameField.setAccessible(true); + sheetNameField.set(this, sheetName); + return this; + } catch (Exception e) { + throw new RuntimeException("设置工作表名称失败", e); + } + } + + /** + * 初始化方法,确保对象正确初始化 + */ + private void init() { + try { + // 调用父类的createExcelField方法创建Excel字段映射 + Method createExcelFieldMethod = getClass().getSuperclass().getDeclaredMethod("createExcelField"); + createExcelFieldMethod.setAccessible(true); + createExcelFieldMethod.invoke(this); + } catch (Exception e) { + throw new RuntimeException("初始化Excel字段映射失败", e); + } + } +} \ No newline at end of file diff --git a/ruoyi-bussiness/src/main/java/com/ruoyi/cms/util/document/MultiSheetExcelUtil.java b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/util/document/MultiSheetExcelUtil.java new file mode 100644 index 0000000..b752feb --- /dev/null +++ b/ruoyi-bussiness/src/main/java/com/ruoyi/cms/util/document/MultiSheetExcelUtil.java @@ -0,0 +1,558 @@ +package com.ruoyi.cms.util.document; + +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.common.utils.poi.ExcelUtil; +import lombok.extern.slf4j.Slf4j; +import org.apache.poi.ss.usermodel.BorderStyle; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.DataFormat; +import org.apache.poi.ss.usermodel.Font; +import org.apache.poi.ss.usermodel.HorizontalAlignment; +import org.apache.poi.ss.usermodel.IndexedColors; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.VerticalAlignment; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.xssf.streaming.SXSSFWorkbook; +import org.springframework.stereotype.Component; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 多工作表Excel导出工具类 + * 支持添加多个ExcelUtil实例,每个实例对应一个工作表 + * + * @author + * @date + */ +@Slf4j +@Component +public class MultiSheetExcelUtil { + + private final List> excelUtilList; + private final Workbook workbook; + // 临时变量,存储外部指定的文件名 + private String customFileName; + + // 缓存反射方法,避免重复查找 + private static final Map METHOD_CACHE = new ConcurrentHashMap<>(); + // 缓存反射字段,避免重复查找 + private static final Map FIELD_CACHE = new ConcurrentHashMap<>(); + + /** + * 构造函数 + */ + public MultiSheetExcelUtil() { + this.excelUtilList = new ArrayList<>(); + // 使用SXSSFWorkbook以支持大数据量导出 + this.workbook = new SXSSFWorkbook(100); // 100条记录保存在内存中 + } + + /** + * 添加一个ExcelUtil实例(对应一个工作表) + * + * @param excelUtil ExcelUtil实例 + * @return 当前实例,支持链式调用 + */ + public MultiSheetExcelUtil addSheet(ExcelUtil excelUtil) { + excelUtilList.add(excelUtil); + return this; + } + + /** + * 设置外部指定的文件名 + * + * @param customFileName 外部指定的文件名(不包含扩展名) + * @return 当前实例,支持链式调用 + */ + public MultiSheetExcelUtil setFileName(String customFileName) { + this.customFileName = customFileName; + return this; + } + + /** + * 获取工作表名称 + * + * @param excelUtil ExcelUtil实例 + * @return 工作表名称 + */ + private String getSheetName(ExcelUtil excelUtil) { + try { + if (excelUtil == null) { + return "Sheet"; // 返回默认名称 + } + + // 使用缓存的方法获取字段 + String sheetName = (String) getPrivateField(excelUtil, "sheetName"); + + // 如果从字段获取的名称为空,则返回默认名称 + if (sheetName == null || sheetName.trim().isEmpty()) { + return "Sheet"; + } + + // 确保工作表名称不超过Excel限制 + if (sheetName.length() > 31) { + sheetName = sheetName.substring(0, 31); + } + + return sheetName; + } catch (Exception e) { + System.err.println("获取工作表名称时出错: " + e.getMessage()); + // 如果出错,返回默认名称 + return "Sheet"; + } + } + + /** + * 导出Excel文件(直接输出到HttpServletResponse) + * + * @param response HttpServletResponse + * @throws IOException IOException + */ + public void exportExcel(HttpServletResponse response) throws IOException { + try { + if (excelUtilList.isEmpty()) { + throw new IOException("没有要导出的工作表"); + } + + // 获取第一个ExcelUtil实例 + ExcelUtil firstExcelUtil = excelUtilList.get(0); + + // 设置所有ExcelUtil实例使用同一个Workbook并重新创建样式 + for (ExcelUtil excelUtil : excelUtilList) { + if (excelUtil != null) { + try { + // 尝试设置wb字段,如果不存在则忽略错误 + setPrivateField(excelUtil, "wb", workbook); + // 为每个ExcelUtil实例创建新的样式,确保与当前Workbook关联 + Map styles = createStylesForSheet(workbook); + setPrivateField(excelUtil, "styles", styles); + } catch (Exception e) { + // 如果设置字段失败,继续处理下一个ExcelUtil实例 + System.err.println("处理ExcelUtil实例时出错: " + e.getMessage()); + } + } + } + + // 为每个ExcelUtil实例创建工作表并填充数据 + for (int i = 0; i < excelUtilList.size(); i++) { + ExcelUtil excelUtil = excelUtilList.get(i); + + // 设置当前工作表对象 + Sheet newSheet = workbook.createSheet(); + workbook.setSheetName(i, getSheetName(excelUtil)); + setPrivateField(excelUtil, "sheet", newSheet); + + // 重置rownum为0,确保每个工作表都从第一行开始 + setPrivateField(excelUtil, "rownum", 0); + + // 调用createTitle方法创建标题行 + invokeCachedMethod(excelUtil, "createTitle", new Class[]{}); + + // 获取字段列表 + List fields = (List) getPrivateField(excelUtil, "fields"); + + // 获取子字段列表 + // List subFields = (List) getPrivateField(excelUtil, "subFields"); + + // 调用createSubHead方法创建子标题行(如果需要) + try { + invokeCachedMethod(excelUtil, "createSubHead", new Class[]{}); + } catch (Exception e) { + // 如果没有createSubHead方法或调用失败,忽略该异常 + } + + // 填充每一行数据 + List dataList = null; + try { + dataList = (List) getPrivateField(excelUtil, "list"); + } catch (Exception e) { + System.err.println("获取数据列表失败: " + e.getMessage()); + } + + if (dataList != null && !dataList.isEmpty()) { + // 从excelUtil对象中获取sheet字段的值 + Sheet sheet = (Sheet) getPrivateField(excelUtil, "sheet"); + if (sheet == null) { + continue; // 如果sheet为空,跳过当前工作表 + } + + // 字段列表已经在第360行定义,直接使用 + if (fields == null || fields.isEmpty()) { + continue; // 跳过没有字段的工作表 + } + + // 获取当前行号 + Object rownumObj = getPrivateField(excelUtil, "rownum"); + int currentRowNum = rownumObj != null ? (int) rownumObj : 0; + + // 遍历数据列表,填充每一行数据 + for (int j = 0; j < dataList.size(); j++) { + // 创建新行 + Row dataRow = sheet.createRow(currentRowNum++); + + // 获取当前数据对象 + Object dataObj = dataList.get(j); + + // 遍历字段列表,填充每一列数据 + int column = 0; + for (Object[] os : fields) { + if (os.length < 2) { + continue; + } + + Field field = (Field) os[0]; + //Excel excel = (Excel) os[1]; + + try { + // 设置字段可访问 + field.setAccessible(true); + + // 获取字段值 + Object fieldValue = field.get(dataObj); + + // 创建单元格 + Cell cell = dataRow.createCell(column++); + + // 设置单元格值 + if (fieldValue != null) { + cell.setCellValue(fieldValue.toString()); + } else { + cell.setCellValue(""); + } + } catch (Exception e) { + System.err.println("填充数据失败: " + e.getMessage()); + log.error("发生异常", e); + } + } + } + + // 更新行号 + setPrivateField(excelUtil, "rownum", currentRowNum); + } + + // 调用addStatisticsRow方法添加统计行(如果需要) + try { + invokeCachedMethod(excelUtil, "addStatisticsRow", new Class[]{}); + } catch (Exception e) { + // 如果没有addStatisticsRow方法或调用失败,忽略该异常 + } + } + + // 使用第一个ExcelUtil实例的exportExcel方法导出所有工作表 + // 但需要替换其writeSheet调用,因为我们已经创建了所有工作表 + exportAllSheetsToResponse(firstExcelUtil, response); + } catch (Exception e) { + log.error("发生异常", e); + throw new IOException("导出Excel失败,请联系管理员", e); + } finally { + // 确保清理资源 + dispose(); + } + } + + /** + * 导出所有工作表到HttpServletResponse(绕过writeSheet调用) + * + * @param excelUtil ExcelUtil实例 + * @param response HttpServletResponse + * @throws Exception Exception + */ + private void exportAllSheetsToResponse(ExcelUtil excelUtil, HttpServletResponse response) throws Exception { + // 获取Workbook和工作表名称 + Workbook wb = (Workbook) getPrivateField(excelUtil, "wb"); + + String baseFilename; + // 优先使用外部指定的文件名 + if (customFileName != null && !customFileName.trim().isEmpty()) { + baseFilename = customFileName.trim(); + } else { + String sheetName = (String) getPrivateField(excelUtil, "sheetName"); + baseFilename = (sheetName == null || sheetName.trim().isEmpty()) ? "数据导出" : sheetName; + } + + // 编码文件名并设置响应头 + // 使用格式:名称_时分秒.xlsx,替换原来的UUID格式 + String filename = baseFilename + "_" + DateUtils.dateTimeNow(DateUtils.YYYYMMDDHHMMSS) + ".xlsx"; + + // 设置响应头 + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setCharacterEncoding(StandardCharsets.UTF_8.name()); + // 对文件名编码,解决中文乱码 + String encodedFileName = URLEncoder.encode(filename, StandardCharsets.UTF_8.name()).replaceAll("\\+", "%20"); + response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + encodedFileName); + + // 将Workbook写入响应流 + wb.write(response.getOutputStream()); + } + + /** + * 获取缓存的字段 + */ + private Field getCachedField(Class clazz, String fieldName) { + String key = clazz.getName() + ":" + fieldName; + return FIELD_CACHE.computeIfAbsent(key, k -> { + try { + // 递归查找父类中的字段 + Class currentClass = clazz; + while (currentClass != null) { + try { + Field field = currentClass.getDeclaredField(fieldName); + field.setAccessible(true); + return field; + } catch (NoSuchFieldException e) { + currentClass = currentClass.getSuperclass(); + } + } + return null; + } catch (Exception e) { + System.err.println("缓存字段失败: " + key + ", 错误: " + e.getMessage()); + return null; + } + }); + } + + /** + * 获取缓存的方法 + */ + private Method getCachedMethod(Class clazz, String methodName, Class... parameterTypes) { + String key = clazz.getName() + ":" + methodName + ":" + parameterTypes.length; + return METHOD_CACHE.computeIfAbsent(key, k -> { + try { + // 递归查找父类中的方法 + Class currentClass = clazz; + while (currentClass != null) { + try { + Method method = currentClass.getDeclaredMethod(methodName, parameterTypes); + method.setAccessible(true); + return method; + } catch (NoSuchMethodException e) { + currentClass = currentClass.getSuperclass(); + } + } + return null; + } catch (Exception e) { + System.err.println("缓存方法失败: " + key + ", 错误: " + e.getMessage()); + return null; + } + }); + } + + /** + * 获取私有字段的值 + * + * @param obj 对象 + * @param fieldName 字段名 + * @return 字段值 + */ + private Object getPrivateField(Object obj, String fieldName) { + try { + if (obj == null || fieldName == null || fieldName.trim().isEmpty()) { + System.err.println("参数无效: obj或fieldName为null或空"); + return null; + } + + Class clazz = obj.getClass(); + Field field = getCachedField(clazz, fieldName); + + if (field == null) { + // 如果缓存中没有,递归查找父类中的字段 + Class currentClass = clazz; + while (currentClass != null) { + try { + field = currentClass.getDeclaredField(fieldName); + field.setAccessible(true); + break; + } catch (NoSuchFieldException e) { + currentClass = currentClass.getSuperclass(); + } + } + } + + if (field != null) { + return field.get(obj); + } else { + System.err.println("找不到字段: " + fieldName); + return null; + } + } catch (IllegalAccessException e) { + System.err.println("访问字段失败: " + fieldName + ", 错误: " + e.getMessage()); + log.error("发生异常", e); + return null; + } catch (Exception e) { + System.err.println("获取私有字段失败: " + fieldName + ", 错误: " + e.getMessage()); + log.error("发生异常", e); + return null; + } + } + + /** + * 设置私有字段的值 + * + * @param obj 对象 + * @param fieldName 字段名 + * @param value 值 + */ + private boolean setPrivateField(Object obj, String fieldName, Object value) { + try { + if (obj == null || fieldName == null || fieldName.trim().isEmpty()) { + System.err.println("参数无效: obj或fieldName为null或空"); + return false; + } + + // 先尝试从直接类查找 + Class clazz = obj.getClass(); + Field field = null; + + // 递归查找父类中的字段 + while (clazz != null && field == null) { + try { + field = clazz.getDeclaredField(fieldName); + } catch (NoSuchFieldException e) { + clazz = clazz.getSuperclass(); + } + } + + if (field != null) { + field.setAccessible(true); + field.set(obj, value); + return true; + } else { + // 字段不存在,记录日志但不抛出异常 + System.err.println("缓存字段失败: " + obj.getClass().getName() + ":" + fieldName + ", 错误: " + fieldName); + return false; + } + } catch (Exception e) { + // 捕获所有异常,记录日志但不中断程序 + System.err.println("设置私有字段失败: " + obj.getClass().getName() + ":" + fieldName + ", 错误: " + e.getMessage()); + return false; + } + } + + /** + * 调用缓存的方法 + */ + private Object invokeCachedMethod(Object obj, String methodName, Class[] paramTypes, Object... args) { + try { + if (obj == null || methodName == null || methodName.trim().isEmpty()) { + System.err.println("参数无效: obj或methodName为null或空"); + return null; + } + + Class clazz = obj.getClass(); + Method method = getCachedMethod(clazz, methodName, paramTypes); + + if (method == null) { + // 如果缓存中没有,递归查找父类中的方法 + Class currentClass = clazz; + while (currentClass != null) { + try { + method = currentClass.getDeclaredMethod(methodName, paramTypes); + method.setAccessible(true); + break; + } catch (NoSuchMethodException e) { + currentClass = currentClass.getSuperclass(); + } + } + } + + if (method != null) { + return method.invoke(obj, args); + } else { + System.err.println("找不到方法: " + methodName); + return null; + } + } catch (Exception e) { + System.err.println("调用方法失败: " + methodName + ", 错误: " + e.getMessage()); + log.error("发生异常", e); + return null; + } + } + + /** + * 为工作表创建样式,确保样式关联到当前workbook + */ + private Map createStylesForSheet(Workbook wb) { + Map styles = new HashMap<>(); + + try { + // 创建默认样式 + CellStyle style = wb.createCellStyle(); + style.setAlignment(HorizontalAlignment.CENTER); + style.setVerticalAlignment(VerticalAlignment.CENTER); + Font titleFont = wb.createFont(); + titleFont.setFontName("Arial"); + titleFont.setFontHeightInPoints((short) 16); + titleFont.setBold(true); + style.setFont(titleFont); + DataFormat dataFormat = wb.createDataFormat(); + style.setDataFormat(dataFormat.getFormat("@")); + styles.put("title", style); + + // 创建数据样式 + style = wb.createCellStyle(); + style.setAlignment(HorizontalAlignment.CENTER); + style.setVerticalAlignment(VerticalAlignment.CENTER); + style.setBorderRight(BorderStyle.THIN); + style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setBorderLeft(BorderStyle.THIN); + style.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setBorderTop(BorderStyle.THIN); + style.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setBorderBottom(BorderStyle.THIN); + style.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + Font dataFont = wb.createFont(); + dataFont.setFontName("Arial"); + dataFont.setFontHeightInPoints((short) 10); + style.setFont(dataFont); + styles.put("data", style); + + // 创建合计样式 + style = wb.createCellStyle(); + style.setAlignment(HorizontalAlignment.CENTER); + style.setVerticalAlignment(VerticalAlignment.CENTER); + style.setDataFormat(dataFormat.getFormat("######0.00")); + Font totalFont = wb.createFont(); + totalFont.setFontName("Arial"); + totalFont.setFontHeightInPoints((short) 10); + style.setFont(totalFont); + styles.put("total", style); + + } catch (Exception e) { + System.err.println("创建样式失败: " + e.getMessage()); + log.error("发生异常", e); + } + + return styles; + } + + /** + * 获取工作簿对象 + * + * @return Workbook + */ + public Workbook getWorkbook() { + return workbook; + } + + /** + * 清理资源 + */ + public void dispose() { + if (workbook instanceof SXSSFWorkbook) { + ((SXSSFWorkbook) workbook).dispose(); + } + } + +} \ No newline at end of file