後臺生成單個Word文檔

在實際項目開發中經常會遇到一種場景,客戶希望點擊頁面上的生成文件按鈕,執行程序動態填充數據到Word模板,直接在後臺生成Word文檔,而無需顯示Word文檔。目前網上有一些針對此需求的方案,但每個方案都存在很多各自的問題。

與其他方案對比

採用 Jacob 生成Word文檔方案動態生成的Word文檔,與PageOffice一樣,文檔格式是最完美的,因爲都是調用的Office原生接口生成的文件,所以生成的是原汁原味的Word文檔,但是Jacob侷限於 windows 平臺,往往許多 Java 程序運行於 Linux 等其他操作系統,而且Jacob的代碼複雜、運行穩定性差且不安全,因爲調用Office的自動化接口需要服務器端安裝Office軟件,服務器自動化調用有很大的風險,容易導致Office進程死鎖、頁面無響應、阻塞Web服務進程,從而影響整個網站的吞吐量,如果再考慮到多個用戶同時併發操作生成Word文檔的情況,Jacob程序對服務器端造成的壓力足以卡死整個網站,並且Web Server需配置交互賬戶權限。針對這個問題,微軟在MSDN上公開聲明Word、Excel不適宜運行在服務進程裏,因爲Word、Excel僅被設計爲桌面運行的程序。而PageOffice在客戶端運行,100%的標準託管代碼,服務器端不用安裝Office,也不用引入自動化類型庫,所以運行安全穩定,不必擔心Web服務崩潰的風險。另外服務器自動化調用的API接口複雜難用,參數傳遞繁瑣;而PageOffice提供了簡化的對象調用模型,所以調用代碼簡單,開發效率高,運行穩定可靠。服務器自動化編程對於定位Word、Excel要填充內容的位置和定位要讀取內容的位置比較困難,而PageOffice的簡易對象模型可以輕鬆定位,精確填充和讀取文檔內容。所以Jacob已經是一個十幾年前就淘汰的技術,僅限於個人研究,無法應用於實際項目。

再就是使用OpenXML SDK、POI、iText等開源庫的方案。OpenXML SDK是Microsoft提供的一組庫和工具,用於創建、修改和提取Office Open XML格式的文件,但是操作OpenXML SDK需要對Open XML文檔結構有一定的瞭解,因此在處理Word文檔時可能需要編寫大量的代碼,使用OpenXML SDK還需要學習Open XML標記語言和SDK的API,這可能需要一些時間和精力來掌握,並且由於 OpenXML SDK 是用 C# 編寫的,因此在 Java 中直接調用 OpenXML SDK 是很困難的。而使用 POI 生成文檔對服務器的壓力很大,而且它的 Excel 處理勉強可以, Word 模塊還侷限於讀取 Word 的文本內容,寫 Word 文件的功能就更弱;另一個致命的問題是,處理 doc 格式和處理 docx 格式的類幾乎完全不同,要分開針對不同的格式寫不同的代碼,這就意味着用戶上傳的 docx 格式文件如果使用了 doc 的擴展名,程序馬上崩潰。目前已知POI用來解析.doc、.xls那部分的組件是殘缺不全的並且也已經不再更新了。而且 POI 結構混亂,編碼比較複雜,開發過程非常消耗時間和精力。從性能上看,POI使用的xml處理對象本身就消耗內存,它要把整個文檔都加載到內存,加上其他開銷,比實際Word、Excel文檔還大,遇到打開較大的Word、Excel文檔時,JVM很容易內存溢出。而iText是一個用於處理PDF文件的Java庫,它是可以操作Word文檔,但是iText主要是用來處理PDF文檔的,不支持Excel,且對於Word支持很有限,iText 生成的 Word 文件在不同版本的 Word 軟件中可能會出現排版或樣式兼容性的問題。iText 也需要較大的學習曲線,特別是對於沒有使用過該庫的開發人員來說。總的來說,使用開源庫方案存在調用代碼複雜、中文亂碼,功能較弱等許多問題,最大的問題還是在生成文件的格式、樣式、排版等方面存在很大問題,由於這些方案不是直接調用原生Office接口操作文件內容的,所以即使已經有一個模板文檔做基礎了,但是生成的文檔中字體、樣式、段落等還是會出現錯亂的情況,尤其是處理頁眉頁腳、表格等內容時,出現的排版格式問題會更大。

所以針對這一系列的問題,PageOffice 開發了 FileMaker 組件,該組件完全符合 PageOffice 的架構設計,提供了最簡單的對象模型,沒有任何學習成本。FileMaker 在客戶端後臺填充數據到Office模板生成文檔並自動上傳到服務器,不會打開顯示生成的文檔。由於FileMaker是調用Office原生接口操作文檔內容的,所以生成的是原汁原味的Office文檔格式,而且由於FileMaker調用的是客戶端電腦上的Office,所以服務器上無需安裝Office軟件,也不要求服務器必須是Windows平臺,生成文檔的工作不會對服務器端造成任何壓力,更不存在併發問題,實現了完美生成文檔的目的,且避免了上述的所有問題。

FileMakerCtrl 和 PageOfficeCtrl 的區別
FileMakerCtrl 本質上就是一個沒有界面的 PageOfficeCtrl,也是調用客戶端 Office 程序處理文件的,FileMakerCtrl和PageOfficeCtrl都可以實現對文檔進行動態填充、動態轉 PDF 等功能,唯一的區別就是 FileMakerCtrl 在線打開填充和轉換文檔的時候,Web頁面不會打開顯示文檔內容,而 PageOfficeCtrl 會彈出窗口打開顯示文檔內容。

PageOffice的解決方案

下面就以生成一份榮譽證書的效果爲例,介紹一下如何使用FileMaker組件動態生成Word文檔。

  1. 需求效果:用戶點擊生成word文件按鈕,執行程序把某公司信息動態填充到榮譽證書模板中,生成一份榮譽證書文件。

  2. 榮譽證書模板如下圖所示,爲了簡單起見,模板中只使用了公司名稱來代表公司的所有信息,所以只用了一個數據區域“PO_company”來標記公司名稱的位置。
    image

  3. 點擊按鈕後,執行把公司信息動態填充到Word模板中生成榮譽證書的後臺代碼(比如:FileMakerSingle.jsp),在服務器端文件夾下生成一份榮譽證書文件:maker.doc,文件內容如下圖所示。
    image

後端代碼

  1. 在後端編寫代碼實現文檔動態填充(比如FileMakerSingle.jsp),關鍵代碼如下:
// 獲取id後可以根據id從數據庫中查詢公司信息,爲簡單起見,就不再演示
String id = request.getParameter("id"); 
FileMakerCtrl fmCtrl = new FileMakerCtrl(request);
WordDocument doc = new WordDocument();
//給數據區域賦值,即把數據填充到模板中相應的位置
doc.openDataRegion("PO_company").setValue("北京卓正志遠軟件有限公司");
fmCtrl.setSaveFilePage("SaveMaker.jsp");
fmCtrl.setWriter(doc);
fmCtrl.fillDocument("doc/template.doc", DocumentOpenType.Word);
  1. 保存文件:在SaveFilePage指向的地址接口中,創建FileSaver對象保存文件。
FileSaver fs = new FileSaver(request, response);
String fileName = "maker" + fs.getFileExtName();
fs.saveToFile(request.getSession().getServletContext().getRealPath("FileMakerSingle/doc") + "/" + fileName);
fs.close();

前端代碼

編寫前端網頁代碼,調用執行後端生成文件代碼,並實現生成文件進度條的效果。

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
    <title></title>
	<script type="text/javascript" src="../pageoffice.js"></script>
    <script type="text/javascript">
        function ConvertFile() {
			document.getElementById("Button1").disabled = true;

            CallFileMaker({
                url: "FileMakerSingle.jsp?id=1", //FileMakerSingle.jsp實現動態生成文件
                success: function () {
                    setProgress(100);
                },
                progress: function (pos) {
                    setProgress(pos);
                },
                error: function (msg) {
                    console.log("error occurred: "+msg);
                }
            });
        }

		function setProgress(percent) {
			var progressBar = document.getElementById("progressBar");
			progressBar.style.width = percent + '%';
			progressBar.innerText = percent + '%';
		}
    </script>
	<style>
		#progressBarContainer {
		  width: 500px;
		  background-color: #e0e0e0;
		  border-radius: 5px;
		  padding: 3px;
		  margin: 10px auto;
		}

		#progressBar {
		  height: 20px;
		  width: 0%;
		  background-color: #76b900;
		  border-radius: 5px;
		  text-align: center;
		  line-height: 20px; /* 使文字垂直居中 */
		  color: white;
		}
	</style>
</head>
<body>
    <div style="text-align: center;">
      <input id="Button1" type="button" value="生成Word文件" onclick="ConvertFile()"/>
      <div id="progressBarContainer">
        <div id="progressBar"></div>
      </div>
    </div>
</body>
</html>

參考鏈接:後臺生成單個Word文檔

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章