利用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;
}
}