EasyExcel导出复杂表格到邮箱

简介: 自定义单元格合并策略、调用示例、工具类Bo、Excel 模板导出工具类、Vo配置、效果图、MailUtils、模板配置

EasyExcel导出复杂表格到邮箱

📔 千寻简笔记介绍

千寻简笔记已开源,Gitee与GitHub搜索chihiro-notes,包含笔记源文件.md,以及PDF版本方便阅读,且是用了精美主题,阅读体验更佳,如果文章对你有帮助请帮我点一个Star

更新:支持在线阅读文章,根据发布日期分类。

@[toc]

简介

在企业开发中,我们经常需要面对复杂的Excel导出,本文基于EasyExcel,自定义单元格合并策略,达到我们需要导出的效果。

邮箱配置可以参考主页的ruoyi-vue-plus 配置邮箱

效果图

image-20230828185404792

版本

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>easyexcel</artifactId>
            <version>3.3.2</version>
        </dependency>

本文关键词

自定义单元格合并策略调用示例工具类BoExcel 模板导出工具类Vo配置效果图MailUtils模板配置

实现步骤

导出到邮箱

1 调用示例

  • 模板存放位置src/main/resources/templates 目录下。
  • templateName只传文件名:WorkSafetyReportTemplate.xlsx
  • setMergeRowIndex:从哪一行开始合并0开始。
  • setUniqueColumnIndex:唯一标识列。
  • setMergeColumnIndex:需要合并的列。
  • setList:导出的数据。
  • MailUtils.sendText:使用ruoyi提供的MailUtils工具发送邮件。
    /***
     * 1 根据模板的生成对应excel文件
     * 2.使用ruoyi提供的MailUtils工具发送邮件,附件为生成的excel文件
     * **/
    public Boolean exportExcelToEmail(XXXX bo) {
   
   
        // 省略业务查询
        List<Xxxxxx> dataList = iRiskXxxxxxService.queryList();

        EasyExcelExportBo exportBo = new EasyExcelExportBo();
        exportBo.setTemplateName("WorkXxxxxTemplate.xlsx");
        exportBo.setMergeRowIndex(1);
        exportBo.setUniqueColumnIndex(1);
        exportBo.setMergeColumnIndex(Sets.newHashSet(1,2,3,11));
        exportBo.setList(dataList);
        // 返回文件路径
        String filePath = new EasyExcelExportUtil().export(exportBo);
        File file = new File(filePath);
        MailUtils.sendText(bo.getEmail(), "导出附件", "导出测试清单", file);
        return true;
    }

2 工具类Bo

  • 用于传输导出必要参数。

EasyExcelExportBo.java

import lombok.Data;
import java.util.List;
import java.util.Set;

/**
 * 导出必要参数
 * @author Star
 */
@Data
public class EasyExcelExportBo {
   
   
    /**
     * 唯一标识列(该列cell内容一定是全局唯一的,当cell的值相等时才能进行下一列合并)
     */
    private Integer uniqueColumnIndex;

    /**
     * 从哪一行开始合并,从0开始
     */
    private Integer mergeRowIndex;

    /**
     * 合并列编号,从0开始:需要合并的列
     */
    private Set<Integer> mergeColumnIndex;

    /**
     * 模板文件名
     *
     * 默认存放在src/main/resources/templates 目录下
     * templateName只传文件名:WorkSafetyReportTemplate.xlsx
     */
    private String templateName;

    /**
     * 导出数据Vo
     */
    private List<?> list;
}

3 Excel 模板导出工具类

  • 封装的Excel 模板导出工具类。

EasyExcelExportUtil.java

import cn.hutool.core.io.resource.ResourceUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.google.common.collect.Sets;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.papp.model.EasyExcelExportBo;
import com.ruoyi.project.domain.vo.RiskAssessmentVo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;

/**
 * Excel 模板导出工具类
 * @author Star
 */
public class EasyExcelExportUtil {
   
   
    private static Logger logger = LoggerFactory.getLogger(EasyExcelExportUtil.class);

    /**
     * 获取resources路径
     * @return
     */
    public static String getPath() {
   
   
        return EasyExcelExportUtil.class.getResource("/").getPath();
    }


    /**
     * 导出方法
     * @param paramet
     * @return 文件路径
     */
    public String export(EasyExcelExportBo paramet){
   
   
        if (paramet.getTemplateName() == null) {
   
   
            throw new ServiceException("请填写模板文件名");
        }
        if (paramet.getMergeRowIndex() == null) {
   
   
            throw new ServiceException("请填写合并行");
        }
        if (paramet.getUniqueColumnIndex() == null) {
   
   
            throw new ServiceException("请填写唯一标识列");
        }
        if (paramet.getList() == null || paramet.getList().size() == 0) {
   
   
            throw new ServiceException("请填充导出的数据");
        }
        if (paramet.getMergeColumnIndex() == null || paramet.getMergeColumnIndex().size() == 0) {
   
   
            throw new ServiceException("请输入需要合并的列");
        }

        //处理文件导出
        InputStream template = null;
        try {
   
   
            // 模板路径
            template = ResourceUtil.getStream("classpath:templates/"+paramet.getTemplateName());
            // 导出文件名称
            String fileName = getPath() + "templateWrite" + System.currentTimeMillis() + ".xlsx";

            //读取Excel
            ExcelWriter excelWriter = EasyExcel.write(fileName)
                .withTemplate(template)
                .excelType(ExcelTypeEnum.XLSX)
                .registerWriteHandler(new CellMergeStrategy(paramet.getUniqueColumnIndex(), paramet.getMergeRowIndex(), paramet.getMergeColumnIndex()))
                .build();
            WriteSheet writeSheet = EasyExcel.writerSheet().build();

            //list map 是查询并需导出的数据,并且里面的字段和excel需要导出的字段对应
            // 直接写入Excel数据
            excelWriter.fill(paramet.getList(), writeSheet);
            excelWriter.finish();
            logger.info("文件导出完毕,路径:"+fileName);
            return fileName;
        } catch (Exception e) {
   
   
            System.out.println(e.getMessage());
            throw new RuntimeException("导出文件错误");
        } finally {
   
   
            if (template != null) {
   
   
                try {
   
   
                    template.close();
                } catch (IOException e) {
   
   
                    e.printStackTrace();
                }
            }
        }
    }

}

4 自定义单元格合并策略(重点)

  • 自定义单元格合并策略,可以指定某一个唯一列uniqueColumnIndex做为合并的条件。

CellMergeStrategy.java

import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.write.merge.AbstractMergeStrategy;
import com.google.common.collect.Lists;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddress;

import java.util.List;
import java.util.Set;


/**
 * 自定义单元格合并策略:推荐使用
 * @author Star
 */
public class CellMergeStrategy extends AbstractMergeStrategy {
   
   

    /** 唯一标识列(该列cell内容一定是全局唯一的,当cell的值相等时才能进行下一列合并) */
    private Integer uniqueColumnIndex;
    /** 从哪一行开始合并 */
    private Integer mergeRowIndex = 0;
    /** 合并列编号,从0开始 */
    private List<Integer> mergeColumnIndex = Lists.newArrayList();

    private CellMergeStrategy() {
   
   
    }

    /**
     * @param uniqueColumnIndex 唯一标识列(该列cell内容一定是全局唯一的,当cell的值相等时才能进行下一列合并)
     * @param mergeRowIndex 从哪一行开始合并
     * @param mergeColumnIndex 合并列编号,从0开始:需要合并的列
     */
    public CellMergeStrategy(Integer uniqueColumnIndex, Integer mergeRowIndex, Set<Integer> mergeColumnIndex) {
   
   
        mergeColumnIndex.stream().forEach(item -> {
   
   
            this.mergeColumnIndex.add(item);
        });
        this.mergeColumnIndex.stream().sorted();

        if (null == uniqueColumnIndex) {
   
   
            this.uniqueColumnIndex = this.mergeColumnIndex.get(0);
        } else {
   
   
            this.uniqueColumnIndex = uniqueColumnIndex;
        }
        this.mergeRowIndex = mergeRowIndex;
    }

    @Override
    protected void merge(Sheet sheet, Cell cell, Head head, Integer relativeRowIndex) {
   
   
        int curColIndex = cell.getColumnIndex();
        int curRowIndex = cell.getRowIndex();

        // 判断该列是否需要合并
        if (!mergeColumnIndex.contains(curColIndex)) {
   
   
            return;
        }

        if (curRowIndex > mergeRowIndex) {
   
   
            for (int i = 0; i < mergeColumnIndex.size(); i++) {
   
   
                if (curColIndex == mergeColumnIndex.get(i)) {
   
   
                    this.mergeRow(sheet, cell, curRowIndex, curColIndex, uniqueColumnIndex);
                    break;
                }
            }
        }
    }

    /**
     * 向上合并单元格
     * @param cell              当前单元格
     * @param rowIndex          当前行
     * @param colIndex          当前列
     * @param uniqueColIndex    唯一标识列(该列cell内容一定是全局唯一的,当cell的值相等时才能进行下一列合并)
     */
    private void mergeRow(Sheet sheet, Cell cell, int rowIndex, int colIndex, int uniqueColIndex) {
   
   
        Object curCellValue = getCellValue(cell);
        Object preCellValue = getCellValue(cell.getSheet().getRow(rowIndex - 1).getCell(colIndex));
        boolean cellEqual = preCellValue.equals(curCellValue);

        boolean baseCellEqual = true;
        if (colIndex >= uniqueColIndex) {
   
   
            Object baseCellValue = getCellValue(cell.getRow().getCell(uniqueColIndex));
            Object preBaseCellValue = getCellValue(cell.getSheet().getRow(rowIndex - 1).getCell(uniqueColIndex));
            baseCellEqual = baseCellValue.equals(preBaseCellValue);
        }

        /**
         * 合并条件
         * 1. 将当前单元格数据与上一个单元格数据比较,相同则执行合并逻辑
         * 2. 唯一标识列内容相同,才能进行下一列合并
         */
        if (!(cellEqual && baseCellEqual)) {
   
   
            return;
        }

        List<CellRangeAddress> mergeRegionList = sheet.getMergedRegions();
        boolean isMerged = false;
        for (int i = 0; i < mergeRegionList.size() && !isMerged; i++) {
   
   
            CellRangeAddress cellRange = mergeRegionList.get(i);
            // 若上一个单元格已经被合并,则先移出原有的合并单元,再重新添加合并单元
            if (cellRange.isInRange(rowIndex - 1, colIndex)) {
   
   
                sheet.removeMergedRegion(i);
                cellRange.setLastRow(rowIndex);
                sheet.addMergedRegion(cellRange);
                isMerged = true;
            }
        }
        // 若上一个单元格未被合并,则新增合并单元
        if (!isMerged) {
   
   
            CellRangeAddress cellRange = new CellRangeAddress(rowIndex - 1, rowIndex, colIndex, colIndex);
            sheet.addMergedRegion(cellRange);
        }
    }

    private Object getCellValue(Cell baseCell) {
   
   
        return CellType.STRING.equals(baseCell.getCellType()) ? baseCell.getStringCellValue() : baseCell.getNumericCellValue();
    }

}

5 Vo配置

  • 业务的数据

  • @ColumnWidth(100 / 8):列宽

  • @HeadStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER):表头样式 居中
  • @ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER,verticalAlignment = VerticalAlignmentEnum.CENTER):主题样式:水平垂直居中
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.alibaba.excel.annotation.write.style.ContentStyle;
import com.alibaba.excel.annotation.write.style.HeadStyle;
import com.alibaba.excel.enums.poi.HorizontalAlignmentEnum;
import com.alibaba.excel.enums.poi.VerticalAlignmentEnum;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

/**
 * 测试使用
 */
@Data
@ColumnWidth(100 / 8)
@HeadStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER)//表头样式
@ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER,verticalAlignment = VerticalAlignmentEnum.CENTER)
public class WuYeDemo {
   
   
    @ExcelProperty(value = "属地")
    private String possession;

    @ExcelProperty(value = "风险点")
    private String riskPoint;

    @ExcelProperty(value = "风险点分类")
    private String riskPointClassification;

    @ExcelProperty(value = "风险场景")
    private String riskScenario;

    @ExcelProperty(value = "风险等级\n" +
        "(风险点)")
    private String riskGrade;
}

6 模板配置

  • {.字段名}:固定导出数据的位置

image-20230828190337018

7 效果图

文件导出路径:target/classes/templateWrite1693195524899.xlsx

image-20230828185404792

目录
相关文章
|
算法 计算机视觉 开发者
|
运维 安全 网络协议
Hfish蜜罐部署及防护经验分享
Hfish蜜罐部署及防护经验分享
4923 1
Hfish蜜罐部署及防护经验分享
|
存储 网络协议 小程序
软考中级网络工程师备考分享
分享我参与2022年下半年软考网工的心得体会
1736 0
软考中级网络工程师备考分享
|
6月前
|
机器学习/深度学习 弹性计算 应用服务中间件
阿里云服务器租赁价格标准整理汇总:一年/按月/按小时报价明细出炉
最新的阿里云服务器价格多少钱1年?阿里云服务器产品包含 ECS 云服务器、轻量应用服务器和 GPU 服务器,涵盖多种实例规格与配置,满足不同使用需求。今天小编总结一篇关于最新的阿里云服务器租赁价格标准整理汇总:包含一年/按月/按小时报价明细。
|
XML Java Maven
Jar包下载失败的解决方案
Jar包下载失败的解决方案
847 0
|
调度 vr&ar 图形学
【干货】实时云渲染与本地渲染的技术对比
实时渲染分为本地渲染和云渲染两种模式。随着XR技术在建筑、教育、医疗等领域的广泛应用,越来越多企业选择云渲染以提升效率、降低成本并增强协同能力。本文对比分析了这两种渲染模式的优劣,并重点介绍了实时云渲染方案具备便捷性、高效资源调度、超低时延网络、数据安全、终端轻量化及跨系统运行等优势,满足多种XR应用场景需求。
788 13
|
搜索推荐 Android开发 iOS开发
安卓vs. iOS:操作系统的终极对决####
本文探讨了安卓和iOS两种主流移动操作系统的特点,从用户体验、系统生态、硬件兼容性等维度进行对比分析。通过深入浅出的方式,揭示了两者在技术层面的优劣及未来发展趋势,旨在帮助用户更好地理解并选择适合自己的平台。 ####
|
存储 弹性计算 固态存储
阿里云服务器按量付费是什么?划算吗?
阿里云ECS按量付费模式,先使用后付费,按小时结算,适合临时扩展、测试、电商抢购等短期应用。开通需账户余额不低于100元。实例、镜像、云盘、带宽等支持按量计费,价格根据配置和地区而异。可随时转为包年包月。
2637 1
|
API 数据库 数据安全/隐私保护
蓝奏云直链获取在线解析网站源码
蓝奏云直链获取在线解析网站源码 蓝奏云链接解析 本地API接口 支持有无密码和短期直链和永久直链,同时还可以显示文件名和大小。 这个解析器无需数据库即可搭建,API接口已经本地化,非常简单易用。
989 2
蓝奏云直链获取在线解析网站源码
|
机器学习/深度学习 计算机视觉 异构计算
【保姆级教程|YOLOv8改进】【4】添加双层路由注意力机制:BiLevelRoutingAttention,性能和效率十分不错
【保姆级教程|YOLOv8改进】【4】添加双层路由注意力机制:BiLevelRoutingAttention,性能和效率十分不错

热门文章

最新文章