为了导出docx格式看了等多文档,最后做个总结依赖包用到dom4j和freemarker,最为方便。

<!-- https://mvnrepository.com/artifact/freemarker/freemarker --><dependency><groupId>freemarker</groupId><artifactId>freemarker</artifactId><version>2.3.9</version></dependency><!-- https://mvnrepository.com/artifact/dom4j/dom4j --><dependency><groupId>dom4j</groupId><artifactId>dom4j</artifactId><version>1.6.1</version></dependency>

0.主要目的:将这样一个页面导出为word文档为doc格式,包含一些文本和循环遍历出来的echarts图表。

1.新建一个word文档(docx格式或doc),生成模板内容,例如下面这种。

整体思路

-保存后,复制出来一份,

-修改后缀名为zip。

-解压到一个文件夹中。

-打开文件夹看到如下目录


-获取word里的document.xml文档以及_rels文件夹下的document.xml.rels文档
-把内容填充到document.xml里,以及图片配置信息填充至document.xml.rels文档里
-在输入docx文档的时候把填充过内容的的 document.xml、document.xml.rels用流的方式写入zip(详见下面代码)。
-把图片写入zip文件下word/media文件夹中
-输出docx文档(因为word文档本身就是ZIP格式实现的)

2. 目录结构如下:主要文件由上一步拷贝过来的

  • document.xml里存放主要数据
  • media存放图片信息
  • _rels里存放配置信息

document.xml中存放图片的模板主要内容

3.document.xml修改模板内容加上freemarker遍历map集合,填入数据

4.document.xml.rels修改模板引用内容

注意:这里图片配置信息是根据 rId来获取的。docx模板总的${mdl.rId}就是rId的具体值。
为了避免重复,我的图片rId从12开始(在我没有修改之前,里面最大的rId是rId12)。

5.header1.xml页眉 (可不要)

6.[Content_Types].xml文件模板

7.前端页面关键就是请求,根据自己需求构建json字符串格式,map数据传到后台

//导出word docxfunction download_reportNew() {
//此处遍历页面得数据,放到json中,可根据自己需要省略-----------------startconsole.log("new wordx");var title= '${reportData.title}';var reportUnit= '${reportData.reportUnit!}';var reportTypeDate= '${reportData.reportTypeDate!}';var json;var jsonHead = {"title": title, "reportUnit": reportUnit,"reportTypeDate":reportTypeDate};
//此处用到了freemarker的模板遍历数据<#list reportData.reportModels as model>var modelTitle_${model ? index}= '${model.title!}';var modelDataSource_${model ? index}= '${model.dataSource!}';var modelShowContent_${model ? index}= '${model.showContent!}';var model_pic_${model ? index} = null ;var pic_${model ? index} = null ;if (model_${model ? index}.option != null) {pic_${model ? index}=model_${model ? index}.chart.getConnectedDataURL();model_pic_${model ? index}=pic_${model ? index}.substr(22,pic_${model ? index}.length);}var jsonBody = {"model_${model ? index}":{"modelTitle": modelTitle_${model ? index},"modelDataSource": modelDataSource_${model ? index},"modelShowContent":modelShowContent_${model ? index},"model_pic":model_pic_${model ? index}}};
//最后的json对象json=$.extend(true,jsonHead,jsonBody);</#list>
//此处遍历页面得数据,放到json中,可根据自己需要省略-----------------end$.ajax({
//第一次请求生成doc临时文件url: '${base}/report/reportView/reportExportNew.do',method: 'POST',contentType: 'application/json;charset=utf-8',data: JSON.stringify(json),success: function (data) {if (data.status == 0) {
//第二次请求读取文件写入response输出流,实现下载。window.location.href =  '${base}/report/reportView/reportExportLast.do'+ "?filepath=" + data.retinfo ;} else {alert("下载word失败!");}},error: function (data) {alert('文件下载失败' + data);}})}

8.第一次请求生成doc临时文件:word导出为doc格式的后台controller类

/*** @param* @Description 报表导出* @Date 2019/11/20 11:53* @Param map 填入模板的数据* @Author */@PostMapping("/reportExportNew")public void reportExportNew(HttpServletRequest request, HttpServletResponse response, @RequestBody Map<String,Object> map) throws IOException {WebResult res = new WebResult();try {String lastFilePath = reportViewService.createWordDocx(map);String lastPath = lastFilePath.replace(SEPARATOR,"~");String text=filastPath;
//生成的文件名放到response中返回(此处根据自己需要可直接返回text)  ---------startPrintWriter out = null;try {response.setContentType("application/json;charset=UTF-8");out = response.getWriter();out.write(text);} catch (IOException var9) {LOGGER.error(var9.getMessage(), var9);} finally {if (out != null) {out.print("");out.close();}}
//生成的文件名放到response中返回(此处根据自己需要可直接返回text)  ---------end} catch (Exception e) {e.printStackTrace();}}

9.后台构建map填入模板需要的数据createWordDocx(map)方法

/*** 创建docx 返回临时路径* @param map* @return* @throws IOException*/public String createWordDocx(Map<String, Object> map) throws IOException{/*** @param dataMap               参数数据* @param docxTemplateFile      docx模主板名称* @param xmlDocument           docx中document.xml模板文件  用来存在word文档的主要数据信息* @param xmlDocumentXmlRels    docx中document.xml.rels 模板文件  用来存在word文档的主要数据配置 包括图片的指向* @param xmlContentTypes       docx中 [Content_Types].xml 模板文件 用来配置 docx文档中所插入图片的类型 如 png、jpeg、jpg等* @param xmlHeader             docx中 header1.xml 模板文件 用来配置docx文档的页眉文件* @param templatePath          模板存放路径 如 /templates/* @param outputFileTempPath    所生成的docx文件的临时路径文件夹 如果 temp/20180914051811/* @param outputFileName        所生成的docx文件名称  如  xxx.docx 或  xxx.doc* */String timeStr = LocalDateUtils.getCurrentTime_yyyyMMddHHmmssSSS();String docxTemplateFile = "docxTemplates.docx";String xmlDocument = "document.xml";String xmlDocumentXmlRels = "document.xml.rels";String xmlContentTypes = "[Content_Types].xml";//可以用来修改页眉的一些信息String xmlHeader = "header1.xml";String templatePath = SEPARATOR + "template" + SEPARATOR;String outputFileTempPath = templatePath+"temp" + SEPARATOR + timeStr + SEPARATOR;String outputFileName = timeStr + "."+SUFFIX_DOCX;String classPath=ReportViewServiceImpl.class.getResource("/").getPath().toString();LOGGER.info("classPath:{}",classPath);LOGGER.info("templatePath:{}",templatePath);LOGGER.info("outputFileTempPath:{}",outputFileTempPath);LOGGER.info("outputFileName:{}",outputFileName);//填充整体数据Map<String, Object> dataMap = new HashMap<>(16);//模块内容列表List<Map<String, Object>> modelList = new ArrayList<>(16);//单个模块Map<String, Object> model;//        页眉dataMap.put("ymdhis", LocalDateUtils.getCurrentTime_yyyyMMddHHmmss());//      图片类型List<String> modelTypes = new ArrayList<>();modelTypes.add("png");dataMap.put("mdlTypes", modelTypes);//取空白图片Base64码String url = classPath+"template/blank.png";String blankEncode = getImageStr(url);//        文档标题dataMap.put("title", map.get("title"));dataMap.put("reportUnit", map.get("reportUnit"));dataMap.put("reportTypeDate", map.get("reportTypeDate"));//模块数量int modelNum = map.size() - dataMap.size()+2;for (int i = 0; i < modelNum; i++) {model = (Map<String, Object>) map.get("model_" + i);if (model.get("model_pic") == null) {//64位编码格式改成path和namemodel.put("model_pic", blankEncode);}//每个文件路径String fileName = "pic"+i+".png";String filePath = classPath+outputFileTempPath+fileName;model.put("path",filePath);model.put("name",fileName);modelList.add(model);}//批量生成文件for (Map<String, Object> mod:modelList) {baseToFile(mod);}dataMap.put("modelList", modelList);try {String lastFilePath = WordUtils.createDocx(dataMap, docxTemplateFile, xmlDocument, xmlDocumentXmlRels, xmlContentTypes,xmlHeader, templatePath, outputFileTempPath, outputFileName);return lastFilePath;} catch (Exception e) {e.printStackTrace();}return null;}

10.wordUtiles类的createDocx

import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;import java.io.*;
import java.net.URL;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;/*** @Description docx、doc文档生成工具类  (改变后缀名即可)* 在使用制作模板的过程中如果模板中有图片那就保留图片,注意[Content_Types].xml和document.xml.rels文档* 如果模板中没有图片 则不需要设置[Content_Types].xml和document.xml.rels* 由于word模板的个性化 所以 每次做模板都要重新覆盖原来的模板*  @Author */
public class WordUtils {private final static String SEPARATOR = File.separator;/*** @param dataMap            参数数据* @param docxTemplateFile   docx模主板名称* @param xmlDocument        docx中document.xml模板文件  用来存在word文档的主要数据信息* @param xmlDocumentXmlRels docx中document.xml.rels 模板文件  用来存在word文档的主要数据配置 包括图片的指向* @param xmlContentTypes    docx中 [Content_Types].xml 模板文件 用来配置 docx文档中所插入图片的类型 如 png、jpeg、jpg等* @param xmlHeader          docx中 header1.xml 模板文件 用来配置docx文档的页眉文件* @param templatePath       模板存放路径 如 /templates/* @param outputFileTempPath 所生成的docx文件的临时路径文件夹 如果 temp/20180914051811/* @param outputFileName     所生成的docx文件名称  如  xxx.docx  或  xxx.doc*/public static String createDocx(Map dataMap, String docxTemplateFile, String xmlDocument, String xmlDocumentXmlRels,String xmlContentTypes, String xmlHeader, String templatePath,String outputFileTempPath, String outputFileName) throws Exception {URL basePath = WordUtils.class.getClassLoader().getResource("");String realTemplatePath = basePath.getPath() + templatePath;//临时文件产出的路径String outputPath = basePath.getPath() + outputFileTempPath;String lastFilePath = outputFileTempPath+outputFileName;List<String> delFileList = new ArrayList<>();try {//获取 document.xml.rels 输入流String xmlDocumentXmlRelsComment = FreeMarkUtils.getFreemarkerContent(dataMap, xmlDocumentXmlRels, templatePath);ByteArrayInputStream documentXmlRelsInput = new ByteArrayInputStream(xmlDocumentXmlRelsComment.getBytes());//获取 header1.xml 输入流ByteArrayInputStream headerInput = FreeMarkUtils.getFreemarkerContentInputStream(dataMap, xmlHeader, templatePath);//获取 [Content_Types].xml 输入流ByteArrayInputStream contentTypesInput = FreeMarkUtils.getFreemarkerContentInputStream(dataMap, xmlContentTypes, templatePath);//读取 document.xml.rels  文件 并获取rId 与 图片的关系 (如果没有图片 此文件不用编辑直接读取就行了)Document document = DocumentHelper.parseText(xmlDocumentXmlRelsComment);// 获取根节点Element rootElt = document.getRootElement();// 获取根节点下的子节点headIterator iter = rootElt.elementIterator();List<Map<String, String>> picList = (List<Map<String, String>>) dataMap.get("modelList");// 遍历Relationships节点while (iter.hasNext()) {Element recordEle = (Element) iter.next();String id = recordEle.attribute("Id").getData().toString();String target = recordEle.attribute("Target").getData().toString();if (target.indexOf("media") == 0) {for (Map<String, String> picMap : picList) {if (target.endsWith(picMap.get("name"))) {picMap.put("rId", id);}}}}//覆盖原来的picList;dataMap.put("modelList", picList);//获取 document.xml 输入流ByteArrayInputStream documentInput = FreeMarkUtils.getFreemarkerContentInputStream(dataMap, xmlDocument, templatePath);File docxFile = new File(realTemplatePath + SEPARATOR + docxTemplateFile);if (!docxFile.exists()) {docxFile.createNewFile();}ZipFile zipFile = new ZipFile(docxFile);Enumeration<? extends ZipEntry> zipEntrys = zipFile.entries();File tempPath = new File(outputPath);//如果输出目标文件夹不存在,则创建if (!tempPath.exists()) {tempPath.mkdirs();}ZipOutputStream zipout = new ZipOutputStream(new FileOutputStream(outputPath + outputFileName));//覆盖文档int len = -1;byte[] buffer = new byte[1024];while (zipEntrys.hasMoreElements()) {ZipEntry next = zipEntrys.nextElement();InputStream is = zipFile.getInputStream(next);if (next.toString().indexOf("media") < 0) {// 把输入流的文件传到输出流中 如果是word/document.xml由我们输入zipout.putNextEntry(new ZipEntry(next.getName()));//写入图片配置类型if ("[Content_Types].xml".equals(next.getName())) {if (contentTypesInput != null) {while ((len = contentTypesInput.read(buffer)) != -1) {zipout.write(buffer, 0, len);}contentTypesInput.close();}} else if (next.getName().indexOf("document.xml.rels") > 0) {//写入填充数据后的主数据配置信息if (documentXmlRelsInput != null) {while ((len = documentXmlRelsInput.read(buffer)) != -1) {zipout.write(buffer, 0, len);}documentXmlRelsInput.close();}} else if ("word/document.xml".equals(next.getName())) {//写入填充数据后的主数据信息if (documentInput != null) {while ((len = documentInput.read(buffer)) != -1) {zipout.write(buffer, 0, len);}documentInput.close();}} else if ("word/header1.xml".equals(next.getName())) {//写入填充数据后的页眉信息if (headerInput != null) {while ((len = headerInput.read(buffer)) != -1) {zipout.write(buffer, 0, len);}headerInput.close();}} else {while ((len = is.read(buffer)) != -1) {zipout.write(buffer, 0, len);}is.close();}}}//覆盖文档//写入新图片len = -1;if (picList != null && !picList.isEmpty()) {for (Map<String, String> pic : picList) {ZipEntry next = new ZipEntry("word" + SEPARATOR + "media" + SEPARATOR + pic.get("name"));zipout.putNextEntry(new ZipEntry(next.toString()));InputStream in = new FileInputStream(pic.get("path"));while ((len = in.read(buffer)) != -1) {zipout.write(buffer, 0, len);}in.close();}}zipout.close();return lastFilePath;} catch (Exception e) {e.printStackTrace();throw new Exception("生成docx文件失败!");}}/*** 递归删除文件夹** @param dir*/public static void delFiles(String dir) {try {File file = new File(dir);if(!file.exists()){return;}if(file.isFile() || file.list()==null) {file.delete();System.out.println("删除了"+file.getName());}else {File[] files = file.listFiles();for(File a:files) {a.delete();}file.delete();System.out.println("删除了"+file.getName());}} catch (Exception e) {e.printStackTrace();}}
}

11.第二次请求读取文件写入response输出流,实现下载。:将文件输出到response中,浏览器实现下载

/*** @param* @Description 报表导出* @Date 2019/11/13 11:53* @Author */@RequestMapping("/reportExportLast")public void reportExportLast(HttpServletRequest request, HttpServletResponse response) throws IOException {String templateName = request.getParameter("filepath");if (templateName.isEmpty()) {templateName = "report" + System.currentTimeMillis();}String classPath=ReportViewAction.class.getResource("/").getPath().toString();String fileName = templateName.replace("~",SEPARATOR);String filePath = classPath+fileName;String name = "";//文件后缀名String fileExt = fileName.substring(fileName.lastIndexOf(".")+1);File file = new File(filePath);try (InputStream inputStream = new FileInputStream(file);ServletOutputStream out = response.getOutputStream()) {if (SUFFIX_DOCX.equals(fileExt)){name = new String("大数据报告.docx".getBytes("UTF-8"),"UTF-8");response.setContentType("application/msword;charset=UTF-8");}else if (SUFFIX_PDF.equals(fileExt)){name = new String("大数据报告.pdf".getBytes("UTF-8"),"UTF-8");response.setContentType("application/pdf;charset=UTF-8");}name = URLEncoder.encode(name,"UTF-8");response.setHeader("Content-Disposition", "attachment;filename=" + name);byte[] buffer = new byte[1024];int bytesToRead;while ((bytesToRead = inputStream.read(buffer)) != -1) {out.write(buffer, 0, bytesToRead);}} catch (IOException e) {e.printStackTrace();} finally {//删除临时文件String outputPath = filePath.substring(0,filePath.lastIndexOf(SEPARATOR)+1);WordUtils.delFiles(outputPath);}}

具体代码

https://gitee.com/zc0709/JavaUtilsProject

主要工具类

https://gitee.com/zc0709/JavaUtilsProject/blob/master/src/main/java/com/sl/utils/office/word/WordUtils.java

导出word文档生成docx格式,包含freemarker遍历多张图片相关推荐

  1. 导出word文档生成docx格式 添加水印

    为了导出docx格式看了等多文档,最后做个总结依赖包用到dom4j和freemarker,最为方便. <!-- https://mvnrepository.com/artifact/freema ...

  2. 如何批量将 Doc 格式的 Word 文档转为 Docx 格式

    概要:我们都知道 Word 格式有多种.比如常见的有 Doc.Docx,这两种类型是能够相互兼容的,也是能够相互转化的.那今天给大家介绍的是如何将多个 Doc 格式文档批量转为 Docx 格式. 我们 ...

  3. SpringBoot 导出Word文档(doc\docx) Office无法打开,WPS正常等坑

    以下对于生成doc文档来说哒, 对于生成docx请移步https://blog.csdn.net/wantLight/article/details/106105416 首先引入freemarker依 ...

  4. [PYTHON]批量合并WORD文档,DOCX格式

    应用场景 某些时候按照不同的要求需要导出单个的word文档,也有可能导出整个word文档.该脚本作为[PYTHON]使用python将Oracle数据库结构导出为定制的word文档的补充,可以直接合并 ...

  5. js 导出word 文档 doc docx

    在做项目时,要将富文本编辑器,或是html内容 导出为word. 先引入文件保存js <script src="FileSaver.js"></script> ...

  6. Java操作word文档将docx转换为pdf格式

    Java操作word文档将docx转换为pdf格式 一.整体说明 在上传 Office 课件时,格式有:doc,docx,xls,xlsx,ppt,pptx,程序需要将其 转换成 pdf 格式, 才能 ...

  7. 使用freemarker导出word文档包含多张图片

    使用freemarker导出word文档包含多张图片 最近项目中有个需求就是导出word文档并且文档中需要有多张图片,我当时一脸懵逼啊,之前没有搞过这个啊,但是不要灰心,肯定会有很多人搞过的,百度一下 ...

  8. npoi html导出word,NPOI插件生成导出word文档

    由于以前没有接触NPOI过这个插件,因此几乎都是本身一边百度摸索一边学习.html 这个插件对于Excel的数据导入和导出,能够说是很方便了,前端 可是对于导出word文档,能够说是不多的,百度了不少 ...

  9. 【day22】java导出word文档(包含导出图片)

    1.[Java]使用freemarker模板技术导出word main方法测试 package com.havenliu.document;import java.io.UnsupportedEnco ...

  10. freemarker导出word文档——WordXML格式解析

    前不久,公司一个项目需要实现导出文档的功能,之前是一个同事在做,做了3个星期,终于完成了,但是在项目上线之后却发现导出的文档有问题,此时,这个同事已经离职,我自然成为接班者,要把导出功能实现,但是我看 ...

最新文章

  1. linux 安装 tao环境,linux环境安装hbase------不一定需要hadoop
  2. 进程与线程||线程应用:异步调用||多线程与单线程
  3. .NET框架类库中的命名空间
  4. asp.net2.0跨域问题
  5. linux怎么判断全局符号,Linux下全局符号覆盖有关问题
  6. 史无前例,阿里云或将空降 M7 级高管
  7. L1、L2、Batch Normalization、Dropout为什么能够防止过拟合呢?
  8. 西南科技大学OJ题 平衡二叉树的判定1077
  9. Structs的执行流程
  10. lomboz连接mysql数据库_将Lomboz Eclipse 连接上 Oracle 11g
  11. 北大计算机陈旭,北大图灵班——欢迎来到计算机王国
  12. 内存数据库及技术选型
  13. 【信息学奥赛】1005:地球人口承载力估计(C++)
  14. 基于SPH的流体仿真过程
  15. sql中模糊查询的字段中包含百分号%的语句
  16. 精通正则表达式第1讲(源码)
  17. php漏洞是什么意思,PHP程序漏洞产生的原因分析与防范方法说明
  18. 数据结构补习 --- malloc函数
  19. java gui 图片_java gui怎样在特定窗口中插入图片?
  20. 走进“开源SDR实验室” 一起玩转GNU Radio:调制解调大合集

热门文章

  1. 【废墟】知我者谓我心忧,不知我者谓我何求~
  2. vue 管理系统顶部tags浏览历史实现
  3. ES改变主分片数量,动态拆分primary shard
  4. 第4章 手机平板要兼顾,探究碎片
  5. flutter11 - 调试程序
  6. i排版html居中,i排版-i排版官网:微信公众号文章编辑软件-禾坡网
  7. i微信编辑器服务器,i排版微信编辑器
  8. 算法:工作窃取算法(work-stealing)。
  9. 对比企业邮箱,使用企业邮箱有哪些好处?
  10. LeetCode 714. 买卖股票的最佳时机含手续费--动态规划