WKHtmltoPdf

2023-05-16

踩过的坑

请一定要使用下面的这种方式获取系统的可执行命令,否则会报一堆的找不到目录等错误!!!

 String osname = System.getProperty("os.name").toLowerCase();
 String cmd = osname.contains("windows") ? "where wkhtmltopdf" : "which wkhtmltopdf";
 p = Runtime.getRuntime().exec(cmd);

由于wkhtmltoPdf是基于操作系统层面的pdf转换,因此,程序想获得Html转换pdf就需要经历四次IO操作,如果pdf的大小大于3M时,就会变得缓慢,建议考虑使用itext5进行pdf转换。下面是itext4、itext5和wkhtmltoPdf之间的耗时对比;

 

原理

1、wkhtmltopdf是一个独立安装、通过命令行交互、开源免费的将html内容转为pdf或图片的工具,命令行交互意味着只要能够调用本地命令(cmd或shell等)的开发语言均可使用,比如Java。其本质是使用内置浏览器内核渲染目标网页,然后再将网页渲染结果转换为PDF文档或图片。wkhtmltopdf官网地址:wkhtmltopdf,选择合适的系统版本安装即可。

2、创建待转换的目标HTML页面,可用任何熟悉的技术栈,要注意的一点是尽量保存页面为静态,尽量减少动态效果、交互。wkhtmltopdf也可支持直接转换html文件,不过还是建议以url方式来转换,更简便。

3、 部署运行html web服务,切换到bin目录,运行命令行进行转换:

/wkhtmltopdf http://yourdomain/target.html SAVE_PATH/target.pdf

4、命令结构:wkhtmltopdf [GLOBAL OPTION]... [OBJECT]... <output file>

在命令行上可通过 wkhtmltopdf –H 来查看所有的配置说明。官网文档:https://wkhtmltopdf.org/usage/wkhtmltopdf.txt

JAVA调用

1、首先需要封装命令参数

private static String buildCmdParam(String srcAbsolutePath, String destAbsolutePath, Integer pageHeight, Integer pageWidth) {
        StringBuilder cmd = new StringBuilder();
        cmd.append(findExecutable()).append(space)
                .append("--margin-left").append(space)
                .append("0").append(space)
                .append("--margin-right").append(space)
                .append("0").append(space)
                .append("--margin-top").append(space)
                .append("0").append(space)
                .append("--margin-bottom").append(space)
                .append("0").append(space)
                .append("--page-height").append(space)
                .append(pageHeight).append(space)
                .append("--page-width").append(space)
                .append(pageWidth).append(space)

                .append(srcAbsolutePath).append(space)
//                .append("--footer-center").append(space)
//                .append("[page]").append(space)
//                .append("--footer-font-size").append(space)
//                .append("14").append(space)
//
//                .append("--disable-smart-shrinking").append(space)
//                .append("--load-media-error-handling").append(space)
//                .append("ignore").append(space)
//                .append("--load-error-handling").append(space)
//                .append("ignore").append(space)
//                .append("--footer-right").append(space)
//                .append("WanG提供技术支持").append(space)
                .append(destAbsolutePath);
        return cmd.toString();
    }
    /**
     * 获取当前系统的可执行命令
     *
     * @return
     */
    public static String findExecutable() {
        Process p;
        try {
            String osname = System.getProperty("os.name").toLowerCase();
            String cmd = osname.contains("windows") ? "where wkhtmltopdf" : "which wkhtmltopdf";
            p = Runtime.getRuntime().exec(cmd);
            new Thread(new ProcessStreamHandler(p.getErrorStream())).start();
            p.waitFor();
            return IOUtils.toString(p.getInputStream(), Charset.defaultCharset());
        } catch (IOException e) {
            log.info("根据当前系统返回 wkhtmltopdf 执行命令失败,IO异常:", e);
        } catch (InterruptedException e) {
            log.info("根据当前系统返回 wkhtmltopdf 执行命令失败,中断异常:", e);
        }
        return "";
    }

2、获取当前系统的命令参数

Process proc = Runtime.getRuntime().exec(finalCmd);

3、等待程序执行结果,并以ByteArrayOutputStream形式返回,最后在finally里面删除由于工具转换过程中生成的临时文件

private static ByteArrayOutputStream doProcess(String finalCmd, File htmlTempFile, File wkpdfDestTempFile) {
        InputStream is = null;
        try {
            Process proc = Runtime.getRuntime().exec(finalCmd);
            new Thread(new ProcessStreamHandler(proc.getInputStream())).start();
            new Thread(new ProcessStreamHandler(proc.getErrorStream())).start();

            proc.waitFor();
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            is = new FileInputStream(wkpdfDestTempFile);
            byte[] buf = new byte[1024];

            while (is.read(buf, 0, buf.length) != -1) {
                baos.write(buf, 0, buf.length);
            }

            return baos;
        } catch (IOException | InterruptedException e) {
            log.error("html转换pdf出错", e);
            throw new RuntimeException("html转换pdf出错了");
        } finally {
            if (htmlTempFile != null) {
                boolean delete = htmlTempFile.delete();
            }
            if (wkpdfDestTempFile != null) {
                boolean delete = wkpdfDestTempFile.delete();
            }
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

完整代码如下

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;

import java.io.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Random;

@Slf4j
public class WkHtmltoxPdf {
    //空格
    private static final String space = " ";

    //文件前缀
    private static final String PREFIX = "tempFile";

    //文件后缀-html
    private static final String SUFIX_HTML = ".html";
    //文件后缀pdf
    private static final String SUFIX_PDF = ".pdf";

    private static final Random RANDOM = new Random(100);

    private static String FILEDIR_PATH = "/Users/yangfan/tools/wkhtmltox";

    private static final Integer PAGE_HEIGHT = 60;

    private static final Integer PAGE_WIDTH = 100;

    public static void main(String[] args) {
        testWkPdf(getHtml(), PAGE_HEIGHT, PAGE_WIDTH);
    }

    public static void testWkPdf(String html, Integer pageHeight, Integer pageWidth) {
        byte[] bytes = html2pdf(html, pageHeight, pageWidth).toByteArray();
        storagePdf(bytes, FILEDIR_PATH, RANDOM.nextInt() + SUFIX_PDF);
    }

    /**
     * 存储pdf文件
     *
     * @param bfile    pdf字节流
     * @param filePath 文件路径
     * @param fileName 文件名称
     */
    public static void storagePdf(byte[] bfile, String filePath, String fileName) {
        BufferedOutputStream bos = null;
        FileOutputStream fos = null;
        File file = null;
        try {
            File dir = new File(filePath);
            if ((!dir.exists()) && (dir.isDirectory())) {
                boolean mkdirs = dir.mkdirs();
            }
            file = new File(filePath + "/" + fileName);
            fos = new FileOutputStream(file);
            bos = new BufferedOutputStream(fos);
            bos.write(bfile);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (bos != null) {
                try {
                    bos.close();
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
            }
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
            }
        }
    }

    /**
     * 将传入的页面转换成pdf,返回pdf字节数组
     * 默认自动随机生成文件名称
     *
     * @param html html页面信息
     * @return byte[] pdf字节流
     */
    public static ByteArrayOutputStream html2pdf(String html, Integer pageHeight, Integer pageWidth) {
        String fileName = System.currentTimeMillis() + RANDOM.nextInt() + "";
        String dest = FILEDIR_PATH;
        return doHtml2pdf(html, dest, pageHeight, pageWidth);
    }

    private static ByteArrayOutputStream doHtml2pdf(String html, String dest, Integer pageHeight, Integer pageWidth) {
        String wkhtmltopdf = findExecutable();
        //将内存中的html文件存储到一个临时地方
        File htmlTempFile = createFile(PREFIX, SUFIX_HTML, dest);
        FileUtil.writeString(html, htmlTempFile, CharsetUtil.UTF_8);

        //wk转换pdf之后的pdf存储文件地址
        File wkpdfDestTempFile = createFile(PREFIX, SUFIX_PDF, dest);

        if (StrUtil.isBlank(wkhtmltopdf)) {
            log.info("no wkhtmltopdf found!");
            throw new RuntimeException("html转换pdf出错了,未找到wkHtml工具");
        }

        String srcAbsolutePath = htmlTempFile.getAbsolutePath();
        String destAbsolutePath = wkpdfDestTempFile.getAbsolutePath();

        File parent = wkpdfDestTempFile.getParentFile();
        if (!parent.exists()) {
            boolean dirsCreation = parent.mkdirs();
            log.info("create dir for new file,{}", dirsCreation);
        }

        String finalCmd = buildCmdParam(srcAbsolutePath, destAbsolutePath, pageHeight, pageWidth);

        return doProcess(finalCmd, htmlTempFile, wkpdfDestTempFile);

    }

    /**
     * 执行wkHtmltox命令,读取生成的的pdf文件,输出执行结果,最后删除由于执行wk命令生成的零时的pdf文件和html文件
     *
     * @param finalCmd          cmd命令
     * @param htmlTempFile      html零时文件
     * @param wkpdfDestTempFile 生成的pdf文件
     * @return byte[] pdf字节流
     */
    private static ByteArrayOutputStream doProcess(String finalCmd, File htmlTempFile, File wkpdfDestTempFile) {
        InputStream is = null;
        try {
            Process proc = Runtime.getRuntime().exec(finalCmd);
            new Thread(new ProcessStreamHandler(proc.getInputStream())).start();
            new Thread(new ProcessStreamHandler(proc.getErrorStream())).start();

            proc.waitFor();
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            is = new FileInputStream(wkpdfDestTempFile);
            byte[] buf = new byte[1024];

            while (is.read(buf, 0, buf.length) != -1) {
                baos.write(buf, 0, buf.length);
            }

            return baos;
        } catch (IOException | InterruptedException e) {
            log.error("html转换pdf出错", e);
            throw new RuntimeException("html转换pdf出错了");
        } finally {
            if (htmlTempFile != null) {
                boolean delete = htmlTempFile.delete();
            }
            if (wkpdfDestTempFile != null) {
                boolean delete = wkpdfDestTempFile.delete();
            }
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static File createFile(String prefix, String sufix, String fileDirPath) {
        File file = null;
        File fileDir = new File(fileDirPath);
        try {
            file = File.createTempFile(prefix, sufix, fileDir);
        } catch (IOException e) {
            log.info("创建文件失败:", e.getCause());
        }
        return file;
    }

    private static String buildCmdParam(String srcAbsolutePath, String destAbsolutePath, Integer pageHeight, Integer pageWidth) {
        StringBuilder cmd = new StringBuilder();
        cmd.append(findExecutable()).append(space)
                .append("--margin-left").append(space)
                .append("0").append(space)
                .append("--margin-right").append(space)
                .append("0").append(space)
                .append("--margin-top").append(space)
                .append("0").append(space)
                .append("--margin-bottom").append(space)
                .append("0").append(space)
                .append("--page-height").append(space)
                .append(pageHeight).append(space)
                .append("--page-width").append(space)
                .append(pageWidth).append(space)

                .append(srcAbsolutePath).append(space)
//                .append("--footer-center").append(space)
//                .append("[page]").append(space)
//                .append("--footer-font-size").append(space)
//                .append("14").append(space)
//
//                .append("--disable-smart-shrinking").append(space)
//                .append("--load-media-error-handling").append(space)
//                .append("ignore").append(space)
//                .append("--load-error-handling").append(space)
//                .append("ignore").append(space)
//                .append("--footer-right").append(space)
//                .append("WanG提供技术支持").append(space)
                .append(destAbsolutePath);
        return cmd.toString();
    }

    /**
     * 获取当前系统的可执行命令
     *
     * @return
     */
    public static String findExecutable() {
        Process p;
        try {
            String osname = System.getProperty("os.name").toLowerCase();
            String cmd = osname.contains("windows") ? "where wkhtmltopdf" : "which wkhtmltopdf";
            p = Runtime.getRuntime().exec(cmd);
            new Thread(new ProcessStreamHandler(p.getErrorStream())).start();
            p.waitFor();
            return IOUtils.toString(p.getInputStream(), Charset.defaultCharset());
        } catch (IOException e) {
            log.info("根据当前系统返回 wkhtmltopdf 执行命令失败,IO异常:", e);
        } catch (InterruptedException e) {
            log.info("根据当前系统返回 wkhtmltopdf 执行命令失败,中断异常:", e);
        }
        return "";
    }

    private static class ProcessStreamHandler implements Runnable {
        private InputStream is;

        public ProcessStreamHandler(InputStream is) {
            this.is = is;
        }

        @Override
        public void run() {
            BufferedReader reader = null;
            try {
                InputStreamReader isr = new InputStreamReader(is, StandardCharsets.UTF_8);
                reader = new BufferedReader(isr);
                String line;
                while ((line = reader.readLine()) != null) {
                    log.debug("---++++++++++--->" + line);
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (reader != null) {
                    try {
                        reader.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

WKHtmltoPdf 的相关文章

随机推荐

  • c#日志组件Serilog使用

    NuGet 安装 Serilog 核心的包是 Serilog 和 Serilog AspNetCore 建议安装 Serilog AspNetCore xff0c 几乎包含了Serilog常用的所有包 异步写入 Serilog Sinks
  • Zynq-7000系列之linux开发学习笔记:PS和PL端的GPIO使用(三)

    开发板 xff1a Zynq7030数据采集板 PC平台 xff1a Ubuntu 18 04 43 MobaXterm 开发环境 xff1a Xilinx Vivado 43 SDK 18 3 学习目标 xff1a PS通过 EMIO A
  • 人工智能革命(上):通往超级智能之路

    导读 xff1a 本系列文章讲述了人工智能革命的爆发以及人类未来的出路 xff0c 由于篇幅较长分为上下两篇 xff0c 原英文载于神奇的网站 WaitButWhy com xff0c 作者Tim Urban还写过一篇有关脑机接口的文章 N
  • kubelet源码分析

    做个笔记记录k8s中赫赫有名的LIST amp WATCH 其实之前的文章中都有过watch的身影了 分别是informer篇和configCh篇这两篇其实都共用了client go包中的LIST amp WATCH方法 这篇内容详细讲一下
  • Li‘s 核磁共振影像数据处理-20-FSL数学工具fslmaths

    讲解视频内容请移步Bilibili xff1a https space bilibili com 542601735 入群讨论请加v hochzeitstorte 请注明 核磁共振学习 公众号 xff1a 美好事物中转站 FSLeyes F
  • CentOS7 防火墙(firewalld、iptables)-端口相关问题

    firewalld Centos7默认安装了firewalld xff0c 如果没有安装的话 xff0c 可以使用 yum install firewalld firewalld config进行安装 1 启动防火墙 systemctl s
  • centos 7.5 安装桌面环境及报错

    一 查看运行级别 xff0c 输入命令 xff1a runlevel 二 查看centos7 5系统中没有是否安装过桌面环境工具 xff0c 输入命令 xff1a yum grouplist more 注 xff1a 此命令还显示了系统安装
  • Seata(一) 服务配置以及启动

    文章目录 Seata 介绍Seata 简介Seata 演进历史Seata 设计理念Seata 的三大组件seata 实现的 2PC 与传统 2PC 的区别 Seata Server 安装Seata Server 下载Seata Server
  • 新手入门:PyCharm 的使用

    初次接触 pycharm 不要怕 xff0c 这篇文章帮你快速入门 xff0c 点击收藏不迷路 相关文章 xff1a Windows 10 同时安装 Python 2 和 Python 3 推荐一个视频 xff1a pycharm使用教程
  • EntityFrameworkCore 运行时数据迁移

    EntityFrameworkCore 以后简称EFCore 是 net core的一个orm框架 xff0c 以前在 net framework中使用时候利用code first可以在程序运行的时候自动迁移数据库 xff0c 更新数据库表
  • query.unwrap(SQLQuery.class).setResultTransformer弃用

    替换为 query unwrap span class token punctuation span NativeQueryImpl class span class token punctuation span setResultTran
  • pyinstaller报错AttributeError: type object ‘Callable‘ has no attribute ‘_abc_registry‘

    遇到问题 xff1a pyinstaller 打包文件时失败 xff0c 报错 xff1a AttributeError type object Callable has no attribute abc registry 解决方法 xff
  • Mysql数据库完全备份与恢复

    一 数据备份的重要性 在生产环境中 xff0c 数据的安全性是至关重要的 xff0c 任何数据的丢失都可能产生严重的后果 造成数据丢失的原因如下 程序错误人为错误计算机失败磁盘失败灾难 如火灾 地震 和盗窃 二 数据库备份的分类 1 从物理
  • [sourcetree] rebase的使用

    相关问题 最近写自己的辣鸡代码使用git时 xff0c 因为个人不大熟悉git又是个强迫症 xff0c 被来回不同的版本折腾来折腾去的 xff0c 十分不爽 xff0c 于是在此小结下sourcetree怎么使用变基 变基rebase这个操
  • mac下word有何无格式粘贴快捷键设置为默认方法

    补充说明 xff1a 后来使用发现可能是默认的快捷键 xff1a Command 43 V 粘贴无格式文本 xff1b control 43 V 粘贴格式文本 mac word 16 3之前的版本 xff0c 粘贴有Bug xff0c 只能
  • JVM内存管理

    一 物理内存与虚拟内存 xff1a 1 物理内存即RAM 随机存储器 2 寄存器 xff0c 用于存储计算单元指令 xff08 如浮点 xff0c 整数等运算 xff09 3 地址总线 xff1a 连接处理器和RAM 4 虚拟内存使得多个进
  • mongo

    mongo分页操作 与mysql数据库不同 xff0c mongo数据库是一种NoSQL数据库 xff0c 它的存储方式是以文档的形式进行存储的 本文主要记录了在开发过程中遇到的索引问题及数据库分页查询问题 常见的分页查询方式 1 使用li
  • RocketMQ本地环境搭建

    官网下载源码 建议到官网去下载 xff0c 本文使用的rocket的版本是基于4 7 1 xff0c 点击这里开始下载 导入idea rocketMQ是以maven的形式进行构建的 xff0c 因此直接使用ideaopen打开即可 xff0
  • 分布式事务-seata

    记录基于seata官网本地搭建seata的过程 下载seata软件报 Releases seata seata GitHub 将其解压缩 启动seata服务 sh seata server sh p 8091 h 127 0 0 1 m f
  • WKHtmltoPdf

    踩过的坑 请一定要使用下面的这种方式获取系统的可执行命令 xff0c 否则会报一堆的找不到目录等错误 xff01 xff01 xff01 String osname 61 System getProperty 34 os name 34 t