使用wkhtmltopdf将网页转换成pdf文件+前台下载

  公司最近来了一个需求,需要将网页下载成pdf.在此期间,尝试了很多方法.

  有尝试在前台使用html2canvas.js+jsPdf.js生成.不过尝试之后,这种方案就被pass掉了,原因是:

    1.生成的pdf品质不高,不够清晰而且有样式丢失的情况.

    2.html2canvas.js原理是将网页"截图"(遍历dom将每一个标签画在canvas中)后,再由jspdf.js将其转化成pdf,而生成pdf需要使用canvas.toDataUrl()方法.浏览器对此长度是有限制的.我的页面生成的canvas大概是220000+高度.明显已经超长了.就算使用toBlob()也达不到要求.

  然后转换了思路,查阅了网上很多资料.尝试了用iText等方式进行转换.但是这种方式也不友好,对html页面要求比较严格.比如<br/>这样的标签是无法通过的.而且对中文支持也不太好,以及生成的pdf依旧会有样式丢失的情况存在.所以此方案也pass.

  最后尝试使用wkhtmltopdf这个软件来进行转化.

  1. 首先下载软件 官网:https://wkhtmltopdf.org/.

  2. 选择合适的版本下载后直接进行安装.并配置环境变量.                                                                                                                                         

3.wkhtmltopdf使用非常简单,windows的cmd窗口输入命令行wkhtmltopdf  z:\firefox\123.html z:\firefox\12345.pdf

这样就可以将z盘下的firefox文件夹下的123.html转换成12345.pdf文件了.(此命令可以加参数,网站上很多有参数详解的,我就不在赘述了.)

4. 也可以直接将网页链接直接进行转换: wkhtmltopdf  www.baidu.com z:\firefox\12345.pdf (注:wkhtmltopdf html文件或链接 生成的pdf名.中间都有空格)

5.在java中实现调用wkhtmltopdf.

//wkhtmltopdf在系统中的路径
    private static String toPdfTool = "Z:\\wkhtmltopdf\\bin\\wkhtmltopdf.exe";
/**
     * html转pdf
     * @param srcPath html路径,可以是硬盘上的路径,也可以是网络路径
     * @param destPath pdf保存路径
     * @return 转换成功返回true
     */
    public static boolean htmlToPdf(String srcPath, String destPath){
        File file = new File(destPath);
        File parent = file.getParentFile();
        //如果pdf保存路径不存在,则创建路径
        if(!parent.exists()){
            parent.mkdirs();
        }
        StringBuilder cmd = new StringBuilder();
        if(System.getProperty("os.name").indexOf("Windows") == -1){
        //linux 系统
       
        }
        cmd.append(toPdfTool);
        cmd.append(" ");
        //cmd.append(" --disable-javascript ");//页眉下面的线
        //cmd.append(" --header-center 这里是页眉这里是页眉这里是页眉这里是页眉 ");//页眉中间内容
        //cmd.append(" --margin-top 3cm ");//设置页面上边距 (default 10mm)
        //cmd.append(" --page-width 30cm ");//设置页面上边距 (default 10mm) 
        //cmd.append(" --header-html file:///"+WebUtil.getServletContext().getRealPath("")+FileUtil.convertSystemFilePath("\\style\\pdf\\head.html"));// (添加一个HTML页眉,后面是网址)
        //cmd.append(" --header-spacing 5 ");// (设置页眉和内容的距离,默认0)
        //cmd.append(" --footer-center (设置在中心位置的页脚内容)");//设置在中心位置的页脚内容
        //cmd.append(" --footer-html file:///"+WebUtil.getServletContext().getRealPath("")+FileUtil.convertSystemFilePath("\\style\\pdf\\foter.html"));// (添加一个HTML页脚,后面是网址)
        //cmd.append(" --footer-line");//* 显示一条线在页脚内容上)
        //cmd.append(" --footer-spacing 5 ");// (设置页脚和内容的距离)
        cmd.append(srcPath);
        cmd.append(" ");
        cmd.append(destPath);
        boolean result = true;
        try{
            Process proc = Runtime.getRuntime().exec(cmd.toString());
            HtmlToPdfInterceptor error = new HtmlToPdfInterceptor(proc.getErrorStream());
            HtmlToPdfInterceptor output = new HtmlToPdfInterceptor(proc.getInputStream());
            error.start();
            output.start();
            proc.waitFor();
        }catch(Exception e){
            result = false;
            e.printStackTrace();
        }
      return true;
    }

我们的项目中使用security安全框架,所以直接放链接会有问题.所以采用将网页保存后再进行转换.这样的话,调用是没有问题了.

6.还有问题就是我需要将网页保存下来.先想使用js模拟按键ctrl+s保存,转念一想是行不通的.因为这种保存只能保存在客户端上.本地跑项目没问题,但是之后到服务器上就有大问题了.所以 多思考一下还是有点用的,至少可以少做点事.哈哈.

   把网页保存下来也废了我不少功夫.从url流访问保存,到htmlunit爬取网页,方法都试了,可是因为项目的jsp使用异步ajax进行页面渲染.所以总是只能拿到初始的界面,htmlunit的等待js执行等方式都试过了,还是没有用.所以就采取了简单粗暴的办法.直接用js获取整个网页的代码.发送到后台.然后直接将string字符串生成一个html文件好了.(不知道会不会挨打)

7. 上述方式只有html代码,所以还需要将原网页的css文件拉下来先存到html读取的位置上去.后期能想到更好的办法再来替换吧,先出此下策了.

这样的话,就可以调用上面的方法进行html转换为pdf啦.

8.pdf生成之后,还需要给前台提供下载.这里又出现一个小问题.ajax方式里没有文件这种类型,所以不能使用ajax方式进行前后台交互.又是麻烦事.于是破罐子破摔.在html上加上一个input输入框.在页面渲染的方法的最后将整个网页的源码赋值给输入框.

var html=document.getElementsByTagName('html')[0].innerHTML; 
$("#input").val(html);
<form method="POST" style="float:right;" target='ifr' action="xxx.do">
 <input id="input" name="html" type="text" style=" display:none;"/>
 <button class="download-button" iconCls="icon-print" id="renderPdf" onclick="downloadPDF();">下载</button>
</form>
<iframe name='ifr' id="ifr" style='display: none;'></iframe>

 下面的iframe是为了实现form表单提交时,页面不刷新.网上也有相关方法解释.

9.这样,终于快要大功告成,要实现html转pdf并下载了.

    /**
     * 下载成pdf
     * @param request
     * @return
     */
    @RequestMapping(value="/xxx")
    @ResponseBody
    public Map<String, Object> downloadPdf(HttpServletRequest request,HttpServletResponse response){
        String html = request.getParameter("html");
        Map<String, Object> returnMap = new HashMap<String, Object>();
        String newDate = new Date().getTime()+"";
        String pdffileName = newDate + ".pdf";
        String htmlfileName = newDate + ".html";
        String pdfPath = PATH + pdffileName;
        String javaScriptStr = "<base href="+"\"http://localhost:8081/crs2/\""+">";
        html = html.replace(javaScriptStr, "");
        FileInputStream input = null;
        try {
            File file = new File(PATH + htmlfileName);
            if(!file.exists()){ 
                file.createNewFile(); 
             } 
            byte bytes[]=new byte[1024];
            bytes=html.getBytes(); 
            FileOutputStream fos=new FileOutputStream(file); 
            fos.write(bytes);
            fos.close();
            HtmlToPdf.convert(PATH + htmlfileName,pdfPath);
            //根据文件名获取 MIME 类型
            String contentType = request.getServletContext().getMimeType(pdfPath);
            FileDownloadUtil.download(request, response, contentType, pdffileName, pdfPath);
        } catch (Exception e) {
            e.printStackTrace();
        } finally{
            if(input != null){
                try {
                    input.close();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
        returnMap.put("success", true);
        
        return returnMap;
    }

运行到HtmlToPdf.convert(PATH + htmlfileName,pdfPath)时,pdf文件已经生成.FileDownLoadUtil类是自己编写的下载类.就不贴出来了.代码亲测可用.不过应该网页超长加上自己电脑配置不高的情况下,从点击下载按钮,到弹出文件下载框的时间是有点久的.

 学而不思则罔,想把自己学的东西记录下来.加油吧码农! 有大佬觉得哪里不合理的话,欢迎指点!!!

  • 0
    点赞
  • 1
    评论
  • 5
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页

打赏作者

xd_coder

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值