package com.ruoyi.cms.util; import com.alibaba.excel.EasyExcel; import com.alibaba.excel.context.AnalysisContext; import com.alibaba.excel.event.AnalysisEventListener; import com.alibaba.excel.write.builder.ExcelWriterSheetBuilder; import com.alibaba.excel.write.metadata.style.WriteCellStyle; import com.alibaba.excel.write.metadata.style.WriteFont; import com.alibaba.excel.write.style.HorizontalCellStyleStrategy; import org.apache.poi.ss.usermodel.HorizontalAlignment; import org.apache.poi.ss.usermodel.VerticalAlignment; import javax.servlet.http.HttpServletResponse; import java.io.*; import java.net.URLEncoder; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.function.Consumer; /** * EasyExcel 工具类(基于 alibaba easyexcel 3.x+) */ public class EasyExcelUtils { /** * 读取 Excel 文件(一次性读取所有数据) * * @param file 上传的 Excel 文件 * @param head 实体类字节码(需使用 @ExcelProperty 注解映射表头) * @param 实体类泛型 * @return 解析后的数据集 */ public static List readExcel(File file, Class head) { return EasyExcel.read(file) .head(head) .sheet() .doReadSync(); } /** * 读取 Excel 输入流(一次性读取所有数据) * * @param inputStream Excel 输入流(如 MultipartFile 的 getInputStream()) * @param head 实体类字节码 * @param 实体类泛型 * @return 解析后的数据集 */ public static List readExcel(InputStream inputStream, Class head) { return EasyExcel.read(inputStream) .head(head) .sheet() .doReadSync(); } /** * 分批读取 Excel(适用于大数据量,避免内存溢出) * * @param inputStream Excel 输入流 * @param head 实体类字节码 * @param batchSize 每批处理的数据量 * @param consumer 数据处理函数(如批量保存到数据库) * @param 实体类泛型 */ public static void readExcelByBatch(InputStream inputStream, Class head, int batchSize, Consumer> consumer) { EasyExcel.read(inputStream) .head(head) .sheet() .registerReadListener(new AnalysisEventListener() { private List batchList; // 临时存储批数据 @Override public void invoke(T data, AnalysisContext context) { if (batchList == null) { batchList = new java.util.ArrayList<>(batchSize); } batchList.add(data); // 达到批处理量时执行消费逻辑 if (batchList.size() >= batchSize) { consumer.accept(batchList); batchList.clear(); // 清空集合,释放内存 } } @Override public void doAfterAllAnalysed(AnalysisContext context) { // 处理剩余不足一批的数据 if (batchList != null && !batchList.isEmpty()) { consumer.accept(batchList); } } }) .doRead(); } /** * 生成 Excel 并写入到输出流(通用样式) * * @param outputStream 输出流(如 HttpServletResponse 的 getOutputStream()) * @param data 数据集 * @param head 实体类字节码 * @param sheetName 工作表名称 * @param 实体类泛型 */ public static void writeExcel(OutputStream outputStream, List data, Class head, String sheetName) { // 构建通用样式策略(表头居中加粗,内容居中) HorizontalCellStyleStrategy styleStrategy = getDefaultStyleStrategy(); ExcelWriterSheetBuilder writerBuilder = EasyExcel.write(outputStream, head) .sheet(sheetName) .registerWriteHandler(styleStrategy); writerBuilder.doWrite(data); } /** * 生成 Excel 并通过 HttpServletResponse 下载(前端直接触发下载) * * @param response HttpServletResponse * @param data 数据集 * @param head 实体类字节码 * @param sheetName 工作表名称 * @param fileName 下载的文件名(不带后缀) * @param 实体类泛型 * @throws IOException IO异常 */ public static void downloadExcel(HttpServletResponse response, List data, Class head, String sheetName, String fileName) throws IOException { // 设置响应头,触发前端下载 response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setCharacterEncoding("UTF-8"); // 文件名编码,避免中文乱码 String encodedFileName = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20"); response.setHeader("Content-disposition", "attachment;filename*=UTF-8''" + encodedFileName + ".xlsx"); // 写入数据到响应流 writeExcel(response.getOutputStream(), data, head, sheetName); } /** * 填充 Excel 模板(适用于带固定格式的模板文件) * * @param templateInputStream 模板文件输入流 * @param outputStream 输出流(如响应流或文件流) * @param dataMap 填充数据(key为模板中的占位符,value为填充值) * @param sheetName 工作表名称 */ public static void fillTemplate(InputStream templateInputStream, OutputStream outputStream, Map dataMap, String sheetName) { EasyExcel.write(outputStream) .withTemplate(templateInputStream) .sheet(sheetName) .doFill(dataMap); } /** * 获取默认单元格样式策略(表头加粗居中,内容居中) */ private static HorizontalCellStyleStrategy getDefaultStyleStrategy() { // 表头样式 WriteCellStyle headStyle = new WriteCellStyle(); WriteFont headFont = new WriteFont(); headFont.setBold(true); // 加粗 headFont.setFontHeightInPoints((short) 11); headStyle.setWriteFont(headFont); headStyle.setHorizontalAlignment(HorizontalAlignment.CENTER); // 水平居中 headStyle.setVerticalAlignment(VerticalAlignment.CENTER); // 垂直居中 // 内容样式 WriteCellStyle contentStyle = new WriteCellStyle(); WriteFont contentFont = new WriteFont(); contentFont.setFontHeightInPoints((short) 11); contentStyle.setWriteFont(contentFont); contentStyle.setHorizontalAlignment(HorizontalAlignment.CENTER); contentStyle.setVerticalAlignment(VerticalAlignment.CENTER); // 返回样式策略 return new HorizontalCellStyleStrategy(headStyle, contentStyle); } /** * 关闭流(工具类内部使用) */ private static void closeStream(Closeable... closeables) { if (closeables != null) { for (Closeable closeable : closeables) { if (Objects.nonNull(closeable)) { try { closeable.close(); } catch (IOException e) { // 日志记录(建议替换为实际项目的日志框架) e.printStackTrace(); } } } } } }