编码技巧——使用Easypoi导出Excel、多sheet、模板导出

本文主要介绍easypoi导出Excel的代码示例;自己之前手动实现过导出工具类《编码技巧——导出工具类》,基于实体和注解,通过反射来映射实体字段和exce列的关系;在部分工程里面看到了easypoi的二方包,于是准备试用下,记录下如何引入并使用Easypoi的过程;

总的来说,easypoi基于Apache poi二次封装的开源二方包,用起来也比较简单,也提供了注解方式导出,并且在顺序、层级、颜色宽度、单元格合并等样式处理上,提供了丰富的注解属性和接口,推荐使用;

功能简介

easypoi是为了让开发者快速的实现excel、word、pdf的导入导出,基于Apache poi基础上的一个工具包;功能如下:
(1)基于注解的导入导出,可以灵活定义的表头字段,修改注解就可以修改Excel;
(2)支持常用的样式自定义;
(3)支持一对多的导出/导入;
(4)支持模板的导出,一些常见的标签,自定义标签;
(5)支持HTML/Excel转换;
(6)支持word、图片、excel的导出;

常用注解

因为本篇介绍的是使用easypoi注解方式的示例,先介绍下常用的注解;

1. @ExcelTarget

@ExcelTarget注解作用于最外层的对象,可以这么理解——这个类与一个excel对应(类属性对应excel的列);在使用easypoi的API导出excel生成Workbook对象时,作为参数传入,如下:

/**
 * @description 导出实体类
 */
@Data
@ExcelTarget("auditFlowExport")
public class AuditFlowExport {
    ...
}

// 调用easypoi的API生成excel对象
final Workbook sheets = ExcelExportUtil.exportExcel(exportParams, AuditFlowExport.class, exportList);

2. @Excel

@Excel 注解是作用到Filed上面,是对Excel一列的一个描述,这个注解是必须要的注解,使用示例如下:

    /**
     * 一级审批人,excel列名:一级审批人、顺序6、宽度20、分组"审批人信息"
     */
    @Excel(name = "一级审批人", orderNum = "6", width = 20, groupName = "审批人信息")
    private String firstAuditor;

3. @ExcelEntity

@ExcelEntity注解表示一个继续深入导出的实体,是作用一个类型为实体的属性上面;

4. @ExcelCollection

@ExcelCollection注解表示一个集合,主要针对一对多的导出,作用在类型是List的属性上面;比如一个工单对应多个审批人,审批人就可以用集合表示如下:

    /**
     * 审核人信息
     */
    @ExcelCollection(name = "审核人信息", orderNum = "6")
    private List<StepAndAuditorExport> stepAndAuditorList;

Easypoi的使用示例

需求背景

将工单记录以Excel格式导出给审计人员,每条记录包括工单的基本信息;由于不同工单的审核层级不同,因此将审核人信息合并展示;

以下是接入及实现的步骤:

1. 引入依赖

当前工程为SpringBoot项目,引入以下依赖;

        <!--easypoi-->
        <dependency>
            <groupId>cn.afterturn</groupId>
            <artifactId>easypoi-spring-boot-starter</artifactId>
            <version>4.0.0</version>
        </dependency>

需要注意的是由于easypoi的依赖内部依赖原生的poi,所以引入了easypoi的依赖之后,需要把原生的poi的依赖删掉,不然可能遇到类冲突;

2. 定义导出数据对应的类

通过注解方式定义字段的excel列名、宽度、字体等;因为里面存在一对多(List类型给的属性),因此其他字段的注解属性加了needMerge = true,表示合并单元格;

import cn.afterturn.easypoi.excel.annotation.Excel;
import cn.afterturn.easypoi.excel.annotation.ExcelCollection;
import cn.afterturn.easypoi.excel.annotation.ExcelTarget;
import lombok.Data;

import java.util.Date;
import java.util.List;

/**
 * @author Akira
 * @description 导出实体类
 */
@Data
@ExcelTarget("auditFlowExport")
public class AuditFlowExport {

    /**
     * 工单id
     */
    @Excel(name = "审批单号", orderNum = "1", needMerge = true)
    private Long flowId;

    /**
     * 工单标题
     */
    @Excel(name = "流程名称", orderNum = "2", width = 50, needMerge = true)
    private String title;

    /**
     * 发起人
     */
    @Excel(name = "发起人", orderNum = "3", width = 18, needMerge = true)
    private String submitorName;

    /**
     * 工单创建时间 格式yyyy-MM-dd HH:mm:ss
     */
    @Excel(name = "发起时间", orderNum = "4", exportFormat = "yyyy-MM-dd HH:mm:ss", width = 25, needMerge = true)
    private Date createTime;

    /**
     * 业务模块编码
     */
    @Excel(name = "归属项目", orderNum = "5", width = 25, needMerge = true)
    private String secondSubModuleCodeName;

    /**
     * 审核人信息 因为存在一对多,因此其他列属性定义needMerge = true 即合并单元格
     */
    @ExcelCollection(name = "审核人信息", orderNum = "6")
    private List<StepAndAuditorExport> stepAndAuditorList;

    /**
     * 详情信息
     */
    @Excel(name = "详情信息", orderNum = "7", width = 120, needMerge = true)
    private String displayText;

    @Data
    public static class StepAndAuditorExport {
        @Excel(name = "审核层级", orderNum = "1", width = 10)
        private int step;
        @Excel(name = "审核人", orderNum = "2", width = 18)
        private String auditor;
    }
}

3. 定义导出格式

调用导出方法时,其中一个参数就是样式;easypoi已经默认了一套样式,有自定义样式需求时,支持实现原默认样式类来扩展样式;下面的样式比较简单,仅将表头的字体设置大一号且加粗;

/**
 * @author Akira
 * @description 简单样式 仅统一字体
 */
public class ExcelSimpleStyleUtil extends ExcelExportStylerDefaultImpl {

    public ExcelSimpleStyleUtil(Workbook workbook) {
        super(workbook);
    }

    /**
     * 这里设置表头的格式,最上面的一行
     * @see ExportParams#title
     */
    @Override
    public CellStyle getHeaderStyle(short color) {
        CellStyle cellStyle = super.getHeaderStyle(color);
        cellStyle.setFont(getFont(workbook, 11, true));
        cellStyle.setFillBackgroundColor(HSSFColor.HSSFColorPredefined.GREY_50_PERCENT.getIndex());
        return cellStyle;
    }

    /**
     * 列标题
     */
    @Override
    public CellStyle getTitleStyle(short color) {
        CellStyle cellStyle = super.getTitleStyle(color);
        // 仅将表头的字体设置大一号且加粗
        cellStyle.setFont(getFont(workbook, 11, true));
        cellStyle.setFillForegroundColor(HSSFColor.HSSFColorPredefined.LIGHT_TURQUOISE.getIndex());
        return cellStyle;
    }

    /*以下都是行样式,交替*/

    /**
     * 行样式
     */
    @Override
    public CellStyle stringSeptailStyle(Workbook workbook, boolean isWarp) {
        CellStyle cellStyle = super.stringSeptailStyle(workbook, isWarp);
        cellStyle.setFont(getFont(workbook, 10, false));
        return cellStyle;
    }

    /**
     * 这里设置循环行,没有样式的一行
     */
    @Override
    public CellStyle stringNoneStyle(Workbook workbook, boolean isWarp) {
        CellStyle cellStyle = super.stringNoneStyle(workbook, isWarp);
        cellStyle.setFont(getFont(workbook, 10, false));
        return cellStyle;
    }

    /**
     * 字体样式
     *
     * @param size   字体大小
     * @param isBold 是否加粗
     * @return
     */
    private Font getFont(Workbook workbook, int size, boolean isBold) {
        Font font = workbook.createFont();
        // 字体样式
        font.setFontName("微软雅黑");
        // 是否加粗
        font.setBold(isBold);
        // 字体大小
        font.setFontHeightInPoints((short) size);
        return font;
    }

}

4. 导出方法

其核心就是调用easypoi封装好的ExcelExportUtil#exportExcel方法,返回Workbook对象,然后向response里面写字节流;

    /**
     * 导出
     *
     * @param response
     * @param pageQuery
     */
    public void export(HttpServletResponse response, AuditFlowPageQuery query) {
			// 查询结果并转换成导出类型AuditFlowExport
            final List<AuditFlowExport> exportList = buildExportList(query);

			// 不需要标题栏和二级标题 这里仅定义sheet的名称
            final ExportParams exportParams = new ExportParams(null, null, "导出数据");
            // 设置导出样式
            exportParams.setStyle(ExcelStyleUtil.class);
			// easypoi的核心方法 将java的List写成excel的每一行数据
            final Workbook sheets = ExcelExportUtil.exportExcel(exportParams, AuditFlowExport.class, exportList);

            // 导出文件
            try {
                String fileName = new String("记录导出结果.xls".getBytes("GBK"), StandardCharsets.ISO_8859_1);
                response.setContentType("application/octet-stream");
                response.setHeader("Content-disposition", "attachment;filename=" + fileName);
                OutputStream outputStream = response.getOutputStream();
                response.flushBuffer();
                sheets.write(outputStream);
                // 写完数据关闭流
                outputStream.close();
                log.warn("export success. [pageQuery={} total={}] ", JSON.toJSONString(pageQuery), exportList.size());
            } catch (IOException e) {
                log.error("export error! [pageQuery={}] e:{}", JSON.toJSONString(pageQuery), e);
            }
        }
    }

5. 接口定义

一般的导出接口controller写法,默认GET方式,比较简单;

    /**
     * 我的可读工单导出
     */
    @RequestMapping(value = {"/audit/flow/export"})
    public DefaultResponseDTO<Boolean> export(AuditFlowPageQuery pageQuery, HttpServletResponse response) {
        try {
            userAuditFlowService.export(response, pageQuery);
            return DefaultResponseDTO.success(Boolean.TRUE);
        } catch (Exception e) {
            log.error("audit flow export error", e);
            return DefaultResponseDTO.fail(ResultCodeEnum.SERVER_BUSYNESS, "请稍后重试");
        }
    }

场景和代码都比较简单,如果想使用更加复杂的excel相关API的用法,可以参考下面的文章;

补充:同一个excel导出多个sheet

关于在同一个excel文件中导出多个sheet的方法,在网上搜了一圈,没有写的很清楚的,要么是复制粘贴的帖子,要么是把自己的代码原封不动复制上去,能不能编译通过都另说;自己通过查看easypoi的API,看到有一条这样的方法,如下:

// cn.afterturn.easypoi.excel.ExcelExportUtil#exportExcel

    /**
     * 根据Map创建对应的Excel(一个excel 创建多个sheet)
     *
     * @param list 多个Map key title 对应表格Title key entity 对应表格对应实体 key data
     *             Collection 数据
     * @return
     */
    public static Workbook exportExcel(List<Map<String, Object>> list, ExcelType type) {
        Workbook workbook = getWorkbook(type, 0);
        for (Map<String, Object> map : list) {
            ExcelExportService service = new ExcelExportService();
            service.createSheet(workbook, (ExportParams) map.get("title"),
                    (Class<?>) map.get("entity"), (Collection<?>) map.get("data"));
        }
        return workbook;
    }

注意,这个方法仅支持导出xls类型的excel,也就是第二个参数必须为ExcelType.HSSF,如果改成ExcelType.XSSF,执行时会报错,原因具体不清楚,以验证过;一般来说单sheet行数小于65535,差不多够用了,否则就去操作原生的Apache poi吧;

注意:以上的描述有误,感谢评论区小伙伴的提醒,此方法在使用时,方法参数的第二位ExcelType,需要和导出配置ExportParams对象中指定的类型一致,否则会报错

示例代码如下:

    @Override
    public void exportMultiSheetWorkbook(List<List<MyExport>> exportDataSet, HttpServletResponse response) {
		// 多个sheet配置参数
        final List<Map<String, Object>> sheetsList = Lists.newArrayList();
        exportDataSet.forEach(exportList -> {
            final String sheetName = "sheet名称";           
            Map<String, Object> exportMap = Maps.newHashMap();
            final ExportParams exportParams = new ExportParams(null, sheetName, ExcelType.HSSF);
            exportParams.setStyle(ExcelSimpleStyleUtil.class);
			// 以下3个参数为API中写死的参数名 分别是sheet配置/导出类(注解定义)/数据集
            exportMap.put("title", exportParams);
            exportMap.put("entity", MyExport.class);
            exportMap.put("data", exportList);
			// 加入多sheet配置列表
            sheetsList.add(exportMap);
        });

        // 导出文件
        try {
			// 核心方法:导出含多个sheet的excel文件 【注意,该方法第二个参数必须与上述的ExportParams对象指定的导出类型一致,默认ExcelType.HSSF格式,否则执行此方法时会报错!!!】
            final Workbook workbook = ExcelExportUtil.exportExcel(sheetsList, ExcelType.HSSF);
			
            String fileName = new String("文件名.xls".getBytes("GBK"), StandardCharsets.ISO_8859_1);
            response.setContentType("application/octet-stream");
            response.setHeader("Content-disposition", "attachment;filename=" + fileName);
            OutputStream outputStream = response.getOutputStream();
            response.flushBuffer();
            workbook.write(outputStream);
            // 写完数据关闭流
            outputStream.close();
            log.warn("导出成功");
        } catch (IOException e) {
            log.error("导出异常 e:{}", e);
        }
    }

补充:使用excel模板导出

实际工程中,可能会遇到"模板导出"的场景,如固定的表标题、表头,仅需要在固定的格式下面新增列去填充数据;例如:

这时候,可以使用easypoi的另一个API,带TemplateExportParams参数的方法;

其步骤是:

准备excel模板,在指定的单元格填充好easypoi支持的表达式;

再根据模板excel文件生成TemplateExportParams对象;

调用API,传入上面的TemplateExportParams对象和数据对象集合,返回excel对象;

示例代码:

	@Override
	public Workbook export(UnitInfoQuery unitInfoQuery) {
		TemplateExportParams params = new TemplateExportParams("doc/导出模板.xls");
		List<UnitInfoDO> unitInfoDOList = unitInfoDAO.selectByCondition(unitInfoQuery);
		List<ExcelUnitInfoVO> units = unitInfoDOList.stream().map(beanConvertMapper::convert2ExcelUnitInfoVO).collect(Collectors.toList());
		Map<String, Object> map = new HashMap<>();
		// 字段名与导出excel模板表达式对应
		map.put("units", units);
		return ExcelExportUtil.exportExcel(params, map);
	}

注意表达式的写法,变量名这些,踩坑经验可参考:EasyPOI模板导出的坑TemplateExportParams

参考:

easypoi - gitee

使用easypoi导入导出Excel的操作手册

使用easypoi合并单元格(groupName)

easypoi导出一对多,合并单元格,且根据内容自适应行高

超级简单POI导出Excel实战

EasyPOI模板导出的坑TemplateExportParams

  • 8
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值