前言
本次封装是基于 POI 的二次开发,最终使用只需要调用一个工具类中的方法,就能满足业务中绝大部门的导入和导出需求。
1. 功能测试
1.1 测试准备
在做测试前,我们需要將【2. 环境准备】中的四个文件拷贝在工程里(如:我这里均放在了com.zyq.util.excel 包下)。
1.2 数据导入
1.2.1 导入解析为JSON
比如,我们有下面一个表格:
Controller 代码:
@PostMapping("/import")
public JSONArray importUser(@RequestPart("file")MultipartFile file) throws Exception {
JSONArray array = ExcelUtils.readMultipartFile(file);
System.out.println("导入数据为:" + array);
return array;
}
测试效果:
1.2.2 导入解析为对象(基础)
首先,你需要创建一个与导入表格对应的Java实体对象,并打上对应的Excel解析的导入注解,@ExcelImport注解的value则为表头名称。
Controller 代码:
@PostMapping("/import")
public void importUser(@RequestPart("file")MultipartFile file) throws Exception {
List<User> users = ExcelUtils.readMultipartFile(file, User.class);
for (User user : users) {
System.out.println(user.toString());
}
}
测试效果:
1.2.3 导入解析为对象(字段自动映射)
对于有的枚举数据,通常我们导入的时候,表格中的数据是值,而在数据保存时,往往用的是键,比如:我们用sex=1可以表示为男,sex=2表示为女,那么我们通过配置也可以达到导入时,数据的自动映射。
那么,我们只需要将Java实体中的对象sex字段的类型改为对应的数字类型Integer,然后再注解中配置好 kv 属性(属性格式为:键1-值1;键2-值2;键3-值3;.....)
Cotroller 代码略(和 1.2.2 完全一致)。
测试效果:可以看到已经自动映射成功了。
1.2.4 导入解析为对象(获取行号)
我们在做页面数据导入时,有时候可能需要获取行号,好追踪导入的数据。
那么,我们只需要在对应的实体中加入一个 int 类型的 rowNum 字段即可。
Cotroller 代码略(和 1.2.2 完全一致)。
测试效果:
1.2.5 导入解析为对象(获取原始数据)
在做页面数据导入的时候,如果某行存在错误,一般我们会将原始的数据拿出来分析,为什么会造成数据错误。那么,我们在实体类中,增加一个 String 类型的 rowData 字段即可。
Cotroller 代码略(和 1.2.2 完全一致)。
测试效果:
1.2.6 导入解析为对象(获取错误提示)
当我们在导入数据的时候,如果某行数据存在,字段类型不正确,长度超过最大限制(详见1.2.7),必填字段验证(1.2.8),数据唯一性验证(1.2.9)等一些错误时候,我们可以往对象中添加一个 String 类型的 rowTips 字段,则可以直接拿到对应的错误信息。
比如,我们将表格中赵子龙的性别改为F(F并不是映射数据),将大乔的性别改为二十八(不能转换为Integer类型数据)。
Cotroller 代码略(和 1.2.2 完全一致)。
测试效果:可以看到,我们可以通过 rowTips 直接拿到对应的错误数据提示。
1.2.7 导入解析为对象(限制字段长度)
比如,我们手机通常为11为长度,那么不妨限制电话的最大长度位数为11位。
对应的做法,就是在 @ExcelImport 注解中,设置 maxLength = 11 即可。
比如,我们将诸葛孔明的电话长度设置为超过11位数的一个字符串。
Cotroller 代码略(和 1.2.2 完全一致)。
测试效果:
1.2.8 导入解析为对象(必填字段验证)
我们在做数据导入的时候,往往还会有一些必填字段,比如用户的名称,电话。
那么,我们只需要在 @ExcelImport 注解属性中,加上 required = true 即可。
我们将诸葛孔明的电话,以及第4行的姓名去掉,进行测试。
Cotroller 代码略(和 1.2.2 完全一致)。
测试效果:
1.2.9 导入解析为对象(数据唯一性验证)
(1) 单字段唯一性验证
我们在导入数据的时候,某个字段是具有唯一性的,比如我们这里假设规定姓名不能重复,那么则可以在对应字段的 @ExcelImport 注解上加上 unique = true 属性。
这里我们构建2条姓名一样的数据进行测试。
Cotroller 代码略(和 1.2.2 完全一致)。
测试效果:
(2)多字段唯一性验证
如果你导入的数据存在多字段唯一性验证这种情况,只需要将每个对应字段的 @ExcelImport 注解属性中,都加上 required = true 即可。
比如:我们将姓名和电话两个字段进行联合唯一性验证(即不能存在有名称和电话都一样的数据,单个字段属性重复允许)。
首先,我们将刚刚(1)的数据进行导入。
测试效果:可以看到,虽然名称有相同,但电话不相同,所以这里并没有提示唯一性验证错误。
现在,我们将最后一行的电话也改为和第1行一样的,于是,现在就存在了违背唯一性的两条数据。
测试效果:可以看到,我们的联合唯一性验证生效了。
1.3 数据导出
1.3.1 动态导出(基础)
这种方式十分灵活,表中的数据,完全自定义设置。
Controller 代码:
@GetMapping("/export")
public void export(HttpServletResponse response) {
// 表头数据
List<Object> head = Arrays.asList("姓名","年龄","性别","头像");
// 用户1数据
List<Object> user1 = new ArrayList<>();
user1.add("诸葛亮");
user1.add(60);
user1.add("男");
user1.add("https://profile.csdnimg.cn/A/7/3/3_sunnyzyq");
// 用户2数据
List<Object> user2 = new ArrayList<>();
user2.add("大乔");
user2.add(28);
user2.add("女");
user2.add("https://profile.csdnimg.cn/6/1/9/0_m0_48717371");
// 将数据汇总
List<List<Object>> sheetDataList = new ArrayList<>();
sheetDataList.add(head);
sheetDataList.add(user1);
sheetDataList.add(user2);
// 导出数据
ExcelUtils.export(response,"用户表", sheetDataList);
}
代码截图:
由于是 get 请求,我们直接在浏览器上输入请求地址即可触发下载。
打开下载表格,我们可以看到,表中的数据和我们代码组装的顺序一致。
1.3.2 动态导出(导出图片)
如果你的导出中,需要将对应图片链接直接显示为图片的话,那么,这里也是可以的,只需要将对应的类型转为 java.net.URL 类型即可(注意:转的时候有异常处理,为了方便演示,我这里直接抛出)
测试效果:
1.3.3 动态导出(实现下拉列表)
我们在做一些数据导出的时候,可能要对某一行的下拉数据进行约束限制。
比如,当我们下载一个导入模版的时候,我们可以将性别,城市对应的列设置为下拉选择。
测试效果:
1.3.4 动态导出(横向合并)
比如,我们将表头横向合并,只需要将合并的单元格设置为 ExcelUtils.COLUMN_MERGE 即可。
测试效果:可以看到表头的地址已经被合并了。
1.3.5 动态导出(纵向合并)
除了横向合并,我们还可以进行纵向合并,只需要将合并的单元格设置为 ExcelUtils.ROW_MERGE 即可。
测试效果:
1.3.6 导出模板(基础)
我们在做数据导入的时候,往往首先会提供一个模版供其下载,这样用户在导入的时候才知道如何去填写数据。导出模板除了可以用上面的动态导出,这里还提供了一种更加便捷的写法。只需要创建一个类,然后再对应字段上打上 @ExcelExport 注解类即可。
Controller 代码:
@GetMapping("/export")
public void export(HttpServletResponse response) {
ExcelUtils.exportTemplate(response, "用户表", User.class);
}
代码截图:
测试效果:
1.3.7 导出模板(附示例数据)
我们在做模版下载时候,有时往往会携带一条样本数据,好提示用户数据格式是什么,那么我们只需要在对应字段上进行配置即可。
Controller代码:
测试效果:
1.3.8 按对象导出(基础)
我们还可以通过 List 对象,对数据直接进行导出。首先,同样需要在对应类的字段上,设置导出名称。
Controller 代码:
测试效果:
1.3.9 按对象导出(数据映射)
在上面 1.3.8 的导出中,我们可以看到,性别数据导出来是1和2,这个不利于用户体验,应该需要转换为对应的中文,我们可以在字段注解上进行对应的配置。
Controller 代码略(和1.3.8完全一致)
测试效果:可以看到1和2显示为了对应的男和女
1.3.10 按对象导出(调整表头顺序)
如果你需要对表头字段进行排序,有两种方式:
第一种:按照表格的顺序,排列Java类中的字段;
第二种:在 @ExcelExport 注解中,指定 sort 属性,其值越少,排名越靠前。
Controller 代码略(和1.3.8完全一致)
测试效果:可以看到,此时导出数据的表头顺序,和我们指定的顺序完全一致。
2. 环境准备
2.1 Maven 依赖
本次工具类的封装主要依赖于阿里巴巴的JSON包,以及表格处理的POI包,所以我们需要导入这两个库的依赖包,另外,我们还需要文件上传的相关包,毕竟我们在浏览器页面,做Excel导入时,是上传的Excel文件。
<!-- 文件上传 -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
<artifactId>4.5.7</artifactId>
</dependency>
<!-- JSON -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.41</version>
</dependency>
<!-- POI -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.16</version>
</dependency>
2.2 类文件
ExcelUtils
package com.zyq.util.excel;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.net.URL;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.Map.Entry;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import com.zyq.entity.User;
import org.apache.poi.hssf.usermodel.HSSFDataValidation;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.ClientAnchor.AnchorType;
import org.apache.poi.ss.usermodel.DataValidation;
import org.apache.poi.ss.usermodel.DataValidationConstraint;
import org.apache.poi.ss.usermodel.DataValidationHelper;
import org.apache.poi.ss.usermodel.Drawing;
import org.apache.poi.ss.usermodel.FillPatternType;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.VerticalAlignment;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.CellRangeAddressList;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import or