Bootstrap

【POI技巧】把 Excel 图片资源导入至项目中

最近公司要通过表格导入有图片的多行试题数据,发现网上都是导出图片到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();
            }
        }
    }

对各位道友有用的话请给个赞和收藏,有疑问或问题,请留言。

;