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


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

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

    读取图片

    poi 读取图片

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

    
    <dependency>
        <groupId>com.alibabagroupId>
        <artifactId>easyexcelartifactId>
        <version>3.0.5version>
    dependency>
    <dependency>
        <groupId>net.sf.jxlsgroupId>
        <artifactId>jxls-coreartifactId>
        <version>1.0.6version>
    dependency>
    

    读取图片核心代码如下:

    Workbook workbook = WorkbookFactory.create(inputStream);
    // 默认读取第一页
    XSSFSheet sheet = (XSSFSheet) workbook.getSheetAt(0);
    List documentPartList = sheet.getRelations();
    for (POIXMLDocumentPart part : documentPartList) {
        if (part instanceof XSSFDrawing) {
            XSSFDrawing drawing = (XSSFDrawing) part;
            List 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  void readImage(InputStream inputStream, List list) {
            try {
                Workbook workbook = WorkbookFactory.create(inputStream);
                // 默认读取第一页
                XSSFSheet sheet = (XSSFSheet) workbook.getSheetAt(0);
                List documentPartList = sheet.getRelations();
                Integer size = list.size();
                for (POIXMLDocumentPart part : documentPartList) {
                    if (part instanceof XSSFDrawing) {
                        XSSFDrawing drawing = (XSSFDrawing) part;
                        List 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 demoExcelInputs = EasyExcelFactory.read(multipartFile.getInputStream()).head(DemoExcelInput.class).sheet().doReadSync();
    ExcelReadImageUtil.readImage(inputStream,demoExcelInputs);
    

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

    写图片

    EasyExcel 支持多种格式的写图片,包括:

    • URL
    • InputStream
    • byte[]
    • File
    • 自定义转换器

    添加写的实体:

    @Data
    public class DemoExcelInput {
    
        @ExcelProperty("姓名")
        private String name;
    
        @ExcelProperty(value = "图片",converter = ExcelUrlImageConverter.class)
        private String imageStr;
    
        @ExcelProperty("url")
        private URL imageUrl;
    
        @ExcelProperty("inputstream")
        private InputStream inputStream;
    
        @ExcelProperty("bytes")
        private byte[] bytes;
    }
    

    读取图片

    List demoExcelInputs = new ArrayList<>();
    DemoExcelInput demoExcelInput = new DemoExcelInput();
    demoExcelInput.setName("aa");
    String url = "https://p26-passport.byteacctimg.com/img/user-avatar/82b069ce17bb5b0eccb7ee67d3f6f3bc~180x180.awebp";
    demoExcelInput.setImageStr(url);
    demoExcelInput.setImageUrl(new URL(url));
    demoExcelInputs.add(demoExcelInput);
    
    InputStream inputStream = new URL(url).openStream();
    demoExcelInput.setInputStream(inputStream);
    byte[] bytes = IoUtils.toByteArray(new URL(url).openStream());
    demoExcelInput.setBytes(bytes);
    
    response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
    response.setCharacterEncoding("utf-8");
    // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
    String fileName= "导出excel模板";
    String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.toString()).replaceAll("\\+", "%20");
    response.setHeader("Content-disposition","attachment;filename*=utf-8''"+encodedFileName+".xlsx");
    EasyExcel.write(response.getOutputStream(),DemoExcelInput.class).sheet("模板").doWrite(demoExcelInputs);
    
    

    导出文件截图:

    但是上面的 imageStr 对应的 String 类型 EasyExcel 并不支持,但是却能导出图片,这就需要使用到自定义转换器

    创建 ExcelUrlImageConverter 转换器:

    import com.alibaba.excel.converters.Converter;
    import com.alibaba.excel.converters.WriteConverterContext;
    import com.alibaba.excel.metadata.data.WriteCellData;
    import com.alibaba.excel.util.IoUtils;
    import com.alibaba.excel.util.StringUtils;
    
    import java.io.InputStream;
    import java.net.URL;
    
    public class ExcelUrlImageConverter implements Converter {
    
        @Override
        public WriteCellData convertToExcelData(WriteConverterContext context) throws Exception {
            String urlString = context.getValue();
            if (StringUtils.isBlank(urlString)) {
                return new WriteCellData<>("");
            }
            URL url = new URL(urlString);
            InputStream inputStream = url.openStream();
            byte[] bytes = IoUtils.toByteArray(inputStream);
            return new WriteCellData<>(bytes);
        }
    }
    

    将读取到图片流转到对象 WriteCellData 中,就能写图片了。

  • 相关阅读:
    收集Stream流
    信号采样基本概念 —— 7.数模转换(DAC & ADC)
    【mq】从零开始实现 mq-01-生产者、消费者启动
    yolov5分割+检测c++ qt 中部署,以opencv方式(详细代码(全)+复制可用)
    4507. 子数组异或和
    过拟合与过拟合的经典例子
    localStorage设置过期时间用法
    chatgpt赋能python:Python怎么入侵手机
    docker配置阿里云镜像加速、常用命令、docker原理
    ESP32_esp-idf环境搭建
  • 原文地址:https://www.cnblogs.com/jeremylai7/p/18264172