springboot集成easypoi实现excel导入导出
作者:mmseoamin日期:2023-12-11

前言

文章通过springboot集成easypoi实现基础的excel导入和导出

1.maven依赖和工具类

(1)集成maven依赖

 
     cn.afterturn
     easypoi-spring-boot-starter
     4.4.0
     
         
             hutool-all
             cn.hutool
         
         
             poi
             org.apache.poi
         
         
             poi-ooxml
             org.apache.poi
         
         
             poi-ooxml-schemas
             org.apache.poi
         
     
 
 
     org.apache.poi
     poi
     4.1.2
 
 
     org.apache.poi
     poi-ooxml
     4.1.2
 

(2)封装工具类

根据项目业务需要封装的工具类:

@Slf4j
public class ExcelUtil {
    /**
     * 通过Excel获取数据集
     * @param fileName
     * @param pojoClass
     * @return
     */
    public static  List getDataList(String fileName, Class 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 list = importExcel(fileInputStream, 0, 1, pojoClass);
        return CollUtil.emptyIfNull(list);
    }
    /**
     *
     * @param file excel文件
     * @param pojoClass
     * @param 
     * @return
     * @throws FileNotFoundException
     */
    public static  List getDataList(File file, Class pojoClass) throws FileNotFoundException {
        if (!file.exists()){
            throw new RRException("文件不存在");
        }
        FileInputStream fileInputStream = new FileInputStream(file);
        List list = importExcel(fileInputStream, 0, 1, pojoClass);
        return CollUtil.emptyIfNull(list);
    }
    public static  List importExcel(InputStream inputStream, Integer titleRows, Integer headerRows, Class pojoClass){
        ImportParams params = new ImportParams();
        params.setTitleRows(titleRows);
        params.setHeadRows(headerRows);
        List 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);
            }
        }
    }
}

2. excel的导入

这里介绍基础的一行为一个数据的导入,不存在一对多关系间的导入.

excel如下图:

springboot集成easypoi实现excel导入导出,在这里插入图片描述,第1张

这里没有标题,但是有表头(id,学校),所以适用工具类中的导入:

// 0表示标题占0行,1表示表头占1行,这里也可以根据自己业务需要修改或者当做一个参数传入
List list = importExcel(fileInputStream, 0, 1, pojoClass);

excel对应实体类封装

@Data
@ApiModel("excel数据")
public class ExcelData {
    @Excel(name = "id")
    private String id;
    @Excel(name = "学校")
    @ApiModelProperty("学校")
    private String school;
}

Excel导入时就调用上面的工具类进行导入,根据@Excel注解中的name属性值,作数据字段映射。

3. excel导出

(1)基础导出

导出成上面截图中的excel形式,这里也是使用的注解方式导出。

private static void defaultExport(List 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());
    }    
}

(2)注解导出

基于前面(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 gradeList;
    @Data
    @ApiModel("年级")
    @Builder
    public static class Grade {
        @Excel(name = "年级")
        private String name;
    }
}

导出代码:

public Result export() {
        // 准备数据
        List list = new ArrayList<>();
        List 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();
    }

导出效果如下图:

springboot集成easypoi实现excel导入导出,在这里插入图片描述,第2张

这里可以设置一些样式,例如表头黑色背景色,表格黑色线条等样式:

params.setStyle(style); // 这里的style可以百度,有很多的

(3)模板导出

有些表头比较复杂,使用注解导出不能导出成我们想要的样子,这里就可以使用模板导出,为Excel制定一些样式模板,然后使用easypoi提供的语法,实现模板导出。

这里介绍基础的模板语法,更多的可以查看官方文档:easypoi教程V1.0

a.首先制定模板

springboot集成easypoi实现excel导入导出,在这里插入图片描述,第3张

b.封装实体类
@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 gradeList;
    @ApiModelProperty("管理者")
    private String manager;
    @Data
    @ApiModel("年级")
    @Builder
    public static class Grade {
        @Excel(name = "年级")
        private String name;
        @ApiModelProperty("所属")
        private String part;
    }
}
c.导出代码
public void excelTemplate() {
        TemplateExportParams params = new TemplateExportParams("E:\\study-java\\template.xlsx");
        // 封装数据
        Map data = new HashMap<>();
        List list = new ArrayList<>();
        List 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();
            }
        }
    }
d.导出效果图

springboot集成easypoi实现excel导入导出,在这里插入图片描述,第4张这里看到有部分线条缺失,这里是easypoi自身的bug。

(4)导出带图片的excel

有时会遇到导出的excel需要有图片的需求,最简单的方式就是用注解导出,在注解设置对应的属性值,这里可以参考官方文档去查看注解属性值怎么设置。

当图片导出遇到复杂表头导出时,这里可以选择模板导出的方式,但是图片需要特殊处理。

这里先给出官方文档写出的图片处理代码:

 @Test
 public void one() throws Exception {
     TemplateExportParams params = new TemplateExportParams(
             "doc/exportTemp_image.xls", true);
     Map map = new HashMap();
     // sheet 2
     map.put("month", 10);
     Map temp;
     for (int i = 1; i < 8; i++) {
         temp = new HashMap();
         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);
    }

(5)easypoi缺陷

使用easypoi工具导出,无论是注解还是模板导出方式,都不支持表格过于复杂的导出,去年做项目遇到的Excel导出,包含三四层数据,表头也比较复杂,还有很多样式,使用注解和模板导出到第三层数据的时候,整个表格就乱掉了,当时因为催得急,也没去试试阿里的EasyExcel能不能解决,后来用原生poi写的导出,有大佬知道这种怎么解决的可以评论区分享分享