SpringBoot + EasyExcel + Apache POI 实现excel文件导入,文件中包含图片

 

SpringBoot + EasyExcel + Apache POI 实现excel文件导入,文件中包含图片

​ EasyExcel是一个基于Java的、快速、简洁、解决大文件内存溢出的Excel处理工具。他能让你在不用考虑性能、内存的等因素的情况下,快速完成Excel的读、写等功能。EasyExcel的优势外,对于文件中包含图片的处理没有完美的解决方案,结合Apache POI 完成依然可以快速该问题的完美处理。

​ 下面是一个使用 Spring Boot 实现 Excel 文件导入,且文件中包含图片的示例。这里使用 Apache POI 库来处理 Excel 文件,使用 MultipartFile 接收前端上传的文件。下面以.xslx的文件为例

添加依赖

pom.xml 中添加必要的依赖:

<dependencies>
    <!-- Spring Boot Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- Apache POI for Excel -->
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi</artifactId>
        <version>4.1.2</version>
    </dependency>
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-ooxml</artifactId>
        <version>4.1.2</version>
    </dependency>
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>easyexcel</artifactId>
      <version>3.3.4</version>
      <scope>compile</scope>
    </dependency>
</dependencies>

需要注意的是,EasyExcel包下是有对poi的依赖,如果说想使用poi 5.x.x版本,需要排除EasyExcel下对POI的依赖,不然会有版本兼容的问题,在EasyExcel的官网上也有明确的说明。

创建和EasyExcel读文件相关的类

  • 数据模型ExcelData 类用于映射 Excel 中的数据
import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;

/**
 * 封装数据的实体
 */
@Data
public class ExcelData {
    @ExcelProperty(index = 0, value = "名称")
    private String name;

    // 该字段用于保存数据所在行的行号
	@ExcelIgnore
	private Integer rowId;
}

  • 导入文件的监听器,ExcelDataListener 类继承自 AnalysisEventListener,用于处理读取到的数据。invoke 方法在每一行数据解析完成后被调用,doAfterAllAnalysed 方法在所有数据解析完成后被调用,在这个方法里可以对图片进行保存等操作。具体的实现可以根据业务自行实现。
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;

import java.util.ArrayList;
import java.util.List;

public class ExcelDataListener extends AnalysisEventListener<ExcelData> {
    private List<ExcelData> dataList = new ArrayList<>();

    @Override
    public void invoke(ExcelData data, AnalysisContext context) {
        // 获取数据所在行的行号,后续和文件中的图片数据做匹配使用
		Integer rowIndex = context.readRowHolder().getRowIndex();
		data.setRowId(rowIndex);
		dataList.add(data);
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
       
    }

    public List<ExcelData> getDataList() {
        return dataList;
    }
}
  • ImportExcelPuctureController,导入文件的Controller,接收上传的 Excel 文件

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import com.alibaba.excel.EasyExcel;
import org.apache.poi.ss.usermodel.Drawing;
import org.apache.poi.ss.usermodel.PictureData;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.*;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @description 导入excel文件,其中文件内容包含图片
 */
@RestController
@RequestMapping("/test")
public class ImportExcelPuctureController {

	@GetMapping("/import")
	public void importExcelWithPucture(@RequestParam("file") MultipartFile file) {
		ExcelDataListener excelDataListener = new ExcelDataListener();
		try (InputStream inputStream1 = file.getInputStream()) {
            
            // EasyExcel 读取文件中的内容
			EasyExcel.read(inputStream1, ExcelData.class, excelDataListener).sheet().doRead();

			// 使用 Apache POI 读取图片
			InputStream inputStream = file.getInputStream();
			Workbook workbook = new XSSFWorkbook(inputStream);
			Sheet sheet = workbook.getSheetAt(0);
			Map<Integer, Map<Integer, PictureData>> pictureMap = getPictures(sheet);
			List<ExcelData> dataList = excelDataListener.getDataList();
			Map<Integer, ExcelData> map = dataList.stream().collect(Collectors.toMap(ExcelData::getRowId, Function.identity()));
            // 将easyexcel读到的数据和图片进行匹配,如果有云存储 阿里云、者华为云或者minio,可以将文件上传后,把上传后的文件地址和数据匹配。
			for (Map.Entry<Integer, ExcelData> dataEntry : map.entrySet()) {
				Integer rowId = dataEntry.getKey();
				if (pictureMap.containsKey(rowId)) {
					Map<Integer, PictureData> integerPictureDataMap = pictureMap.get(dataEntry.getKey());
					for (Map.Entry<Integer, PictureData> pictureDataEntry : integerPictureDataMap.entrySet()) {
						int colId = pictureDataEntry.getKey();
						PictureData pictureData = pictureDataEntry.getValue();
						byte[] pictureBytes = pictureData.getData();
						File targetFile = new File("your path" + IdUtil.getSnowflake().nextIdStr() + rowId + "-" + colId + getImageSuffix(pictureData));
						File file1 = FileUtil.writeBytes(pictureBytes, targetFile);
						System.out.println(file1);
                        // 组装数据,可以保存到对应的数据源中
						System.out.println("data row " + rowId + " ExcelData data " + dataEntry.getValue() + " Picture in row " + rowId + " col index " + colId + " size: " + pictureBytes.length);
					}
				}
			}
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

    // 根据文件头信息,得到对应的文件后缀名,文件类型提供13+种,下面罗列了一些常用的文件后缀名
	private static String getImageSuffix(PictureData pictureData) {
		int pictureType = pictureData.getPictureType();
		return switch (pictureType) {
			case Workbook.PICTURE_TYPE_JPEG -> ".jpg";
			case Workbook.PICTURE_TYPE_PNG -> ".png";
			case Workbook.PICTURE_TYPE_DIB -> ".dib";
			case XSSFWorkbook.PICTURE_TYPE_GIF -> ".gif";
			case Workbook.PICTURE_TYPE_EMF -> ".emf";
			case Workbook.PICTURE_TYPE_WMF -> ".wmf";
			case Workbook.PICTURE_TYPE_PICT -> ".pict";
			case XSSFWorkbook.PICTURE_TYPE_WPG -> ".wpg";
			case XSSFWorkbook.PICTURE_TYPE_TIFF -> ".tif";
			case XSSFWorkbook.PICTURE_TYPE_EPS -> ".eps";
			case XSSFWorkbook.PICTURE_TYPE_BMP -> ".bmp";
			default -> "未知后缀名";
		};
	}

    // 处理图片
    // 放回结果格式说明:Map<行号,Map<列号,PictureData对象>>
	private Map<Integer, Map<Integer, PictureData>> getPictures(Sheet sheet) {
		Map<Integer, Map<Integer, PictureData>> pictureMap = new HashMap<>();
		Drawing<?> drawing = sheet.getDrawingPatriarch();
		if (drawing == null) {
			drawing = sheet.createDrawingPatriarch();
		}
		if (drawing instanceof XSSFDrawing xssfDrawing) {
			for (XSSFShape shape : xssfDrawing.getShapes()) {
				if (shape instanceof XSSFPicture picture) {
					XSSFClientAnchor anchor = picture.getClientAnchor();
					int row = anchor.getRow1();
					short col = anchor.getCol1();
                    
					System.out.println("row " + row + " col " + col);
					PictureData pictureData = picture.getPictureData();
					if (pictureMap.containsKey(row)) {
						Map<Integer, PictureData> integerPictureDataMap = pictureMap.get(row);
						integerPictureDataMap.put((int) col, pictureData);
						pictureMap.put(row, integerPictureDataMap);
					} else {
						Map<Integer, PictureData> pictureSubMap = new HashMap<>();
						pictureSubMap.put((int) col1, pictureData);
						pictureMap.put(row, pictureSubMap);
					}
				}
			}
		}
		return pictureMap;
	}
}

测试文件

接口测试

测试结果

感谢您阅读,欢迎您给出合理的意见

posted @ 2025-04-16 15:26  Java_lively  阅读(1120)  评论(0)    收藏  举报