前言
本篇博客主要解决java后台动态生成word(docx格式),并将word转换为pdf并添加水印。
思考
项目需求是要导出带水印的pdf,表格样式还是有点复杂的,之前考虑过用itextpdf根据html来生成pdf,但框架用的是前后台分
离的,前台用的是react,并且是在没有展示出表格的情况下,所以没法通过前台获取html代码块生成,后来又自己手动拼接
html,但代码量太大,难维护,且样式不怎么好看。所以决定用freemarker模板生成word,再转成pdf。翻阅网上很多资料给
出了很多方案将word转pdf,有用poi的、有用第三方工具的等等。用poi的写的都太复杂,jar引用很多,用第三方工具的有局
限性,不适合夸平台,需要安装服务。所以决定用docx4j,但docx4j只支持docx格式的word转pdf,所以需要freemarker
生成docx的word。
动手
1、pom引入依赖
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.4.3</version>
</dependency>
<dependency>
<groupId>freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.23</version>
</dependency>
<dependency>
<groupId>org.docx4j</groupId>
<artifactId>docx4j</artifactId>
<version>6.1.2</version>
</dependency>
<dependency>
<groupId>org.docx4j</groupId>
<artifactId>docx4j-export-fo</artifactId>
<version>6.0.0</version>
</dependency>
2、freemarker生成word(docx)
之前用freemarker生成word的时候都是生成doc格式的,将doc模板文件保存为xml格式再生成word,生成出来的其实是XML文件,
只不过文件后缀名是doc的,即使在生成的时候将文件后缀名改为docx,生成出来的文件是打不开的。其实docx本质上是个压缩包,
里面有包含word主要内容的document.xml文件、资源定义文件document.xml.rels、还有页眉页脚文件、图片资源等等,解决思路是替
换内容区的document.xml文件。
1)、准备docx模板文件,编辑插入占位符。
2)、将docx文件复制一份,将复制的文件后缀名改为zip
3)、拷贝出zip包里word路径下的document.xml文件,这个就是word的主内容文件。之前的那个tmp.docx和拷贝出的document.xml
这两个文件是我们所需要用到的两个模板文件,拷贝到项目工程里面去。
4)、工具类代码
1 package com.eazytec.zqtong.common.utils;
2
3 import com.itextpdf.text.*;
4 import com.itextpdf.text.pdf.*;
5 import org.docx4j.Docx4J;
6 import org.docx4j.convert.out.FOSettings;
7 import org.docx4j.fonts.IdentityPlusMapper;
8 import org.docx4j.fonts.Mapper;
9 import org.docx4j.fonts.PhysicalFonts;
10 import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
11 import org.springframework.core.io.ClassPathResource;
12
13 import java.io.*;
14 import java.util.*;
15 import java.util.zip.ZipEntry;
16 import java.util.zip.ZipFile;
17 import java.util.zip.ZipOutputStream;
18
19 public class PdfUtil {
20 private static String separator = File.separator;//文件夹路径分格符
21
22 //=========================================生成申请表pdf===================================
23
24 /**
25 * freemark生成word----docx格式
26 * @param dataMap 数据源
27 * @param documentXmlName document.xml模板的文件名
28 * @param docxTempName docx模板的文件名
29 * @return 生成的文件路径
30 */
31 public static String createApplyPdf(Map<String,Object> dataMap,String documentXmlName,String docxTempName) {
32 ZipOutputStream zipout = null;//word输出流
33 File tempPath = null;//docx格式的word文件路径
34 try {
35 //freemark根据模板生成内容xml
36 //================================获取 document.xml 输入流================================
37 ByteArrayInputStream documentInput = FreeMarkUtils.getFreemarkerContentInputStream(dataMap, documentXmlName, separator + "template" + separator + "downLoad" + separator);
38 //================================获取 document.xml 输入流================================
39 //获取主模板docx
40 ClassPathResource resource = new ClassPathResource("template" + File.separator + "downLoad" + File.separator + docxTempName);
41 File docxFile = resource.getFile();
42
43 ZipFile zipFile = new ZipFile(docxFile);
44 Enumeration<? extends ZipEntry> zipEntrys = zipFile.entries();
45
46 //输出word文件路径和名称
47 String fileName = "applyWord_" + System.currentTimeMillis() + ".docx";
48 String outPutWordPath = System.getProperty("java.io.tmpdir").replaceAll(separator + "$", "") + separator + fileName;
49
50 tempPath = new File(outPutWordPath);
51 //如果输出目标文件夹不存在,则创建
52 if (!tempPath.getParentFile().exists()) {
53 tempPath.mkdirs();
54 }
55 //docx文件输出流
56 zipout = new ZipOutputStream(new FileOutputStream(tempPath));
57
58 //循环遍历主模板docx文件,替换掉主内容区,也就是上面获取的document.xml的内容
59 //------------------覆盖文档------------------
60 int len = -1;
61 byte[] buffer = new byte[1024];
62 while (zipEntrys.hasMoreElements()) {
63 ZipEntry next = zipEntrys.nextElement();
64 InputStream is = zipFile.getInputStream(next);
65 if (next.toString().indexOf("media") < 0) {
66 zipout.putNextEntry(new ZipEntry(next.getName()));
67 if ("word/document.xml".equals(next.getName())) {
68 //写入填充数据后的主数据信息
69 if (documentInput != null) {
70 while ((len = documentInput.read(buffer)) != -1) {
71 zipout.write(buffer, 0, len);
72 }
73 documentInput.close();
74 }
75 }else {//不是主数据区的都用主模板的
76 while ((len = is.read(buffer)) != -1) {
77 zipout.write(buffer, 0, len);
78 }
79 is.close();
80 }
81 }
82 }
83 //------------------覆盖文档------------------
84 zipout.close();//关闭
85
86 //----------------word转pdf--------------
87 return convertDocx2Pdf(outPutWordPath);
88
89 } catch (Exception e) {
90 e.printStackTrace();
91 try {
92 if(zipout!=null){
93 zipout.close();
94 }
95 }catch (Exception ex){
96 ex.printStackTrace();
97 }
98 }
99 return "";
100 }
101
102 /**
103 * word(docx)转pdf
104 * @param wordPath docx文件路径
105 * @return 生成的带水印的pdf路径
106 */
107 public static String convertDocx2Pdf(String wordPath) {
108 OutputStream os = null;
109 InputStream is = null;
110 try {
111 is = new FileInputStream(new File(wordPath));
112 WordprocessingMLPackage mlPackage = WordprocessingMLPackage.load(is);
113 Mapper fontMapper = new IdentityPlusMapper();
114 fontMapper.put("隶书", PhysicalFonts.get("LiSu"));
115 fontMapper.put("宋体", PhysicalFonts.get("SimSun"));
116 fontMapper.put("微软雅黑", PhysicalFonts.get("Microsoft Yahei"));
117 fontMapper.put("黑体", PhysicalFonts.get("SimHei"));
118 fontMapper.put("楷体", PhysicalFonts.get("KaiTi"));
119 fontMapper.put("新宋体", PhysicalFonts.get("NSimSun"));
120 fontMapper.put("华文行楷", PhysicalFonts.get("STXingkai"));
121 fontMapper.put("华文仿宋", PhysicalFonts.get("STFangsong"));
122 fontMapper.put("宋体扩展", PhysicalFonts.get("simsun-extB"));
123 fontMapper.put("仿宋", PhysicalFonts.get("FangSong"));
124 fontMapper.put("仿宋_GB2312", PhysicalFonts.get("FangSong_GB2312"));
125 fontMapper.put("幼圆", PhysicalFonts.get("YouYuan"));
126 fontMapper.put("华文宋体", PhysicalFonts.get("STSong"));
127 fontMapper.put("华文中宋", PhysicalFonts.get("STZhongsong"));
128
129 mlPackage.setFontMapper(fontMapper);
130
131 //输出pdf文件路径和名称
132 String fileName = "pdfNoMark_" + System.currentTimeMillis() + ".pdf";
133 String pdfNoMarkPath = System.getProperty("java.io.tmpdir").replaceAll(separator + "$", "") + separator + fileName;
134
135 os = new java.io.FileOutputStream(pdfNoMarkPath);
136
137 //docx4j docx转pdf
138 FOSettings foSettings = Docx4J.createFOSettings();
139 foSettings.setWmlPackage(mlPackage);
140 Docx4J.toFO(foSettings, os, Docx4J.FLAG_EXPORT_PREFER_XSL);
141
142 is.close();//关闭输入流
143 os.close();//关闭输出流
144
145 //添加水印
146 return addTextMark(pdfNoMarkPath);
147 } catch (Exception e) {
148 e.printStackTrace();
149 try {
150 if(is != null){
151 is.close();
152 }
153 if(os != null){
154 os.close();
155 }
156 }catch (Exception ex){
157 ex.printStackTrace();
158 }
159 }finally {
160 File file = new File(wordPath);
161 if(file!=null&&file.isFile()&&file.exists()){
162 file.delete();
163 }
164 }
165 return "";
166 }
167
168 /**
169 * 添加水印图片
170 * @param inPdfPath 无水印pdf路径
171 * @return 生成的带水印的pdf路径
172 */
173 private static String addTextMark(String inPdfPath) {
174 PdfStamper stamp = null;
175 PdfReader reader = null;
176 try {
177 //输出pdf带水印文件路径和名称
178 String fileName = "pdfMark_" + System.currentTimeMillis() + ".pdf";
179 String outPdfMarkPath = System.getProperty("java.io.tmpdir").replaceAll(separator + "$", "") + separator + fileName;
180
181 //添加水印
182 reader = new PdfReader(inPdfPath, "PDF".getBytes());
183 stamp = new PdfStamper(reader, new FileOutputStream(new File(outPdfMarkPath)));
184 PdfContentByte under;
185 int pageSize = reader.getNumberOfPages();// 原pdf文件的总页数
186 //水印图片
187 ClassPathResource resource = new ClassPathResource("template" + File.separator + "downLoad" + File.separator + "mark.png");
188 File file = resource.getFile();
189 Image image = Image.getInstance(file.getPath());
190 for (int i = 1; i <= pageSize; i++) {
191 under = stamp.getUnderContent(i);// 水印在之前文本下
192 image.setAbsolutePosition(100, 210);//水印位置
193 under.addImage(image);
194 }
195 stamp.close();// 关闭
196 reader.close();//关闭
197
198 return outPdfMarkPath;
199 } catch (Exception e) {
200 e.printStackTrace();
201 try {
202 if (stamp != null) {
203 stamp.close();
204 }
205 if (reader != null) {
206 reader.close();//关闭
207 }
208 } catch (Exception ex) {
209 ex.printStackTrace();
210 }
211 } finally {
212 //删除生成的无水印pdf
213 File file = new File(inPdfPath);
214 if (file != null && file.exists() && file.isFile()) {
215 file.delete();
216 }
217 }
218 return "";
219 }
220
221 public static void main(String[] args) {
222 // createApplyPdf();
223 // convert();
224 // addTextMark("D:\\test\\test.pdf", "D:\\test\\test2.pdf");
225 }
226
227 }
package com.eazytec.zqtong.common.utils;
import freemarker.template.Configuration;
import freemarker.template.Template;
import java.io.*;
import java.util.Map;
/**
* 获取freemarker模板字符串
*/
public class FreeMarkUtils {
/**
* 获取模板字符串
* @param dataMap 参数
* @param templateName 模板名称
* @param temp_path 模板路径 classes下的路径 如果 classes/templates 传入 /templates即可
* @return
*/
public static String getFreemarkerContent(Map dataMap, String templateName, String temp_path) {
String result = "";
try {
//创建配置实例
Configuration configuration = new Configuration();
//设置编码
configuration.setDefaultEncoding("UTF-8");
configuration.setClassForTemplateLoading(FreeMarkUtils.class, temp_path);
//获取模板
Template template = configuration.getTemplate(templateName);
StringWriter swriter = new StringWriter();
//生成文件
template.process(dataMap, swriter);
result = swriter.toString();
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
/**
* 生成主数据模板xml
*
* @param dataMap 数据参数
* @param templateName 模板名称 eg: xxx.xml
* @param pathPrefix 模板路径 eg: /templates/
* @param filePath 生成路径 eg: d:/ex/ee/xxx.xml
*/
public static void createTemplateXml(Map dataMap, String templateName, String pathPrefix, String filePath) {
try {
//创建配置实例
Configuration configuration = new Configuration();
//设置编码
configuration.setDefaultEncoding("UTF-8");
//ftl模板文件统一放至 com.lun.template 包下面
// configuration.setDirectoryForTemplateLoading(new File("D:/idea_workspace/alarm/alarm/src/main/resources/template/"));
// configuration.setClassForTemplateLoading(FreemarkerWordUtils.class,"/template/doc");
configuration.setClassForTemplateLoading(FreeMarkUtils.class, pathPrefix);
//获取模板
Template template = configuration.getTemplate(templateName);
// System.out.println("filePath ==> " + filePath);
//输出文件
File outFile = new File(filePath);
// System.out.println("outFile.getParentFile() ==> " + outFile.getParentFile());
//如果输出目标文件夹不存在,则创建
if (!outFile.getParentFile().exists()) {
outFile.getParentFile().mkdirs();
}
//将模板和数据模型合并生成文件
Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile), "UTF-8"));
//生成文件
template.process(dataMap, out);
//关闭流
out.flush();
out.close();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获取模板字符串输入流
* @param dataMap 参数
* @param templateName 模板名称
* @param tempPath 模板路径 classes下的路径 如果 classes/templates 传入 /templates即可
* @return
*/
public static ByteArrayInputStream getFreemarkerContentInputStream(Map dataMap, String templateName, String tempPath) {
ByteArrayInputStream in = null;
try {
//创建配置实例
Configuration configuration = new Configuration();
//设置编码
configuration.setDefaultEncoding("UTF-8");
//ftl模板文件统一放至 com.lun.template 包下面
// configuration.setDirectoryForTemplateLoading(new File("D:/idea_workspace/alarm/alarm/src/main/resources/template/"));
configuration.setClassForTemplateLoading(FreeMarkUtils.class, tempPath);
//获取模板
Template template = configuration.getTemplate(templateName);
StringWriter swriter = new StringWriter();
//生成文件
template.process(dataMap, swriter);
String result = swriter.toString();
in = new ByteArrayInputStream(swriter.toString().getBytes());
} catch (Exception e) {
e.printStackTrace();
}
return in;
}
}
问题
生成文件下载的时候感觉稍微有点慢,其实文件不大,有个8、9秒的样子才会有下载框出现,不知道是不是前后台分离的原因。
来源:oschina
链接:https://my.oschina.net/u/4347242/blog/4535657