项目中,要求批量导入 Excel 数据并读取其中图片,目前 EasyExcel 不支持读取图片,因此需要使用 Apache POI 进行导入。然而Apache POI 需要开发者手动管理行、列、单元格等对象,相对较为底层且繁琐。
<!-- Apache POI -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>5.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/easyexcel -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.2.1</version>
</dependency>
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.util.ListUtils;
import com.bi.my.vo.ExcelVo;
import lombok.extern.slf4j.Slf4j;
import net.dreamlu.mica.core.result.R;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.xssf.usermodel.*;
import org.springframework.web.bind.annotation.PostMapping;
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.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* 数据+图片导入
*
* @author wss
*/
@Slf4j
@RestController
@RequestMapping("/easy")
public class EasyExcelController {
/**
* 数据导入并识别图片
*
* @param file file
* @return R<Object>
* @author wss
*/
@PostMapping("/import")
public R<Object> dataImport(@RequestParam("file") MultipartFile file) {
List<ExcelVo> excelVos = new ArrayList<>();
//数据处理
this.dataProcess(file, excelVos);
//图像处理
this.imageProcess(file, excelVos);
//返回结果,这里也可以处理excelVos数据,如往mysql存储
return R.success(excelVos);
}
/**
* 图片处理
*/
public void imageProcess(MultipartFile file, List<ExcelVo> excelVos) {
try {
XSSFWorkbook book = new XSSFWorkbook(file.getInputStream());
//方式1 获取sheet数量,采用下标方式遍历读取每个工作表数据
int sheetsNos = book.getNumberOfSheets();
for (int sheetNo = 0; sheetNo < sheetsNos; sheetNo++) {
Sheet sheet = book.getSheetAt(sheetNo);
//...省略,内容为方式2
}
//方式2 获取sheet数量,直接遍历读取每个工作表数据
for (Sheet sheet : book) {
XSSFSheet xssSheet = (XSSFSheet) sheet;
//获取工作表中绘图包
XSSFDrawing drawing = xssSheet.getDrawingPatriarch();
if (drawing == null) {
break;
}
//获取所有图像形状
List<XSSFShape> shapes = drawing.getShapes();
//遍历所有形状
for (XSSFShape shape : shapes) {
//获取形状在工作表中的顶点位置信息(anchor锚点)
XSSFClientAnchor anchor = (XSSFClientAnchor) shape.getAnchor();
//图片形状在工作表中的位置, 所在行列起点和终点位置
short c1 = anchor.getCol1();
int r1 = anchor.getRow1();
String key = r1 + "行," + c1 + "列";
if (shape instanceof XSSFPicture) {
try {
XSSFPicture pic = (XSSFPicture) shape;
//形状获取对应的图片数据
XSSFPictureData picData = pic.getPictureData();
//保存图片到本地
byte[] data = picData.getData();
//TODO 这里上传文件至oss并生成链接,这里不做过多描述,有疑问请参照oss服务调用
String fileName = "https://oss.cn/" + DateUtil.today() + "/" + IdUtil.simpleUUID() + "/" + picData.suggestFileExtension();
//fileTemplate.putObject(properties.getBucketName(), fileName, new ByteArrayInputStream(data));
//TODO 放入excel集合,这里行数要减去1,获取图片是从表头开始(表头位置为0),获取excelVos是从数据开始(第一条数据位置为0)他们相差一个表头,所以要减去1才能对应
excelVos.get(r1 - 1).setPicture(fileName);
} catch (Exception e) {
log.error("asyncImportList XSSFClientAnchor key|{} error|{}", key, e.getMessage());
}
}
}
}
} catch (Exception e) {
log.error("asyncImportList XSSFWorkbook error|{}", e.getMessage());
}
}
/**
* 数据处理
*/
public void dataProcess(MultipartFile file, List<ExcelVo> excelVos) {
// 这里默认读取第一个sheet
try {
EasyExcel.read(file.getInputStream(), ExcelVo.class, new ReadListener() {
/**
* 单次缓存的数据量
*/
public static final int BATCH_COUNT = 100;
/**
*临时存储
*/
private List<ExcelVo> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
@Override
public void invoke(Object object, AnalysisContext context) {
ExcelVo data = (ExcelVo) object;
cachedDataList.add(data);
if (cachedDataList.size() >= BATCH_COUNT) {
saveData();
// 存储完成清理 list
cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
}
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
saveData();
}
/**
* 加上存储数据库
*/
private void saveData() {
log.info("已获取数据|{}条", cachedDataList.size());
excelVos.addAll(cachedDataList);
}
}).sheet().doRead();
} catch (IOException e) {
e.printStackTrace();
}
}
}
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
/**
* 导入VO
*
* @author wss
*/
@Data
public class ExcelVo {
@ExcelProperty("序号")
private Integer ordinal;
@ExcelProperty("标题")
private String title;
@ExcelProperty("图片")
private String picture;
}
打开图片链接发现与excel中图片一致,对应数据一致,读取图片成功!
到这里,EasyExcel 导入带图片已完成!读取图片速度较慢,可以通过异步或其它方式优化处理,根据自己需求修改即可,这里就不进行说明了。
项目中本人是通过@RequestExcel注解直接获取的excel数据,file专用于图片读取,即方法中不用再进行数据处理,代码更加简化。
这篇文章主要是用来处理读取图片的,这里就不再详细说明该注解了,感兴趣的小伙伴可以查阅一下相关资料。
/**
* 数据导入并识别图片
*
* @param file file
* @return R<Object>
* @author wss
*/
@PostMapping("/import")
public R<Object> dataImport(@RequestParam("file") MultipartFile file, @RequestExcel List<ExcelVo> excelVos) {
//图像处理
this.imageProcess(file, excelVos);
//返回结果,这里也可以处理excelVos数据,如往库里存储
return R.success(excelVos);
}