文章通过springboot集成easypoi实现基础的excel导入和导出
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
根据项目业务需要封装的工具类:
@Slf4j public class ExcelUtil { /** * 通过Excel获取数据集 * @param fileName * @param pojoClass * @return */ public staticList 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); } } } }
这里介绍基础的一行为一个数据的导入,不存在一对多关系间的导入.
excel如下图:
这里没有标题,但是有表头(id,学校),所以适用工具类中的导入:
// 0表示标题占0行,1表示表头占1行,这里也可以根据自己业务需要修改或者当做一个参数传入 Listlist = 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(Listlist, 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 ListgradeList; @Data @ApiModel("年级") @Builder public static class Grade { @Excel(name = "年级") private String name; } }
导出代码:
public Result export() { // 准备数据 Listlist = 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(); }
导出效果如下图:
这里可以设置一些样式,例如表头黑色背景色,表格黑色线条等样式:
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 ListgradeList; @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"); // 封装数据 Mapdata = 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(); } } }
这里看到有部分线条缺失,这里是easypoi自身的bug。
有时会遇到导出的excel需要有图片的需求,最简单的方式就是用注解导出,在注解设置对应的属性值,这里可以参考官方文档去查看注解属性值怎么设置。
当图片导出遇到复杂表头导出时,这里可以选择模板导出的方式,但是图片需要特殊处理。
这里先给出官方文档写出的图片处理代码:
@Test public void one() throws Exception { TemplateExportParams params = new TemplateExportParams( "doc/exportTemp_image.xls", true); Mapmap = 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); }
使用easypoi工具导出,无论是注解还是模板导出方式,都不支持表格过于复杂的导出,去年做项目遇到的Excel导出,包含三四层数据,表头也比较复杂,还有很多样式,使用注解和模板导出到第三层数据的时候,整个表格就乱掉了,当时因为催得急,也没去试试阿里的EasyExcel能不能解决,后来用原生poi写的导出,有大佬知道这种怎么解决的可以评论区分享分享