目录
(1)定义word文档模板
(2)模板二次处理
处理模板图片,不涉及图片可以跳过
处理模板内容
(3)java对word模板填值
(4)Notepad++的XML Tools插件安装
工作上要搞一个合同签署功能,小程序上登录人进入功能会对合同进行电子签名,然后后端根据登录人信息和合同word文档模板生成一个合同word文档并保存,踩了不少坑。一开始是用了apache的poi,使用简单,读取word模板,然后遍历每一个段落和节点,判断节点是不是我定义的参数名,是就替换文本值,本以为会很简单就能搞定,结果每次遍历获取的节点经常不完整,${name}获取的时候可能会被分成三段,【${】【name】【}】,导致参数替换不上去,想着直接获取所有文本信息,然后直接string的replace方法替换,结果也不行,他不给直接对段落进行修改,直接麻了,而且pio包引入后,如果当前springboot版本太低,有些包就会出问题,又得特别处理,贼麻烦,后来就换成了freemarker,支持替换图片,不过步骤有些繁琐。
(1)定义word文档模板
既然是对word文档进行填值,那肯定得先定义一个word文档模板了,建一个docx后缀的word文档,并设置好格式,参数等,比如我建了一个word文件名为:wordTemplate.docx,内容是这样的,参数名是用${}包起来
(2)模板二次处理
格式全部调好后保存,并把文件后缀名改成zip,让它变成压缩包,记住你的word模板一定要是docx后缀的,doc后缀是老版的,搞不了。
打开压缩包是这个样子。
进入word文件夹里面是这样的,我们只需要关注【document.xml】【_rels】【media】,如果你不打算放图片,那只关注【document.xml】就行了。
处理模板图片,不涉及图片可以跳过
打开_rels文件夹是这样的。
【document.xml.rels】文件就是存放图片和文档之间的关系,我们把它解压出来并打开。
【打开xml时没有格式化过的,我用了Notepad++打开的,并装了XML Tools插件,然后把它格式化后才成了这个样子的】具体插件安装在文章最后面讲。
接着讲,image1名称是word文档生成的,为了后面填值方便区别,把 media/image1.png 改成 media/headImage.png,然后保存,回到压缩包里面,把原来的【document.xml.rels】替换掉,再打开【media】文件夹,会发现里面有个图片,名为image1.png,就是我们提前放入的头像,我们得把它改名成【headImage.png】,因为上一步,我们修改了【document.xml.rels】文件的映射,这里也得改。
这时候再把压缩包后缀改成docx,你会发现一样还能打开,不过这里我们改回去打开检查没问题后,再把后缀改回成zip。
这里再说一个点,如果模板图片太多,势必会造成模板文件很大,我们可以找一个透明或纯白色的宽高都是1px的图片, 反正很小的图片就行,名字改成【media】里面的图片名,放入【media】里面,相当于占位。也可以一开始创建word模板的时候,放图片直接放这个小体积的图片,把宽高调整到合适的就行。
处理模板内容
打开压缩包把【document.xml】模板内容文件解压出来打开并xml格式化,找到你设置的参数名。
这时候会发现,参数名乱了,${name}可能被分隔七零八落,我们需要重新调一下,确保参数名完整,然后再保存。
这里再讲讲图片跟【document.xml】的关联,我们通过【document.xml.rels】图片映射文件可以看到,每一个图片标签都会有一个id,这个头像这个图片的id是rId4。
我们复制它去【document.xml】里面找,就会发现这个图片的标签以及格式了。
了解一下就行了,回归正题,我们调整好【document.xml】文件并保存,这个文件暂时不需要放回压缩包替换原来文件,到这一步,我们就有两个文件了,这两个文件缺一不可。
找一张图片替换模板里的头像。
(3)java对word模板填值
处理完word模板后,来到java代码。
先引入包。不推荐用最新的包,我引了最新的包,用main方法测试的时候没问题,结果启动spring服务的时候,就不行了,报找不到那个版本的参数,后来降级就可以了。
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.28</version>
</dependency>
代码大致思路是这样的:
- 读取模板【document.xml】
- 值填充
- 重新生成一个【document.xml】
- 把这个新的【document.xml】和头像图片写入定义好的压缩包模板,也就是【wordTemplate.zip】
- 然后把压缩包输出成word文档
写一个main方法测试。
public static void main (String[] args) {
// 模板存放路径
String templastPath = "D:/A";
// zip模板名称
String zipTemplastName = "wordTemplate.zip";
// zip模板存放路径
String zipTemplastPath = new StringBuffer(templastPath).append(File.separator).append(zipTemplastName).toString();
// docx文档模板名称
String docxTemplateName = "document.xml";
// 输出路径
String outPath = "D:/A/out";
// docx模板填充后输出路径,这里的【document.xml】不能改
String outputDocxTemplatePath = new StringBuffer(outPath).append(File.separator).append("document.xml").toString();
// 最终生成的docx文档输出路径,这里的word文档输出文件名随意
String outputDocxFilePath = new StringBuffer(outPath).append(File.separator).append("output.docx").toString();
File outPathFile = new File(outPath);
if (!outPathFile.exists() && !outPathFile.mkdirs()) {
throw new RuntimeException("输出路径创建失败");
}
try (
FileOutputStream out = new FileOutputStream(outputDocxTemplatePath);
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(out);
BufferedWriter bufferedWriter = new BufferedWriter(outputStreamWriter);
// zip模板压缩包输入流
FileInputStream zipTemplastInput = new FileInputStream(zipTemplastPath);
// 最终docx文档输出流
FileOutputStream finalDocxOutput = new FileOutputStream(outputDocxFilePath);
){
// 将要替换的值
Map<String, Object> map = new HashMap<String, Object>() {{
put("name", "韩西景");
put("sex", "男");
put("age", "51");
put("homeAddress", "广东省广州市白云区景山路3612号");
}};
//创建配置实例 VERSION_2_3_28是pom文件引入时的版本号
Configuration configuration = new Configuration(Configuration.VERSION_2_3_28);
//设置编码
configuration.setDefaultEncoding("UTF-8");
// 设置模板路径
configuration.setDirectoryForTemplateLoading(new File(templastPath));
// 获取xml模板
Template template = configuration.getTemplate(docxTemplateName);
// 参数值填充
template.process(map, bufferedWriter);
// 读取模板zip压缩包
ZipInputStream zipInputStream = ZipUtils.wrapZipInputStream(zipTemplastInput);
// 最终生成的word文档输出流
ZipOutputStream zipOutputStream = ZipUtils.wrapZipOutputStream(finalDocxOutput);
// zip压缩包要替换的项
Map<String, String> replaceItemMap = new HashMap<String, String>(){{
// 替换图片,本地路径可以,网络路径不行
// put("word/media/headImage.png", "D:/A/headImage.png");
// 替换图片,base64写入
put("word/media/headImage.png", new StringBuffer("data:image/png;base64,").append(imageToBase64("D:/A/headImage.png")).toString());
// 替换内容
put("word/document.xml", outputDocxTemplatePath);
}};
ZipUtils.replaceItem(zipInputStream, zipOutputStream, replaceItemMap);
} catch (Exception e) {
System.out.println("报错了,这里自己打log啥的,该处理的处理");
} finally {
// 删除填充后xml模板
new File(outputDocxTemplatePath).delete();
}
}
/**
* 图片转base64
* @param inputPath 图片路径
* @return base64字符串
*/
private static String imageToBase64(String inputPath) {
File file = new File(inputPath);
ByteBuffer byteBuffer = ByteBuffer.allocate((int) file.length());
try(
FileInputStream fileInputStream = new FileInputStream(inputPath);
BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
) {
int b;
byte[] bytes = new byte[4096];
while ((b = bufferedInputStream.read(bytes)) > -1) {
byteBuffer.put(bytes, 0, b);
}
return Base64.encodeBase64String(byteBuffer.array());
} catch (Exception e) {
System.out.println("报错了,这里自己打log啥的,该处理的处理");
}
return null;
}
执行之后,可以看到生成word文档了。
部署到服务器的话,就把模板文件放到服务器上面,然后配置模板路径,具体使用引入模板路径就行了。
至于word文档里面的表格,暂时没有研究。
(4)Notepad++的XML Tools插件安装
仅用于格式化模板xml文件,方便调整xml文件,可装可不装,问题不大。
插件安装:顶部菜单栏【插件】->【插件管理】打开后
好了,到这结束了,真够累人,这该死的996,永无止境的打工。
码字不易,于你有利,勿忘点赞