利用freemarker导出word文档,主要分为一下几部分,但是循环写入图片是其中最难的一点,尤其是从未使用freemaker导出word模板的新手。话不多说,开搞。
1 找到需要导出的word模板,我的模板截图如下,其中涉及到了表格,文字以及图片(模板中只放了一张图片,事实是不固定数量的)
2 将word文档另存为xml文件,这里建议使用office来操作,wps没有实践过,网上都是推荐使用office来制作模板,具体有兴趣的可以单独实践。另存为xml文件以后,使用其他工具打开xml文件,并格式化格式。其中将需要实时填充的地方用参数代替,比如我下图中 朝鲜战争需要实时传入,则用${title}代替,模板中的所有文字参数地方都可以使用此方法,然后将.xml后缀修改为.ftl,并将ftl文件放在项目指定位置。
3 模板中含有图片以及图片数量不固定的情况,则需要以下步骤,
3.1 在模板中
<w:body></w:body>标签中 把事先放入占位的图片换为循环展示
的id要和一致 入下图。
3.2 在 <pkg:xmlData>中定义图片id Relationships中循环定义图片id (注意图片id不能重复,重复会导致写入的图片无法显示)
3.3
找到事先放入的图片的字节流位置( pkg:binaryData中一大段字符串),替换为循环的base64,如上图中。其中需要将图片
转换成BASE64字符串,具体方法百度,很多工具类。
注意,以上三个地方图片id必须保持一致否则可能出现填充的图片无法显示的情况。
4 如果文字需要循环填充,则使用下图中的list循环填充即可,if是判断此参数是否存在的,一般来说添加不会出错,不添加则需要保证传入的参数一定要有此参数名称,否则报错
5 编写代码,将需要填充的参数放入一个map中,使用工具类即可生成报告。
6 工具类如下
import freemarker.template.Configuration;
import freemarker.template.Template;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.time.DateFormatUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.zip.ZipOutputStream;
/**
* 导出
* @author
* @date
*/
@Component
public class ExportUtil {
private static Logger logger = LoggerFactory.getLogger(ExportUtil.class);
@Autowired
private Configuration freemarkerConfig;
/**
* 通过浏览器导出excel
*
* @param response
* @param freemarkerTemplateName
* @param map
*/
public void exportExcel(HttpServletResponse response, String freemarkerTemplateName, String excelName, HashMap<String, Object> map) {
Writer out = null;
try {
excelName = excelName.replaceAll(" ", "_");
response.reset(); // 清空输出流
response.setContentType("application/msexcel;charset=utf-8");
response.setHeader("Content-disposition", "attachment; filename=" + excelName + ".xls");// 设定输出文件头
out = response.getWriter();
Template template = freemarkerConfig.getTemplate(freemarkerTemplateName);
template.setEncoding("utf-8");
template.process(map, out);
if (out != null) {
out.flush();
out.close();
}
} catch (Exception e) {
logger.error("导出excel失败", e);
if (out != null) {
try {
out.close();
} catch (IOException ignore) {
}
}
}
}
/**
* 通过浏览器导出word
*
* @param response
* @param freemarkerTemplateName
* @param map
*/
public void exportWord(HttpServletResponse response, String freemarkerTemplateName, String wordName, HashMap<String, Object> map) {
Writer out = null;
try {
response.reset();// 清空输出流
response.setContentType("application/msword;charset=utf-8");
response.setHeader("Content-disposition", "attachment; filename=" + wordName + ".doc");// 设定输出文件头
out = response.getWriter();
Template template = freemarkerConfig.getTemplate(freemarkerTemplateName);
template.setEncoding("utf-8");
template.process(map, out);
if (out != null) {
out.flush();
out.close();
}
} catch (Exception e) {
logger.error("导出word失败", e);
if (out != null) {
try {
out.close();
} catch (IOException e1) {
logger.error("导出word失败", e1);
}
}
}
}
/**
* 导出到指定目录
* @param directory 文件存放的目录
* @param freemarkerTemplateName
* @param fileName 文件名带文件后缀
* @param map
*/
public void exportWord(String directory, String freemarkerTemplateName, String fileName, HashMap<String, Object> map) {
OutputStreamWriter out = null;
try {
File dir = new File(directory);
if(!dir.exists()){
if(!dir.mkdirs()){
logger.error("创建目录异常:{}",dir.toString());
return;
}
}
if(!dir.isDirectory()){
FileUtils.deleteQuietly(dir);
logger.error("目录错误");
return;
}
File file = new File(directory+File.separator+fileName);
out = new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8);
Template template = freemarkerConfig.getTemplate(freemarkerTemplateName);
template.setEncoding("utf-8");
template.process(map, out);
out.flush();
out.close();
} catch (Exception e) {
logger.error("导出word失败", e);
if (out != null) {
try {
out.close();
} catch (IOException e1) {
logger.error("导出word失败", e1);
}
}
}
}
/**
* 导出zip
* @param response
* @param files
* @param path
* @param exportName
*/
public void exportZip(HttpServletResponse response, List<File> files, String path, String exportName){
OutputStream out = null;
String zipPath = path + exportName + ".zip";
File zipFile = new File(zipPath);
try{
FileOutputStream fileOutputStream = new FileOutputStream(zipFile);
ZipOutputStream zipOutputStream = new ZipOutputStream(fileOutputStream);
ZipUtil.zip(files, zipOutputStream);
zipOutputStream.close();
fileOutputStream.close();
//删除文件
files.forEach(
file -> {
file.delete();
}
);
response.reset();
response.setContentType("application/x-zip-compressed;charset=utf-8");
response.setHeader("Content-disposition", "attachment; filename=" + exportName);
InputStream in = new FileInputStream(zipFile);
int len = 0;
byte[] buffer = new byte[1024];
out = response.getOutputStream();
while((len=in.read(buffer))>0){
out.write(buffer,0,len);
}
in.close();
out.flush();
out.close();
}catch (Exception e){
logger.error("导出zip失败", e);
if (out != null) {
try {
out.close();
} catch (IOException e1) {
logger.error("导出zip失败", e1);
}
}
}
if (zipFile.exists()) {
zipFile.delete();
}
}
public void exportZipPath(List<File> files, String path, String exportName){
OutputStream out = null;
String zipPath = path + exportName + ".zip";
File zipFile = new File(zipPath);
try{
FileOutputStream fileOutputStream = new FileOutputStream(zipFile);
ZipOutputStream zipOutputStream = new ZipOutputStream(fileOutputStream);
ZipUtil.zip(files, zipOutputStream);
zipOutputStream.close();
fileOutputStream.close();
//删除文件
files.forEach(
file -> {
file.delete();
}
);
}catch (Exception e){
logger.error("生成zip失败", e);
if (out != null) {
try {
out.close();
} catch (IOException e1) {
logger.error("导出zip失败", e1);
}
}
}
}
@SuppressWarnings("unused")
private String formatTime(Date date, String pattern) {
if (date == null) {
return "";
} else {
return DateFormatUtils.format(date, pattern);
}
}
/**
* 项目名称:pscms_hlj_web_base
*
* @param content
* @return 描述: 截取字符串,截取到最后一个句号,问号,或者感叹号,总字数不超过1000 创建人:yww 创建时间:2012-7-23
* 下午2:32:59 修改人:yww 修改时间:2012-7-23 下午2:32:59 修改备注:
* @version
*/
private String truncateContent(String content) {
int limit = 1000;
String truncatedContent = content;
if (content.endsWith("......")) {
List<Integer> posNum = new ArrayList<Integer>();
posNum.add(truncatedContent.lastIndexOf("。"));
posNum.add(truncatedContent.lastIndexOf("?"));
posNum.add(truncatedContent.lastIndexOf("!"));
posNum.add(truncatedContent.lastIndexOf("?"));
posNum.add(truncatedContent.lastIndexOf("!"));
Collections.sort(posNum);
int finalPos = posNum.get(posNum.size() - 1) + 1; // 因为截取的时候不包含标点符号出现的位置,所以要+1
if (finalPos != 0 && finalPos <= limit) { // 如果找到,那么截取到当前符号的位置
truncatedContent = truncatedContent.substring(0, finalPos);
}
}
if (content.length() > limit) { // 小与1000时直接返回
truncatedContent = content.substring(0, limit);
}
List<Integer> posNum = new ArrayList<Integer>();
posNum.add(truncatedContent.lastIndexOf("。"));
posNum.add(truncatedContent.lastIndexOf("?"));
posNum.add(truncatedContent.lastIndexOf("!"));
posNum.add(truncatedContent.lastIndexOf("."));
posNum.add(truncatedContent.lastIndexOf("?"));
posNum.add(truncatedContent.lastIndexOf("!"));
Collections.sort(posNum);
int finalPos = posNum.get(posNum.size() - 1) + 1; // 因为截取的时候不包含标点符号出现的位置,所以要+1
if (finalPos != 0 && finalPos <= limit) { // 如果找到,那么截取到当前符号的位置
truncatedContent = truncatedContent.substring(0, finalPos);
}
return truncatedContent;
}
}