最近公司要通过表格导入有图片的多行试题数据,发现网上都是导出图片到excel,对于导入图片的资料较少,且很多教程用不了,特此记录分享,让各位道友减少查找资料时间。
一、POI使用模版进行图片导入
1、先用excel创建一个导入模版
注意excel的列是从下标0开始的,我这表格前面有空一列。
如下第四列是图片:
2、导入excel,获得图片位置通用方法
下面两个是xlsx和xls表格的通用方法,复制到项目里即可
如果出现无法导入类,请查看是否有导入POI依赖,本次使用的POI版本是3.9版本。
/**
* 这是.xlsx文件拿到图片位置通用方法
*/
public static Map<String, List<PictureData>> getXlsxPictures(XSSFSheet sheet) throws IOException {
Map<String, List<PictureData>> map = new HashMap<>();
List<POIXMLDocumentPart> list = sheet.getRelations();
for (POIXMLDocumentPart part : list) {
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();
// 行号-列号
String key = marker.getRow() + "-" + marker.getCol();
List<PictureData> pictureDatas = map.get(key);
if (CollectionUtils.isEmpty(pictureDatas)) {
List<PictureData> newList = Lists.newArrayList();
newList.add(picture.getPictureData());
map.put(key, newList);
} else {
pictureDatas.add(picture.getPictureData());
}
}
}
}
return map;
}
/**
* 这是.xls文件拿到图片位置通用方法
*/
public static Map<String, List<PictureData>> getXlsPictures(HSSFSheet sheet) throws IOException {
Map<String, List<PictureData>> map = new HashMap<>();
List<HSSFShape> hapelList = sheet.getDrawingPatriarch().getChildren();
for (HSSFShape shape : hapelList) {
if (shape instanceof HSSFPicture) {
HSSFPicture picture = (HSSFPicture) shape;
HSSFClientAnchor cAnchor = (HSSFClientAnchor) picture.getAnchor();
PictureData pdata = picture.getPictureData();
// 行号-列号
String key = cAnchor.getRow1() + "-" + cAnchor.getCol1();
List<PictureData> pictureDatas = map.get(key);
if (CollectionUtils.isEmpty(pictureDatas)) {
List<PictureData> list = Lists.newArrayList();
list.add(pdata);
map.put(key, list);
} else {
pictureDatas.add(pdata);
}
}
}
return map;
}
3、实战示例,使用上面两个方法来获得图片,绑定所在的行数据
使用poi的代码避免不了一写就是很多,我尽量简洁明了,需要注意和修改的地方我会注释提醒。
Controller层导入方法示例
/**
* 导入Controller方法
*/
@RequestMapping(value = "/importExcel", method = RequestMethod.POST)
@ApiOperation(value = "导入Excel", notes = "")
public String importExcelWapper(@RequestParam("file") MultipartFile file) throws IOException {
Result<List<String>> r = importExcel(file)
return JsonUtils.objectToJson(r);
}
业务层导入方法示例
poi代码多,所以接口分离,放上的代码是按顺序书写
// 需要用到的业务实体类
@Data
public class Question { //试题
private String id;
private String compName;//公司名称
private String type; //类型
private String content;// 内容
private String answer;//正确答案
private List<CloudFile> cloudFiles; //图片
}
@Data
public class CloudFile { //图片文件类
private String id;
private String realPath; // 图片绝对路径
private String recordId; //关联记的试题id
}
/**
* 导入service方法
*/
public Result<List<String>> importExcel( MultipartFile file) throws IOException {
if (file != null && file.getSize() > 0) {
String fileName = file.getOriginalFilename();
Workbook workbook = null;
try {
InputStream is = file.getInputStream();
if (fileName.endsWith(".xlsx")) {
workbook = new XSSFWorkbook(is);
} else if (fileName.endsWith(".xls")) {
workbook = new HSSFWorkbook(is);
}
if (workbook == null) {
return failure("上传文件格式不正确");
}
} catch (Exception e) {
return failure("解析excel失败");
}
//导入的校验问题信息
List<String> errorList = Lists.newArrayList();
//这里真正处理excel数据
errorList.addAll(service.insertBatchFromExcel(workbook,fileName));
return success(errorList);
} else {
return failure("文件不存在");
}
}
/**
* 处理表格数据方法
*/
public List<String> insertBatchFromExcel(Workbook wb, String fileName) throws IOException {
boolean isValue = true;
List<String> errorList = new ArrayList<String>();
//sheet
Sheet sheet = wb.getSheetAt(0);
//excel中我表格导入的试题数据
List<Question> questions = Lists.newArrayList();
//从下标2行,此循环只拿每行的文字数据,还未拿图片
for (int j = 2; j < sheet.getPhysicalNumberOfRows(); j++) {
//从下标1列开始拿
int startCell = 1;
Row r = sheet.getRow(j);
//转换excel为试题对象
String compName = r.getCell(startCell).getRichStringCellValue().toString().trim();
if (StringUtils.isEmpty(compName)){
errorList.add("试题页 第" + j+1 + "行所属公司不存在");
isValue = false;
}
String typeValue= r.getCell(startCell+1).getRichStringCellValue().toString().trim();
String content= r.getCell(startCell+2).getRichStringCellValue().toString().trim();
String answer= r.getCell(startCell+4).getRichStringCellValue().toString().trim();
if (isValue){
Question question = new Question(compName,typeValue,content,answer);
if (question != null) {
questions.add(question);
}
}
}
//======> 使用第2节写的两个通用方法,拿到图片位置 <======
//得到图片和位置集合
Map<String, List<PictureData>> pictureDataMap = null;
if (fileName.endsWith(".xlsx")) {
XSSFSheet xSSFSheet = (XSSFSheet) questionSheet;
pictureDataMap = getXlsxPictures(xSSFSheet);
} else if (fileName.endsWith(".xls")) {
HSSFSheet hSSFSheet = (HSSFSheet) questionSheet;
pictureDataMap = getXlsPictures(hSSFSheet);
}
if(CollectionUtil.isNotEmpty(questions) && CollectionUtil.isNotEmpty(pictureDataMap)){
//把试题插入数据库,获得id
//questionService.insertBatch(questions);
//上传图片,得到图片位置后储存图片并绑定到对应的行
List<CloudFile> cloudFiles = printAndCreateCloudFileImg(pictureDataMap, questions,errorList);
//插入图片信息到文件表里
if (!CollectionUtils.isEmpty(cloudFiles)) {
//cloudFileService.insertBatch(cloudFiles);
}
}
return errorList ;
}
得到图片位置后储存图片并绑定到对应的行
/**
* 上传图片
*/
private List<CloudFile> printAndCreateCloudFileImg(Map<String, List<PictureData>> pictureDataMap, List<Question> questions, List<String> errorList) throws IOException {
String path = "D:\\test\\temp_excel"
String ctxpath = "D:\\test \\temp_excel"
for (int i = 0; i < pictureDataMap.size(); i++) {
// 获取图片索引
String picName = key[i].toString();
//主要逻辑:图片位置:2-4,则可以拆分出图片所在:2行,4列
String[] split = picName.split("-");
// 获取图片流
List<PictureData> pics = pictureDataMap.get(key[i]);
String val = split[1];
String recordId = "";
String dataType = "question"; //试题
String fileType = "training"; //培训管理
}
Integer fg=null;
if (val!=null){
fg= Integer.valueOf(val);
}
//做一个标识,这个标识用于校验是否跳出当前单元格
if ((Integer.parseInt(split[0]) - 2)<0){
errorList.add("第"+(Integer.parseInt(split[0])+1)+"行"+(fg+1)+"列图片位置不正确");
continue;
}
//主要逻辑:绑定图片到所属行的数据,数据从第2行开始,如图片表格位置:2行,则对应集合里的第一条数据0
recordId = questions.get(Integer.parseInt(split[0]) - 2).getId();
if (pics.size() > 1) {
errorList.add("第"+(Integer.parseInt(split[0])+1)+"行"+(fg+1)+"列图片数量不能超过1张");
continue;
}
for (PictureData pic : pics) {
// 获取图片格式
String ext = pic.suggestFileExtension();
byte[] data = pic.getData();
Long size = Long.valueOf(data.length);
//size不能大于2m
if (size > 2097152) {
errorList.add("第"+(Integer.parseInt(split[0])+1)+"行"+(fg+1)+"列图片不能大于2M");
continue;
}
String newFileName = UUID.randomUUID() + "." + ext;
//图片保存路径
String imgPath = path + newFileName;
CloudFile cloudFile = new CloudFile();
cloudFile.setRealPath(imgPath);
//主要逻辑:绑定所属行的对象id
cloudFile.setRecordId(recordId);
cloudFiles.add(cloudFile);
// apache的文件工具类写入图片
org.apache.commons.io.FileUtils.writeByteArrayToFile(new File(imgPath), data);
}
return cloudFiles;
}
二、导出带图片
导出带图片网上例子很多,我这里只做参考,关键代码就是设置图片位置
public void exportExcel(HttpServletResponse res) throws IOException {
Workbook wb = new XSSFWorkbook();
Sheet sheet = wb.createSheet("导出数据");
Drawing patriarch = sheet.createDrawingPatriarch();
//Question question = questionService.getById(1);
if(CollectionUtil.isNotEmpty(question.getCloudFiles())){
//拿到试卷所有图片
for (CloudFile cloudFile : question.getCloudFiles()) {
File pictrueBefore = new File(cloudFile.getRealPath());
// 如果图片不存在则不处理
if (!pictrueBefore.exists()) {
continue;
}
ByteArrayOutputStream byteArrayOut = new ByteArrayOutputStream();
BufferedImage bufferImg = ImageIO.read(pictrueBefore);
ImageIO.write(bufferImg, "jpg", byteArrayOut);
//主要逻辑:设置图片位置 rownum为当前行
ClientAnchor anchor = new XSSFClientAnchor(0, 0, 0, 0, 4, rownum+2, 5, rownum+3);
patriarch.createPicture(anchor, wb.addPicture(
byteArrayOut.toByteArray(), Workbook.PICTURE_TYPE_JPEG));
}
row.setHeight((short) 1134);
}
String fileName = DateUtil.formatDate(new Date()) + ".xlsx";
writeExcel(wb, fileName, res);
}
public void writeExcel(SXSSFWorkbook wb, String fileName, HttpServletResponse res) throws IOException {
res.reset();
res.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + URLEncoder.encode(fileName, "UTF-8"));
res.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
OutputStream os = null;
try {
os = res.getOutputStream();
wb.write(os);
} finally {
if(os != null) {
os.close();
}
if(wb != null) {
wb.dispose();
}
}
}
对各位道友有用的话请给个赞和收藏,有疑问或问题,请留言。