文章通过springboot集成easypoi实现基础的excel导入和导出
<dependency>
<groupId>cn.afterturngroupId>
<artifactId>easypoi-spring-boot-starterartifactId>
<version>4.4.0version>
<exclusions>
<exclusion>
<artifactId>hutool-allartifactId>
<groupId>cn.hutoolgroupId>
exclusion>
<exclusion>
<artifactId>poiartifactId>
<groupId>org.apache.poigroupId>
exclusion>
<exclusion>
<artifactId>poi-ooxmlartifactId>
<groupId>org.apache.poigroupId>
exclusion>
<exclusion>
<artifactId>poi-ooxml-schemasartifactId>
<groupId>org.apache.poigroupId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>org.apache.poigroupId>
<artifactId>poiartifactId>
<version>4.1.2version>
dependency>
<dependency>
<groupId>org.apache.poigroupId>
<artifactId>poi-ooxmlartifactId>
<version>4.1.2version>
dependency>
根据项目业务需要封装的工具类:
@Slf4j
public class ExcelUtil {
/**
* 通过Excel获取数据集
* @param fileName
* @param pojoClass
* @return
*/
public static <T> List<T> getDataList(String fileName, Class<T> pojoClass) throws FileNotFoundException {
FileUtils fileUtils = FileUtils.getInstance();
File file = new File(fileUtils.getResourcePath() + "/temp/" + fileName);
if (!file.exists()){
throw new RRException("文件不存在");
}
FileInputStream fileInputStream = new FileInputStream(file);
List<T> list = importExcel(fileInputStream, 0, 1, pojoClass);
return CollUtil.emptyIfNull(list);
}
/**
*
* @param file excel文件
* @param pojoClass
* @param
* @return
* @throws FileNotFoundException
*/
public static <T> List<T> getDataList(File file, Class<T> pojoClass) throws FileNotFoundException {
if (!file.exists()){
throw new RRException("文件不存在");
}
FileInputStream fileInputStream = new FileInputStream(file);
List<T> list = importExcel(fileInputStream, 0, 1, pojoClass);
return CollUtil.emptyIfNull(list);
}
public static <T> List<T> importExcel(InputStream inputStream, Integer titleRows, Integer headerRows, Class<T> pojoClass){
ImportParams params = new ImportParams();
params.setTitleRows(titleRows);
params.setHeadRows(headerRows);
List<T> list = null;
try {
list = ExcelImportUtil.importExcel(inputStream, pojoClass, params);
}catch (NoSuchElementException e){
throw new RRException("系统导入Excel异常");
} catch (Exception e) {
log.info("导入异常 -> {}", e.getMessage());
throw new RRException("系统导入Excel异常");
}
return list;
}
/**
* 原生poi导出 - 用poi自带的工具类来处理合并后的边框
* @param border
* @param region
* @param sheet
*/
public static void setBorderStyle(BorderStyle border, CellRangeAddress region, HSSFSheet sheet){
RegionUtil.setBorderBottom(border,region, sheet);
RegionUtil.setBorderLeft(border,region, sheet);
RegionUtil.setBorderRight(border,region, sheet);
RegionUtil.setBorderTop(border,region, sheet);
}
/**
* 原生poi导出 - 设置合并后的单元格样式
* @param sheet
* @param region
* @param cs
*/
public static void setRegionStyle(XSSFSheet sheet, CellRangeAddress region, XSSFCellStyle cs) {
for (int i = region.getFirstRow(); i <= region.getLastRow(); i++) {
XSSFRow row = sheet.getRow(i);
if (row == null) {
row = sheet.createRow(i);
}
for (int j = region.getFirstColumn(); j <= region.getLastColumn(); j++) {
XSSFCell cell = row.getCell(j);
if (cell == null) {
cell = row.createCell(j);
}
cell.setCellStyle(cs);
}
}
}
}
这里介绍基础的一行为一个数据的导入,不存在一对多关系间的导入.
excel如下图:
这里没有标题,但是有表头(id,学校),所以适用工具类中的导入:
// 0表示标题占0行,1表示表头占1行,这里也可以根据自己业务需要修改或者当做一个参数传入
List<T> list = importExcel(fileInputStream, 0, 1, pojoClass);
@Data
@ApiModel("excel数据")
public class ExcelData {
@Excel(name = "id")
private String id;
@Excel(name = "学校")
@ApiModelProperty("学校")
private String school;
}
Excel导入时就调用上面的工具类进行导入,根据@Excel注解中的name属性值,作数据字段映射。
导出成上面截图中的excel形式,这里也是使用的注解方式导出。
private static void defaultExport(List<ExcelData> list, String fileName, HttpServletResponse response) {
Workbook workbook = ExcelExportUtil.exportExcel(list, ExcelType.HSSF);
try{
response.setCharacterEncoding("UTF-8");
response.setHeader("content-Type", "application/vnd.ms-excel");
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
workbook.write(response.getOutputStream());
} catch (IOException e) {
//throw new NormalException(e.getMessage());
}
}
基于前面(1)中提到的注解方式导出,这里使用注解导出一对多的excel。
对应的实体类和注解,多级使用@ExcelCollection注解,同时其他层级使用needMerge = true表示需要合并单元格。
@Data
@ApiModel("excel数据")
@Builder
public class ExcelData {
@Excel(name = "id", needMerge = true)
private String id;
@Excel(name = "学校", needMerge = true)
@ApiModelProperty("学校")
private String school;
@ExcelCollection(name = "年级集合")
@ApiModelProperty("年级")
private List<Grade> gradeList;
@Data
@ApiModel("年级")
@Builder
public static class Grade {
@Excel(name = "年级")
private String name;
}
}
导出代码:
public Result export() {
// 准备数据
List<ExcelData> list = new ArrayList<>();
List<ExcelData.Grade> gradeList = new ArrayList<>();
gradeList.add(ExcelData.Grade.builder().name("高三年级").build());
gradeList.add(ExcelData.Grade.builder().name("高二年级").build());
list.add(ExcelData.builder().id("1").school("大竹中学").gradeList(gradeList).build());
String headTitle = "学校";
ExportParams params = new ExportParams(headTitle, "sheet1", ExcelType.XSSF);
Workbook workbook = ExcelExportUtil.exportExcel(params, ExcelData.class, list);
FileOutputStream fos = null;
try {
fos = new FileOutputStream("D:\\study\\" + "test.xlsx");
workbook.write(fos);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fos != null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return Result.success();
}
导出效果如下图:
这里可以设置一些样式,例如表头黑色背景色,表格黑色线条等样式:
params.setStyle(style); // 这里的style可以百度,有很多的
有些表头比较复杂,使用注解导出不能导出成我们想要的样子,这里就可以使用模板导出,为Excel制定一些样式模板,然后使用easypoi提供的语法,实现模板导出。
这里介绍基础的模板语法,更多的可以查看官方文档:easypoi教程V1.0
@Data
@ApiModel("excel数据")
@Builder
public class ExcelData {
@Excel(name = "id", needMerge = true)
private String id;
@Excel(name = "学校", needMerge = true)
@ApiModelProperty("学校")
private String school;
@ExcelCollection(name = "年级集合")
@ApiModelProperty("年级")
private List<Grade> gradeList;
@ApiModelProperty("管理者")
private String manager;
@Data
@ApiModel("年级")
@Builder
public static class Grade {
@Excel(name = "年级")
private String name;
@ApiModelProperty("所属")
private String part;
}
}
public void excelTemplate() {
TemplateExportParams params = new TemplateExportParams("E:\\study-java\\template.xlsx");
// 封装数据
Map<String, Object> data = new HashMap<>();
List<ExcelData> list = new ArrayList<>();
List<ExcelData.Grade> gradeList = new ArrayList<>();
gradeList.add(ExcelData.Grade.builder().name("高三年级").part("高中部").build());
gradeList.add(ExcelData.Grade.builder().name("初三年级").part("初中部").build());
list.add(ExcelData.builder().id("1").school("大竹中学").gradeList(gradeList).manager("xxx").build());
list.add(ExcelData.builder().id("2").school("大竹二中").gradeList(gradeList).manager("ooo").build());
data.put("list", list);
data.put("area", "大竹县");
Workbook workbook = ExcelExportUtil.exportExcel(params, data);
FileOutputStream fos = null;
try {
fos = new FileOutputStream("E:\\study-java\\" + "testTemplate.xlsx");
workbook.write(fos);
System.out.println("导出成功");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fos != null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
有时会遇到导出的excel需要有图片的需求,最简单的方式就是用注解导出,在注解设置对应的属性值,这里可以参考官方文档去查看注解属性值怎么设置。
当图片导出遇到复杂表头导出时,这里可以选择模板导出的方式,但是图片需要特殊处理。
这里先给出官方文档写出的图片处理代码:
@Test
public void one() throws Exception {
TemplateExportParams params = new TemplateExportParams(
"doc/exportTemp_image.xls", true);
Map<String, Object> map = new HashMap<String, Object>();
// sheet 2
map.put("month", 10);
Map<String, Object> temp;
for (int i = 1; i < 8; i++) {
temp = new HashMap<String, Object>();
temp.put("per", i * 10);
temp.put("mon", i * 1000);
temp.put("summon", i * 10000);
ImageEntity image = new ImageEntity();
image.setHeight(200);
image.setWidth(500);
image.setUrl("imgs/company/baidu.png");
temp.put("image", image);
map.put("i" + i, temp);
}
Workbook book = ExcelExportUtil.exportExcel(params, map);
File savefile = new File("D:/excel/");
if (!savefile.exists()) {
savefile.mkdirs();
}
FileOutputStream fos = new FileOutputStream("D:/excel/exportTemp_image.xls");
book.write(fos);
fos.close();
}
通过官方文档描述和 代码可以看到图片处理为ImageEntity,然后放到数据集map中,在模板对应的取值模板去赋值图片,但是我这想要每一行数据有个图片,用这种方式实现不了,我就将ImageEntity 放到我的对象中,作为属性尝试,这样尝试发现导出是没有图片的,我就想到对导出的excel再次进行处理,添加图片到excel中,使用这种方式,注意设置图片的大小适应,避免超出理想范围。
使用easypoi导出excel后得到一个workbook,通过workbook获取对应sheet,对sheet中的指定单元格做图片插入操作,代码如下:
/**
* 插入图片到excel
* 设置图片覆盖范围,不设置图片大小适应
* @param sheet
* @param imagePath 图片路径
* @param startRow 图片起始行,从0开始
* @param startCol 图片起始列,从0开始
* @param endRow 图片结束行,从0开始
* @param endCol 图片结束列,从0开始
* @throws IOException
*/
public static void insertImage(Sheet sheet, String imagePath, int startRow, int startCol, int endRow, int endCol) throws IOException {
File file = new File(imagePath);
if (!file.exists()) {
log.info("文件没找到 path -> {}", imagePath);
return;
}
FileInputStream fis = new FileInputStream(imagePath);
byte[] imageBytes = IOUtils.toByteArray(fis);
fis.close();
int pictureIdx = sheet.getWorkbook().addPicture(imageBytes, Workbook.PICTURE_TYPE_JPEG);
CreationHelper helper = sheet.getWorkbook().getCreationHelper();
Drawing<?> drawing = sheet.createDrawingPatriarch();
ClientAnchor anchor = helper.createClientAnchor();
// 设置图片的位置和大小
anchor.setCol1(startCol);
anchor.setRow1(startRow);
anchor.setCol2(endCol);
anchor.setRow2(endRow);
// 创建图片并插入到单元格中
Picture picture = drawing.createPicture(anchor, pictureIdx);
picture.resize();
}
/**
* 插入到指定单元格,并适应单元格大小
* @param sheet
* @param imagePath
* @param row
* @param col
* @throws IOException
*/
public static void insertImageAndFitToCell(Sheet sheet, String imagePath, int row, int col) throws IOException {
File file = new File(imagePath);
if (!file.exists()) {
log.info("文件没找到 path -> {}", imagePath);
return;
}
FileInputStream fis = new FileInputStream(imagePath);
byte[] imageBytes = IOUtils.toByteArray(fis);
fis.close();
int pictureIdx = sheet.getWorkbook().addPicture(imageBytes, Workbook.PICTURE_TYPE_JPEG);
CreationHelper helper = sheet.getWorkbook().getCreationHelper();
Drawing<?> drawing = sheet.createDrawingPatriarch();
ClientAnchor anchor = helper.createClientAnchor();
// 设置图片的位置和大小
anchor.setCol1(col);
anchor.setRow1(row);
// 创建图片并插入到单元格中
Picture picture = drawing.createPicture(anchor, pictureIdx);
// 调整图片大小,以适应单元格
picture.resize(1.0, 1.0);
}
使用easypoi工具导出,无论是注解还是模板导出方式,都不支持表格过于复杂的导出,去年做项目遇到的Excel导出,包含三四层数据,表头也比较复杂,还有很多样式,使用注解和模板导出到第三层数据的时候,整个表格就乱掉了,当时因为催得急,也没去试试阿里的EasyExcel能不能解决,后来用原生poi写的导出,有大佬知道这种怎么解决的可以评论区分享分享