公司里边有生成pdf报告的业务需求,之前有过尝试用tcpdf,直接生成的pdf的过程,但是pdf报告的内容数据,根据不同内容的变化,都是各种各样的bug,一直处理修修补补的状态,让后台开发人员很是头疼.
经过思索和甄选,总结出我们的业务中是由于样式不可控导致的,当时从逻辑上就思考到用html转pdf的思维,后边在搜索中发现wkhtmltopdf(一下简称wk)和其他几款软件及插件的比较,决定选用wk,是由于样式灵活可控,并且我们都没有js动态加载的数据,只是常规的数据渲染到html页面中,然后导出pdf,上手使用后,发现带来了很好的效果,
- 1.解决了以往pdf报告容量大的弊病
- 2.解决了出报告效率的问题.
- 3.提升开发效率
- 4.适用于windows和linux平台
我们的操作流程是:
web页面中发起请求---->生成一个经过数据渲染的静态页面--->调用wk生成pdf--->返回生成pdf报告地址--->为使用者提供下载入口
wk可以直接把任何一个可以在浏览器中浏览的网页直接转换成一个pdf,首先说明一下它不是一个php 类,而是一个把html页面转换成pdf的一个软件(需要安装在服务器上),但是它并不是一个简单的桌面软件,而且它直接cmd批处理的,使用php中的 shell_exec()函数就可以调用它。下面就介绍如何用php+js+html来让它生成pdf文件的方法(不过有个缺陷就是他需要在服务器端生成一个缓存文件,如果你使用thinkphp框架的话就可以将其缓存文件放在runtime 文件夹中暂存就行)。
- 下载地址:http://wkhtmltopdf.org/downloads.html
栗子:使用1
<?php
//转成pdf
$html=$_POST['html'];
//Turn on output buffering
ob_start();
$html='<link rel="stylesheet" href="css/common.css" rel="external nofollow" rel="external nofollow" >;
<link rel="stylesheet" href="css/myCenter.css" rel="external nofollow" rel="external nofollow" >;
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" >;'.$html;
//这儿可以引入生成的Html的样式表 路径可以是绝对路径也可以是相对路径,也可以把样式表文件复制到临时html文件的目录下 即这儿的demo文件目录下(默认) 也可以直接把样式写在html页面中直接传递过来
//$html = ob_get_contents();
//$html=$html1.$html;
$filename = "hld";
//save the html page in tmp folder 保存的html临时文件位置 可以是相对路径也是可以是绝对路径 下面用相对路径
file_put_contents("{$filename}.html", $html);
//Clean the output buffer and turn off output buffering
ob_end_clean();
//convert HTML to PDF
shell_exec("wkhtmltopdf -q {$filename}.html {$filename}.pdf");
if(file_exists("{$filename}.pdf")){header("Content-type:application/pdf");header("Content-Disposition:attachment;filename={$filename}.pdf");echo file_get_contents("{$filename}.pdf");//echo "{$filename}.pdf";
}else{exit;
}
>;<div>使用2:</div>
<div>
<pre>phpwkhtmltopdf类库的使用,</pre>
<pre><code>composer require mikehaertl/phpwkhtmltopdf</code></pre>
</div>
———————————分割线———————————
以下是其他朋友在客户端中运用的思路:
一年前产品提了一个让我们试着实现看看的需求,前端要支持用户将系统中java动态页面一键导出为pdf文件。当时项目组决定使用wkhtmltopdf这个第三方软件来实现。当时是由一位后端开发的同学来实现的,逻辑是前端点击按钮,后端生成动态文件,调用wkhtmltopdf生成pdf文件,存储在服务器中,并将文件地址返回给前端进行调用下载,并且要加上页眉页脚(页眉中包含客户商标)。逻辑图如下:
发送请求–>生成动态文件–>调用wkhtmltopdf生成pdf–>返回pdf地址–>下载pdf文件
总算是试着实现了,后来理所当然的三番五次接到各种生成pdf的需求了,wkhtmltopdf在系统中也开始渐渐“活跃”了起来,同时也bug不断。再后来, 由于人员变动,我承接了使用wkhtmltopdf的全部bug。
wkhtmltopdf相信用过的人大多数用过的人对其实又爱又恨。优缺点不多说了,用过的自然会明白。简单总结就是:功能强大,漏洞百出。
———————————分割线———————————
最近接到一个新需求,在动态页面中的过程变量(就是各种交互组件,涉及到产品保密不做多描述)需要支持用户设置宽高,其中图片有一种配置是上传后,如果配置了默认,则显根据图片原始大小显示。最近在对前端项目进行改造升级,毫不犹豫的使用了React作为输出渲染,功能实现的很愉快。
最终,从测试那里接到噩耗,生成pdf内容不全,甚至说没有内容。鉴于wkhtmltopdf的使用者较少,或者用来生成文件的目标页面几乎都比较简单,网上也没有太多的相关问题解答,于是本老中医根据病情列出了如下可能进行排查:
javascript脚本执行太慢导致部分dom元素无法加载
wkhtmltopdf版本太低
图片以及其他元素动态缩放引起的reflow导致页面加载迟缓
wkhtmltopdf不支持动态页面或者react
第一个猜想很快被排除了,除了调整–javascript-delay参数以外,各种参数调整都无效
第二个猜想也很快排除,wkhtmltopdf已经停止维护好久了(年中的时候本打算替换为phantomjs来实现生成pdf的,结果刚要决定就接到其核心成员退出项目停止维护的噩耗)
第三个测试比较费力气,单个搭建动态页面进行测试,排除每一种动态缩放的可能,但是最终还是一样
第四个测试简单粗暴,先删除了react以及相关组件的引用,居然成功了。注意这里只测试了react。于是当前晚上大张旗鼓,对前三天的功能进行重构,使用传统的javascript对图片过程变量进行了重构,好在轻车熟路,一晚上加上第二天一上午搞定了,编译+测试。。。。。。失败最后冥思苦想,突然想到把动态页面里面的资源拷贝出来,存储成html文件在本地调用生成pdf,成功了!。在请教了部门老大(公司大牛)后,决定使用静态化过程进行处理,方案如下:
发起请求
生成动态页面—>返回动态页面—>获取动态资源—>中间层服务存储为html—>将静态化url传递给后端—>生成pdf并下载
完美解决问题。不过为了赶项目,方案解决的优点仓促。有个弊端就是上行数据量太大,要修改中间层服务器的上传限制。完美的方案是应该把页面静态化和打印访问全部做在中间层中。当然这部分后来慢慢优化吧。
总结:
wkhtmltopdf对动态页面的支持效果一般,动态页面输出pdf的需求中最好不要有太复杂的javascript逻辑,尤其是一些比较大的视图框架(至少react是的)。加载的js文件最好不要太大。如果可以最好先静态化再输出。对于输出页面的样式,尤其是圆角等css3特性,能少则少,否则缺失个线条啥的都是家常便饭
参考
- https://blog.csdn.net/qq_20867249/article/details/84643252