EasyExcel导出导入excel工具类

接上一篇EasyExcel导出导入excel的文章,附上一份完整的工具类代码。对于字体颜色名称,请参考这篇文章。
POI字体颜色

小技巧

  • 类转换用属性拷贝
  • 不同类如果有相同属性,则使用反射验证,减少代码量
    private List<Person> validateAndSetTypeThenReturn(List<Long> list, List<?> objects, String key) {
        Consumer<Object> validator = item -> {
            try {
                Method setType = item.getClass().getMethod("setType", String.class);
                Method getId = item.getClass().getMethod("getId");
                // 设置类型
                setType.invoke(item, key);
                // 校验ID必填
                if (!list.contains((Long) getId.invoke(item))) {
                    throw new ServiceException("无效的ID" + getId.invoke(item));
                }
            } catch (ServiceException ex) {
                throw new ServiceException(ex.getMessage());
            } catch (Exception e) {
                throw new ServiceException("数据验证失败");
            }
        };
        objects.forEach(validator);
        return BeanUtil.copyToList(objects, Person.class);
    }
  • 遍历List不如遍历枚举
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelReader;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.exception.ExcelAnalysisException;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.read.metadata.ReadSheet;
import com.alibaba.excel.write.handler.CellWriteHandler;
import com.alibaba.excel.write.handler.SheetWriteHandler;
import com.alibaba.excel.write.handler.WorkbookWriteHandler;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteTableHolder;
import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import com.yfld.common.core.convert.ExcelBigNumberConvert;
import com.yfld.common.core.exception.ServiceException;
import lombok.Data;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.hssf.usermodel.HSSFRichTextString;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFFont;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @ClassName : ExcelUtilAdvance
 * @Description : excel高级工具
 */
@Slf4j
public class ExcelUtilAdvance {
    /**
     * 导出多 Sheet 版本的 Excel 文件(动态生成,无需模板)
     *
     * @param sheetDataMap Sheet 数据映射,key 为 Sheet 名称,value 为数据列表(列表元素需为同一 DTO 类型)
     * @param filename     导出的 Excel 文件名
     * @param response     HttpServletResponse
     * @throws IOException 文件操作异常
     */
    public static void exportDynamicMultiSheet(LinkedHashMap<String, List<?>> sheetDataMap, String filename, HttpServletResponse response) {
        try {
            // 1. 设置 HTTP 响应头(文件类型和编码)
            setResponse(filename, response);

            // 2. 创建写入处理器-指定样式
            HorizontalCellStyleStrategy styleStrategy = new HorizontalCellStyleStrategy(createHeaderStyle(), createContentStyle());

            // 3. 创建 ExcelWriter 对象(自动关闭流)
            try (ExcelWriter excelWriter = EasyExcel
                    .write(response.getOutputStream())
                    .autoCloseStream(true)
//                    .autoTrim(true)
                    .registerConverter(new ExcelBigNumberConvert())
                    .registerWriteHandler(new FirstRedStarHeaderHandler())  // 表头着色器,将*号标记为红色
                    .registerWriteHandler(styleStrategy)  // 样式处理器
                    .registerWriteHandler(new AutoColumnWidthStyleStrategy())  // 自动列宽适配
                    .registerWriteHandler(new ZoomScaleHandler(150))  // 缩放比例
                    .build()) {

                // 4. 遍历所有 Sheet 数据
                for (Map.Entry<String, List<?>> entry : sheetDataMap.entrySet()) {
                    String sheetName = entry.getKey();
                    List<?> dataList = entry.getValue();

                    // 4.1 动态推断 DTO 类型(通过列表第一个元素)
                    if (CollUtil.isEmpty(dataList)) {
                        throw new IllegalArgumentException("Sheet [" + sheetName + "] 数据列表不能为空");
                    }
                    Class<?> dtoClass = dataList.get(0).getClass();

                    // 4.2 创建 WriteSheet(动态绑定表头和样式)
                    WriteSheet writeSheet = EasyExcel.writerSheet(sheetName)
                            .head(dtoClass)          // 根据 DTO 类自动生成表头
                            .needHead(Boolean.TRUE)  // 强制生成表头
                            .registerWriteHandler(new CommentWriteHandler())
                            .build();

                    // 4.3 写入数据(自动跳过空列表)
                    excelWriter.write(dataList, writeSheet);
                }
            }
        } catch (IOException e) {
            throw new ServiceException("导出 Excel 失败:" + e.getMessage(), e);
        }
    }

    /**
     * 获取excel sheet名称
     */
    public static List<String> getSheetNames(MultipartFile file) {
        try (ExcelReader excelReader = EasyExcel.read(file.getInputStream()).build()) {
            return CollUtil.newArrayList(excelReader.excelExecutor().sheetList().stream().map(ReadSheet::getSheetName)
                    .collect(Collectors.toList()));
        } catch (Exception e) {
            throw new RuntimeException("获取 Sheet 名称失败:" + e.getMessage(), e);
        }
    }

    /**
     * 导入多 Sheet 版本的 Excel 文件,自动将每个 Sheet 映射到指定实体类
     *
     * @param file         上传的 Excel 文件
     * @param sheetNameMap Sheet 配置信息,key 为 Sheet 名称,value 为对应实体类的 Class 对象
     * @return 解析后的数据 Map,key 为 Sheet 名称,value 为对应实体对象列表
     */
    public static Map<String, List<?>> importDynamicMultiSheet(MultipartFile file, Map<String, Class<?>> sheetNameMap) {
        try {
            // 1. 创建 ExcelReader 对象(自动关闭流)
            try (ExcelReader excelReader = EasyExcel.read(file.getInputStream())
                    .registerConverter(new ExcelBigNumberConvert())
                    .autoCloseStream(true) // 确保流自动关闭
                    .build()) {

                // 2. 初始化数据容器,存储每个 Sheet 的解析结果
                Map<String, List<?>> dataMap = new LinkedHashMap<>(); // 保持插入顺序

                // 3. 遍历 Sheet 配置信息
                for (Map.Entry<String, Class<?>> entry : sheetNameMap.entrySet()) {
                    String sheetName = entry.getKey();
                    Class<?> clazz = entry.getValue();

                    // 4. 创建数据监听器(泛型安全)
                    GenericSheetListener<Object> listener = new GenericSheetListener<>();

                    // 5. 构建 ReadSheet(指定 Sheet 名称和表头类)
                    ReadSheet readSheet = EasyExcel.readSheet(sheetName)
                            .head(clazz)
                            .registerReadListener(listener)
                            .headRowNumber(1) // 表头行数
                            .build();

                    try {
                        // 6. 读取当前 Sheet 数据
                        excelReader.read(readSheet);
                    } catch (Exception e) {
                        throw new RuntimeException("解析 Sheet [" + sheetName + "] 失败: " + e.getMessage(), e);
                    }

                    // 7. 将解析结果存入 Map(类型安全转换)
                    dataMap.put(sheetName, convertToTypedList(listener.getDataList(), clazz));
                }
                return dataMap;
            }
        } catch (Exception e) {
            throw new RuntimeException("导入 Excel 失败:" + e.getMessage(), e);
        }
    }

    private static <T> List<T> convertToTypedList(List<Object> dataList, Class<T> clazz) {
        return dataList.stream()
                .map(clazz::cast)
                .collect(Collectors.toList());
    }

    /**
     * 创建统一的表头样式(灰色背景、居中、加粗)
     *
     * @return WriteCellStyle
     */
    private static WriteCellStyle createHeaderStyle() {
        WriteCellStyle style = new WriteCellStyle();
        // 表头背景色-青绿色
        style.setFillForegroundColor(IndexedColors.AQUA.getIndex());
        style.setWrapped(false);
//        style.setQuotePrefix(true);  // 强制以文本格式处理,关闭转义
        // 水平剧中
        style.setHorizontalAlignment(HorizontalAlignment.CENTER);
        // 自动调整列宽
        // 字体
        WriteFont font = new WriteFont();
        font.setBold(true);
        font.setFontName("宋体");
        font.setFontHeightInPoints((short) 11);
//        font.setColor(IndexedColors.BLACK.getIndex());
        style.setWriteFont(font);
        return style;
    }

    /**
     * 创建内容样式(中对齐、黑色字体)
     */
    private static WriteCellStyle createContentStyle() {
        WriteCellStyle style = new WriteCellStyle();
        style.setHorizontalAlignment(HorizontalAlignment.CENTER);
        WriteFont font = new WriteFont();
        font.setFontName("宋体");
        font.setFontHeightInPoints((short) 10);
        style.setWriteFont(font);
        return style;
    }

    private static void setResponse(String filename, HttpServletResponse response) throws UnsupportedEncodingException {
        // 设置响应头-excel文件类型和文件名
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(filename, "UTF-8"));
    }

    /**
     * 通用 Sheet 数据监听器(泛型类)
     *
     * @param <T> 实体类型
     */
    @Getter
    private static class GenericSheetListener<T> implements ReadListener<T> {
        private final List<T> dataList = new ArrayList<>();

        @Override
        public void invoke(T data, AnalysisContext context) {
            if (StrUtil.isEmpty(data.toString())) {
                throw new ExcelAnalysisException("第 " + context.readRowHolder().getRowIndex() + " 行数据错误:不能为空");
            }
            dataList.add(data); // 收集每行数据
        }

        @Override
        public void doAfterAllAnalysed(AnalysisContext context) {
            // 可在此添加后处理逻辑(如校验、日志)
            log.info("解析完成,共处理 {} 行数据", dataList.size());
        }
    }

    /**
     * 自动调整列宽,用于在写入完成后动态设置列宽
     */
    public static class AutoColumnWidthStyleStrategy implements WorkbookWriteHandler {
        @Override
        public void afterWorkbookDispose(WriteWorkbookHolder writeWorkbookHolder) {
            SXSSFWorkbook workbook = (SXSSFWorkbook) writeWorkbookHolder.getWorkbook();
            // 遍历所有Sheet
            for (int i = 0; i < workbook.getNumberOfSheets(); i++) {
                SXSSFSheet sheet = workbook.getSheetAt(i);
                adjustColumnWidth(sheet);
            }
        }

        private void adjustColumnWidth(SXSSFSheet sheet) {
            sheet.trackAllColumnsForAutoSizing();
            // 获取第一行(表头)
            short columnCount = sheet.rowIterator().next().getLastCellNum();
            for (int i = 0; i < columnCount; i++) {
                // 自动调整列宽
                sheet.autoSizeColumn(i);
                // 解决自动设置列宽中文失效的问题
//                sheet.setColumnWidth(i, sheet.getColumnWidth(i) * 17 / 10);
                // 设置列宽范围(示例:10到50字符)
                int currentWidth = sheet.getColumnWidth(i);
                int minWidth = 15 * 256;  // 10字符
                int maxWidth = 25 * 256;  // 25字符
                if (currentWidth < minWidth) {
                    sheet.setColumnWidth(i, minWidth);
                } else if (currentWidth > maxWidth) {
                    sheet.setColumnWidth(i, maxWidth);
                }
            }
        }
    }

    /**
     * 自定义缩放比例处理器,用于在写入完成后设置 Sheet 的显示比例
     */
    @Data
    public static class ZoomScaleHandler implements SheetWriteHandler {

        private final int zoomScale; // 缩放比例(如 150 表示 150%)

        @Override
        public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
            Sheet sheet = writeSheetHolder.getSheet();
            // 设置缩放比例(POI 的 setZoom 方法直接接受百分比值)
            sheet.setZoom(zoomScale); // 例如 150 表示 150%
        }
    }

    /**
     * 表头着色器
     */
    public static class FirstRedStarHeaderHandler implements CellWriteHandler {
        @Override
        public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<WriteCellData<?>> cellDataList,
                                     Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
            if (isHead) { // 仅处理表头
                String cellValue = cell.getStringCellValue();
                if (cellValue != null && cellValue.startsWith("*")) {
                    applyRedStarStyle(cell, cellValue);
                }
            }
        }

        private void applyRedStarStyle(Cell cell, String text) {
            Workbook workbook = cell.getSheet().getWorkbook();
            Font redFont = workbook.createFont();
            redFont.setColor(Font.COLOR_RED); // 红色
            Font blackFont = workbook.createFont();
            blackFont.setColor(Font.COLOR_NORMAL); // 黑色

            // 拆分文本:* 和后续文字
            String star = text.substring(0, 1); // *
            String content = text.substring(1); // 基线人数

            // 根据 Excel 格式创建富文本
            if (workbook instanceof XSSFWorkbook) {
                XSSFRichTextString richText = new XSSFRichTextString();
                richText.append(star, (XSSFFont) redFont);
                richText.append(content, (XSSFFont) blackFont);
                cell.setCellValue(richText);
            } else {
                HSSFRichTextString richText = new HSSFRichTextString(text);
                richText.applyFont(0, 1, redFont);
                richText.applyFont(1, text.length(), blackFont);
                cell.setCellValue(richText);
            }
        }
    }

    public static class CommentWriteHandler implements SheetWriteHandler {

        @Override
        public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
            Sheet sheet = writeSheetHolder.getSheet();
            Workbook workbook = writeWorkbookHolder.getWorkbook();

            // 1. 获取表头列数(假设数据模型类通过 @ExcelProperty 定义列)
            int columnCount = writeSheetHolder.getExcelWriteHeadProperty().getHeadMap().size();

            // 2. 创建提示语行(第1行)
            Row commentRow = sheet.createRow(0);
            commentRow.setHeightInPoints(43); // 设置行高(单位:磅)

            // 3. 设置提示语内容和样式
            Cell commentCell = commentRow.createCell(0);
            commentCell.setCellValue("注意:\n1、第1行、第2行请勿修改或删除!!\n2、第1行、第2行请勿修改或删除!!\n3、第1行、第2行请勿修改或删除!!");

            // 4. 合并单元格(根据表头列数合并)
            CellRangeAddress mergedRegion = new CellRangeAddress(0, 0, 0, columnCount - 1);
            sheet.addMergedRegion(mergedRegion);

            // 5. 设置单元格样式(红色字体、自动换行)
            CellStyle style = workbook.createCellStyle();
            Font font = workbook.createFont();
            font.setColor(Font.COLOR_RED);
            font.setFontHeightInPoints((short) 8);
            font.setBold(false);
            style.setFont(font);
            style.setWrapText(true); // 自动换行
            commentCell.setCellStyle(style);

            // 6. 调整数据起始行(表头从第2行开始,数据从第3行开始)
            writeSheetHolder.setRelativeHeadRowIndex(1);
//            writeSheetHolder.setIn(2);
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值