造轮子--Excel报表工具

由于公司内部之前对于excel封装操作并不是很方便,而且对于特殊的需求不是很容易满足,这个月的任务是迁移部分业务小报表顺便重构下,因此这里造个轮子,便于导入和导出对应的excel报表。

代码

https://github.com/mrdear/easy-excel

编写原则

  1. 统一操作入口,作为工具架包,其对外的使用策略应当保证简单性。
  2. 链式操作,报表获取数据之后,导出应当一气呵成,也就是一个链式操作完成。
  3. 导入导出的可定制性,报表业务往往各种奇葩需求,因此需要暴露出钩子定制相应逻辑。
  4. Excel Header多种策略支持,注解,Map,实体类等等。

核心类

  • EasyExcel : 入口类,所有对外的操作都是由该类发起,主要有export与read两个操作。
  • ExcelWriter:导出类,其方法分为非终端操作与终端操作,终端操作会输出并关闭该流,非终端操作则可以继续接着读取,应对一张excel中含有多个sheet的情况。
  • ExcelReader:读取类,与上述ExcelWriter一样的操作。
  • ExcelField:修饰实体类注解,Excel中最麻烦的是header,因此提倡每一张报表单独对应一个POJO类,使用注解标识相应字段。
  • ExcelWriteContext:针对导出过程中一张sheet的配置,使用Builder模式构建。
  • ExcelReadContext:针对读取过程中一张sheet的配置,使用Builder模式构建。

Example

实体类 实体类使用注解标识字段,不使用的话则属性名会作为对应的columnName

public class UserWithAnnotation {

  @ExcelField(columnName = "用户名")
  private String username;

  @ExcelField(columnName = "用户密码")
  private String passwd;

  @ExcelField(columnName = "登录日期",
      writerConvert = DateToStringConvert.class,
      readerConvert = StringToDateConvert.class)
  private Date date;
}

单张表

export

@Test
 public void testSimpleWithAnnotationExport() {
   List<UserWithAnnotation> users = mockUserWithAnnotation(5);
   EasyExcel.export("/tmp/test.xlsx")
       .export(ExcelWriteContext.builder()
           .datasource(users)
           .sheetName("user")
           .build())
       .write();
 }

import

@Test
 public void testRead2() {
   InputStream inputStream = SimpleExcelReaderTest.class
       .getClassLoader().getResourceAsStream("user2.xlsx");
   ExcelReader reader = EasyExcel.read(inputStream);

   List<UserWithAnnotation> result = reader.resolve(ExcelReadContext.<UserWithAnnotation>builder()
       .clazz(UserWithAnnotation.class)
       .build());

   Assert.assertEquals(result.size(), 5);
   Assert.assertEquals(result.get(0).getPasswd(), "0b6df627-5975-417b-abc9-1f2bad5ca1e2");
   Assert.assertEquals(result.get(1).getUsername(), "张三1");

   reader.close();
 }

多张表+自定义header

sheet1最顶部有自定义的title

sheet2为普通表格

export 由于自定义的title往往非常复杂且多变,很难做到通用,因此这里是直接抛出一个钩子,可以自己实现自己想要的任何操作。

@Test
 public void testCustom() {
   List<UserWithAnnotation> users = mockUserWithAnnotation(5);
   EasyExcel.export("/tmp/test.xlsx")
       .export(ExcelWriteContext.builder()
           .datasource(users)
           .sheetName("user1")
           .createSheetHook((sheet, context) -> {
             Row row = sheet.createRow(0);
             sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, 2));
             Cell cell = row.createCell(0);
             cell.setCellValue("custom header");
           })
           .startRow(1)
           .build())
       .export(ExcelWriteContext.builder()
           .datasource(users)
           .sheetName("user2")
           .build())
       .write();
 }

import

@Test
 public void testCustom() {
   InputStream inputStream = SimpleExcelReaderTest.class
       .getClassLoader().getResourceAsStream("user3.xlsx");
   ExcelReader reader = EasyExcel.read(inputStream);

   List<UserWithAnnotation> sheet1Result = reader.resolve(ExcelReadContext.<UserWithAnnotation>builder()
       .clazz(UserWithAnnotation.class)
       .headerStart(1)
       .sheetIndex(0)
       .readSheetHook((sheet, context) -> {
         Row row = sheet.getRow(0);
         Assert.assertEquals(row.getCell(0).getStringCellValue(), "custom header");
       })
       .build());

   Assert.assertEquals(sheet1Result.size(), 5);
   Assert.assertEquals(sheet1Result.get(1).getUsername(), "张三1");


   List<UserWithAnnotation> sheet2Result = reader.resolve(ExcelReadContext.<UserWithAnnotation>builder()
       .clazz(UserWithAnnotation.class)
       .sheetIndex(1)
       .build());

   Assert.assertEquals(sheet2Result.size(), 5);
   Assert.assertEquals(sheet2Result.get(1).getUsername(), "张三1");

 }

写入HttpServletResponse

提供ResponseHelperHttpServletResponse获取对应的输出流,然后放入

OutputStream outputStream = ResponseHelper.wrapper(response, "order.xlsx");
EasyExcel.export(outputStream)....

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏微服务那些事儿

关键数据变更监控

在经过了对mybatis的一番检索之后,没有发现对该需求的解决方式.在认知范围内,想到了使用mabatis拦截器解决该问题。

887190
来自专栏程序猿DD

Spring框架中的设计模式(四)​

本文是Spring框架中使用的设计模式第四篇。本文将在此呈现出新的3种模式。一开始,我们会讨论2种结构模式:适配器和装饰器。在第三部分和最后一部分,我们将讨论单...

40960
来自专栏Java学习网

常见的 Java 错误及避免方法之第五集(每集10个错误后续持续发布)

当输入期间意外终止文件或流时,将抛出“EOFException”。 以下是抛出EOFException异常的一个示例,来自JavaBeat应用程序:

17230
来自专栏Java成神之路

Java企业微信开发_05_消息推送之被动回复消息

微信加解密包 下载地址:http://qydev.weixin.qq.com/java.zip      ,此包中封装好了AES加解密方法,直接调用方法即可。

38420
来自专栏跟着阿笨一起玩NET

以读取博客园随笔备份为例 将xml 序列化成json,再序列化成对象

资源下载:http://files.cnblogs.com/codealone/ConsoleApplication2.zip

11510
来自专栏刘望舒

LeakCanary看这一篇文章就够了

LeakCanary是Square公司基于MAT开源的一个内存泄漏检测工具,在发生内存泄漏的时候LeakCanary会自动显示泄漏信息。

3.3K50
来自专栏zhisheng

渣渣菜鸡的 ElasticSearch 源码解析 —— 启动流程(上)

上篇文章写了 ElasticSearch 源码解析 —— 环境搭建 ,其中里面说了启动 打开 server 模块下的 Elasticsearch 类:org.e...

14910
来自专栏Flutter入门

Weex是如何在Android客户端上跑起来的

Weex可以通过自己设计的DSL,书写.we文件或者.vue文件来开发界面,整个页面书写分成了3段,template、style、script,借鉴了成熟的MV...

52950
来自专栏Ryan Miao

使用dropwizard(6)-国际化-easy-i18n

前言 Dropwizard官方文档并没有提供国际化的模块,所以只能自己加。Spring的MessageResource用的很顺手,所以copy过来。 Easy...

399120
来自专栏技术墨客

Spring核心——IOC处理器扩展 原

Spring一直标注自己是一个非侵入式框架。非侵入式设计的概念并不新鲜,目标就是降低使用者和框架代码的耦合,毕竟框架的开发者和使用者几乎肯定不是同一个团队。Sp...

7620

扫码关注云+社区

领取腾讯云代金券