WKHtmltoPdf

news2025/2/25 18:17:36

踩过的坑

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

 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();
                    }
                }
            }
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/405510.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何运行vue项目(超详细图解)

&#x1f4d6;本篇超级详细案例截图教学 如何运行别人的vue项目&#xff0c;图片点击可放大仔细看 一、查看node.js版本 Vue环境配置教程 &#xff1a;https://blog.csdn.net/m0_57545353/article/details/124366678 配置完成后分别在cmd中执行node -v查看是否安装成功&…

js常用的加密/解密方法

1.前言(老司机直接跳过) 为什么js需要加密 谈到加密&#xff0c;大多数人应用场景都在于后端接口的加密签名校验。这种一般都用于服务端与服务端之间的相互调用&#xff0c;避免第三方使用你的接口做违法违规的事情&#xff0c;这种加密校验比较安全&#xff0c;因为没有暴露在…

React中使用Redux (一) - 在React中直接使用Redux

React中使用Redux 开始之前需要强调一下&#xff0c;redux和react没有直接的关系&#xff0c;你完全可以在React, Angular, Ember, jQuery, or vanilla JavaScript中使用Redux。 尽管这样说&#xff0c;redux依然是和React库结合的更好&#xff0c;因为他们是通过state函数来描…

Vue3中Vuex的使用

Vuex是做什么的&#xff1f; Vue官方&#xff1a;状态管理工具 状态管理是什么? 需要在多个组件中共享的状态、且是响应式的、一个变&#xff0c;全都改变。 例如一些全局要用的的状态信息&#xff1a;用户登录状态、用户名称、地理位置信息、购物车中商品、等等 这时候我…

el-input无法输入的问题和表单验证失败问题

1.el-input无法输入的问题原因1、el-input组件没有绑定双向响应式数据(v-model)解决方案:在data中定义一个变量&#xff0c;然后在el-input组件中使用v-model进行双向数据绑定&#xff0c;这样子就会解决el-input组件无法输入的问题了。原因2、组件嵌套太深还是该组件是一个坑(…

【node】升级 Node 版本教程

文章目录Window 系统Mac 或 Linux系统Window 系统 window系统升级node只能到node官网下载window安装包来覆盖之前的node。node 安装教程附下载地址&#xff1a;https://blog.csdn.net/qq_45677671/article/details/114535955因为 n 模块是不支持window系统&#xff1a; PS C:…

el-table表格动态合并行、合并行列及详解

&#x1f4dd; 个人简介 ⭐ 个人主页&#xff1a;我是段段&#x1f64b;‍ &#x1f34a; 博客领域&#xff1a;编程基础、前端&#x1f4bb; &#x1f345; 写作风格&#xff1a;干货&#xff01;干货&#xff01;都是干货&#xff01; &#x1f351; 精选专栏&#xff1a;Vue…

解决Vuex刷新页面数据丢失的问题

一&#xff1a;数据丢失的原因 vuex存储的数据只是在页面中&#xff0c;相当于全局变量&#xff0c;页面刷新的时候vuex里的数据会重新初始化&#xff0c;导致数据丢失。因为vuex里的数据是保存在运行内存中的&#xff0c;当页面刷新时&#xff0c;页面会重新加载vue实例&#…

Nuxt3项目搭建(Nuxt3+element-plus+scss详细步骤)

小聊&#xff1a; 本次记录一次使用Nuxt3搭建前端项目的过程&#xff0c;内容包含Nuxt3的安装&#xff0c;基于Vite脚手架&#xff08;默认&#xff09;构建的vue3项目&#xff0c;element-plus的安装配置&#xff08;可选&#xff09;&#xff0c;scss的安装&#xff08;可选&…

HTML的基本标签及属性

HTML1. 标题与段落标签2. 文本修饰标签3. 图片标签4. 链接标签5. 无序、有序列表与定义列表6. 表格、表单标签7. div与span标签1. 标题与段落标签 (1) 标题标签是一对双标签&#xff1a;<h1></h1>&#xff0c;<h2></h2>&#xff0c;<h3></h3&…

Layui表格可编辑 可动态新增一行 删除当前行

Layui 表格 可编辑&#xff1a;点击表格实现可编辑 cols: [[ //表头 {type: numbers, title: ID, width: 80, align: "center", sort: true} , {field: project, title: 项目, minWidth: 80, align: "center", edit: text} ]] 在表头的对象中增加 edit: …

前端必学的CSS3波浪效果演示

目录 文章目录 前言 CSS3波浪效果 1.Html构建 2.CSS编写 3.完整代码 index.html文件 style.css文件 总结 前言 随着前端技术的不断发展与进步&#xff0c;界面交互的样式要求和美感也越来越高&#xff0c;很多网页的交互都加上了css3动画,这里作者给大家分享一个前端开…

vue路由配置

1、路由的使用 一、安装路由 npm i vue-router 二、配置路由 在根目录下创建文件夹router&#xff0c;在router文件夹下创建index.js文件&#xff0c;如下图所示 在index.js文件中写入如下代码&#xff0c;实现创建一个路由器 import VueRouter from "vue-router&qu…

Object.defineproperty方法(详解)

Object.defineproperty 的作用就是直接在一个对象上定义一个新属性&#xff0c;或者修改一个已经存在的属性 Object.defineproperty可以接收三个参数 Object.defineproperty(obj, prop, desc) obj : 第一个参数就是要在哪个对象身上添加或者修改属性 prop : 第二个参数就是…

HTML网站导航栏的制作

一、导航条的制作 &#xff08;1&#xff09;代码图 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport…

photo-sphere-viewer中文文档

photo-sphere-viewer中文文档安装插件Configuration 配置项Standard options 常规配置container (required)adapter 适配器panorama (required)plugins 插件caption 标题size 全景图宽度高度markers 标注navbar 导航栏minFovmaxFovdefaultZoomLvlfisheyedefaultLongdefaultLatl…

vue+element-ui前端使用print-js实现打印,可自定义样式(横纵向,缩放比,字体大小,背景色)

vueelement-ui前端使用print-js实现打印下载依赖使用print-js实现打印功能需要打印的内容按钮调用打印函数打印函数设置默认打印横纵向&#xff08;IE不生效&#xff09;设置默认打印缩放比调整打印字体大小自定义字体大小生效自定义背景颜色生效参数print-js官网链接: https:/…

vue 3 项目实战一(绘制登录界面)

目录 一、概述 二、创建vue项目 三、需求分析 四、构建组件 五、vue组件之间的通信 一、概述 本文记录了项目实现的详细步骤以及原理&#xff0c;十分适合初学vue的萌新练手&#xff0c;也是阶段性学习的一个总结&#xff0c;可能会有些啰嗦&#xff0c;勿怪~。 先从登录…

PostMan——安装使用教程(图文详解)

为了验证接口能否被正常访问&#xff0c;我们常常需要使用测试工具&#xff0c;来对数据接口进行检测。 好处&#xff1a;接口测试工具能让我们在不写任何代码的情况下&#xff0c;对接口进行调用和调试。 下载并安装PostMan 首先&#xff0c;下载并安装PostMan&#xff0c;请…

若依管理系统RuoYi-Vue(前后端分离版)项目启动教程

RuoYi-Vue 是一个 Java EE 企业级快速开发平台&#xff0c;基于经典技术组合&#xff08;Spring Boot、Spring Security、MyBatis、Jwt、Vue&#xff09;&#xff0c;内置模块如&#xff1a;部门管理、角色用户、菜单及按钮授权、数据权限、系统参数、日志管理、代码生成等。在…