之前写过freemarker生成pdf的文章,这次应需求的不同,来使用freemarker生成word文档,与生成pdf不同的是
生成PDF:生成pdf使用itextpdf配合freemarker生成的,原理是相当于把我们写好的html模板,原封不动的印到了上面
生成WORD:生成word更像是直接将文件的后缀换成了doc或docx,这就有一个问题,并不是所有的htem标签和css样式word都能兼容的,所以有些时候会发现html预览的样式和导出的文件并不是一样的比如我下面的例子
freemarker版本
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.28</version>
</dependency>
先上java代码,工具类
默认生成的是docx文件,读取的则是ftl(原因是ftl文件freemarker官方指定文件类型,在IDEA里面写相关代码会有高亮提示,使用html问题也不大)
@Slf4j
public class WordUtils {
/**
* 生成 word 文档方法
*
* @param data 要填充的数据
* @param templateName 模版名称(不需要代后缀)
* @param fileName 要输出的文件名称(不需要代后缀)
*/
public static void generateWord(Object data, String templateName, String fileName) {
// 设置FreeMarker的版本和编码格式日期格式
Configuration configuration = new Configuration(new Version("2.3.28"));
configuration.setDefaultEncoding("UTF-8");
configuration.setDateFormat("yyyy-MM-dd");
// 此处把模版文件都放在 resources 下的 templates 中
configuration.setClassForTemplateLoading(WordUtils.class, "/templates/");
// 创建一个Word文档的输出流
try (ServletOutputStream os = getHttpServletResponse(fileName).getOutputStream();
OutputStreamWriter osw = new OutputStreamWriter(os, StandardCharsets.UTF_8);
Writer bw = new BufferedWriter(osw)) {
// 设置FreeMarker生成Word文档所需要的模板
Template tem = configuration.getTemplate(templateName + ".ftl", "UTF-8");
// FreeMarker使用Word模板和数据生成Word文档
tem.process(data, bw);
} catch (Exception e) {
log.error("导出" + fileName + "失败! 【{}】:", e.getMessage());
throw new RuntimeException("导出" + fileName + "失败!");
}
}
/**
* 设置响应头信息
*
* @param fileName 输出文件名称
*/
private static HttpServletResponse getHttpServletResponse(String fileName) throws Exception {
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
HttpServletResponse response = servletRequestAttributes.getResponse();
response.setContentType("application/octet-stream");
response.setHeader("content-type", "application/octet-stream");
response.setContentType("application/json;charset=utf-8");
response.setCharacterEncoding("utf-8");
// 这里URLEncoder.encode可以防止中文乱码
fileName = URLEncoder.encode(fileName + ".docx", "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-disposition", "attachment;filename=" + fileName);
return response;
}
}
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<style>
@page {
@bottom-left {
content: element(footer);
vertical-align: top;
padding-top: 10px;
}
@top-right {
content: element(header);
vertical-align: bottom;
padding-bottom: 10px;
}
/*页面布局*/
margin-top: 1.5cm;
margin-left: 1.5cm;
margin-right: 1.5cm;
margin-bottom: 1.5cm;
size: A4 portrait;
}
/*默认字体*/
body {
font-family: 宋体;
font-size: 10.5pt;
}
div.header {
position: running(header);
font-size: 14px;
}
div.footer {
display: block;
margin-top: 0.5cm;
position: running(footer);
}
#pagenumber:before {
content: counter(page);
}
#pagecount:before {
content: counter(pages);
}
div.header {
position: running(header);
font-size: 14px;
}
div.footer {
display: block;
margin-top: 0.5cm;
position: running(footer);
}
.three {
font-family: 宋体;
font-size: 16pt;
}
.threeb {
font-family: 宋体;
font-size: 16pt;
font-weight: bold;
}
/*换页(在这一行之前分页 / 之后分页是page-break-after: always)*/
.next-page {
page-break-before: always
}
/*宋体四号字*/
.four {
font-family: 宋体;
font-size: 14pt;
}
/*宋体四号加粗字*/
.fourb {
font-family: 宋体;
font-size: 14pt;
font-weight: bold;
}
.five {
font-family: 宋体;
font-size: 10.5pt;
}
.fiveb {
font-family: 宋体;
font-size: 10.5pt;
font-weight: bold;
}
/*分页*/
.next-page {
page-break-before: always
}
/*左对齐*/
.right {
text-align: right;
}
/*右对齐*/
.left {
text-align: left;
}
/*首行缩进*/
.indent {
text-indent: 2em;
}
/*居中*/
.center {
text-align: center;
}
/*默认1.5倍行高*/
p {
line-height: 150%;
}
table {
width: 100%;
border-collapse: collapse;
margin-left: 0pt;
border: solid;
}
th {
text-align: center;
border: solid windowtext 1.0pt;
height: 26pt;
}
td {
text-align: center;
border: solid windowtext 1.0pt;
height: 26pt;
}
hr {
background-color: #000000;
border: dashed #000000 0.5px;
height: 1px;
}
</style>
</head>
<body>
<page>
<p><span class="threeb">领导干部个人基本信息</span></p>
<div style="width: 50%;float: left" disabled="inline-block">
<p class="indent"><span class="fiveb">姓 名 :</span></p>
<p class="indent"><span class="fiveb">民 族 :</span></p>
<p class="indent"><span class="fiveb">工作单位 :</span></p>
<p class="indent"><span class="fiveb">现任职务 :</span></p>
<p class="indent"><span class="fiveb">户籍地址 :</span></p>
</div>
<div disabled="inline-block">
<p class="indent"><span class="fiveb">性 别 :</span></p>
<p class="indent"><span class="fiveb">政治面貌 :</span></p>
<p class="indent"><span class="fiveb">现任部门 :</span></p>
<p class="indent"><span class="fiveb">身份证号码 :</span></p>
<p class="indent"><span class="fiveb">常住地址 :</span></p>
</div>
</page>
</body>
</html>
html预览样式
我们期望导出后也是这种样式
实际导出后的样式
可以发现分栏信息都跑到了下面,这个问题我尝试了N多种css样式,始终是无法实现,
有一种可以完美导出的方法,就是先写好word模板.然后把word模板转换成xml文件,在把xml文件的后缀改成ftl,其实原理也就是freemarker拼好参数后,改成了word后缀文件,但xml文件可读性太差,个人不推荐,如果数据量小还可以,尤其是涉及到循环展示项,就更需要慎用了,以上的问题,最终我选择把分栏信息做成了表格的样式,算是另辟蹊径了吧,如果有什么更好的实现方法,请告知