WORD文档导出
1.实现原理
将模板文件放置项目中,通过freemarker处理模板文件,将相应的值填入对应的模板中,最后再导出下载
2.工具类
freemarker的Jar包导入项目
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.30</version>
</dependency>
freemarker调用工具类
package cn.gov.customs.hb.common.utility.util;
import freemarker.template.Configuration;
import freemarker.template.Template;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
/**
* @author: Baymax
* @Date: 2022年06月17日 15:57
* @Description:
*/
public class GenerateDocxUtil {
private static Logger logger = LoggerFactory.getLogger(GenerateDocxUtil.class);
public static void freemarkerDocxTest(Map<String, Object> lists, String fileName, HttpServletResponse response) throws Exception {
String reportPath = GenerateDocxUtil.class.getResource("/static/opinionTemplate").getPath();
String rootPath =reportPath + "\\";
String docxPath =reportPath + "\\"+fileName+".docx";
//配置freemarker模板
Configuration configuration = new Configuration();
String fileDirectory = rootPath;
configuration.setDirectoryForTemplateLoading(new File(fileDirectory));
String temName = fileName + ".xml";
String docxZipPath = rootPath + fileName + ".zip";
Template template = configuration.getTemplate(temName);
//通过模板生成的xml临时文件 方法结束后删除该临时文件
String outFilePath = rootPath + UUID.randomUUID().toString().replace("-", "") + ".xml";
//指定输出word xml文件的路径
File docXmlFile = new File(outFilePath);
FileOutputStream fos = new FileOutputStream(docXmlFile);
Writer out = new BufferedWriter(new OutputStreamWriter(fos), 10240);
template.process(lists, out);
if (out != null) {
out.close();
}
//以下代码 主要用来加密已经生成的xml文件,把xml文件正式转换成加密的word文档
//包装输入流
ZipInputStream zipInputStream = wrapZipInputStream(new FileInputStream(new File(docxZipPath)));
//包装输出流
ZipOutputStream zipOutputStream = wrapZipOutputStream(new FileOutputStream(new File(docxPath)));
//正式加密替换成docx格式文档
List<String> itemNameList = new ArrayList<>();
itemNameList.add("word/document.xml");
List<InputStream> itemInputStreamList = new ArrayList<>();
itemInputStreamList.add(new FileInputStream(new File(outFilePath)));
replaceItemList(zipInputStream, zipOutputStream, itemNameList, itemInputStreamList);
new File(outFilePath).delete();
logger.info("Word-docx文档生成完成");
XWPFDocument doc = new XWPFDocument(OPCPackage.open(docxPath));
DownloadFileUtil.download(fileName, doc, response);
logger.info("文档下载完成!");
}
public static ZipInputStream wrapZipInputStream(InputStream inputStream) {
ZipInputStream zipInputStream = new ZipInputStream(inputStream);
return zipInputStream;
}
public static ZipOutputStream wrapZipOutputStream(OutputStream outputStream) {
ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream);
return zipOutputStream;
}
public static void replaceItemList(ZipInputStream zipInputStream, ZipOutputStream zipOutputStream, List<String> itemNameList, List<InputStream> itemInputStreamList) {
if (null == zipInputStream) {
return;
}
if (null == zipOutputStream) {
return;
}
ZipEntry entryIn;
try {
while ((entryIn = zipInputStream.getNextEntry()) != null) {
String entryName = entryIn.getName();
ZipEntry entryOut = new ZipEntry(entryName);
zipOutputStream.putNextEntry(entryOut);
byte[] buf = new byte[8 * 1024];
int len;
if (itemNameList.indexOf(entryName) != -1) {
// 使用替换流
while ((len = (itemInputStreamList.get(itemNameList.indexOf(entryName)).read(buf))) > 0) {
zipOutputStream.write(buf, 0, len);
}
} else {
// 输出普通Zip流
while ((len = (zipInputStream.read(buf))) > 0) {
zipOutputStream.write(buf, 0, len);
}
}
// 关闭此 entry
zipOutputStream.closeEntry();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//e.printStackTrace();
for (InputStream itemInputStream : itemInputStreamList) {
close(itemInputStream);
}
close(zipInputStream);
close(zipOutputStream);
}
}
private static void close(InputStream inputStream) {
if (null != inputStream) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private static void close(OutputStream outputStream) {
if (null != outputStream) {
try {
outputStream.flush();
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
"/static/opinionTemplate"是项目中Resources下的地址,注意根据模板的具体位置修改,注意要将xml文件以及zip文件放置在同一个文件夹
zip文件是将docx模板文件后缀直接修改成zip,后来解压成文件夹,其中word下的document.xml文件即为需要修改的xml文件。
后续freemaker工具会将填充数据后的xml文件,替换掉zip文件中的document.xml文件。然后修改后缀名,最后下载
通过浏览器下载工具类
package cn.gov.customs.hb.common.utility.util;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedOutputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
/**
* @author: Baymax
* @Date: 2022年06月16日 16:20
* @Description:
*/
public class DownloadFileUtil {
private final static String UTF8 = "UTF-8";
private final static String CONTENTYPE = "application/msword";
private static Logger logger = LoggerFactory.getLogger(DownloadFileUtil.class);
/**
* 下载
* @param
* @param response
*/
public static void download(String fileName, XWPFDocument document, HttpServletResponse response) {
try {
if (!fileName.contains(".docx")) {
fileName += ".docx";
}
response.setCharacterEncoding(UTF8);
response.setContentType(CONTENTYPE);
response.setHeader("Content-Disposition", "attachment;filename="
.concat(String.valueOf(URLEncoder.encode(fileName, UTF8))));
// word写入到文件
OutputStream output = response.getOutputStream();
BufferedOutputStream bufferedOutPut = new BufferedOutputStream(output);
//刷新此缓冲的输出流,保证数据全部都能写出
bufferedOutPut.flush();
document.write(bufferedOutPut);
bufferedOutPut.close();
logger.info("Word导出成功");
} catch (Exception e) {
logger.info("Word导出失败");
throw new RuntimeException(e);
}
}
}
xml文件修改
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:document xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" xmlns:cx="http://schemas.microsoft.com/office/drawing/2014/chartex" xmlns:cx1="http://schemas.microsoft.com/office/drawing/2015/9/8/chartex" xmlns:cx2="http://schemas.microsoft.com/office/drawing/2015/10/21/chartex" xmlns:cx3="http://schemas.microsoft.com/office/drawing/2016/5/9/chartex" xmlns:cx4="http://schemas.microsoft.com/office/drawing/2016/5/10/chartex" xmlns:cx5="http://schemas.microsoft.com/office/drawing/2016/5/11/chartex" xmlns:cx6="http://schemas.microsoft.com/office/drawing/2016/5/12/chartex" xmlns:cx7="http://schemas.microsoft.com/office/drawing/2016/5/13/chartex" xmlns:cx8="http://schemas.microsoft.com/office/drawing/2016/5/14/chartex" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:aink="http://schemas.microsoft.com/office/drawing/2016/ink" xmlns:am3d="http://schemas.microsoft.com/office/drawing/2017/model3d" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:oel="http://schemas.microsoft.com/office/2019/extlst" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" xmlns:w16cex="http://schemas.microsoft.com/office/word/2018/wordml/cex" xmlns:w16cid="http://schemas.microsoft.com/office/word/2016/wordml/cid" xmlns:w16="http://schemas.microsoft.com/office/word/2018/wordml" xmlns:w16sdtdh="http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash" xmlns:w16se="http://schemas.microsoft.com/office/word/2015/wordml/symex" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" mc:Ignorable="w14 w15 w16se w16cid w16 w16cex w16sdtdh wp14">
<w:body>
<w:p w14:paraId="2D496CC2" w14:textId="77777777" w:rsidR="00365812" w:rsidRPr="00F04D53" w:rsidRDefault="00F04D53" w:rsidP="00F04D53">
<w:pPr>
<w:spacing w:afterLines="100" w:after="312" w:line="360" w:lineRule="auto"/>
<w:jc w:val="center"/>
<w:rPr>
<w:b/>
<w:bCs/>
</w:rPr>
</w:pPr>
<w:r w:rsidRPr="00F04D53">
<w:rPr>
<w:rFonts w:ascii="黑体"/>
<w:b/>
<w:bCs/>
<w:position w:val="50"/>
<w:sz w:val="44"/>
</w:rPr>
<w:t>${title}</w:t>
</w:r>
</w:p>
<#list lists as map>
<w:p w14:paraId="74DE0DF0" w14:textId="523506EA" w:rsidR="00365812" w:rsidRPr="00F04D53" w:rsidRDefault="00F04D53" w:rsidP="00F04D53">
<w:pPr>
<w:spacing w:line="360" w:lineRule="auto"/>
<w:ind w:left="560" w:hangingChars="200" w:hanging="560"/>
<w:jc w:val="left"/>
<w:rPr>
<w:b/>
<w:bCs/>
</w:rPr>
</w:pPr>
<w:r w:rsidRPr="00F04D53">
<w:rPr>
<w:rFonts w:ascii="华文宋体"/>
<w:b/>
<w:bCs/>
<w:position w:val="28"/>
<w:sz w:val="28"/>
</w:rPr>
<w:t>${map.contentTitle}</w:t>
</w:r>
</w:p>
<#list map.list as maps>
<w:p w14:paraId="2B9ECFC0" w14:textId="545C3C8A" w:rsidR="009755F5" w:rsidRPr="00414FB1" w:rsidRDefault="00414FB1" w:rsidP="00414FB1">
<w:pPr>
<w:pStyle w:val="a3"/>
<w:numPr>
<w:ilvl w:val="0"/>
<w:numId w:val="1"/>
</w:numPr>
<w:spacing w:line="360" w:lineRule="auto"/>
<w:ind w:firstLineChars="0"/>
<w:rPr>
<w:rFonts w:ascii="华文宋体" w:hint="eastAsia"/>
<w:position w:val="28"/>
<w:sz w:val="28"/>
</w:rPr>
</w:pPr>
<w:r>
<w:rPr>
<w:rFonts w:ascii="华文宋体" w:hint="eastAsia"/>
<w:position w:val="28"/>
<w:sz w:val="28"/>
</w:rPr>
<w:t>${maps.content}</w:t>
</w:r>
<w:r w:rsidR="00F04D53" w:rsidRPr="00414FB1">
<w:rPr>
<w:rFonts w:ascii="华文宋体"/>
<w:position w:val="28"/>
<w:sz w:val="28"/>
</w:rPr>
<w:t>(</w:t>
</w:r>
<w:r w:rsidR="00F04D53" w:rsidRPr="00414FB1">
<w:rPr>
<w:rFonts w:ascii="华文宋体"/>
<w:position w:val="28"/>
<w:sz w:val="28"/>
</w:rPr>
<w:t xml:space="preserve"> </w:t>
</w:r>
<w:r>
<w:rPr>
<w:rFonts w:ascii="华文宋体" w:hint="eastAsia"/>
<w:position w:val="28"/>
<w:sz w:val="28"/>
</w:rPr>
<w:t>${maps.username}</w:t>
</w:r>
<w:r w:rsidR="00F04D53" w:rsidRPr="00414FB1">
<w:rPr>
<w:rFonts w:ascii="华文宋体"/>
<w:position w:val="28"/>
<w:sz w:val="28"/>
</w:rPr>
<w:t xml:space="preserve"> </w:t>
</w:r>
<w:r>
<w:rPr>
<w:rFonts w:ascii="华文宋体" w:hint="eastAsia"/>
<w:position w:val="28"/>
<w:sz w:val="28"/>
</w:rPr>
<w:t>${maps.date}</w:t>
</w:r>
<w:r w:rsidR="00F04D53" w:rsidRPr="00414FB1">
<w:rPr>
<w:rFonts w:ascii="华文宋体"/>
<w:position w:val="28"/>
<w:sz w:val="28"/>
</w:rPr>
<w:t xml:space="preserve"> </w:t>
</w:r>
<w:r w:rsidR="00F04D53" w:rsidRPr="00414FB1">
<w:rPr>
<w:rFonts w:ascii="华文宋体"/>
<w:position w:val="28"/>
<w:sz w:val="28"/>
</w:rPr>
<w:t>)</w:t>
</w:r>
</w:p>
</#list>
</#list>
<w:sectPr w:rsidR="009755F5" w:rsidRPr="00414FB1">
<w:pgSz w:w="11906" w:h="16838"/>
<w:pgMar w:top="1440" w:right="1800" w:bottom="1440" w:left="1800" w:header="851" w:footer="992" w:gutter="0"/>
<w:cols w:space="425"/>
<w:docGrid w:type="lines" w:linePitch="312"/>
</w:sectPr>
</w:body>
</w:document>
将xml模板文件中需要替换的内容,使用占位符替代,需要循环输出的位置可以通过加上<#list lists as map></#list>包裹住相应的片段,嵌套循环同理(目前只测试了二层嵌套)
3.代码实现
package cn.gov.customs.hb.common.server.processopinion.service;
import cn.gov.customs.hb.common.utility.util.GenerateDocxUtil;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.text.SimpleDateFormat;
import java.util.*;
/**
* @author: Baymax
* @Date: 2022年06月16日 20:52
* @Description: 办理意见打印
*/
public class DealWithDownLoad {
public void downLoad(HttpServletResponse response, HttpServletRequest request) throws Exception {
//根据模板导出docx,创建文档标题层map
Map<String, Object> dataMap = new HashMap<>();
dataMap.put("title", "办理意见");
List<Map<String, Object>> list = new ArrayList<>();
for (int i = 0; i < 3; i++) {
Map<String, Object> data = new HashMap<>();
data.put("contentTitle", "办理意见" + i);
List<Map<String, Object>> list2 = new ArrayList<>();
for (int j = 0; j < 5; j++) {
Map<String, Object> data1 = new HashMap<>();
data1.put("content", "这是一个办理意见" + i);
data1.put("date", "2022-2-2 10:10" + i);
data1.put("username", "大白" + i);
list2.add(data1);
}
data.put("list", list2);
list.add(data);
}
dataMap.put("lists", timeList);
String fileName = "OpinionByTime";
GenerateDocxUtil.freemarkerDocxTest(dataMap, fileName, response);
}
}