package com.ruoyi.common.core.service; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.github.pagehelper.Page; import com.ruoyi.common.annotation.Excel; import com.ruoyi.common.config.RuoYiConfig; import com.ruoyi.common.constant.Constants; import com.ruoyi.common.core.page.PageDomain; import com.ruoyi.common.core.redis.RedisCache; import com.ruoyi.common.utils.PageUtils; import com.ruoyi.common.utils.file.DownloadExportUtil; import com.ruoyi.common.utils.poi.ExcelUtil; import org.apache.poi.ss.usermodel.*; import org.apache.poi.xssf.streaming.SXSSFWorkbook; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import java.io.File; import java.io.FileInputStream; import java.io.OutputStream; import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Paths; import java.util.List; import java.util.function.Function; /** * 通用服务 */ @Service public abstract class BaseService,T> extends ServiceImpl { protected final Logger logger = LoggerFactory.getLogger(this.getClass()); @Autowired private RedisCache redisCache; /** * 通用异步导出方法 * * @param exportKey 导出任务的唯一标识 * @param sheetName 工作表名称 * @param dataFetcher 数据获取函数,接受分页参数返回数据列表 */ @Async public void export(Class c, String exportKey, String sheetName, Function> dataFetcher) { String fileName = ExcelUtil.encodeFileName(sheetName); // 设置当前任务为“下载中”状态 DownloadExportUtil.deleteDownloadFile(redisCache, exportKey, "0"); try { // 执行导出并获取文件名 fileName = exportData( c,fileName, sheetName, dataFetcher); // 设置下载完成状态 DownloadExportUtil.setDownloadFile(redisCache, exportKey, fileName); logger.info("Export completed for key: {}, file: {}", exportKey, fileName); } catch (Exception e) { logger.error("Export failed for key: {}, error: {}", exportKey, e.getMessage(), e); DownloadExportUtil.deleteDownloadFile(redisCache, exportKey, "1"); // 设置失败状态 throw e; } } /** * 通用分页导出逻辑 * * @param fileName 文件名 * @param sheetName 工作表名称 * @param dataFetcher 数据获取函数 * @return 导出后的文件名 */ protected String exportData(Class c, String fileName, String sheetName, Function> dataFetcher) { ExcelUtil excelUtil = new ExcelUtil<>(c); excelUtil.initialize(sheetName, null, Excel.Type.EXPORT); int pageNum = 1; boolean hasMoreData = true; while (hasMoreData) { List pageData = dataFetcher.apply(pageNum); if (pageData != null && !pageData.isEmpty()) { // 导出当前页的数据 excelUtil.exportExcel(pageData); pageNum++; } else { // 没有数据时退出 hasMoreData = false; } } excelUtil.finishExport(fileName); return fileName; } /** * 导出两个不同sheet的数据到同一个Excel文件 * * @param clazz1 第一个sheet对应的实体类 * @param clazz2 第二个sheet对应的实体类 * @param fileName 文件名 * @param sheetName1 第一个sheet名称 * @param sheetName2 第二个sheet名称 * @param dataList1 第一个sheet的数据列表 * @param dataList2 第二个sheet的数据列表 * @return 导出后的文件名 */ protected String exportMultiSheetData(Class clazz1, Class clazz2, String fileName, String sheetName1, String sheetName2, List dataList1, List dataList2) { // 创建第一个sheet的ExcelUtil并导出数据 ExcelUtil excelUtil1 = new ExcelUtil<>(clazz1); excelUtil1.initialize(sheetName1, null, Excel.Type.EXPORT); if (dataList1 != null && !dataList1.isEmpty()) { excelUtil1.exportExcel(dataList1); } // 使用ExcelUtil的finishExport方法导出到临时文件 String tempFileName = "temp_" + System.currentTimeMillis() + ".xlsx"; excelUtil1.finishExport(tempFileName); try { // 读取临时文件并添加第二个sheet FileInputStream fis = new FileInputStream(getAbsoluteFile(tempFileName)); SXSSFWorkbook workbook = new SXSSFWorkbook(new org.apache.poi.xssf.usermodel.XSSFWorkbook(fis)); fis.close(); // 创建第二个sheet Sheet sheet2 = workbook.createSheet(sheetName2); // 使用ExcelUtil的方式导出第二个sheet的数据 if (dataList2 != null && !dataList2.isEmpty()) { // 创建第二个sheet的ExcelUtil用于生成表头 ExcelUtil excelUtil2 = new ExcelUtil<>(clazz2); excelUtil2.initialize(sheetName2, null, Excel.Type.EXPORT); // 获取表头样式信息 SXSSFWorkbook tempWorkbook = getWorkbookFromExcelUtil(excelUtil2); Sheet tempSheet = tempWorkbook.getSheetAt(0); Row headerRow = tempSheet.getRow(1); // 表头通常在第二行 // 复制表头到第二个sheet if (headerRow != null) { Row newHeaderRow = sheet2.createRow(0); copyRow(headerRow, newHeaderRow); } // 导出数据到第二个sheet int startRow = 1; for (T2 data : dataList2) { Row dataRow = sheet2.createRow(startRow++); exportDataRow(clazz2, data, dataRow); } tempWorkbook.close(); } // 完成导出 finishMultiSheetExport(workbook, fileName); // 删除临时文件 new File(getAbsoluteFile(tempFileName)).delete(); } catch (Exception e) { logger.error("Failed to export multi-sheet data: {}", e.getMessage(), e); throw new RuntimeException("多sheet导出失败", e); } return fileName; } /** * 从ExcelUtil中获取工作簿对象 */ private SXSSFWorkbook getWorkbookFromExcelUtil(ExcelUtil excelUtil) { try { Field workbookField = ExcelUtil.class.getDeclaredField("workbook"); workbookField.setAccessible(true); return (SXSSFWorkbook) workbookField.get(excelUtil); } catch (Exception e) { logger.error("Failed to get workbook from ExcelUtil: {}", e.getMessage(), e); throw new RuntimeException("获取工作簿失败", e); } } /** * 将数据导出到指定的sheet */ private void exportDataToSheet(Class clazz, Sheet sheet, List dataList) { try { // 创建临时ExcelUtil用于生成表头 ExcelUtil tempUtil = new ExcelUtil<>(clazz); tempUtil.initialize("temp", null, Excel.Type.EXPORT); // 获取表头行 SXSSFWorkbook tempWorkbook = getWorkbookFromExcelUtil(tempUtil); Sheet tempSheet = tempWorkbook.getSheetAt(0); Row headerRow = tempSheet.getRow(1); // 表头通常在第二行 // 复制表头到目标sheet if (headerRow != null) { Row newHeaderRow = sheet.createRow(0); copyRow(headerRow, newHeaderRow); } // 导出数据 if (dataList != null && !dataList.isEmpty()) { int startRow = 1; // 数据从第二行开始 for (T data : dataList) { Row dataRow = sheet.createRow(startRow++); exportDataRow(clazz, data, dataRow); } } tempWorkbook.close(); } catch (Exception e) { logger.error("Failed to export data to sheet: {}", e.getMessage(), e); throw new RuntimeException("导出数据到sheet失败", e); } } /** * 复制行数据 */ private void copyRow(Row sourceRow, Row targetRow) { if (sourceRow == null || targetRow == null) return; for (int i = 0; i < sourceRow.getLastCellNum(); i++) { Cell sourceCell = sourceRow.getCell(i); Cell targetCell = targetRow.createCell(i); if (sourceCell != null) { // 复制单元格样式 targetCell.setCellStyle(sourceCell.getCellStyle()); // 复制单元格值 switch (sourceCell.getCellType()) { case STRING: targetCell.setCellValue(sourceCell.getStringCellValue()); break; case NUMERIC: targetCell.setCellValue(sourceCell.getNumericCellValue()); break; case BOOLEAN: targetCell.setCellValue(sourceCell.getBooleanCellValue()); break; case FORMULA: targetCell.setCellFormula(sourceCell.getCellFormula()); break; default: targetCell.setCellValue(""); } } } } /** * 导出数据行 */ private void exportDataRow(Class clazz, T data, Row row) { try { ExcelUtil tempUtil = new ExcelUtil<>(clazz); tempUtil.initialize("temp", null, Excel.Type.EXPORT); // 获取字段信息 Field[] fields = clazz.getDeclaredFields(); int columnIndex = 0; for (Field field : fields) { field.setAccessible(true); Excel excel = field.getAnnotation(Excel.class); if (excel != null && excel.isExport()) { Cell cell = row.createCell(columnIndex++); Object value = field.get(data); if (value != null) { cell.setCellValue(value.toString()); } else { cell.setCellValue(""); } } } } catch (Exception e) { logger.error("Failed to export data row: {}", e.getMessage(), e); } } /** * 完成多sheet导出 */ private void finishMultiSheetExport(SXSSFWorkbook workbook, String fileName) { try (OutputStream out = Files.newOutputStream(Paths.get(getAbsoluteFile(fileName)))) { workbook.write(out); } catch (Exception e) { logger.error("Failed to finish multi-sheet export: {}", e.getMessage(), e); throw new RuntimeException("多sheet导出失败", e); } finally { try { workbook.close(); } catch (Exception e) { logger.error("Failed to close workbook: {}", e.getMessage(), e); } } } /** * 获取文件的绝对路径 */ private String getAbsoluteFile(String fileName) { String downloadPath = RuoYiConfig.getDownloadPath(); File file = new File(downloadPath + fileName); if (!file.exists()) { file.getParentFile().mkdirs(); } return file.getAbsolutePath(); } }