后台生成单个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文档

posted on 2024-02-01 17:18  qianxi  阅读(46)  评论(0编辑  收藏  举报