为什么会写Easypoi
以前的以前(岁月真TMD的快)我虽然写了不少代码但还是很少写poi,然后跳到一家公司之后就和业务人员聊上了,来这个需要个报表,这个报表样式是这样的,这个表头是这样的,就这样我写了大量的poi代码,每次都是大量的篇幅,copy to copy,无聊的一逼,然后加入了jeecg,jeecg中有一个小的工具类,虽然我也不知道是谁写的,然是可以用注解搞定最简单的导出,突然豁然开朗,我可以完善,让我从报表的苦海当中脱离出来,这样我花了一周的时间做了第一个版本支持导入导出放到了jeecg,发现还是不错的,慢慢的用的人越来越多,我就把这块独立出来了,再然后有人提出了模板,然后就加入了模板功能,提出了word的需求,加入了word的功能,后来工作忙了虽然没再参与jeecg,但还是一直维持这easypoi的更新,根据见识的增长也不断的重构这代码,直到现在
独特的功能
基于注解的导入导出,修改注解就可以修改Excel支持常用的样式自定义基于map可以灵活定义的表头字段支持一堆多的导出,导入支持模板的导出,一些常见的标签,自定义标签支持HTML/Excel转换,如果模板还不能满足用户的变态需求,请用这个功能支持word的导出,支持图片,Excel
小白如何开始
下载demo运行看看,基本上常见的用用法都在里面easypoi-test查看几个*Util的用法,Easypoi的主要输出就是这个看看注解的意思看看模板的标签用法可以出师了
Easypoi 为谁而开发
不太熟悉poi的不想写太多重复太多的只是简单的导入导出的喜欢使用模板的
都可以使用easypoi
Easypoi的目标是什么
Easypoi的目标不是替代poi,而是让一个不懂导入导出的快速使用poi完成Excel和word的各种操作,而不是看很多api才可以完成这样工作
1.3 使用
1.easypoi 父包--作用大家都懂得2.easypoi-annotation 基础注解包,作用与实体对象上,拆分后方便maven多工程的依赖管理3.easypoi-base 导入导出的工具包,可以完成Excel导出,导入,Word的导出,Excel的导出功能4.easypoi-web 耦合了spring-mvc 基于AbstractView,极大的简化spring-mvc下的导出功能5.sax 导入使用xercesImpl这个包(这个包可能造成奇怪的问题哈),word导出使用poi-scratchpad,都作为可选包了
如果不使用spring mvc的便捷福利,直接引入easypoi-base 就可以了,easypoi-annotation
1.4 测试项目
测试这个事情真不是个容易的事情
测试项目包括两块 Junit 的常见测试和spring 的view测试
1.spring view测试
运行application就可以了,访问界面,然后看到界面
对应的代码在view下面
2.Junit的测试目录结构如下
tohtml html预览测试view 导出的view测试cache 自定义缓存测试html html互转测试testexcelread 读取Excel测试check 导入检查测试hanlder 导入数据处理img 含图片导入测试styler 导出样式自定义测试template 模板导出测试sum 导出含统计测试test 导出测试groupname groupname 属性测试img 导出图片测试pdf pdf测试word word导出测试util util 内部测试
目前的测试覆盖率
2. Excel 注解版
2.1 Excel导入导出
Excel的导入导出是Easypoi的核心功能,前期基本也是围绕这个打造的,主要分为三种方式的处理,其中模板和Html目前只支持导出,因为支持Map.class其实导入应该是怎样都支持的
注解方式,注解变种方式模板方式Html方式
下面分别就这三种方式进行讲解
2.2 注解
注解介绍
easypoi起因就是Excel的导入导出,最初的模板是实体和Excel的对应,model--row,filed--col 这样利用注解我们可以和容易做到excel到导入导出
经过一段时间发展,现在注解有5个类分别是
@Excel 作用到filed上面,是对Excel一列的一个描述@ExcelCollection 表示一个集合,主要针对一对多的导出,比如一个老师对应多个科目,科目就可以用集合表示@ExcelEntity 表示一个继续深入导出的实体,但他没有太多的实际意义,只是告诉系统这个对象里面同样有导出的字段@ExcelIgnore 和名字一样表示这个字段被忽略跳过这个导导出@ExcelTarget 这个是作用于最外层的对象,描述这个对象的id,以便支持一个对象可以针对不同导出做出不同处理
注解中的ID的用法
这个ID算是一个比较独特的例子,比如
@ExcelTarget("teacherEntity")public class TeacherEntity implements java.io.Serializable { /** name */ @Excel(name = "主讲老师_teacherEntity,代课老师_absent", orderNum = "1", mergeVertical = true,needMerge=true,isImportField = "true_major,true_absent") private String name;
这里的@ExcelTarget 表示使用teacherEntity这个对象是可以针对不同字段做不同处理
同样的ExcelEntity 和ExcelCollection 都支持这种方式
当导出这对象时,name这一列对应的是主讲老师,而不是代课老师还有很多字段都支持这种做法
@Excel
这个是必须使用的注解,如果需求简单只使用这一个注解也是可以的,涵盖了常用的Excel需求,需要大家熟悉这个功能,主要分为基础,图片处理,时间处理,合并处理几块,name_id是上面讲的id用法,这里就不累言了
属性类型默认值功能name
String
null
列名,支持name_id
needMerge
boolean
fasle
是否需要纵向合并单元格(用于含有list中,单个的单元格,合并list创建的多个row)
orderNum
String
"0"
列的排序,支持name_id
replace
String[]
{}
值得替换 导出是{a_id,b_id} 导入反过来
savePath
String
"upload"
导入文件保存路径,如果是图片可以填写,默认是upload/className/ IconEntity这个类对应的就是upload/Icon/
type
int
1
导出类型 1 是文本 2 是图片,3 是函数,10 是数字 默认是文本
width
double
10
列宽
height
double
10
列高,后期打算统一使用@ExcelTarget的height,这个会被废弃,注意
isStatistics
boolean
fasle
自动统计数据,在追加一行统计,把所有数据都和输出
这个处理会吞没异常,请注意这一点
isHyperlink
boolean
false
超链接,如果是需要实现接口返回对象
isImportField
boolean
true
校验字段,看看这个字段是不是导入的Excel中有,如果没有说明是错误的Excel,读取失败,支持name_id
exportFormat
String
""
导出的时间格式,以这个是否为空来判断是否需要格式化日期
importFormat
String
""
导入的时间格式,以这个是否为空来判断是否需要格式化日期
format
String
""
时间格式,相当于同时设置了exportFormat 和 importFormat
databaseFormat
String
"yyyyMMddHHmmss"
导出时间设置,如果字段是Date类型则不需要设置 数据库如果是string 类型,这个需要设置这个数据库格式,用以转换时间格式输出
numFormat
String
""
数字格式化,参数是Pattern,使用的对象是DecimalFormat
imageType
int
1
导出类型 1 从file读取 2 是从数据库中读取 默认是文件 同样导入也是一样的
suffix
String
""
文字后缀,如% 90 变成90%
isWrap
boolean
true
是否换行 即支持\n
mergeRely
int[]
{}
合并单元格依赖关系,比如第二列合并是基于第一列 则{0}就可以了
mergeVertical
boolean
fasle
纵向合并内容相同的单元格
fixedIndex
int
-1
对应excel的列,忽略名字
isColumnHidden
boolean
false
导出隐藏列
@ExcelTarget
限定一个到处实体的注解,以及一些通用设置,作用于最外面的实体
属性类型默认值功能value
String
null
定义ID
height
double
10
设置行高
fontSize
short
11
设置文字大小
@ExcelEntity
标记是不是导出excel 标记为实体类,一遍是一个内部属性类,标记是否继续穿透,可以自定义内部id
属性类型默认值功能id
String
null
定义ID
@ExcelCollection
一对多的集合注解,用以标记集合是否被数据以及集合的整体排序
属性类型默认值功能id
String
null
定义ID
name
String
null
定义集合列名,支持nanm_id
orderNum
int
0
排序,支持name_id
type
Class>
ArrayList.class
导入时创建对象使用
@ExcelIgnore
忽略这个属性,多使用需循环引用中,无需多解释吧^^
2.3 注解导出,导入
2.3.1 对象定义
注解介绍了这么多,大家基本上也了解我们的注解是如何定义Excel的了吧,下面我们来跟着路飞实战吧
这天老师吧路飞叫到了办公室,让给给老师实现一个报表的需求,就是从教育平台把某个班级的人员导出来
需求是,导出我们班的所有学生的姓名,性别,出生日期,进校日期
正巧路飞刚看到Easypo,就打算用Easypoi来实现,实现方法如下:
首先定义一个我们导出的对象,为了节省篇幅,统一忽略getter,setter
public class StudentEntity implements java.io.Serializable { /** * id */ private String id; /** * 学生姓名 */ @Excel(name = "学生姓名", height = 20, width = 30, isImportField = "true_st") private String name; /** * 学生性别 */ @Excel(name = "学生性别", replace = { "男_1", "女_2" }, suffix = "生", isImportField = "true_st") private int sex; @Excel(name = "出生日期", databaseFormat = "yyyyMMddHHmmss", format = "yyyy-MM-dd", isImportField = "true_st", width = 20) private Date birthday; @Excel(name = "进校日期", databaseFormat = "yyyyMMddHHmmss", format = "yyyy-MM-dd") private Date registrationDate; }
这里设置我们的4列分别是学生姓名,学生性别,出生日期,进校日期
其中学生姓名定义了我们的列的行高,学生性别因为我们基本上都是存在数据库都是数字所以我们转换下,两个日期我们都是进行了格式化输出了,这样我们就完成了业务对我们Excel的样式需求,后面只有把这个学生列表输出就可以了
生成Excel代码如下
Workbook workbook = ExcelExportUtil.exportExcel(new ExportParams("计算机一班学生","学生"), StudentEntity .class, list);
这样我们就得到的一个java中的Excel,然后把这个输出就得到我们的Excel了
2.3.2 集合定义
路飞很快的完成了老师的任务,花了也就是喝杯茶的时间,就交差了,但过了一会就又被老师叫去了,让他给出一个某个班级选择选择某些课的学生以及对应的老师
路飞又很快的想到了Easypoi,其中有一对多的导出,这不正是一对多的体现吗,然后他继续定义实体:
一个课程对应一个老师
一个课程对应N个学生
课程的实体
@ExcelTarget("courseEntity") public class CourseEntity implements java.io.Serializable { /** 主键 */ private String id; /** 课程名称 */ @Excel(name = "课程名称", orderNum = "1", width = 25) private String name; /** 老师主键 */ @ExcelEntity(id = "absent") private TeacherEntity mathTeacher; @ExcelCollection(name = "学生", orderNum = "4") private List
教师的实体
@ExcelTarget("teacherEntity")public class TeacherEntity implements java.io.Serializable { private String id; /** name */ @Excel(name = "主讲老师_major,代课老师_absent", orderNum = "1", isImportField = "true_major,true_absent") private String name;
这里在课程这个实体里面就完成了一堆多的导出,达到了我们基础需求,同时使用了orderNum对我们的列进行了排序,满足老师的需求,导出代码如下
Workbook workbook = ExcelExportUtil.exportExcel(new ExportParams("", "测试", "测试"), CourseEntity.class, list);
这样我们就完成了老师的需求,效果如图2.3.2-1
但是课程名和代课老师没有合并,不太美观
路飞又果断给课程名称和代课老师加了needMerge = true的属性,就可以完成单元格的合并
/** 课程名称 */ @Excel(name = "课程名称", orderNum = "1", width = 25,needMerge = true) private String name; //-------------------------------- /** name */ @Excel(name = "主讲老师_major,代课老师_absent", orderNum = "1",needMerge = true, isImportField = "true_major,true_absent")
效果如图2.3.2-2
到这里,路飞就完美的完成了老师的任务,快乐的去交差了
图2.3.2-1
图2.3.2-2
2.3.3 图片的导出
在日常运作中不可避免的会遇到图片的导入导出,这里提供了两种类型的图片导出方式
@Excel(name = "公司LOGO", type = 2 ,width = 40 , height = 20,imageType = 1) private String companyLogo;表示type =2 该字段类型为图片,imageType=1 (默认可以不填),表示从file读取,字段类型是个字符串类型 可以用相对路径也可以用绝对路径,绝对路径优先依次获取
@Excel(name = "公司LOGO", type = 2 ,width = 40 , height = 20,imageType = 1) private byte[] companyLogo;2.表示type =2 该字段类型为图片,imageType=2 ,表示从数据库或者已经读取完毕,字段类型是个字节数组 直接使用 同时,image 类型的cell最好设置好宽和高,会百分百缩放到cell那么大,不是原尺寸,这里注意下
效果如下
List
运行效果
2.3.3 -1
2.3.4 Excel导入介绍
有导出就有导入,基于注解的导入导出,配置配置上是一样的,只是方式反过来而已,比如类型的替换 导出的时候是1替换成男,2替换成女,导入的时候则反过来,男变成1 ,女变成2,时间也是类似
导出的时候date被格式化成 2017-8-25 ,导入的时候2017-8-25被格式成date类型
下面说下导入的基本代码,注解啥的都是上面讲过了,这里就不累赘了
@Test public void test2() { ImportParams params = new ImportParams(); params.setTitleRows(1); params.setHeadRows(1); long start = new Date().getTime(); List
基本是写法也很简单,ImportParams 参数介绍下
属性类型默认值功能titleRows
int
0
表格标题行数,默认0
headRows
int
1
表头行数,默认1
startRows
int
0
字段真正值和列标题之间的距离 默认0
keyIndex
int
0
主键设置,如何这个cell没有值,就跳过 或者认为这个是list的下面的值
这一列必须有值,不然认为这列为无效数据
startSheetIndex
int
0
开始读取的sheet位置,默认为0
sheetNum
int
1
上传表格需要读取的sheet 数量,默认为1
needSave
boolean
false
是否需要保存上传的Excel
needVerfiy
boolean
false
是否需要校验上传的Excel
saveUrl
String
"upload/excelUpload"
保存上传的Excel目录,默认是 如 TestEntity这个类保存路径就是
upload/excelUpload/Test/yyyyMMddHHmss* 保存名称上传时间五位随机数
verifyHanlder
IExcelVerifyHandler
null
校验处理接口,自定义校验
lastOfInvalidRow
int
0
最后的无效行数,不读的行数
readRows
int
0
手动控制读取的行数
importFields
String[]
null
导入时校验数据模板,是不是正确的Excel
keyMark
String
":"
Key-Value 读取标记,以这个为Key,后面一个Cell 为Value,多个改为ArrayList
readSingleCell
boolean
false
按照Key-Value 规则读取全局扫描Excel,但是跳过List读取范围提升性能
仅仅支持titleRows + headRows + startRows 以及 lastOfInvalidRow
dataHanlder
IExcelDataHandler
null
数据处理接口,以此为主,replace,format都在这后面
2.3.5 Excel导入小功能
读取指定的sheet比如要读取上传得第二个sheet 那么需要把startSheetIndex = 1 就可以了读取几个sheet 比如读取前2个sheet,那么 sheetNum=2 就可以了读取第二个到第五个sheet设置 startSheetIndex = 1 然后sheetNum = 4读取全部的sheetsheetNum 设置大点就可以了保存Excel设置 needVerfiy = true,默认保存的路径为upload/excelUpload/Test/yyyyMMddHHmss* 保存名称上传时间五位随机数如果自定义路径 修改下saveUrl 就可以了,同时saveUrl也是图片上传时候的保存的路径判断一个Excel是不是合法的Excel importFields 设置下值,就是表示表头必须至少包含的字段,如果缺一个就是不合法的excel,不导入
2.3.6 图片的导入
有图片的导出就有图片的导入,导入的配置和导出是一样的,但是需要设置保存路径
1.设置保存路径saveUrl 默认为"upload/excelUpload"
可以手动修改 ImportParams 修改下就可以了
@Test public void test() { try { ImportParams params = new ImportParams(); params.setNeedSave(true); List
导入日志
16:35:43.081 [main] DEBUG c.a.e.e.imports.ExcelImportServer - Excel import start ,class is class cn.afterturn.easypoi.test.entity.img.CompanyHasImgModel16:35:43.323 [main] DEBUG c.a.e.e.imports.ExcelImportServer - start to read excel by is ,startTime is :35:43.344 [main] DEBUG c.a.e.e.imports.ExcelImportServer - end to read excel by is ,endTime is :35:43.429 [main] DEBUG c.a.e.e.imports.ExcelImportServer - end to read excel list by pos ,endTime is cn.afterturn.easypoi.test.entity.img.CompanyHasImgModel@1b[companyName=百度,companyLogo=upload/CompanyHasImgModel/pic.PNG,companyAddr=北京市海淀区西北旺东路10号院百度科技园1号楼]cn.afterturn.easypoi.test.entity.img.CompanyHasImgModel@105fece7[companyName=阿里巴巴,companyLogo=upload/CompanyHasImgModel/pic.PNG,companyAddr=北京市海淀区西北旺东路10号院百度科技园1号楼]cn.afterturn.easypoi.test.entity.img.CompanyHasImgModel@3ec300f1[companyName=Lemur,companyLogo=upload/CompanyHasImgModel/pic.PNG,companyAddr=亚马逊热带雨林]cn.afterturn.easypoi.test.entity.img.CompanyHasImgModel@482cd91f[companyName=一众,companyLogo=upload/CompanyHasImgModel/pic.PNG,companyAddr=山东济宁俺家]
2.3.5-1
2.3.7 Excel多Sheet导出
目前单Sheet和单Class的方式比较多,对于多Sheet的方式还是一片空白,这里做一下说明:
导出基本采用ExportParams 这个对象,进行参数配置;
我们需要进行多Sheet导出,那么就需要定义一个基础配置对象
public class ExportView { public ExportView(){ } private ExportParams exportParams; private List> dataList; private Class> cls; public ExportParams getExportParams() { return exportParams; } public void setExportParams(ExportParams exportParams) { this.exportParams = exportParams; } public Class> getCls() { return cls; } public void setCls(Class> cls) { this.cls = cls; } public List> getDataList() { return dataList; } public void setDataList(List> dataList) { this.dataList = dataList; } public ExportView(Builder builder) { this.exportParams = builder.exportParams; this.dataList = builder.dataList; this.cls = builder.cls; } public static class Builder { private ExportParams exportParams=null; private List> dataList=null; private Class> cls=null; public Builder() { } public Builder exportParams(ExportParams exportParams) { this.exportParams = exportParams; return this; } public Builder dataList(List> dataList) { this.dataList = dataList; return this; } public Builder cls(Class> cls) { this.cls = cls; return this; } public ExportView create() { return new ExportView(this); } }}
对象主要有三个属性:
// 该注解配置的导出属性
ExportParams exportParams // 对应注解 class 实例对象的数据集合List> dataList // 对应注解的 classClass> cls
这里没有用泛型,因为多Sheet导出时,会引用到不同的注解对象;
定义基础配置的集合
public class ExportMoreView { private List
最后在实现调用的方法中,对整个集合进行配置和解析
List
如果不是采用的MVC的方式,请将转换的配置采用以下的方式实现:
参见ExcelExportUtil
2.4 注解变种-更自由的导出
这天老师又把路飞喊道的办公室,要求路飞导出班级学生的整体信息
@Excel(name = "学生姓名", height = 20, width = 30, isImportField = "true_st") private String name; @Excel(name = "学生性别", replace = { "男_1", "女_2" }, suffix = "生", isImportField = "true_st") private int sex; @Excel(name = "出生日期", databaseFormat = "yyyyMMddHHmmss", format = "yyyy-MM-dd", isImportField = "true_st", width = 20) private Date birthday; @Excel(name = "进校日期", databaseFormat = "yyyyMMddHHmmss", format = "yyyy-MM-dd") private Date registrationDate;
路飞飞快的用到上面的学到的知识搞定了,这这时有一个老师把路飞叫去,说想要导出一个不要出生日期的Excel,感觉用户需求很无奈,路飞又造两个一个bean,把这个注解去掉了,来导出
@Excel(name = "学生姓名", height = 20, width = 30, isImportField = "true_st") private String name; @Excel(name = "学生性别", replace = { "男_1", "女_2" }, suffix = "生", isImportField = "true_st") private int sex; @Excel(name = "进校日期", databaseFormat = "yyyyMMddHHmmss", format = "yyyy-MM-dd") private Date registrationDate;
虽然解决了老师的需求,但这个并不是一个完美的解决方案,下面介绍一个更自由的解决方案
注解的导出,规定我们必须把model写好,并且注解写好,每次导出的Excel都是固定的,无法动态控制导出的列,虽然可以通过id来处理一个案例,但是自由度远远不够,这里介绍个变种支持,基本支持注解所有的功能
基于List
下面我们看下这个类
/** * 如果是MAP导出,这个是map的key */ private Object key; private double width = 10; private double height = 10; /** * 图片的类型,1是文件,2是数据库 */ private int exportImageType = 0; /** * 排序顺序 */ private int orderNum = 0; /** * 是否支持换行 */ private boolean isWrap; /** * 是否需要合并 */ private boolean needMerge; /** * 单元格纵向合并 */ private boolean mergeVertical; /** * 合并依赖 */ private int[] mergeRely; /** * 后缀 */ private String suffix; /** * 统计 */ private boolean isStatistics; private String numFormat; private List
基本上是和注解对应的, List
下面给出正常的demo
public void test() { try { List
路飞想到了这个方案,并且用上面做了测试可以完美解决所以他把之前的代码改为了(代码有删减,基本上都是和注解对应的)
List
用同一套代买完美了支持了老师的需求,心满意足的回宿舍了^^
2.5 Map导入,自由发挥
这天,老师把路飞叫到办公室,总是被叫,能者的悲哀啊,让他临时导入一批数据,到数据库,但是中间需要处理一些字段逻辑没办法直接导入到数据库,
这时路飞首先想到构造一个bean然后标记注解,导入处理对象,但是想想一次的对象太过于浪费,不如用map试试,获取map处理map也是一样的
导入的逻辑就变成了
ImportParams params = new ImportParams(); params.setDataHanlder(new MapImportHanlder()); long start = new Date().getTime(); List
导入后,处理每个map,然后入库完美的解决了老师的需求,简单更快捷,和bean导入基础没有区别,省去了bean的构造时间
PS:这个作者也只是在临时方案中或者一次性活当中使用,一般还是推荐注解这种方式,拥有更高的代码阅读性
!!!测试了时间的,最好导入使用文本格式,可以获取时间格式可能无法获取
2.6 Excel的样式自定义
"路飞,来办公室一趟",就这样路飞又被叫到了办公室,这次老师的需求是,想要一个漂亮点的Excel,希望路飞可以点缀下Excel,思来想去还是需要用poi的style来解决,但是如果每个都写style是不是太麻烦,而且Excel的styler数量是有限制的,这里就需要尽量复用已经创造的style,看看之前的Excel表格,大体上可以分为[标题,表头,表体],那可以说的就是创建一个接口每次调用这三个接口就可以了不说干就干
public interface IExcelExportStyler { /** * 列表头样式 * @param headerColor * @return */ public CellStyle getHeaderStyle(short headerColor); /** * 标题样式 * @param color * @return */ public CellStyle getTitleStyle(short color); /** * 获取样式方法 * @param Parity * @param entity * @return */ public CellStyle getStyles(boolean Parity, ExcelExportEntity entity);}
实现类尽量复用已经创建的Styler,切记
这样路飞先造了一个带边框的styler ,ExcelExportStylerBorderImpl
效果如下
然后路飞又手痒写了个带换行颜色的 ExcelExportStylerColorImpl
效果如下
客官看到这里应该就大体理解了我们的实现方法了吧,
最后路飞实现了一个复杂的按照老师要求的样式交差了
styler接口用法
上面两个表头和标题样式不用解释
后面这个是传入当前列的以及奇偶行,用户可以根据需求实现业务,包括去掉Excel的小箭头(也就是设置数字为数字格式的Cell),完成居中,字体等等各式各样的需求
但是这里无法实现特别没的Excel,如果有这种需求可以使用模板来实现,在Excel点点就可以完美实现
获取源码方式 转发+【关注】,私信回复【eypoi】,即可免费获取源码地址
标签: 导出
②文章观点仅代表原作者本人不代表本站立场,并不完全代表本站赞同其观点和对其真实性负责。
③文章版权归原作者所有,部分转载文章仅为传播更多信息、受益服务用户之目的,如信息标记有误,请联系站长修正。
④本站一律禁止以任何方式发布或转载任何违法违规的相关信息,如发现本站上有涉嫌侵权/违规及任何不妥的内容,请第一时间反馈。发送邮件到 88667178@qq.com,经核实立即修正或删除。