添加功能

This commit is contained in:
马宝龙
2026-06-27 12:30:33 +08:00
parent f86e29a554
commit 18e90dacbf
8 changed files with 931 additions and 0 deletions

View File

@@ -0,0 +1,66 @@
package com.ruoyi.cms.util.document;
import java.lang.reflect.Method;
import java.util.List;
/**
* 就业服务Excel工具类直接使用原始的ExcelUtil类
* 提供便捷的方法设置数据列表和工作表名称
*/
public class ExcelUtil<T> extends com.ruoyi.common.utils.poi.ExcelUtil<T> {
public ExcelUtil(Class<T> pojoClass) {
super(pojoClass);
// 不在这里调用init方法而是在setDataList之后调用
}
/**
* 设置数据列表
* @param dataList 数据列表
* @return 当前实例,支持链式调用
*/
public ExcelUtil<T> setDataList(List<T> 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<T> 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);
}
}
}

View File

@@ -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<ExcelUtil<?>> excelUtilList;
private final Workbook workbook;
// 临时变量,存储外部指定的文件名
private String customFileName;
// 缓存反射方法,避免重复查找
private static final Map<String, Method> METHOD_CACHE = new ConcurrentHashMap<>();
// 缓存反射字段,避免重复查找
private static final Map<String, Field> 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<String, CellStyle> 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<Object[]> fields = (List<Object[]>) getPrivateField(excelUtil, "fields");
// 获取子字段列表
// List<Field> subFields = (List<Field>) 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<String, CellStyle> createStylesForSheet(Workbook wb) {
Map<String, CellStyle> 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();
}
}
}