文章目录
前言: 一共找了两个方案,第一个使用比较方便。
第一个方案比较方便,但是局限性也大,适合没用太大变化,只需要替换期中一些文字的模板。
第二个方案比较麻烦,需要编写freemarker模板。 详见 2.3.1 freemarker的参考。
** 可以使用for循环,对象等。。(直接看方案二,2.1和2.2)**
方案一 poi-tl
可以用office,也可用用wps
1.1 依赖
<!--poi-tl生成word-->
<dependency>
<groupId>com.deepoove</groupId>
<artifactId>poi-tl</artifactId>
<version>1.6.0</version>
</dependency>
1.2 使用
1.2.1 工具类 (先要有模版)
package com.mods.study.lessons;
import com.deepoove.poi.XWPFTemplate;
import org.apache.commons.lang3.time.DateFormatUtils;
import java.io.File;
import java.io.IOException;
import java.util.Date;
import java.util.Map;
public class DocUtils {
//map里传的是数据
public static void generateWord(Map<String, Object> map) throws IOException {
String mobanPath = "D:\\Users\\Administrator\\Desktop\\add.docx"
String outPath = "c:\\word-doc2\\" + DateFormatUtils.format(new Date(), "yyyyMMddHHmmssSS") + "-" + "一个word文档.docx";//文件输出地址,指定到文件
//.文件地址的目录 是否存在,不存在新建目录
File dest = new File(outPath);
// 检测是否存在目录
if (!dest.getParentFile().exists()) {
dest.getParentFile().mkdirs();// 新建文件夹
}
XWPFTemplate render = XWPFTemplate.compile(mobanPath).render(map);
//这个路径指定的是
render.writeToFile(outPath);
}
}
1.2.2 使用
package com.mods.study.controller;
import com.mods.common.result.Result;
import com.mods.study.lessons.DocUtils;
import io.swagger.annotations.Api;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/word-doc")
@Api(tags = "WriteWordController", description = "生成word文件")
public class WriteWordController {
@GetMapping
public Result write() {
try {
Map<String, Object> dataMap = new HashMap<>();
dataMap.put("title", "这是一个标题");
dataMap.put("author", "这是一个作者名称");
DocUtils.generateWord(dataMap);
} catch (IOException e) {
e.printStackTrace();
}
return new Result();
}
}
1.3 word模版的创建
新建一个word,正常写内容。需要java替换的地方, 用两层花括号包起来就好了
标题 : {
{
title}}
作者 : {
{
author}}
方案二 freemarker
注意:
需要使用freemarker语法制作模板。。 详见 2.3.1 freemarker的参考
2.1 依赖
注意:和mybatis升成用的可能是同一个东西,都是freemarker引擎。
<!--word写入,需要模版引擎-->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.30</version>
</dependency>
2.2 使用
- 要使用freemarker语法,模板后缀是 .ftl(生成的需要是doc格式)
2.2.1 工具类
注意生成doc,不要docx
package com.mods.common.utils;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.springframework.web.multipart.MultipartFile;
import sun.misc.BASE64Encoder;
import java.io.*;
import java.util.Date;
import java.util.Map;
/**
* @program: world
* @description: doc工具类
* @author: leaves
* @create: 2022-05-09 18:54
*/
public class DocUtils {
//生成word
public static void generateWord(Map<String, Object> map) throws IOException, TemplateException {
// ftl模板目录
String ftlUrl = "d:\\";
// ftl模板名称
String ftlName = "template.ftl";
String filePath = "c:\\word-doc3\\" + DateFormatUtils.format(new Date(), "yyyyMMddHHmmssSS") + "-" + "一个word文档.doc";//文件输出地址,指定到文件
//.文件地址的目录 是否存在,不存在新建目录
File dest = new File(filePath);
// 检测是否存在目录
if (!dest.getParentFile().exists()) {
dest.getParentFile().mkdirs();// 新建文件夹
}
Configuration configuration = new Configuration();
configuration.setDefaultEncoding("utf-8");
//方式一:指定模板所在文件夹 和 指定模板文件
configuration.setDirectoryForTemplateLoading(new File(ftlUrl));
Template template = configuration.getTemplate(ftlName, "utf-8");
//方式二,找项目内的,相对路径...建议方式一
//configuration.setClassForTemplateLoading(DocUtils.class, "/");//resource下
//Template template = configuration.getTemplate("templates/template.ftl");
//方式3 自己读文件成字符串,再创建模板template(需要绝对路径)
//String str = readFile(templatePath + templateName);
//Template template = new Template("name1", new StringReader(str), configuration);
Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(filePath), "utf-8"), 10240);
template.process(map, out);
out.close();
}
//图片转base64
public static String getImageBase64(MultipartFile file) {
InputStream in = null;
try {
in = file.getInputStream();
byte[] data = new byte[in.available()];
int read = in.read(data);
BASE64Encoder encoder = new BASE64Encoder();
return encoder.encode(data);
} catch (IOException e) {
System.out.println("加载图片未找到,错误原因:" + e);
e.printStackTrace();
} finally {
try {
if (in != null) {
in.close();
}
} catch (IOException ignored) {
}
}
return null;
}
//读取文件成字符串
private static String readFile(String filePath) {
String str = "";
File file = new File(filePath);
try {
FileInputStream in = new FileInputStream(file);
int size = in.available();
byte[] buffer = new byte[size];
int read = in.read(buffer);
in.close();
str = new String(buffer, StandardCharsets.UTF_8);
} catch (IOException ignore) {
}
return str;
}
}
2.2.2 使用
package com.mods.study.controller;
import com.mods.common.result.Result;
import com.mods.common.utils.DocUtils;
import freemarker.template.TemplateException;
import io.swagger.annotations.Api;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/word-doc")
@Api(tags = "WriteWordController", description = "生成word文件")
public class WriteWordController {
@GetMapping
public Result write() {
try {
Map<String, Object> dataMap = new HashMap<>();
dataMap.put("title", "这是一个标题");
dataMap.put("author", "这是一个作者名称");
//缩进部分是table里的内容。如果列也要变动,要两层list,双层循环
List<Map<String, Object>> tableData = new ArrayList<>();
Map<String, Object> line1 = new HashMap<>();
line1.put("cent1", "1行1列");
line1.put("cent2", "1行2列");
line1.put("cent3", "1行3列");
line1.put("cent4", "1行4列");
line1.put("cent5", "1行5列");
tableData.add(line1);
Map<String, Object> line2 = new HashMap<>();
line2.put("cent1", "2行1列");
line2.put("cent3", "2行3列");
line2.put("cent4", "2行4列");//特地没放满
tableData.add(line2);
dataMap.put("tableData", tableData);
//dataMap.put("picture",pictureBase64)//如果要放图片,需要放base64
DocUtils.generateWord(dataMap);
} catch (IOException | TemplateException e) {
e.printStackTrace();
}
return new Result();
}
}
2.3 模板编辑
2.3.1 freemarker 在线编辑
- 在线编辑测试网站 https://try.freemarker.apache.org/
测试步骤:填template模板->填model data -> 点Evaluate 出现result
-
freemarker指令解析 http://www.51gjie.com/javaweb/887.html
-
freemarker中文官方参考手册 http://freemarker.foofun.cn/ref_directive_ftl.html
2.3.2 freemarker 语法示例
- 示例模板:template
如果 是图片,则传base64(也是字符串,不过外边包的标签不一样)
这是一个字符串: ${word}
这是一个数组:
<#if list?? && (list?size > 0)>
list不为空,内容是
<#list list as item>
${item}<#sep>,</#sep>
</#list>
<#else>
list为空
</#if>
这是一个对象:
<#if obj??>
obj不为空
${obj.name}
${obj.age}
<#else>
obj为空
</#if>
- 参数:model data
word = "alice",
list = [1,2,3],
obj = {
"name":"obj名称","age":12}
- 效果:result
这是一个字符串: alice
这是一个数组:
list不为空,内容是
1
2
3
这是一个对象:
obj不为空
obj名称
12
2.3.3 ftl 模板的创建
注意小心使用格式化,别把freemarker的内容格式化没了
-
用office或者wps新建一个docx文档,在里边填充一些文字、表格、等内容(图表太难了,慢慢研究)
如下:
-
将word保存成xml文件(文件->另存为 -> 格式选(xml文档: word2003.xml,wps直接选xml))
-
vscode打开xml文档,里边内容是被压缩了的代码,格式化一下。(用编辑器打开即可,vscode开源,可以使用插件
xml
,可以格式化一下) -
找到需要替换的内容(ctrl+f查找然后替换即可),用freemarker语法替换处理即可。。之后尽量别格式化,不被识别的xml语法(freemarker)会被删掉
# 标题 -> <#if title??>${title}<#else>无标题</#if> # 作者名称 -> <#if author??>${author}<#else>匿名</#if> # 图片, 后边的一大坨 -> ${picture} (找这个标签就行:``<w:pict><w:binData w:name=`` ,后边一大串字符串,就是base64。最好在最外层判断一下,没有图片,整个标签都不要) # 表格, 需要在w:tr 外边套一个list循环(跟html标签很类似,就是前边多个w:),里边就是正常item(如果是对象,可以用点的形式,使用对象的值)
-
最后重命名成ftl格式。(不改好像也没事,文件名和后缀正确,能找到文件即可)
2.4 案例中的模板
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<?mso-application progid="Word.Document"?>
<w:wordDocument xmlns:w="http://schemas.microsoft.com/office/word/2003/wordml" xmlns:v="urn:schemas-microsoft-com:vml"
xmlns:w10="urn:schemas-microsoft-com:office:word"
xmlns:sl="http://schemas.microsoft.com/schemaLibrary/2003/core"
xmlns:aml="http://schemas.microsoft.com/aml/2001/core"
xmlns:wx="http://schemas.microsoft.com/office/word/2003/auxHint"
xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882"
w:macrosPresent="no" w:embeddedObjPresent="no" w:ocxPresent="no" xml:space="preserve"
xmlns:wpsCustomData="http://www.wps.cn/officeDocument/2013/wpsCustomData">
<o:DocumentProperties>
<o:Author>Administrator</o:Author>
<o:LastAuthor>Administrator</o:LastAuthor>
<o:Created>2022-05-12T01:35:00Z</o:Created>
<o:LastSaved>2022-05-12T02:15:50Z</o:LastSaved>
<o:TotalTime>14400</o:TotalTime>
<o:Pages>1</o:Pages>
<o:Words>41</o:Words>
<o:Characters>41</o:Characters>
<o:Lines>0</o:Lines>
<o:Paragraphs>0</o:Paragraphs>
<o:CharactersWithSpaces>41</o:CharactersWithSpaces>
<o:Version>14</o:Version>
</o:DocumentProperties>
<o:CustomDocumentProperties>
<o:KSOProductBuildVer dt:dt="string">2052-11.1.0.11636</o:KSOProductBuildVer>
<o:ICV dt:dt="string">AA494BCD86734597A35EAE4E6BC1D982</o:ICV>
</o:CustomDocumentProperties>
<w:fonts>
<w:defaultFonts w:ascii="Calibri" w:fareast="宋体" w:h-ansi="Calibri" w:cs="Times New Roman"/>
<w:font w:name="Times New Roman">
<w:panose-1 w:val="02020603050405020304"/>
<w:charset w:val="00"/>
<w:family w:val="Auto"/>
<w:pitch w:val="Default"/>
<w:sig w:usb-0="E0002EFF" w:usb-1="C000785B" w:usb-2="00000009" w:usb-3="00000000" w:csb-0="400001FF"
w:csb-1="FFFF0000"/>
</w:font>
<w:font w:name="宋体">
<w:panose-1 w:val="02010600030101010101"/>
<w:charset w:val="86"/>
<w:family w:val="Auto"/>
<w:pitch w:val="Default"/>
<w:sig w:usb-0="00000203" w:usb-1="288F0000" w:usb-2="00000006" w:usb-3="00000000" w:csb-0="00040001"
w:csb-1="00000000"/>
</w:font>
<w:font w:name="Wingdings">
<w:panose-1 w:val="05000000000000000000"/>
<w:charset w:val="02"/>
<w:family w:val="Auto"/>
<w:pitch w:val="Default"/>
<w:sig w:usb-0="00000000" w:usb-1="00000000" w:usb-2="00000000" w:usb-3="00000000" w:csb-0="80000000"
w:csb-1="00000000"/>
</w:font>
<w:font w:name="Arial">
<w:panose-1 w:val="020B0604020202020204"/>
<w:charset w:val="01"/>
<w:family w:val="SWiss"/>
<w:pitch w:val="Default"/>
<w:sig w:usb-0="E0002EFF" w:usb-1="C000785B" w:usb-2="00000009" w:usb-3="00000000" w:csb-0="400001FF"
w:csb-1="FFFF0000"/>
</w:font>
<w:font w:name="黑体">
<w:panose-1 w:val="02010609060101010101"/>
<w:charset w:val="86"/>
<w:family w:val="Auto"/>
<w:pitch w:val="Default"/>
<w:sig w:usb-0="800002BF" w:usb-1="38CF7CFA" w:usb-2="00000016" w:usb-3="00000000" w:csb-0="00040001"
w:csb-1="00000000"/>
</w:font>
<w:font w:name="Courier New">
<w:panose-1 w:val="02070309020205020404"/>
<w:charset w:val="01"/>
<w:family w:val="Modern"/>
<w:pitch w:val="Default"/>
<w:sig w:usb-0="E0002EFF" w:usb-1="C0007843" w:usb-2="00000009" w:usb-3="00000000" w:csb-0="400001FF"
w:csb-1="FFFF0000"/>
</w:font>
<w:font w:name="Symbol">
<w:panose-1 w:val="05050102010706020507"/>
<w:charset w:val="02"/>
<w:family w:val="Roman"/>
<w:pitch w:val="Default"/>
<w:sig w:usb-0="00000000" w:usb-1="00000000" w:usb-2="00000000" w:usb-3="00000000" w:csb-0="80000000"
w:csb-1="00000000"/>
</w:font>
<w:font w:name="Calibri">
<w:panose-1 w:val="020F0502020204030204"/>
<w:charset w:val="00"/>
<w:family w:val="SWiss"/>
<w:pitch w:val="Default"/>
<w:sig w:usb-0="E4002EFF" w:usb-1="C000247B" w:usb-2="00000009" w:usb-3="00000000" w:csb-0="200001FF"
w:csb-1="00000000"/>
</w:font>
</w:fonts>