导出数据与图片到word中,插入等比例图片!!!
一、效果
话不多说,先看效果:
二、准备
1.引入poi的依赖
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.15</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.15</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-scratchpad</artifactId>
<version>3.15</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>ooxml-schemas</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>com.deepoove</groupId>
<artifactId>poi-tl</artifactId>
<version>1.0.0</version>
</dependency>
2.准备word模板
使用poi导出word需要准备一份word模板,变量用{{变量}}标识,在代码中会将这些变量替换为需要的数据
三、代码实现
本是使用springboot搭建的项目环境,由于是给浏览器用户导出word,所以代码分为service与controller两部分:
1.WordService代码
/**
* @param data :需要替换的模板中的变量键值对
* @param TemplatePath :word模板在项目中的路径
* @param pictureTag :要替换为图片的变量名
* @param wordName :生成的word名称
* @param request
* @param response
* @throws IOException
*/
public void downWord(Map<String, Object> data, String TemplatePath, List<String>pictureTag,String wordName, HttpServletRequest request, HttpServletResponse response) throws IOException {
try {
ClassPathResource template = new ClassPathResource(TemplatePath);
String projectPath = System.getProperty("user.dir"); //获取当前项目所在路径
//创建一个临时模板,放在系统上(这步很无奈)
File file = new File(projectPath+SEPARATER+"template.docx");
InputStream templateInputStream = template.getInputStream();
FileUtils.copyInputStreamToFile(templateInputStream,file);
XWPFTemplate xwpfTemplate = XWPFTemplate.compile(file);
Configure config = xwpfTemplate.getConfig();
//设置处理策略,这一步很重要,本人在这个坑上跌了很久!!!
for (String tag:pictureTag
) {
config.customPolicy(tag,new PictureRenderPolicy());
}
//将数据递交给模板处理
xwpfTemplate.render(data);
String docName = wordName + ".docx";
File targetFile = new File(docName);
FileOutputStream out = new FileOutputStream(targetFile);
xwpfTemplate.write(out);
out.flush();
out.close();
xwpfTemplate.close();
// 下载输出到浏览器
FileUtil.downFile(request,response,docName,targetFile);
FileUtil.deleteDir(targetFile.getPath());
} catch (Exception e) {
log.info("文件生成失败:"+e.getMessage());
throw e;
}
}
2.WordController
public void exportData(PatrolWordDto dto,HttpServletRequest request, HttpServletResponse response) throws Exception {
List<String> pictureTag =new ArrayList<>();
Map<String,Object>data =new HashMap<>();
//需要填充的数据,key为模板中的变量
data.put("projectId",dto.getProjectId());
data.put("patrolTypeId",dto.getPatrolTypeId());
data.put("inspector",dto.getInspector());
data.put("date",dto.getDate());
data.put("beginTime",dto.getBeginTime());
data.put("content",dto.getContent());
data.put("result",dto.getResult());
data.put("endTime",dto.getEndTime());
data.put("note",dto.getNote());
//这里是前台传给我的查询图片的id
String[] files = dto.getFiles().split("-");
int num =files.length;
String templatePath=null;
//因为图片数量不确定,设计了多种模板处理
for (int i=0;i<num;i++){
//这里我是从数据库获取图片路径
String photoPath = wordService.getPhotoPath(files[i]);
//这个类完全是为了获取图片长宽
BufferedImage bufferImg = ImageIO.read(new File(photoPath));
// 获取的长宽数值较大,避免插入图片过大,我等比例缩小了10倍
int width = bufferImg.getWidth()/10;
int height = bufferImg.getHeight()/10;
PictureRenderData pictureRenderData = new PictureRenderData(width, height,photoPath);
data.put("image"+(i+1),pictureRenderData);
//标识出其中的图片变量,需要特殊处理
pictureTag.add("image"+(i+1));
}
// 对应三种数量图片的模板路径
switch (num){
case 1:templatePath= "files/download/template/patroltemplate1.docx";break;
case 2:templatePath= "files/download/template/patroltemplate2.docx";break;
case 3:templatePath= "files/download/template/patroltemplate3.docx";break;
}
wordService.downWord(data,templatePath,pictureTag,"patrol",request,response);
}
3.工具类FileUtil
/**
* 下载文件到浏览器
* @param request
* @param response
* @param filename 要下载的文件名
* @param file 需要下载的文件对象
* @throws IOException
*/
public static void downFile(HttpServletRequest request, HttpServletResponse response, String filename, File file) throws IOException {
// 文件存在才下载
if (file.exists()) {
OutputStream out = null;
FileInputStream in = null;
// 1.读取要下载的内容
in = new FileInputStream(file);
// 2. 告诉浏览器下载的方式以及一些设置
// 解决文件名乱码问题,获取浏览器类型,转换对应文件名编码格式,IE要求文件名必须是utf-8, firefo要求是iso-8859-1编码
String agent = request.getHeader("user-agent");
if (agent.contains("FireFox")) {
filename = new String(filename.getBytes("UTF-8"), "iso-8859-1");
} else {
filename = URLEncoder.encode(filename, "UTF-8");
}
// 设置下载文件的mineType,告诉浏览器下载文件类型
String mineType = request.getServletContext().getMimeType(filename);
response.setContentType(mineType);
// 设置一个响应头,无论是否被浏览器解析,都下载
response.setHeader("Content-disposition", "attachment; filename=" + filename);
// 将要下载的文件内容通过输出流写到浏览器
out = response.getOutputStream();
int len = 0;
byte[] buffer = new byte[1024];
while ((len = in.read(buffer)) > 0) {
out.write(buffer, 0, len);
}
if (out != null) {
out.close();
}
if (in != null) {
in.close();
}
}
}
/**
* 递归删除目录下的所有文件及子目录下所有文件
*
* @param filePath 将要删除的文件目录路径
* @return boolean Returns "true" if all deletions were successful.
* If a deletion fails, the method stops attempting to
* delete and returns "false".
*/
public static boolean deleteDir(String filePath) {
File dir = new File(filePath);
if (dir.isDirectory()) {
String[] children = dir.list();
//递归删除目录中的子目录下
for (int i = 0; i < children.length; i++) {
boolean success = deleteDir(filePath + File.separator + children[i]);
if (!success) {
return false;
}
}
}
// 目录此时为空,可以删除
return dir.delete();
然后直接在浏览器访问controller的路径并传参就可以直接下载导出的word了,本人的项目路径是酱紫的:
http://127.0.0.1:8080/exportPatrolWord?projectId=sada&patrolTypeId=asd&inspector=asd&date=asd&beginTime=asd&endTime=asd&content=asd&result=asd&files=1¬e=asd
四、总结
本人也是被项目逼着翻了很多博客找到的代码,本来以为拿来能直接用,结果还有暗坑。主要有两个:
1.下载出来的word中本该是图片的地方是一串字符。调试看源码才发现默认的数据处理策略是TextRenderPolicy,这样就会对图片数据错误处理,结果就是把图片类的类名输出,而不是把图片输出,所以在递交模板数据前一定要配置对于图片标签用PictureRenderPolicy进行处理。
for (String tag:pictureTagRenderPolicy()) {
config.customPolicy(tag,new PictureRenderPolicy());
}
2.项目在idea中能运行,打包发布到linux后报异常。主要原因是打包成jar后,程序无法直接读取放在jar包中的word模板文件,只能获取到资源文件的二进制流。所以我不得已新建了一个file,然后将流复制到新文件中,使用该文件作为模板。只是这样会造成资源浪费,每次都会重新复制模板文件,所以最好是在项目初始化时进行该操作即可。各位大佬有更好的办法麻烦指点,谢谢!
ClassPathResource template = new ClassPathResource(TemplatePath);
File file = new File(projectPath+SEPARATER+"template.docx");
InputStream templateInputStream = template.getInputStream();
FileUtils.copyInputStreamToFile(templateInputStream,file);