推薦:親身體驗,數次踩坑,遂撰寫此文,以備各位不時之需。
一天,産品經理遞給我了一份word報告,我定睛一看

這個文檔有大大小小的标題層級,還有排版好的段落、各種一目了然的餅圖、走勢圖,當然還少不了顔色循環交替的報表。精緻程度不亞于小明同學的學習報告。
魯迅:身為一名Java程式員,任何時候都不要忘記站在巨人的肩膀上。
通過某歌搜尋關鍵詞:java+word+導出,我立馬得出了很多成熟的方案,通過橫向、縱向比較,再結合本次報告樣式比較多、使用者可靈活選擇不同子產品導出的特點,最終,我決定使用Freemarker 動态替換模版資料來導出word文檔。至于導出文檔的最終格式,有兩種選擇:
那到底使用doc還是docx格式的文檔?
每當人生當中每次面臨選擇我都很慎重。最終我選擇使用docx格式(原因文末會講),但是為了讓大家有更多的選擇,滿足更多的業務場景,借此機會,小明會給大家分别介紹使用freemarker導出兩種格式的word文檔方式。
FreeMarker是一個基于Java的模闆引擎,最初專注于使用MVC軟體架構生成動态網頁。但是,它是一個通用的模闆引擎,不依賴于servlets或HTTP或HTML,是以它通常還用于生成源代碼,配置檔案或電子郵件。
此時,我們用它動态生成xml檔案,進而導出word文檔。
整體流程如下:
WPS
由金山軟體股份有限公司釋出,用于辦公軟體最常用的文字編輯、表格、示範稿等功能。
對,就是這個國産的辦公軟體。我也是第一次發現在導出文檔這件事上,它如多年好友般友好。(word解析後的xml檔案閱讀性很強,一般人我不告訴他)
開發工具(IDEA、Visual Studio Code等)
你喜歡的,順手的,就是最好的。
本次項目使用的架構依舊是Springboot,這個架構在內建各個元件表現都很便捷,不再贅述,這次內建Freemarker也不例外。
首先我們在項目中增添依賴<code>spring-boot-starter-freemarker</code>
pom.xml檔案如下所示:
按照預設約定,我們可以在resources下建立一個templates檔案夾(檢視FreeMarkerProperties源碼可以發現預設目錄就是這個),用于存放模版文檔。
application.yml增加配置
這裡先拿使用freemarker導出doc格式的word文檔舉例。
首先将docxTemplate.docx(調整好樣式的模版文檔)另存為WORD 2003 XML文檔(*.xml)
此處命名為docTemplete.xml,使用編輯工具首次打開時,會發現這個文檔裡面是壓縮的xml,是以我們首先需要格式化一下。
注意:如果你使用的是Visual Studio Code開發工具,一定要檢查你所使用的xml格式化插件,是否會優化你的xml标簽 。比如:<code><w:rPr></code>會變成<code><rPr></code>。使用Visual Studio Code的同學,oh my god ! 小明在這裡推薦大家使用這個插件:XML Language Support by Red Hat
現在,我們就使用freemarker文法編輯docTemplete.xml,比如使用占位符<code>${}</code>替換目前文檔中的文本,以達到動态生成文本的目的,直接上代碼。
高能預警! 在成功使用Freemarker動态導出doc格式的文檔之後,相信大家和我的心情一樣非常激動。但以上操作隻是一個小鋪墊,接下來我們來看看如何實作docx格式的文檔導出,小明相信一定會讓各位看官大跌眼鏡!不,大開眼界!
首先,告訴大家一個秘密:docx格式的文檔其實是一個ZIP格式的壓縮檔案哦! 什麼?你不信?驗證如下:
windows的小夥伴
将docx文檔修改為ZIP格式(修改.docx字尾名為.zip),然後通過解壓工具解壓。
MacOS的小夥伴
直接使用<code>unzip</code>指令解壓word文檔,解壓過後我們會發現該文檔其實還有自己的目錄結構!
當然,這麼多檔案我們不必一一知悉,隻需關注小明紅線标注的檔案和目錄即可:
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時,就可能會遇到以下問題。
問題:有些文本資料中難免含有特殊字元,如:<code>< > @ ! $ &</code> 等等。
解決方案:這些特殊字元如果不進行轉義,就會引起word打不開的現象,比如表格中的超連結的<code>&</code>符号,就需要替換為<code>&amp;</code>,如果你的文檔用office打開時提示檔案損壞,九成是因為特殊符号引起的,我們可以打開documet.xml定位報錯位置;當然還有終極方案,我們可以利用Freemarker的文法直接在模闆中使用<code><![CDATA[ ]]></code> 處理。比如:
問題:因為echarts生成的圖表是響應式的,不同的螢幕大小、分辨率,會造成每次前端傳過來的圖檔寬高比例不一緻,如果還直接将圖檔按照之前的比例放進文檔,會造成生成後文檔中的圖檔變形。
思路:首先将文檔中的圖檔設定為原圖,然後鎖定寬高比,将圖檔調整到合适大小,解壓文檔從<code>document.xml</code>,得到此時word中該圖檔寬高對應的值,如下所示:
要想保證不同像素比例的寬高在文檔中不變形,我們需要固定<code>cy</code>的值,然後根據固定比例動态求得目前像素比例圖檔在word中代表的寬<code>cx</code>的值。計算方法如下所示:
公式:
其中,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
本文可轉載,但需聲明原文出處。 程式員小明,一個很少加班的程式員。歡迎關注微信公衆号,擷取更多優質文章。