天天看點

Springboot 使用freemaker導出word文檔

利用freemarker導出word文檔,主要分為一下幾部分,但是循環寫入圖檔是其中最難的一點,尤其是從未使用freemaker導出word模闆的新手。話不多說,開搞。   1  找到需要導出的word模闆,我的模闆截圖如下,其中涉及到了表格,文字以及圖檔(模闆中隻放了一張圖檔,事實是不固定數量的)

Springboot 使用freemaker導出word文檔

2 将word文檔另存為xml檔案,這裡建議使用office來操作,wps沒有實踐過,網上都是推薦使用office來制作模闆,具體有興趣的可以單獨實踐。另存為xml檔案以後,使用其他工具打開xml檔案,并格式化格式。其中将需要實時填充的地方用參數代替,比如我下圖中 北韓戰争需要實時傳入,則用${title}代替,模闆中的所有文字參數地方都可以使用此方法,然後将.xml字尾修改為.ftl,并将ftl檔案放在項目指定位置。

Springboot 使用freemaker導出word文檔
Springboot 使用freemaker導出word文檔

3 模闆中含有圖檔以及圖檔數量不固定的情況,則需要以下步驟,     3.1  在模闆中  <w:body></w:body>标簽中 把事先放入占位的圖檔換為循環展示 的id要和一緻  入下圖。

Springboot 使用freemaker導出word文檔

      3.2 在 <pkg:xmlData>中定義圖檔id Relationships中循環定義圖檔id (注意圖檔id不能重複,重複會導緻寫入的圖檔無法顯示)    

Springboot 使用freemaker導出word文檔

      3.3  找到事先放入的圖檔的位元組流位置( pkg:binaryData中一大段字元串),替換為循環的base64,如上圖中。其中需要将圖檔 轉換成BASE64字元串,具體方法百度,很多工具類。             注意,以上三個地方圖檔id必須保持一緻否則可能出現填充的圖檔無法顯示的情況。   4 如果文字需要循環填充,則使用下圖中的list循環填充即可,if是判斷此參數是否存在的,一般來說添加不會出錯,不添加則需要保證傳入的參數一定要有此參數名稱,否則報錯  

Springboot 使用freemaker導出word文檔

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