首先还是先说说我的思路:
- 1、先读取Excel的图片,使用map将图片对应的行位置和存储路径记录下来。
- 2、正常读取Excel数据并和实体类进行转换。
- 3、通过注解反射读取到图片字段。
- 4、映射图片地址到该字段。
具体实现:
1、引入Apache POI 依赖
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.3</version>
</dependency>
2、编写自定义注解,用于标记图片字段
@Target(ElementType.FIELD)//该注解作用于字段上
@Retention(RetentionPolicy.RUNTIME)//运行时保留,保证在运行时通过反射拿到
public @interface ExcelImageField {
}
3、编写图片提取工具类( XSSFWorkbook 打开传入的 Excel 文件流,然后获取到第一个工作表,这里大家有多个可以循环去提取。再根据 sheet.createDrawingPatriarch()获取到绘图对象。通过绘图对象拿到所有图片,再通过锚点去获取图片位置,然后保存图片,记录行号与路径)
java
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.*;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.*;
import java.util.*;
public class ExcelImageUtils {
/**
* 从Excel中提取图片并保存到指定目录
* @param inputStream Excel文件流
* @param saveDir 图片保存目录(如:/upload/images/)
* @return Map<行号, 图片路径> 如:{1: "/images/abc.jpg", 2: "/images/def.jpg"}
*/
public static Map<Integer, String> extractImages(InputStream inputStream, String saveDir) throws IOException {
Map<Integer, String> imageMap = new HashMap<>();
try (Workbook workbook = new XSSFWorkbook(inputStream)) {
Sheet sheet = workbook.getSheetAt(0);
XSSFDrawing drawing = (XSSFDrawing) sheet.createDrawingPatriarch();
if (drawing != null) {
// 创建图片保存目录
Path dirPath = Paths.get(saveDir);
if (!Files.exists(dirPath)) {
Files.createDirectories(dirPath);
}
// 处理所有图片
for (XSSFShape shape : drawing.getShapes()) {
if (shape instanceof XSSFPicture) {
XSSFPicture picture = (XSSFPicture) shape;
XSSFClientAnchor anchor = picture.getPreferredSize();
// 获取图片行号(Excel行号从0开始)
int rowNum = anchor.getRow1();
// 生成唯一文件名
String fileName = UUID.randomUUID() + getImageExtension(picture.getPictureData());
Path imagePath = dirPath.resolve(fileName);
// 保存图片
Files.write(imagePath, picture.getPictureData().getData());
// 存储访问路径(如:/images/filename.jpg)
imageMap.put(rowNum, "/images/" + fileName);
}
}
}
}
return imageMap;
}
private static String getImageExtension(XSSFPictureData pictureData) {
switch (pictureData.getPictureType()) {
case Workbook.PICTURE_TYPE_JPEG: return ".jpg";
case Workbook.PICTURE_TYPE_PNG: return ".png";
default: return ".png";
}
}
}
4、编写反射工具,通过反射动态设置值字段
java
/**
* 根據對象获取图片字段名
*
* @param clazz
* @return
*/
private String getImageFieldName(Class<?> clazz) {
for (Field field : clazz.getDeclaredFields()) {
if (field.isAnnotationPresent(ExcelImageField.class)) {
return field.getName();
}
}
log.warn("实体类 {} 未找到@ExcelImageField注解字段", clazz.getSimpleName());
return null;
}
5、编写完整的导入流程(这里不包含Excel正常导入数据,只是在原来已有的Excel导入加入对图片的处理逻辑)
java
/**
* 通过excel导入数据帶圖片
*
* @param request
* @param response
* @return
*/
protected Result<?> importExcel2(HttpServletRequest request, HttpServletResponse response, Class<T> clazz) {
MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
Map<String, MultipartFile> fileMap = multipartRequest.getFileMap();
for (Map.Entry<String, MultipartFile> entity : fileMap.entrySet()) {
// 获取上传文件对象
MultipartFile file = entity.getValue();
ImportParams params = new ImportParams();
params.setTitleRows(2);
params.setHeadRows(1);
params.setNeedSave(true);
// 1. 提前读取图片(這裡先读取流需要重置)
Map<Integer, String> imageMap = new HashMap<>();
try (InputStream tempStream = file.getInputStream()) {
imageMap = ExcelImageUtils.extractImages(tempStream, "/upload/images/");
} catch (Exception e) {
log.warn("读取图片失败(可能无图片): " + e.getMessage());
}
try {
List<T> list = ExcelImportUtil.importExcel(file.getInputStream(), clazz, params);
// 2. 为每条数据设置图片路径
for (int i = 0; i < list.size(); i++) {
// 计算对应Excel行号(标题行+表头行+数据偏移)
int excelRowNum = params.getTitleRows() + params.getHeadRows() + i;
if (imageMap.containsKey(excelRowNum)) {
// 使用反射设置字段值,后改用註解
//setFieldValue(list.get(i), "verificationResults", imageMap.get(excelRowNum));
String imageField = getImageFieldName(clazz);
if (imageField != null) {
setFieldValue(list.get(i), imageField, imageMap.get(excelRowNum));
}
}
}
//update-begin-author:taoyan date:20190528 for:批量插入数据
long start = System.currentTimeMillis();
service.saveBatch(list);
//400条 saveBatch消耗时间1592毫秒 循环插入消耗时间1947毫秒
//1200条 saveBatch消耗时间3687毫秒 循环插入消耗时间5212毫秒
log.info("消耗时间" + (System.currentTimeMillis() - start) + "毫秒");
//update-end-author:taoyan date:20190528 for:批量插入数据
return Result.ok("文件导入成功!数据行数:" + list.size());
} catch (Exception e) {
//update-begin-author:taoyan date:20211124 for: 导入数据重复增加提示
String msg = e.getMessage();
log.error(msg, e);
if(msg!=null && msg.indexOf("Duplicate entry")>=0){
return Result.error("文件导入失败:有重复数据!");
}else{
return Result.error("文件导入失败:" + e.getMessage());
}
//update-end-author:taoyan date:20211124 for: 导入数据重复增加提示
} finally {
try {
file.getInputStream().close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return Result.error("文件导入失败!");
}