springboot整合freemaker实现复杂动态word生成
1.引入maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
2.application.yml添加配置
spring:
freemarker:
allow-request-override: false
cache: false
check-template-location: false
charset: UTF-8
content-type: text/html; charset=utf-8
expose-spring-macro-helpers: false
suffix: .ftl
3.初始doc模板另存为xml文件,注意变量分离问题
示例doc模板:
姓名 | 编码 | 密码 | 电话 |
---|---|---|---|
${user} | ${code} | ${password} | ${phone} |
调整前:
<w:tc>
<w:tcPr>
<w:tcW w:w="2074" w:type="dxa"/>
</w:tcPr>
<w:p w14:paraId="4B82BCE8" w14:textId="7FB604DC" w:rsidR="00352125"
w:rsidRDefault="005125BF">
<w:pPr>
<w:rPr>
<w:rFonts w:hint="eastAsia"/>
</w:rPr>
</w:pPr>
<w:r>
<w:rPr>
<w:rFonts w:hint="eastAsia"/>
</w:rPr>
<w:t>$</w:t>
</w:r>
<w:r>
<w:t>{user}</w:t>
</w:r>
</w:p>
</w:tc>
调整后:
<w:tc>
<w:tcPr>
<w:tcW w:w="2074" w:type="dxa"/>
</w:tcPr>
<w:p w14:paraId="4B82BCE8" w14:textId="7FB604DC" w:rsidR="00352125"
w:rsidRDefault="005125BF">
<w:pPr>
<w:rPr>
<w:rFonts w:hint="eastAsia"/>
</w:rPr>
</w:pPr>
<w:r>
<w:rPr>
<w:rFonts w:hint="eastAsia"/>
</w:rPr>
<w:t>${user}</w:t>
</w:r>
</w:p>
</w:tc>
4.xml文件重命名为ftl文件,resource下创建template文件夹,ftl文件放入文件夹下
完整ftl:
<w:body>
<w:p w14:paraId="5E326395" w14:textId="3914992A" w:rsidR="00411E60" w:rsidRDefault="00535F4C">
<w:pPr>
<w:pStyle w:val="4"/>
</w:pPr>
<w:r w:rsidRPr="008D36EA">
<w:rPr>
<w:rFonts w:ascii="仿宋" w:eastAsia="仿宋" w:hAnsi="仿宋" w:cs="仿宋"/>
<w:sz w:val="28"/>
<w:szCs w:val="28"/>
</w:rPr>
<w:t>其中</w:t>
</w:r>
<#list areaList as area>
<w:r>
<w:rPr>
<w:rFonts w:ascii="仿宋" w:eastAsia="仿宋" w:hAnsi="仿宋" w:cs="仿宋" w:hint="eastAsia"/>
<w:sz w:val="28"/>
<w:szCs w:val="28"/>
</w:rPr>
<w:t>,</w:t>
<w:t>${area.district}</w:t>
<w:t>${area.num}</w:t>
<w:t>家占比达到</w:t>
<w:t>${area.percent}</w:t>
</w:r>
</#list>
<w:r w:rsidRPr="008D36EA">
<w:rPr>
<w:rFonts w:ascii="仿宋" w:eastAsia="仿宋" w:hAnsi="仿宋" w:cs="仿宋"/>
<w:sz w:val="28"/>
<w:szCs w:val="28"/>
</w:rPr>
<w:t>。</w:t>
</w:r>
</w:p>
<#list userListAll as userMap>
<w:p w14:paraId="5E326395" w14:textId="3914992A" w:rsidR="00411E60" w:rsidRDefault="00535F4C">
<w:pPr>
<w:pStyle w:val="4"/>
</w:pPr>
<w:r>
<w:t>2.1.1.1.</w:t>
<w:t>${userMap_index+1}</w:t>
<w:t>${userMap.name}</w:t>
</w:r>
</w:p>
<w:tbl>
<w:tblPr>
<w:tblStyle w:val="a3"/>
<w:tblW w:w="0" w:type="auto"/>
<w:tblLook w:val="04A0" w:firstRow="1" w:lastRow="0" w:firstColumn="1" w:lastColumn="0"
w:noHBand="0" w:noVBand="1"/>
</w:tblPr>
<w:tblGrid>
<w:gridCol w:w="2074"/>
<w:gridCol w:w="2074"/>
<w:gridCol w:w="2074"/>
<w:gridCol w:w="2074"/>
</w:tblGrid>
<w:tr w:rsidR="00352125" w14:paraId="3EAB44FF" w14:textId="77777777" w:rsidTr="00352125">
<w:tc>
<w:tcPr>
<w:tcW w:w="2074" w:type="dxa"/>
</w:tcPr>
<w:p w14:paraId="7055CD47" w14:textId="076D8891" w:rsidR="00352125"
w:rsidRDefault="00352125">
<w:r>
<w:rPr>
<w:rFonts w:hint="eastAsia"/>
</w:rPr>
<w:t>姓名</w:t>
</w:r>
</w:p>
</w:tc>
<w:tc>
<w:tcPr>
<w:tcW w:w="2074" w:type="dxa"/>
</w:tcPr>
<w:p w14:paraId="26B106D2" w14:textId="101CCF30" w:rsidR="00352125"
w:rsidRDefault="00352125">
<w:r>
<w:rPr>
<w:rFonts w:hint="eastAsia"/>
</w:rPr>
<w:t>编码</w:t>
</w:r>
</w:p>
</w:tc>
<w:tc>
<w:tcPr>
<w:tcW w:w="2074" w:type="dxa"/>
</w:tcPr>
<w:p w14:paraId="705F7197" w14:textId="3C4513DE" w:rsidR="00352125"
w:rsidRDefault="00352125">
<w:r>
<w:rPr>
<w:rFonts w:hint="eastAsia"/>
</w:rPr>
<w:t>密码</w:t>
</w:r>
</w:p>
</w:tc>
<w:tc>
<w:tcPr>
<w:tcW w:w="2074" w:type="dxa"/>
</w:tcPr>
<w:p w14:paraId="18959CC1" w14:textId="411804AA" w:rsidR="00352125"
w:rsidRDefault="00352125">
<w:r>
<w:rPr>
<w:rFonts w:hint="eastAsia"/>
</w:rPr>
<w:t>电话</w:t>
</w:r>
</w:p>
</w:tc>
</w:tr>
<#list userMap.value as user>
<w:tr w:rsidR="00352125" w14:paraId="01EED409" w14:textId="77777777" w:rsidTr="00352125">
<w:tc>
<w:tcPr>
<w:tcW w:w="2074" w:type="dxa"/>
</w:tcPr>
<w:p w14:paraId="4B82BCE8" w14:textId="1C7883DA" w:rsidR="00352125"
w:rsidRDefault="00352125">
<w:r>
<w:rPr>
<w:rFonts w:hint="eastAsia"/>
</w:rPr>
<w:t>${user.name}</w:t>
</w:r>
</w:p>
</w:tc>
<w:tc>
<w:tcPr>
<w:tcW w:w="2074" w:type="dxa"/>
</w:tcPr>
<w:p w14:paraId="7195FB0D" w14:textId="1C3BF44D" w:rsidR="00352125"
w:rsidRDefault="00352125">
<w:r>
<w:rPr>
<w:rFonts w:hint="eastAsia"/>
</w:rPr>
<w:t>${user.code}</w:t>
</w:r>
</w:p>
</w:tc>
<w:tc>
<w:tcPr>
<w:tcW w:w="2074" w:type="dxa"/>
</w:tcPr>
<w:p w14:paraId="21CD1BB5" w14:textId="36069722" w:rsidR="00352125"
w:rsidRDefault="00352125">
<w:r>
<w:rPr>
<w:rFonts w:hint="eastAsia"/>
</w:rPr>
<w:t>${user.pwd}</w:t>
</w:r>
</w:p>
</w:tc>
<w:tc>
<w:tcPr>
<w:tcW w:w="2074" w:type="dxa"/>
</w:tcPr>
<w:p w14:paraId="54ACBD81" w14:textId="3B4D6346" w:rsidR="00352125"
w:rsidRDefault="00352125">
<w:r>
<w:rPr>
<w:rFonts w:hint="eastAsia"/>
</w:rPr>
<w:t>${user.phone}</w:t>
</w:r>
</w:p>
</w:tc>
</w:tr>
</#list>
</w:tbl>
</#list>
<w:p w14:paraId="6C395266" w14:textId="77777777" w:rsidR="001B2CFA" w:rsidRDefault="001B2CFA"/>
<w:sectPr w:rsidR="001B2CFA">
<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>
段落循环:
<#list areaList as area>
<w:r>
...
<w:t>,</w:t>
<w:t>${area.district}</w:t>
<w:t>${area.num}</w:t>
<w:t>家占比达到</w:t>
<w:t>${area.percent}</w:t>
</w:r>
</#list>
表格循环:
<#list userListAll as userMap>
<w:p w14:paraId="5E326395" w14:textId="3914992A" w:rsidR="00411E60" w:rsidRDefault="00535F4C">
<w:pPr>
<w:pStyle w:val="4"/>
</w:pPr>
<w:r>
<w:t>2.1.1.1.</w:t>
<w:t>${userMap_index+1}</w:t>
<w:t>${userMap.name}</w:t>
</w:r>
</w:p>
<w:tbl>
...
</w:tbl>
</#list>
行循环:
<#list userMap.value as user>
<w:tr w:rsidR="00352125" w14:paraId="01EED409" w14:textId="77777777" w:rsidTr="00352125">
<w:tc>
<w:tcPr>
<w:tcW w:w="2074" w:type="dxa"/>
</w:tcPr>
<w:p w14:paraId="4B82BCE8" w14:textId="1C7883DA" w:rsidR="00352125"
w:rsidRDefault="00352125">
<w:r>
<w:rPr>
<w:rFonts w:hint="eastAsia"/>
</w:rPr>
<w:t>${user.name}</w:t>
</w:r>
</w:p>
</w:tc>
....
</w:tr>
</#list>
5.controller测试
/**
* doc文档导出测试
* @return
* @throws IOException
*/
@PostMapping("/docTest")
@ResponseBody
@ApiOperation(value="导出doc", httpMethod = "POST",produces="application/json",notes = "导出doc测试")
public boolean exportDocTest() throws IOException {
List areaList = new ArrayList();
Map<String, Object> areaMap1 = new HashMap<>();
areaMap1.put("district","昆山");
areaMap1.put("num",4578);
areaMap1.put("percent","24.56%");
Map<String, Object> areaMap2 = new HashMap<>();
areaMap2.put("district","吴江");
areaMap2.put("num",3521);
areaMap2.put("percent","20.12%");
areaList.add(areaMap1);
areaList.add(areaMap2);
List userList1 = new ArrayList();
Map<String, Object> dataMap = new HashMap<>();
dataMap.put("name","wuzhigang");
dataMap.put("code","861225");
dataMap.put("pwd","root");
dataMap.put("phone","138********");
userList1.add(dataMap);
Map<String, Object> dataMap1 = new HashMap<>();
dataMap1.put("name","wuzhigang");
dataMap1.put("code","870124");
dataMap1.put("pwd","root");
dataMap1.put("phone","180********");
userList1.add(dataMap1);
List userList2 = new ArrayList();
Map<String, Object> dataMap2 = new HashMap<>();
dataMap2.put("name","Alex");
dataMap2.put("code","861225");
dataMap2.put("pwd","root");
dataMap2.put("phone","138********");
userList2.add(dataMap2);
Map<String, Object> dataMap3 = new HashMap<>();
dataMap3.put("name","Alex");
dataMap3.put("code","870124");
dataMap3.put("pwd","root");
dataMap3.put("phone","180********");
userList2.add(dataMap3);
Map<String, Object> userMap1 = new HashMap<>();
userMap1.put("name","user1");
userMap1.put("value",userList1);
Map<String, Object> userMap2 = new HashMap<>();
userMap2.put("name","user2");
userMap2.put("value",userList2);
List userListAll = new ArrayList();
userListAll.add(userMap1);
userListAll.add(userMap2);
Map<String, Object> map = new HashMap<>();
map.put("areaList",areaList);
map.put("userListAll",userListAll);
String templateName = "freemarkerTest2.ftl";
String targetPath = "/data/freemarkerTest2.doc";
boolean result = ExportDoc.EconomicReportExport(map,templateName,targetPath);
return result;
}
ExportDoc代码:
import freemarker.template.Configuration;
import freemarker.template.Template;
import java.io.*;
import java.util.Map;
/**
* @description: word文档导出测试
* @author: wzg
* @create: 2021-12-7
**/
public class ExportDoc {
/**
* word导出
* @param dataMap 数据
* @param templateName 模板名
* @param targetPath 导出地址
* @return
*/
public static boolean EconomicReportExport(Map dataMap, String templateName, String targetPath) throws IOException {
Boolean result = true;
Configuration configuration = new Configuration();
configuration.setDefaultEncoding("utf-8");
configuration.setClassForTemplateLoading(configuration.getClass(),"/template");
Writer out = null;
try {
Template template = configuration.getTemplate(templateName);
File outFile = new File(targetPath);
out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile),"UTF-8"));
template.process(dataMap,out);
out.flush();
} catch (Exception e) {
e.printStackTrace();
result = false;
}finally {
out.close();
}
return result;
}
}
6.doc文件效果
其中,昆山4,578家占比达到24.56%,吴江3,521家占比达到20.12%。
2.1.1.1.1user1
姓名 | 编码 | 密码 | 电话 |
---|---|---|---|
wuzhigang | 861225 | root | 138******** |
wuzhigang | 870124 | root | 180******** |
2.1.1.1.2user2
姓名 | 编码 | 密码 | 电话 |
---|---|---|---|
Alex | 861225 | root | 138******** |
Alex | 870124 | root | 180******** |