V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
jeremylai
V2EX  ›  程序员

EasyExcel 无法读取图片?周末用 poi 写了一个小工具类

  •  
  •   jeremylai · 148 天前 · 603 次点击
    这是一个创建于 148 天前的主题,其中的信息可能已经有所发展或是发生改变。

    在平时的开发中,经常要开发 Excel 的导入导出功能。一般使用 poi 或者 EasyExcel 开发,使用 poi 做 excel 比较复杂,大部分开发都会使用 EasyExcel 因为一行代码就能实现导入和导出的功能。但是 EasyExcel 不支持图片的读的操作,本文操作如何实现图片的读和写的功能。

    在 EasyExcel 官网的常见问题可以看到 EasyExcel 是不支持读取图片的功能。

    读取图片

    poi 读取图片

    poi 支持图片的读取,使用 poi 写一个工具类,支持图片的读取,首先添加 maven 依赖, EasyExcel 含有 poi 依赖,无需额外添加 poi 依赖:

    <!-- easyexcel -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>easyexcel</artifactId>
        <version>3.0.5</version>
    </dependency>
    <dependency>
        <groupId>net.sf.jxls</groupId>
        <artifactId>jxls-core</artifactId>
        <version>1.0.6</version>
    </dependency>
    

    读取图片核心代码如下:

    Workbook workbook = WorkbookFactory.create(inputStream);
    // 默认读取第一页
    XSSFSheet sheet = (XSSFSheet) workbook.getSheetAt(0);
    List<POIXMLDocumentPart> documentPartList = sheet.getRelations();
    for (POIXMLDocumentPart part : documentPartList) {
        if (part instanceof XSSFDrawing) {
            XSSFDrawing drawing = (XSSFDrawing) part;
            List<XSSFShape> shapes = drawing.getShapes();
            for (XSSFShape shape : shapes) {
                XSSFPicture picture = (XSSFPicture) shape;
                XSSFClientAnchor anchor = picture.getPreferredSize();
                CTMarker marker = anchor.getFrom();
                int row = marker.getRow();
                int col = marker.getCol();
                // 从第 2 行开始
                if (row > 0 && row <= size) {
                    PictureData pictureData = picture.getPictureData();
                    String extension = pictureData.suggestFileExtension();
                    byte[] bytes = pictureData.getData();
                 }
            }
        }
    }    
    

    读取图片流程:

    • 首先要获取第一页( sheet )数据 workbook.getSheetAt(0)
    • 遍历 sheet.getRelations() 提取 XSSFDrawing ,也就是图片数据。
    • 每一行遍历数据数据,获取 byte 字节流。

    可能代码复制在 idea 会提示某些方法不存在,这里就需要核对 poi 版本,上面引用的 EasyExcel 的版本是 3.0.5,里面的 poi 版本是 4.1.2

    封装工具类

    通过上面的代码可以获取到图片的字节流,然后对字节流做上传图片或者服务存储图片处理,但是每个读取都写一遍这种方式,代码就比较冗余了。所以就需要将上面代码封装成一个工具类。

    比如上传一个文件,需要将数据赋值给两个字段 name 和 imageStr:

    @ExcelProperty("姓名")
    private String name;
    
    @ExcelProperty(value = "图片")
    private String imageStr;
    

    首先配置一个 ExcelImageProperty 注解,确定哪列的图片需要赋值给对应的图片字段

    @Inherited
    @Target({ElementType.FIELD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface ExcelImageProperty {
    
        String[] value() default {""};
    
        /**
         * 图片在第几列 1 开始
         * @return
         */
        int index() default -1;
    }
    

    imageStr 对应第二列,字段上 ExcelImageProperty 注解的 index = 2,上面的实体修改如下:

    @ExcelProperty("姓名")
    private String name;
    
    @ExcelProperty(value = "图片")
    @ExcelImageProperty(index = 2)
    private String imageStr;
    

    写好实体和注解后,再写一个工具类。

    @Slf4j
    public class ExcelReadImageUtil {
    
        public static <T> void readImage(InputStream inputStream, List<T> list) {
            try {
                Workbook workbook = WorkbookFactory.create(inputStream);
                // 默认读取第一页
                XSSFSheet sheet = (XSSFSheet) workbook.getSheetAt(0);
                List<POIXMLDocumentPart> documentPartList = sheet.getRelations();
                Integer size = list.size();
                for (POIXMLDocumentPart part : documentPartList) {
                    if (part instanceof XSSFDrawing) {
                        XSSFDrawing drawing = (XSSFDrawing) part;
                        List<XSSFShape> shapes = drawing.getShapes();
                        for (XSSFShape shape : shapes) {
                            XSSFPicture picture = (XSSFPicture) shape;
                            XSSFClientAnchor anchor = picture.getPreferredSize();
                            CTMarker marker = anchor.getFrom();
                            int row = marker.getRow();
                            int col = marker.getCol();
                            // 从第 2 行开始
                            if (row > 0 && row <= size) {
                                PictureData pictureData = picture.getPictureData();
                                String extension = pictureData.suggestFileExtension();
                                byte[] bytes = pictureData.getData();
                                InputStream imageInputStream = new ByteArrayInputStream(bytes);
                                //String url = iTxCosService.uploadFile(new ByteArrayInputStream(bytes), UUID.randomUUID() + "." + extension);
                                for (int i = 0; i < size; i++) {
                                    T item = list.get(i);
                                    Class clazz = item.getClass();
                                    Field[] fields = clazz.getDeclaredFields();
                                    for (Field field : fields) {
                                        if (field.isAnnotationPresent(ExcelImageProperty.class)) {
                                            ExcelImageProperty excelImageProperty = field.getAnnotation(ExcelImageProperty.class);
                                            int index = excelImageProperty.index();
                                            if (index == col + 1 && row - 1 == i) {
                                                field.setAccessible(true);
                                                field.set(item,new String(bytes));
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            } catch (IOException | IllegalAccessException e) {
                e.printStackTrace();
                log.error("read image error {}",e);
            }
        }
    }
    

    传参一个列表,通过获取读取输入流获取到图片,赋值给对应的字段。

    • 此模板是一个列表模版,不支持自定义模板。
    • 使用 poi 读取图片,第二行读取数据,遍历每列数据,符合注解字段就赋值。一般获取到输入流后会上传图片,返回一个地址,这里仅仅就获取字节流,赋值给对应的字段。

    使用 EasyExcel 读取非图片数据和工具类读取图片数据:

    InputStream inputStream = multipartFile.getInputStream();
    List<DemoExcelInput> demoExcelInputs = EasyExcelFactory.read(multipartFile.getInputStream()).head(DemoExcelInput.class).sheet().doReadSync();
    ExcelReadImageUtil.readImage(inputStream,demoExcelInputs);
    

    inputStream 不能重复使用,不然会报错 inputStream close 错误。

    更多内容请关注我的公众号:小码 A 梦

    目前尚无回复
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1404 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 102ms · UTC 17:30 · PVG 01:30 · LAX 09:30 · JFK 12:30
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.