• 编码技巧——使用Easypoi导出Excel


    本文主要介绍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对象时,作为参数传入,如下:

    1. /**
    2. * @description 导出实体类
    3. */
    4. @Data
    5. @ExcelTarget("auditFlowExport")
    6. public class AuditFlowExport {
    7. ...
    8. }
    9. // 调用easypoi的API生成excel对象
    10. final Workbook sheets = ExcelExportUtil.exportExcel(exportParams, AuditFlowExport.class, exportList);

    2. @Excel

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

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

    3. @ExcelEntity

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

    4. @ExcelCollection

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

    1. /**
    2. * 审核人信息
    3. */
    4. @ExcelCollection(name = "审核人信息", orderNum = "6")
    5. private List stepAndAuditorList;

    Easypoi的使用示例

    需求背景

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

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

    1. 引入依赖

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

    1. <dependency>
    2. <groupId>cn.afterturngroupId>
    3. <artifactId>easypoi-spring-boot-starterartifactId>
    4. <version>4.0.0version>
    5. dependency>

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

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

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

    1. import cn.afterturn.easypoi.excel.annotation.Excel;
    2. import cn.afterturn.easypoi.excel.annotation.ExcelCollection;
    3. import cn.afterturn.easypoi.excel.annotation.ExcelTarget;
    4. import lombok.Data;
    5. import java.util.Date;
    6. import java.util.List;
    7. /**
    8. * @author Akira
    9. * @description 导出实体类
    10. */
    11. @Data
    12. @ExcelTarget("auditFlowExport")
    13. public class AuditFlowExport {
    14. /**
    15. * 工单id
    16. */
    17. @Excel(name = "审批单号", orderNum = "1", needMerge = true)
    18. private Long flowId;
    19. /**
    20. * 工单标题
    21. */
    22. @Excel(name = "流程名称", orderNum = "2", width = 50, needMerge = true)
    23. private String title;
    24. /**
    25. * 发起人
    26. */
    27. @Excel(name = "发起人", orderNum = "3", width = 18, needMerge = true)
    28. private String submitorName;
    29. /**
    30. * 工单创建时间 格式yyyy-MM-dd HH:mm:ss
    31. */
    32. @Excel(name = "发起时间", orderNum = "4", exportFormat = "yyyy-MM-dd HH:mm:ss", width = 25, needMerge = true)
    33. private Date createTime;
    34. /**
    35. * 业务模块编码
    36. */
    37. @Excel(name = "归属项目", orderNum = "5", width = 25, needMerge = true)
    38. private String secondSubModuleCodeName;
    39. /**
    40. * 审核人信息 因为存在一对多,因此其他列属性定义needMerge = true 即合并单元格
    41. */
    42. @ExcelCollection(name = "审核人信息", orderNum = "6")
    43. private List stepAndAuditorList;
    44. /**
    45. * 详情信息
    46. */
    47. @Excel(name = "详情信息", orderNum = "7", width = 120, needMerge = true)
    48. private String displayText;
    49. @Data
    50. public static class StepAndAuditorExport {
    51. @Excel(name = "审核层级", orderNum = "1", width = 10)
    52. private int step;
    53. @Excel(name = "审核人", orderNum = "2", width = 18)
    54. private String auditor;
    55. }
    56. }

    3. 定义导出格式

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

    1. /**
    2. * @author Akira
    3. * @description 简单样式 仅统一字体
    4. */
    5. public class ExcelSimpleStyleUtil extends ExcelExportStylerDefaultImpl {
    6. public ExcelSimpleStyleUtil(Workbook workbook) {
    7. super(workbook);
    8. }
    9. /**
    10. * 这里设置表头的格式,最上面的一行
    11. * @see ExportParams#title
    12. */
    13. @Override
    14. public CellStyle getHeaderStyle(short color) {
    15. CellStyle cellStyle = super.getHeaderStyle(color);
    16. cellStyle.setFont(getFont(workbook, 11, true));
    17. cellStyle.setFillBackgroundColor(HSSFColor.HSSFColorPredefined.GREY_50_PERCENT.getIndex());
    18. return cellStyle;
    19. }
    20. /**
    21. * 列标题
    22. */
    23. @Override
    24. public CellStyle getTitleStyle(short color) {
    25. CellStyle cellStyle = super.getTitleStyle(color);
    26. // 仅将表头的字体设置大一号且加粗
    27. cellStyle.setFont(getFont(workbook, 11, true));
    28. cellStyle.setFillForegroundColor(HSSFColor.HSSFColorPredefined.LIGHT_TURQUOISE.getIndex());
    29. return cellStyle;
    30. }
    31. /*以下都是行样式,交替*/
    32. /**
    33. * 行样式
    34. */
    35. @Override
    36. public CellStyle stringSeptailStyle(Workbook workbook, boolean isWarp) {
    37. CellStyle cellStyle = super.stringSeptailStyle(workbook, isWarp);
    38. cellStyle.setFont(getFont(workbook, 10, false));
    39. return cellStyle;
    40. }
    41. /**
    42. * 这里设置循环行,没有样式的一行
    43. */
    44. @Override
    45. public CellStyle stringNoneStyle(Workbook workbook, boolean isWarp) {
    46. CellStyle cellStyle = super.stringNoneStyle(workbook, isWarp);
    47. cellStyle.setFont(getFont(workbook, 10, false));
    48. return cellStyle;
    49. }
    50. /**
    51. * 字体样式
    52. *
    53. * @param size 字体大小
    54. * @param isBold 是否加粗
    55. * @return
    56. */
    57. private Font getFont(Workbook workbook, int size, boolean isBold) {
    58. Font font = workbook.createFont();
    59. // 字体样式
    60. font.setFontName("微软雅黑");
    61. // 是否加粗
    62. font.setBold(isBold);
    63. // 字体大小
    64. font.setFontHeightInPoints((short) size);
    65. return font;
    66. }
    67. }

    4. 导出方法

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

    1. /**
    2. * 导出
    3. *
    4. * @param response
    5. * @param pageQuery
    6. */
    7. public void export(HttpServletResponse response, AuditFlowPageQuery query) {
    8. // 查询结果并转换成导出类型AuditFlowExport
    9. final List exportList = buildExportList(query);
    10. // 不需要标题栏和二级标题 这里仅定义sheet的名称
    11. final ExportParams exportParams = new ExportParams(null, null, "导出数据");
    12. // 设置导出样式
    13. exportParams.setStyle(ExcelStyleUtil.class);
    14. // easypoi的核心方法 将java的List写成excel的每一行数据
    15. final Workbook sheets = ExcelExportUtil.exportExcel(exportParams, AuditFlowExport.class, exportList);
    16. // 导出文件
    17. try {
    18. String fileName = new String("记录导出结果.xls".getBytes("GBK"), StandardCharsets.ISO_8859_1);
    19. response.setContentType("application/octet-stream");
    20. response.setHeader("Content-disposition", "attachment;filename=" + fileName);
    21. OutputStream outputStream = response.getOutputStream();
    22. response.flushBuffer();
    23. sheets.write(outputStream);
    24. // 写完数据关闭流
    25. outputStream.close();
    26. log.warn("export success. [pageQuery={} total={}] ", JSON.toJSONString(pageQuery), exportList.size());
    27. } catch (IOException e) {
    28. log.error("export error! [pageQuery={}] e:{}", JSON.toJSONString(pageQuery), e);
    29. }
    30. }
    31. }

    5. 接口定义

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

    1. /**
    2. * 我的可读工单导出
    3. */
    4. @RequestMapping(value = {"/audit/flow/export"})
    5. public DefaultResponseDTO export(AuditFlowPageQuery pageQuery, HttpServletResponse response) {
    6. try {
    7. userAuditFlowService.export(response, pageQuery);
    8. return DefaultResponseDTO.success(Boolean.TRUE);
    9. } catch (Exception e) {
    10. log.error("audit flow export error", e);
    11. return DefaultResponseDTO.fail(ResultCodeEnum.SERVER_BUSYNESS, "请稍后重试");
    12. }
    13. }

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

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

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

    1. // cn.afterturn.easypoi.excel.ExcelExportUtil#exportExcel
    2. /**
    3. * 根据Map创建对应的Excel(一个excel 创建多个sheet)
    4. *
    5. * @param list 多个Map key title 对应表格Title key entity 对应表格对应实体 key data
    6. * Collection 数据
    7. * @return
    8. */
    9. public static Workbook exportExcel(List> list, ExcelType type) {
    10. Workbook workbook = getWorkbook(type, 0);
    11. for (Map map : list) {
    12. ExcelExportService service = new ExcelExportService();
    13. service.createSheet(workbook, (ExportParams) map.get("title"),
    14. (Class) map.get("entity"), (Collection) map.get("data"));
    15. }
    16. return workbook;
    17. }

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

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

    示例代码如下:

    1. @Override
    2. public void exportMultiSheetWorkbook(List> exportDataSet, HttpServletResponse response) {
    3. // 多个sheet配置参数
    4. final List> sheetsList = Lists.newArrayList();
    5. exportDataSet.forEach(exportList -> {
    6. final String sheetName = "sheet名称";
    7. Map exportMap = Maps.newHashMap();
    8. final ExportParams exportParams = new ExportParams(null, sheetName, ExcelType.HSSF);
    9. exportParams.setStyle(ExcelSimpleStyleUtil.class);
    10. // 以下3个参数为API中写死的参数名 分别是sheet配置/导出类(注解定义)/数据集
    11. exportMap.put("title", exportParams);
    12. exportMap.put("entity", MyExport.class);
    13. exportMap.put("data", exportList);
    14. // 加入多sheet配置列表
    15. sheetsList.add(exportMap);
    16. });
    17. // 导出文件
    18. try {
    19. // 核心方法:导出含多个sheet的excel文件 【注意,该方法第二个参数必须与上述的ExportParams对象指定的导出类型一致,默认ExcelType.HSSF格式,否则执行此方法时会报错!!!】
    20. final Workbook workbook = ExcelExportUtil.exportExcel(sheetsList, ExcelType.HSSF);
    21. String fileName = new String("文件名.xls".getBytes("GBK"), StandardCharsets.ISO_8859_1);
    22. response.setContentType("application/octet-stream");
    23. response.setHeader("Content-disposition", "attachment;filename=" + fileName);
    24. OutputStream outputStream = response.getOutputStream();
    25. response.flushBuffer();
    26. workbook.write(outputStream);
    27. // 写完数据关闭流
    28. outputStream.close();
    29. log.warn("导出成功");
    30. } catch (IOException e) {
    31. log.error("导出异常 e:{}", e);
    32. }
    33. }

    补充:使用excel模板导出

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

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

    其步骤是:

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

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

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

    示例代码:

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

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

    参考:

    easypoi - gitee

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

    使用easypoi合并单元格(groupName)

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

    超级简单POI导出Excel实战

    EasyPOI模板导出的坑TemplateExportParams

  • 相关阅读:
    高性能分布式对象存储——MinIO实战操作(MinIO扩容)
    自增还是UUID,数据库主键的类型该如何选择?
    SpringBoot项目中使用缓存Cache的正确姿势!!!
    链表的奇偶重排
    FluentValidation在C# WPF中的应用
    python部署项目为什么要用Nginx和uWSGI
    MIxformerV2的onnx和tensorrt加速
    网络安全:个人信息保护,企业信息安全,国家网络安全的重要性
    工业智能网关在能耗“双控”的智能化应用
    JAVA春之梦理发店管理计算机毕业设计Mybatis+系统+数据库+调试部署
  • 原文地址:https://blog.csdn.net/minghao0508/article/details/128101457