# model-excel
**Repository Path**: 14302145/model-excel
## Basic Information
- **Project Name**: model-excel
- **Description**: model-excel是一个简单、易用、功能强大的excel文件导出导入工具库。具有模型驱动,表头可灵活定制,数据层次输出,自动以模型数据转换器,动态表头,灵活输出,灵活导入等特性。
- **Primary Language**: Java
- **License**: MulanPSL-2.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 4
- **Created**: 2022-05-07
- **Last Updated**: 2022-05-07
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# ModelExcel使用手册
## 概述
model-excel是一个简单、易用、功能强大的excel文件导出导入工具库。具有模型驱动,表头可灵活定制,数据层次输出,自动以模型数据转换器,动态表头,灵活输出,灵活导入等特性。
### 主要特性
具有下面特性。
#### 数据模型驱动
通过使用简单注解(@ExcelCol),使得所定义的数据模型(Java Bean)具有驱动excel文件导入,导出的能力。系统从数据模型中获取表头信息,数据的层次结构信息,单元格配置信息(列宽,合并格数,样式等),数据转换器信息等,使得用户在使用时不需要复杂配置,即可实现数据导出导入功能。
#### 表头可灵活定制
系统默认从模型中获取基本的表头信息。此方式得到的是单层表头。可通过@ExcelCol.xspan()属性定制列头的横向合并单元个数。
用户也可以在导出前,通过自定义的HeaderCustomizer,来实现多层次,灵活合并单元格的表头输出。
用户可在使用@ExcelCol.headerCellStyleCustomizer()来定制表头的样式。
#### 数据层次输出
数据为层次结构(树状)的excel文件在企业应用中很常见,多用于复杂报表,例如列车时刻表,财务报表,工程进度表等。目前市面支持此类表格输出的工具比较少。model-excel对你来说是一个好消息。它天生就是为此目的设计的。
#### 自定义模型数据转换器(支持行列转换)
通过使用@ExcelCol.modelToCellsConverter()属性,可指定一个自定义的数据模型转换器。通过此转换器可以把该字段所对应的数据模型转换成单元格列表。
若该字段是一个集合类型的数据模型,可指定@ExcelCol.modelToCellsHorizontal()属性来决定是横向输出,还是纵向输出数据。此特性支持数据的行列转换。
#### 支持动态表头
由于支持模型数据的行列转换,表头实际的横向合并单元个数是动态的,由该表头所对应的excel数据占用的最大列数所决定。model-excel通过完整的数据内容计算得到每个表头横向合并单元个数。
#### 灵活输出
支持多数据模型写入,带偏移量(而不是默认的第一行,第一列)输出,手动写入单元格等功能。
#### 灵活导入
使用与导出一样的数据模型,即可实现数据的导入。
### 使用限制
#### 依赖包
本工具依赖少数第三方jar。主要有hutool, poi, lombok等:
```bash
[INFO] org.apache.commons:commons-math3:jar:3.6.1:provided
[INFO] org.apache.poi:poi-ooxml:jar:4.1.2:provided
[INFO] org.slf4j:slf4j-api:jar:1.7.32:compile
[INFO] org.apache.commons:commons-collections4:jar:4.4:provided
[INFO] org.projectlombok:lombok:jar:1.18.12:provided
[INFO] javax.servlet:javax.servlet-api:jar:3.1.0:provided
[INFO] org.hamcrest:hamcrest-core:jar:1.3:test
[INFO] org.apache.xmlbeans:xmlbeans:jar:3.1.0:provided
[INFO] org.apache.commons:commons-compress:jar:1.19:provided
[INFO] commons-codec:commons-codec:jar:1.13:provided
[INFO] com.zaxxer:SparseBitSet:jar:1.2:provided
[INFO] junit:junit:jar:4.11:test
[INFO] org.apache.poi:poi-ooxml-schemas:jar:4.1.2:provided
[INFO] com.github.virtuald:curvesapi:jar:1.06:provided
[INFO] cn.hutool:hutool-all:jar:5.4.1:compile
[INFO] org.apache.poi:poi:jar:4.1.2:provided
```
#### 性能说明
本工具的定位并非是大而通用的高性能excel工具类。主要定位是解决企业应用系统常见的excel报表的导入导出的痛点。对于非常大的excel文件的处理,建议选用其他更合适的工具。
## 使用方法
### 1.引入jar包
#### 编译打包
从gitee上下载本项目的源码,本地编译,打包后,安装到本地maven仓库。若在企业内网工作,可deploy在内网的maven远程仓库。
```bash
# 从gitee仓库克隆本项目。或者直接从网站上下载zip压缩包后解压到本地目录。
git clone http://xxx.yyy
# 进入到项目根目录,编译打包安装到本地仓库
cd ${projectHomeDir}
mvn clean package install -Dmaven.test.skip=true
# 部署到内网远程仓库
mvn deploy
```
#### 项目中引入jar
在项目中,通过配置pom文件,引入本jar包依赖。
```xml
cn.mong.util
modelexcel
0.0.1-SNAPSHOT
```
### 2.定义数据模型
通过实际的excel文件抽象出数据模型。例如有如下excel文件:

则抽象出下面的数据模型
```java
@Data
public class Role{
@ExcelCol(name="角色code", index=1)
private String code;
@ExcelCol(name="姓名", index=2)
private String name;
}
```
### 3.导出excel
根据业务需要把业务领域模型转换成【导出数据模型】,执行下面代码可完成导出。
```java
/**
* 测试一般表格的导出。单层表头,无合并单元格。每行数据代表一个模型对象。
* @throws IOException
*/
@Test
public void testDoExportListBasic() throws IOException {
// 构建数据模型
List models = Arrays.asList(new Role("role1", "主管"), new Role("role2", "主ren"));
// 构建导出器,执行下面代码挖出导出
ModelExcelExporter ew = new ModelExcelExporter();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
Point result = ew.doExport(Role.class, models, bos);
bos.close();
System.out.println("result" + result + ", size=" + bos.size());
Files.write(Paths.get("d:/tmp", "testExportBasic.xlsx"), bos.toByteArray(),
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
}
```
### 4.导入excel
使用与导出excel相同的数据模型,可轻松完成从excel文件读取数据,转换成java对象的操作。
```java
/**
* 演示最普通的表格导入。表格的每一行代表数据模型的一个对象。
* @throws IOException
*/
@Test
public void testReadModelsBasic() throws IOException {
// 通过导出工具先生成一个excel文件
ModelExcelExporter ew = new ModelExcelExporter();
List models = Arrays.asList(new Role("role1", "主管"), new Role("role2", "主ren"));
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ew.doExport(Role.class, models, bos);
bos.close();
System.out.println("input:" + JSONUtil.toJsonStr(models));
// 通过导入工具把excel文件转成java数据对象
ModelExcelImporter importer = new ModelExcelImporter(new ByteArrayInputStream(bos.toByteArray()));
List readModels = importer.readModels(1, 0, Role.class);
System.out.println("result:" + JSONUtil.toJsonStr(readModels));
}
```
## 使用场景
可参考源码的测试用例,查看具体的导出,导入例子。此处摘取几个场景进行介绍。
### 1.多层次输出,层次结构由基本数据类型为元素的集合类型决定
输出的表格如下图所示。

数据模型定义如下:
```java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Organ{
@ExcelCol(name="组织编码", index=1)
private String code;
@ExcelCol(name="组织名称", index=2)
private String name;
@ExcelCol(name="下级组织", index=4)
private List subOrgans;
@ExcelCol(name="权限", index=4, modelToCellsHorizontal = true)
private List perms;
}
```
构建数据模型:
```java
Organ organ1 = new Organ("SHJ", "上海局", Arrays.asList("南翔段", "南京段"), Arrays.asList("读", "写1"));
Organ organ2 = new Organ("GT", "国铁", Arrays.asList("STJ", "交通部"), Arrays.asList("读", "写1", "写2"));
return Arrays.asList(organ1, organ2);
```
导出控制代码:
```java
ModelExcelExporter ew = new ModelExcelExporter();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
Point result = ew.doExport(Organ.class, createOrgans(false), bos);
bos.close();
```
### 2.多层次输出,层次结构由自定义数据模型决定
输出的表格如下图所示。
数据模型定义如下:
```java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Organ2{
@ExcelCol(name="组织编码", index=1)
private String code;
@ExcelCol(name="组织名称", index=2)
private String name;
@ExcelCol(name="主管信息", index=3, subClass=Manager.class)
private List manager;
@ExcelCol(name="备注", index=4)
private String remark;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Manager{
@ExcelCol(name="头衔", index=1)
private String position;
@ExcelCol(name="姓名",xspan = 2, index=2)
private String name;
// Role定义参考【概述-1.定义数据模型】
@ExcelCol(name="role信息", index=3, subClass=Role.class)
private List manager;
}
```
构建数据模型:
```java
Manager manager = new Manager("mxm2", "mengxianming2", Arrays.asList(new Role("role1", "主管"), new Role("role2", "主ren")));
Organ2 organ5 = new Organ2("JTB", "交通部", Arrays.asList(manager, manager, manager), "合并测试");
return Arrays.asList(organ5, organ5, organ5);
```
导出控制代码: 与【使用场景1】相似,此处略。
### 3.自定义表头
输出的表格如下图所示。
数据模型定义:参考【使用场景2】,此处略。
构建数据模型: 参考【使用场景2】,此处略。
导出控制代码:
```java
/**
* 本用例用来演示如何自定义表头, 使得表头输出为层次结构。
* @throws IOException
*/
@Test
public void testWriteModels3() throws IOException {
ModelExcelExporter ew = new ModelExcelExporter();
HeaderAnnotationModelWriter modelWriter = new HeaderAnnotationModelWriter(Organ2.class, ew.getCellWriter(), (hc) -> {
if(hc.getIndex() == 3) {
return Arrays.asList(Arrays.asList(new HeaderCustomInfo("管理者", 5, hc)), Arrays.asList(new HeaderCustomInfo("管理者", 3, hc)), Arrays.asList(HeaderCustomInfo.fromHeaderCell(hc)));
}else if(hc.getIndex() == 5) {
return Arrays.asList(Arrays.asList(new HeaderCustomInfo("角色", 2, hc)), Arrays.asList(HeaderCustomInfo.fromHeaderCell(hc)));
}
return Arrays.asList(Arrays.asList(HeaderCustomInfo.fromHeaderCell(hc)));
});
List organs = createOrgan2s();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
Point result = ew.writeModels(0, 0, modelWriter, organs, true);
ew.getCellWriter().writeCellValue(result.y + 1, 0, "write by hand");
ew.flushToOutputStream(bos);
bos.close();
System.out.println("result" + result + ", size=" + bos.size());
Files.write(Paths.get("d:/tmp", "test3.xlsx"), bos.toByteArray(),
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
}
```
### 4.多模型输出
输出的表格如下图所示。

数据模型定义:
```java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class EmergencyReport{
@ExcelCol(name="车型", index=1)
private String model;
@ExcelCol(name="车体", index=2)
private Integer body;
@ExcelCol(name="外门及车内设施", index=3)
private Integer door;
@ExcelCol(name="合计(件)", index=4)
private Integer total;
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class EmergencyReportOther{
@ExcelCol(name="key", index=1, valueCellStyleCustomizer = MyValueCellStyleCustomizer.class)
private String name;
@ExcelCol(name="items", index=2, xspan = 3)
List items;
}
}
```
构建数据模型: 此处略。
导出控制代码:
```java
/**
* 本用例演示了如何定义表头输出,如何多模型输出,以及如何通过直接定义单元格内容输出。
* 还演示了自定义单元格时,通过指定ValueCellStyleCustomizer实来实现单元格样式的自定义输出。
* @throws IOException
*/
@Test
public void testWriteModels4() throws IOException {
ModelExcelExporter ew = new ModelExcelExporter();
HeaderAnnotationModelWriter modelWriter = new HeaderAnnotationModelWriter(
EmergencyReport.class, ew.getCellWriter(), (hc) -> {
if (hc.getIndex() == 1) {
return Arrays.asList(Arrays.asList(new HeaderCustomInfo("应 急 台 日 志", 4, hc)),
Arrays.asList(new HeaderCustomInfo("", 3, hc)),
Arrays.asList(HeaderCustomInfo.fromHeaderCell(hc)));
} else if (hc.getIndex() == 4) {
return Arrays.asList(Arrays.asList(
new HeaderCustomInfo(DateUtil.formatChineseDate(new Date(), false, false), 1, hc)),
Arrays.asList(HeaderCustomInfo.fromHeaderCell(hc)));
}
return Arrays.asList(Arrays.asList(HeaderCustomInfo.fromHeaderCell(hc)));
});
HeaderAnnotationModelWriter modelWriter2 = new HeaderAnnotationModelWriter(
EmergencyReportOther.class, ew.getCellWriter(), null);
Point curPos = ew.writeModels(0, 0, modelWriter, createEmergencyReportItems(), true);
curPos = ew.writeModels(curPos.y, 0, modelWriter2, createEmergencyReportOthers(), false);
Point curPos2 = ew.writeCells(curPos.y, 0,
Arrays.asList(
new PositionCell(0, 0, "动 车 台 值 班 人 员", 2, 1, 15,
MyValueCellStyleCustomizer.getInstance()),
new PositionCell(1, 0, "白 班:", 1, 1, 15, MyValueCellStyleCustomizer.getInstance()),
new PositionCell(1, 1, "请人工输入", 1, 1, 15),
new PositionCell(2, 0, "夜 班:", 1, 1, 15, MyValueCellStyleCustomizer.getInstance()),
new PositionCell(2, 1, "请人工输入", 1, 1, 15)));
curPos2 = ew.writeCells(curPos.y, 4,
Arrays.asList(
new PositionCell(0, 0, "动 车 台 值 班 人 员", 2, 1, 15,
MyValueCellStyleCustomizer.getInstance()),
new PositionCell(1, 0, "白 班:", 1, 1, 15, MyValueCellStyleCustomizer.getInstance()),
new PositionCell(1, 1, "请人工输入", 1, 1, 15),
new PositionCell(2, 0, "夜 班:", 1, 1, 15, MyValueCellStyleCustomizer.getInstance()),
new PositionCell(2, 1, "请人工输入", 1, 1, 15)));
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ew.flushToOutputStream(bos);
bos.close();
System.out.println("result" + curPos + ", size=" + bos.size());
Files.write(Paths.get("d:/tmp", "test4.xlsx"), bos.toByteArray(), StandardOpenOption.CREATE,
StandardOpenOption.TRUNCATE_EXISTING);
}
```
### 5.动态表头
展示怎么自定义表头,以及如何通过自定义的 @ExcelCol.modelToCellsConverter() 把字段所对应的数据模型转换成横向的单元格输出。
输出如下excel:

数据模型定义:
```java
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Project{
@ExcelCol(name="序号", index=1)
private Integer no;
@ExcelCol(name="项目名称", index=2)
private String name;
@ExcelCol(name="工作阶段", index=3, subClass = ProjectStage.class)
private List stages;
@ExcelCol(name="备注", index=4)
private String remark;
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class ProjectStage{
@ExcelCol(name="阶段", index=1)
private String name;
@ExcelCol(name="负责人", index=2)
private String manager;
@ExcelCol(name="每天进度", index=3, xspan = 7, modelToCellsConverter = DayProcessesModelToCellsConverter.class)
private DayProcesses dayProcesses;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class DayProcesses{
private List dayProcesses;
}
static class DayProcessesModelToCellsConverter implements ModelToCellsConverter{
@Override
public List extends PositionCell> modelToValCells(DayProcesses model) {
if(model.getDayProcesses() != null) {
AtomicInteger colCnt = new AtomicInteger(0);
return model.getDayProcesses().stream().map(dp -> {
return new PositionCell(0, colCnt.getAndIncrement(), dp, 1, 1, 15);
}).collect(Collectors.toList());
}
return Collections.emptyList();
}
}
}
```
导出控制代码:
```java
/**
* 展示怎么自定义表头,以及如何通过自定义的 @ExcelCol.modelToCellsConverter() 把字段所对应的数据模型转换成横向的单元格输出。
*
* @throws IOException
*/
@Test
public void testWriteModels6() throws IOException {
ModelExcelExporter ew = new ModelExcelExporter();
HeaderAnnotationModelWriter modelWriter = new HeaderAnnotationModelWriter<>(Project.class, ew.getCellWriter(), (hc) -> {
if(hc.getIndex() == 5) {
return Arrays.asList(Arrays.asList(new HeaderCustomInfo("周进度", 7, hc)),
Arrays.asList(new HeaderCustomInfo("周1", 1, hc), new HeaderCustomInfo("周2", 1, hc), new HeaderCustomInfo("周3", 1, hc), new HeaderCustomInfo("周4", 1, hc),
new HeaderCustomInfo("周5", 1, hc), new HeaderCustomInfo("周6", 1, hc), new HeaderCustomInfo("周日", 1, hc)));
}
return Arrays.asList(Arrays.asList(HeaderCustomInfo.fromHeaderCell(hc)));
});
List projects = createProjects();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
Point result = ew.writeModels(0, 0, modelWriter, projects, true);
ew.flushToOutputStream(bos);
bos.close();
System.out.println("result" + result + ", size=" + bos.size());
Files.write(Paths.get("d:/tmp", "testS6.xlsx"), bos.toByteArray(),
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
}
private List createProjects() {
Project.ProjectStage stage = new Project.ProjectStage("需求分析", "张三", new Project.DayProcesses(Arrays.asList("10", "15", "", "25")));
Project.ProjectStage stage2 = new Project.ProjectStage("概要设计", "李四", new Project.DayProcesses(Arrays.asList("10", "15", "20", "25", "30")));
Project.ProjectStage stage3 = new Project.ProjectStage("系统实现", "王二,李晓", new Project.DayProcesses(Arrays.asList("10", "15", "22", "25", "40", "55", "70")));
Project p1 = new Project(1, "用户管理", Arrays.asList(stage, stage2, stage3), "要加快");
return Arrays.asList(p1, p1);
}
```
### 6.模型数据横向输出
使用DynaHeaderAnnotationModelWriter,对于设置为@ExcelCol(modelToCellsHorizontal = true)的字段,可使该字段对应的数据模型横向输出(行列转换)到excel。
输出的表格如下图所示。

数据模型定义:参考【使用场景2】,此处略。
构建数据模型: 参考【使用场景2】,此处略。
导出控制代码:
```java
/**
* 此用例演示了列对应的模型数据可以横向扩展(而不是默认的纵向扩展)
* @throws IOException
*/
@Test
public void testWriteModels() throws IOException {
ModelExcelExporter ew = new ModelExcelExporter();
DynaHeaderAnnotationModelWriter modelWriter = new DynaHeaderAnnotationModelWriter(Organ.class, ew.getCellWriter());
Point curPos = ew.writeModels(0, 0, modelWriter, createOrgans(true), true);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ew.flushToOutputStream(bos);
bos.close();
System.out.println("result" + curPos + ", size=" + bos.size());
Files.write(Paths.get("d:/tmp", "testD1.xlsx"), bos.toByteArray(),
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
}
```
### 7.自定义模型输出及动态列宽
使用DynaHeaderAnnotationModelWriter,对于设置为@ExcelCol(modelToCellsHorizontal=true,modelToCellsConverter= xx)的字段,可使该字段对应的数据模型横向输出(行列转换)到excel, 并使得该列的合并单元格数自动使用内容行。
输出的表格如下图所示。
数据模型定义:
```java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class RouteInfo{
@ExcelCol(name="配属所", index=1)
private String trh;
@ExcelCol(name="车型", index=2)
private String model;
@ExcelCol(name="车次", index=3)
private List tnos;
@ExcelCol(name="交路天", index=4, subClass=DayRoute.class )
private List days;
@ExcelCol(name="开行情况", index=5)
private String runInfo;
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class DayRoute{
@ExcelCol(name="交路天", index=1)
private String daySeq;
@ExcelCol(name="车次及时刻", index=2,
modelToCellsHorizontal = true, modelToCellsConverter = MyModelToCellsConverter.class)
List trainNos;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class TNumber{
private String tn;
private String mile;
private String startSt;
private String endSt;
private String startTime;
private String endTime;
}
public static class MyModelToCellsConverter implements ModelToCellsConverter{
@Override
public List extends PositionCell> modelToValCells(TNumber model) {
return Arrays.asList(new PositionCell(0, 0, model.getTn(), 2, 1, 15)
, new PositionCell(1, 0, model.getMile(), 2, 1, 15)
, new PositionCell(2, 0, model.getStartSt(), 1, 1, 15)
, new PositionCell(2, 1, model.getEndSt(), 1, 1, 15)
, new PositionCell(3, 0, model.getStartTime(), 1, 1, 15)
, new PositionCell(3, 1, model.getEndTime(), 1, 1, 15)
);
}
}
}
```
构建数据模型: 此处略。
导出控制代码:
```java
ModelExcelExporter ew = new ModelExcelExporter();
DynaHeaderAnnotationModelWriter modelWriter = new DynaHeaderAnnotationModelWriter(RouteInfo.class, ew.getCellWriter());
Point curPos = ew.writeModels(0, 0, modelWriter, createRouteInfo(), true);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ew.flushToOutputStream(bos);
bos.close();
```
### 8.层次数据excel导入
待导入的表格如下图所示。
数据模型定义:参考【使用场景2】,此处略。
导入控制代码:
```java
/**
* 演示如何解析两层结构的excel,并转换成数据结模型。第一层为组织,第二层为下级组织与权限。
* 注意第二层的数据不是自定义pojo,而是原始类型为元素的集合对象。
* @throws IOException
*/
@Test
public void testReadModels() throws IOException {
ModelExcelExporter ew = new ModelExcelExporter();
HeaderAnnotationModelWriter modelWriter = new HeaderAnnotationModelWriter(Organ.class,
ew.getCellWriter());
List input = createOrgans();
System.out.println("input:" + JSONUtil.toJsonStr(input));
ew.writeModels(0, 0, modelWriter, createOrgans(), true);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ew.flushToOutputStream(bos);
bos.close();
byte[] content = bos.toByteArray();
ModelExcelImporter importer = new ModelExcelImporter(new ByteArrayInputStream(content));
List readModels = importer.readModels(1, 0, Organ.class);
System.out.println("result:" + JSONUtil.toJsonStr(readModels));
}
```