SpringBoot使用freemarker導出word文件方法詳解

1、前言

在項目中我們有時間需要根據一個word模板文檔,批量生成其他的word文檔,裡面的有些值改變一下而已,那怎麼做呢?

2、需求說明

假如說,現在我有個模板文檔,內容如下:

現在上面文檔裡面有如下變量:

  • username:員工姓名
  • idno:身份證號碼
  • hireDate:入職日期
  • work:職位
  • endDate:離職日期

現在我需要針對不同的員工一鍵生成一份離職證明出來,公司章是一張圖片,也需要生成進去,下面我們開始測試

3、編碼

基礎框架代碼地址:https://gitee.com/colinWu_java/spring-boot-base.git

我會在此主幹基礎上開發

3.1、導入依賴

<!--freemarker-->
<dependency>
    <groupId>org.freemarker</groupId>
    <artifactId>freemarker</artifactId>
    <version>2.3.30</version>
</dependency>

3.2、接口編寫

TestController新增下面接口:

/**
     * 根據ftl模板模板下載word文檔
     */
@GetMapping("/downloadWord")
public void downloadWord(HttpServletResponse response){
    Map<String, Object> map = new HashMap<>();
    map.put("username", "王天霸");//姓名
    map.put("idno", "429004199601521245");//身份證號
    map.put("hireDate", "2021年12月12日");//入職日期
    map.put("work", "Java軟件工程師");//職務
    map.put("endDate", "2022年11月25日");//離職日期
    map.put("imageData", Tools.getBase64ByPath("D:\\demo\\test.png"));//蓋章Base64數據(不包含頭信息)
    try {
        FreemarkerExportWordUtil.exportWord(response, map, "離職證明.doc", "EmploymentSeparationCertificate.ftl");
    } catch (IOException e) {
        e.printStackTrace();
    }
}

D:\demo\test.png這張圖片就是下面這張圖片:

3.3、工具類

Tools工具類:

package org.wujiangbo.utils;
import org.wujiangbo.domain.user.User;
import sun.misc.BASE64Encoder;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
/**
 * <p>工具類</p>
 */
public class Tools {
    /**
     * 獲得指定圖片文件的base64編碼數據
     * @param filePath 文件路徑
     * @return base64編碼數據
     */
    public static String getBase64ByPath(String filePath) {
        if(!hasLength(filePath)){
            return "";
        }
        File file = new File(filePath);
        if(!file.exists()) {
            return "";
        }
        InputStream in = null;
        byte[] data = null;
        try {
            in = new FileInputStream(file);
        } catch (FileNotFoundException e1) {
            e1.printStackTrace();
        }
        try {
            assert in != null;
            data = new byte[in.available()];
            in.read(data);
            in.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        BASE64Encoder encoder = new BASE64Encoder();
        return encoder.encode(data);
    }
    /**
     * @desc 判斷字符串是否有長度
     */
    public static boolean hasLength(String str) {
        return org.springframework.util.StringUtils.hasLength(str);
    }
}

FreemarkerExportWordUtil工具類:

package org.wujiangbo.utils;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.Version;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Map;
/**
 * 模板word導出,工具類
 */
public class FreemarkerExportWordUtil {
    private static Configuration configuration = null;
    static {
        configuration = new Configuration(new Version("2.3.0"));
        configuration.setDefaultEncoding("utf-8");
        //獲取模板路徑    setClassForTemplateLoading 這個方法默認路徑是webRoot 路徑下
        configuration.setClassForTemplateLoading(FreemarkerExportWordUtil.class, "/templates");
    }
    private FreemarkerExportWordUtil() {
        throw new AssertionError();
    }
    /**
     * 根據 /resources/templates 目錄下的ftl模板文件生成文件並寫到客戶端進行下載
     * @param response HttpServletResponse
     * @param map 數據集合
     * @param fileName 用戶下載到的文件名稱
     * @param ftlFileName ftl模板文件名稱
     * @throws IOException
     */
    public static void exportWord(HttpServletResponse response, Map map, String fileName, String ftlFileName) throws IOException {
        Template freemarkerTemplate = configuration.getTemplate(ftlFileName);
        // 調用工具類的createDoc方法生成Word文檔
        File file = createDoc(map, freemarkerTemplate);
        //將word文檔寫到前端
        download(file.getAbsolutePath(), response, fileName);
    }
    private static File createDoc(Map<?, ?> dataMap, Template template) {
        //臨時文件
        String name = "template.doc";
        File f = new File(name);
        Template t = template;
        try {
            // 這個地方不能使用FileWriter因為需要指定編碼類型否則生成的Word文檔會因為有無法識別的編碼而無法打開
            Writer w = new OutputStreamWriter(new FileOutputStream(f), "utf-8");
            t.process(dataMap, w);
            w.close();
        } catch (Exception ex) {
            ex.printStackTrace();
            throw new RuntimeException(ex);
        }
        return f;
    }
    //下載文件的公共方法
    public static void download(String filePath, HttpServletResponse response, String fileName) {
        try {
            setAttachmentResponseHeader(response, fileName);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        try(
                BufferedInputStream bis = new BufferedInputStream(new FileInputStream(filePath));
                // 輸出流
                BufferedOutputStream bos = new BufferedOutputStream(response.getOutputStream());
        ){
            byte[] buff = new byte[1024];
            int len = 0;
            while ((len = bis.read(buff)) > 0) {
                bos.write(buff, 0, len);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    /**
     * 下載文件名重新編碼
     * @param response 響應對象
     * @param realFileName 真實文件名
     * @return
     */
    public static void setAttachmentResponseHeader(HttpServletResponse response, String realFileName) throws UnsupportedEncodingException
    {
        String percentEncodedFileName = percentEncode(realFileName);
        StringBuilder contentDispositionValue = new StringBuilder();
        contentDispositionValue.append("attachment; filename=")
                .append(percentEncodedFileName)
                .append(";")
                .append("filename*=")
                .append("utf-8''")
                .append(percentEncodedFileName);
        response.addHeader("Access-Control-Allow-Origin", "*");
        response.addHeader("Access-Control-Expose-Headers", "Content-Disposition,download-filename");
        response.setHeader("Content-disposition", contentDispositionValue.toString());
        response.setHeader("download-filename", percentEncodedFileName);
    }
    /**
     * 百分號編碼工具方法
     *
     * @param s 需要百分號編碼的字符串
     * @return 百分號編碼後的字符串
     */
    public static String percentEncode(String s) throws UnsupportedEncodingException
    {
        String encode = URLEncoder.encode(s, StandardCharsets.UTF_8.toString());
        return encode.replaceAll("\\+", "%20");
    }
}

3.4、ftl文件

那麼如何制作出ftl模板文件呢?很簡單,跟著下面步驟做即可

1、首先新建一個demo.doc文檔,然後內容像下面這樣寫好:

2、將該word文檔導出成demo.xml文檔,如下:

然後:

這樣桌面就會多一個demo.xml文件,此時關閉demo.doc文件,打開demo.xml文件進行編輯

3、修改Demo.xml文件

在demo.xml文件中找到剛才所有的變量,然後都用 包 裹 一 下 , 如 : u s e r n a m e 改 成 {}包裹一下,如:username改成 包裹一下,如:username改成{username},idno改成${idno},等等,全部改完

最後搜索pkg:binaryData字符串,用這個包裹著很大一段字符串,那就是文檔中公章圖片的Base64格式數據,我們將其全部刪除,用${imageData}變量代替即可,待會從代碼中給這個變量賦值即可,就可以將真正的公章圖片替換到文檔中瞭,而且圖片大小格式什麼的,都會和模板文檔中保持一致,所以模板文檔的格式先調整後之後再另存為xml文件

修改完成之後,將文件的後綴名由xml改成ftl,然後拷貝到項目resources下的templates目錄下,我這裡改成:EmploymentSeparationCertificate.ftl瞭

3.5、測試

打開瀏覽器,訪問接口:http://localhost:8001/downloadWord,就可以下載一個word文檔瞭,打開後內容如下:

OK,到此測試成功瞭

4、word轉pdf

有些場景需要導出pdf,那麼就需要將生成的臨時word文件轉成pdf後再導出瞭,下面就介紹一下word轉成pdf的方法吧

首先需要導入依賴:

<dependency>
    <groupId>com.documents4j</groupId>
    <artifactId>documents4j-local</artifactId>
    <version>1.0.3</version>
</dependency>
<dependency>
    <groupId>com.documents4j</groupId>
    <artifactId>documents4j-transformer-msoffice-word</artifactId>
    <version>1.0.3</version>
</dependency>

工具類:

/**
     * word文檔轉成PDF文檔
     * @param wordPath word文檔路徑
     * @param pdfPath pdf文檔路徑
     */
public static void word2pdf(String wordPath, String pdfPath){
    File inputWord = new File(wordPath);
    File outputFile = new File(pdfPath);
    InputStream docxInputStream = null;
    OutputStream outputStream = null;
    IConverter converter = null;
    try  {
        docxInputStream = new FileInputStream(inputWord);
        outputStream = new FileOutputStream(outputFile);
        converter = LocalConverter.builder().build();
        converter.convert(docxInputStream).as(DocumentType.DOCX).to(outputStream).as(DocumentType.PDF).execute();
        outputStream.close();
    } catch (Exception e) {
        e.printStackTrace();
        log.error("word文檔轉成PDF文檔時,發生異常:{}", e.getLocalizedMessage());
    } finally {
        //關閉資源
        try {
            if(docxInputStream != null){
                docxInputStream.close();
            }
            if(outputStream != null){
                outputStream.close();
            }
            if(converter != null){
                converter.shutDown();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

需要註意的是:要使用這個工具類的話,運行這個代碼的機器上需要安裝WPS或者office軟件,否則轉換過程中會報錯(這可能是一個小瑕疵,我正在尋找其他更加優雅的轉換方案,有更好辦法的小夥伴,歡迎留言)

我們就調用這個工具類將剛才生成的word文檔轉成pdf,成功之後內容如下:

內容和word文檔一致,測試成功

5、總結

本文主要介紹瞭根據word模板如何導出word文件,已經將word文檔如何轉成pdf文件大傢趕緊練習起來吧

到此這篇關於SpringBoot使用freemarker導出word文件方法詳解的文章就介紹到這瞭,更多相關SpringBoot導出word內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: