1. 程式人生 > >我是如何使用freemarker生成Word檔案的?

我是如何使用freemarker生成Word檔案的?

> 推薦:親身體驗,數次踩坑,遂撰寫此文,以備各位不時之需。 ## 背景 一天,產品經理遞給我了一份word報告,我定睛一看 ![類似這樣](https://img2020.cnblogs.com/other/1669068/202009/1669068-20200914134623599-2074305930.png) 這個文件有大大小小的標題層級,還有排版好的段落、各種一目瞭然的餅圖、走勢圖,當然還少不了顏色迴圈交替的報表。精緻程度不亞於小明同學的學習報告。 ![](https://img2020.cnblogs.com/other/1669068/202009/1669068-20200914134623855-1250050364.png) ## 準備 > 魯迅:身為一名Java程式設計師,任何時候都不要忘記站在巨人的肩膀上。 ![](https://img2020.cnblogs.com/other/1669068/202009/1669068-20200914134624202-1166251324.png) 通過某歌搜尋關鍵詞:**java+word+匯出**,我立馬得出了很多成熟的方案,通過橫向、縱向比較,再結合本次報告樣式比較多、使用者可靈活選擇不同模組匯出的特點,最終,我決定使用**Freemarker** 動態替換模版資料來匯出word文件。至於匯出文件的最終格式,有兩種選擇: ![](https://img2020.cnblogs.com/other/1669068/202009/1669068-20200914134624401-1932127823.png) 那到底使用**doc**還是**docx**格式的文件? 每當人生當中每次面臨選擇我都很慎重。最終我選擇使用**docx**格式(原因文末會講),但是為了讓大家有更多的選擇,滿足更多的業務場景,藉此機會,小明會給大家分別介紹使用**freemarker**匯出兩種格式的word文件方式。 ## 思路 > FreeMarker是一個基於Java的模板引擎,最初專注於使用MVC軟體架構生成動態網頁。但是,它是一個通用的模板引擎,不依賴於servlets或HTTP或HTML,因此它通常還用於生成原始碼,配置檔案或電子郵件。 此時,我們用它動態生成xml檔案,進而匯出word文件。 ![](https://img2020.cnblogs.com/other/1669068/202009/1669068-20200914134624590-1511877595.png) 整體流程如下: ![](https://img2020.cnblogs.com/other/1669068/202009/1669068-20200914134624733-683385260.png) ## 準備 * WPS > 由金山軟體股份有限公司釋出,用於辦公軟體最常用的文字編輯、表格、演示稿等功能。 對,就是這個國產的辦公軟體。我也是第一次發現在匯出文件這件事上,它如多年好友般友好。(word解析後的xml檔案閱讀性很強,一般人我不告訴他) * 開發工具(IDEA、Visual Studio Code等) 你喜歡的,順手的,就是最好的。 ## 實現 ### 整合Freemarker模版引擎 本次專案使用的框架依舊是Springboot,這個框架在整合各個元件表現都很便捷,不再贅述,這次整合Freemarker也不例外。 * 首先我們在專案中增添依賴`spring-boot-starter-freemarker` pom.xml檔案如下所示: ```xml org.springframework.boot
spring-boot-starter-freemarker
``` * 按照預設約定,我們可以在resources下建立一個templates資料夾(檢視FreeMarkerProperties原始碼可以發現預設目錄就是這個),用於存放模版文件。 ![](https://img2020.cnblogs.com/other/1669068/202009/1669068-20200914134624869-1126483333.png) * application.yml增加配置 ```yml spring: freemarker: template-loader-path: classpath:/templates cache: false # 開發環境快取關閉 suffix: xml charset: UTF-8 ``` ### 生成doc格式的文件 這裡先拿使用freemarker匯出doc格式的word文件舉例。 * 首先將docxTemplate.docx(調整好樣式的模版文件)另存為WORD 2003 XML文件(*.xml) ![](https://img2020.cnblogs.com/other/1669068/202009/1669068-20200914134625004-1121711697.png) 此處命名為docTemplete.xml,使用編輯工具首次開啟時,會發現這個文件裡面是壓縮的xml,因此我們首先需要格式化一下。 >
注意:如果你使用的是**Visual Studio Code**開發工具,一定要檢查你所使用的xml格式化外掛,是否會優化你的xml標籤 。比如:``會變成``。使用**Visual Studio Code**的同學,oh my god ! 小明在這裡推薦大家使用這個外掛:XML Language Support by Red Hat * 現在,我們就使用freemarker語法編輯docTemplete.xml,比如使用佔位符`${}`替換當前文件中的文字,以達到動態生成文字的目的,直接上程式碼。 ``` public static Configuration getConfiguration(){ //建立配置例項 Configuration configuration = new Configuration(Configuration.VERSION_2_3_28); //設定編碼 configuration.setDefaultEncoding("utf-8"); configuration.setClassForTemplateLoading(WordUtil.class, "/templates"); return configuration; } /** * 生成doc檔案 * * @param ftlFileName 模板ftl檔案的名稱 * @param params 動態傳入的資料引數 * @param outFilePath 生成的最終doc檔案的儲存完整路徑 */ public void ftlToDoc(String ftlFileName, Map params, String outFilePath) { try { /** 載入模板檔案 **/ Template template = configuration.getTemplate(ftlFileName); /** 指定輸出word檔案的路徑 **/ File docFile = new File(outFilePath); FileOutputStream fos = new FileOutputStream(docFile); Writer bufferedWriter = new BufferedWriter(new OutputStreamWriter(fos, "utf-8"), 10240); template.process(params, bufferedWriter); if (bufferedWriter != null) { bufferedWriter.close(); } } catch (TemplateException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } ``` ### 生成docx格式的文件 **高能預警!** 在成功使用Freemarker動態匯出doc格式的文件之後,相信大家和我的心情一樣非常激動。但以上操作只是一個小鋪墊,接下來我們來看看如何實現docx格式的文件匯出,小明相信一定會讓各位看官大跌眼鏡!不,大開眼界! 首先,告訴大家一個祕密:**docx格式的文件其實是一個ZIP格式的壓縮檔案哦!** 什麼?你不信?驗證如下: * windows的小夥伴 將docx文件修改為ZIP格式(修改.docx字尾名為.zip),然後通過解壓工具解壓。 * MacOS的小夥伴 直接使用`unzip`命令解壓word文件,解壓過後我們會發現該文件其實還有自己的**目錄結構**! ![震驚!](https://img2020.cnblogs.com/other/1669068/202009/1669068-20200914134625150-440795862.png) ![](https://img2020.cnblogs.com/other/1669068/202009/1669068-20200914134625328-169500934.png) 當然,這麼多檔案我們不必一一知悉,只需關注小明紅線標註的檔案和目錄即可: * document.xml檔案用於存放核心資料,文字,表格,圖片引用等 * media目錄用於存放所有文件的圖片 * _rels目錄下的document.xml.rels裡存放的是配置資訊,比如圖片引用關係,即在document.xml中引用id對應media中的哪個圖片。 * 獲取zip裡的document.xml文件以及_rels資料夾下的document.xml.rels文件 * 顯而易見,如果我們要想根據資料動態匯出不同的word文件,只需要:通過freemarker將本次資料填充到document.xml中,並將圖片配置資訊填充至document.xml.rels文件裡,再用檔案流把本次圖片寫入到media目錄下替換已經存在的圖片,最後把填充過內容的document.xml、document.xml.rels以及media用流的方式寫入zip即可輸出docx文件!上程式碼。 > 好吧,限於篇幅,程式碼見文末 Github地址 ## 問題及解決方案 當然,大家在第一次嘗試去幹某一件事時,都不一定是一蹴而就的。就比如在匯出word時,就可能會遇到以下問題。​ ### 特殊字元 **問題**:有些文字資料中難免含有特殊字元,如:`< > @ ! $ &` 等等。 **解決方案**:這些特殊字元如果不進行轉義,就會引起word打不開的現象,比如表格中的超連結的`&`符號,就需要替換為`&`,如果你的文件用office開啟時提示檔案損壞,九成是因為特殊符號引起的,我們可以開啟documet.xml定位報錯位置;當然還有終極方案,我們可以利用Freemarker的語法直接在模板中使用` ` 處理。比如: ```
``` ### 圖片變形 **問題**:因為echarts生成的圖表是響應式的,不同的螢幕大小、解析度,會造成每次前端傳過來的圖片寬高比例不一致,如果還直接將圖片按照之前的比例放進文件,會造成生成後文檔中的圖片變形。 **思路**:首先將文件中的圖片設定為原圖,然後鎖定寬高比,將圖片調整到合適大小,解壓文件從`document.xml`,得到此時word中該圖片寬高對應的值,如下所示: ![](https://img2020.cnblogs.com/other/1669068/202009/1669068-20200914134625539-1112976540.png) 要想保證不同畫素比例的寬高在文件中不變形,我們需要固定`cy`的值,然後根據固定比例動態求得當前畫素比例圖片在word中代表的寬`cx`的值。計算方法如下所示: 公式: ``` a/b = x/y ``` 其中,a表示圖片在word中寬的數值,b代表圖片在word中高的數值,x表示前端傳過來圖片的寬(單位:畫素),y表示前端傳過來圖片的高(單位:畫素)。因此,已知b、x、y,根據公式,我們即可求出a; ## 我就是文末 當然,還有用一些其他注意事項: * 如果word中的模組比較多的話,使用Freemarker語法要仔細一點; * 為什麼小明最終選擇匯出docx格式的文件呢?(還不是因為產品經理的需求嘛)因為doc格式的文件,小明嘗試匯出後,發現該文件並不是一個合法的doc文件,體現在:不能在手機上(微信、釘釘)正常預覽,office提示以xml形式開啟等。因此在匯出doc文件時,通過Freemaker填充document.xml後得到的並不是一個合法的word文件,查了相關資料,還需要藉助第三方工具進行簽名,而簽名還需要在windows系統下才能完成,但是我們平時用的生產環境都是Linux……因此,考慮再三,再三權衡,最終選擇匯出docx格式的文件。這種方式再適合不過,而且還能保證在當前主流APP上都能正常預覽。 * 敲黑板!匯出docx文件最重要的一個思想是將本次資料寫入覆蓋模版檔案(在商業中,相當於借殼上市),重新輸出一個zip格式壓縮的檔案,這個檔案就是我們最終想要的文件。 以上,就是小明word匯出的前前後後,如果你也曾經遇到過或者現在正好遇到word文件匯出開發的問題,歡迎一起討論交流。 ## 相關連結 我上傳了工具類,包含doc、docx 的匯出,以及匯出word文件時特殊符號轉義,還有圖片Base64轉換成檔案輸出的方法。 GitHub地址:https://github.com/WhenCoding/coder-xiaoming/blob/master/src/main/java/com/xm/coder/util/WordUtil.java > 本文可轉載,但需宣告原文出處。 程式設計師小明,一個很少加班的程式設計師。歡迎關注微信公眾號,獲取更多優質文章。