• WKHtmltoPdf


    踩过的坑

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

    1. String osname = System.getProperty("os.name").toLowerCase();
    2. String cmd = osname.contains("windows") ? "where wkhtmltopdf" : "which wkhtmltopdf";
    3. 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]...

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

    JAVA调用

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

    1. private static String buildCmdParam(String srcAbsolutePath, String destAbsolutePath, Integer pageHeight, Integer pageWidth) {
    2. StringBuilder cmd = new StringBuilder();
    3. cmd.append(findExecutable()).append(space)
    4. .append("--margin-left").append(space)
    5. .append("0").append(space)
    6. .append("--margin-right").append(space)
    7. .append("0").append(space)
    8. .append("--margin-top").append(space)
    9. .append("0").append(space)
    10. .append("--margin-bottom").append(space)
    11. .append("0").append(space)
    12. .append("--page-height").append(space)
    13. .append(pageHeight).append(space)
    14. .append("--page-width").append(space)
    15. .append(pageWidth).append(space)
    16. .append(srcAbsolutePath).append(space)
    17. // .append("--footer-center").append(space)
    18. // .append("[page]").append(space)
    19. // .append("--footer-font-size").append(space)
    20. // .append("14").append(space)
    21. //
    22. // .append("--disable-smart-shrinking").append(space)
    23. // .append("--load-media-error-handling").append(space)
    24. // .append("ignore").append(space)
    25. // .append("--load-error-handling").append(space)
    26. // .append("ignore").append(space)
    27. // .append("--footer-right").append(space)
    28. // .append("WanG提供技术支持").append(space)
    29. .append(destAbsolutePath);
    30. return cmd.toString();
    31. }
    32. /**
    33. * 获取当前系统的可执行命令
    34. *
    35. * @return
    36. */
    37. public static String findExecutable() {
    38. Process p;
    39. try {
    40. String osname = System.getProperty("os.name").toLowerCase();
    41. String cmd = osname.contains("windows") ? "where wkhtmltopdf" : "which wkhtmltopdf";
    42. p = Runtime.getRuntime().exec(cmd);
    43. new Thread(new ProcessStreamHandler(p.getErrorStream())).start();
    44. p.waitFor();
    45. return IOUtils.toString(p.getInputStream(), Charset.defaultCharset());
    46. } catch (IOException e) {
    47. log.info("根据当前系统返回 wkhtmltopdf 执行命令失败,IO异常:", e);
    48. } catch (InterruptedException e) {
    49. log.info("根据当前系统返回 wkhtmltopdf 执行命令失败,中断异常:", e);
    50. }
    51. return "";
    52. }

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

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

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

    1. private static ByteArrayOutputStream doProcess(String finalCmd, File htmlTempFile, File wkpdfDestTempFile) {
    2. InputStream is = null;
    3. try {
    4. Process proc = Runtime.getRuntime().exec(finalCmd);
    5. new Thread(new ProcessStreamHandler(proc.getInputStream())).start();
    6. new Thread(new ProcessStreamHandler(proc.getErrorStream())).start();
    7. proc.waitFor();
    8. ByteArrayOutputStream baos = new ByteArrayOutputStream();
    9. is = new FileInputStream(wkpdfDestTempFile);
    10. byte[] buf = new byte[1024];
    11. while (is.read(buf, 0, buf.length) != -1) {
    12. baos.write(buf, 0, buf.length);
    13. }
    14. return baos;
    15. } catch (IOException | InterruptedException e) {
    16. log.error("html转换pdf出错", e);
    17. throw new RuntimeException("html转换pdf出错了");
    18. } finally {
    19. if (htmlTempFile != null) {
    20. boolean delete = htmlTempFile.delete();
    21. }
    22. if (wkpdfDestTempFile != null) {
    23. boolean delete = wkpdfDestTempFile.delete();
    24. }
    25. if (is != null) {
    26. try {
    27. is.close();
    28. } catch (IOException e) {
    29. e.printStackTrace();
    30. }
    31. }
    32. }
    33. }

    完整代码如下

    1. import cn.hutool.core.io.FileUtil;
    2. import cn.hutool.core.util.CharsetUtil;
    3. import cn.hutool.core.util.StrUtil;
    4. import lombok.extern.slf4j.Slf4j;
    5. import org.apache.commons.io.IOUtils;
    6. import java.io.*;
    7. import java.nio.charset.Charset;
    8. import java.nio.charset.StandardCharsets;
    9. import java.util.Random;
    10. @Slf4j
    11. public class WkHtmltoxPdf {
    12. //空格
    13. private static final String space = " ";
    14. //文件前缀
    15. private static final String PREFIX = "tempFile";
    16. //文件后缀-html
    17. private static final String SUFIX_HTML = ".html";
    18. //文件后缀pdf
    19. private static final String SUFIX_PDF = ".pdf";
    20. private static final Random RANDOM = new Random(100);
    21. private static String FILEDIR_PATH = "/Users/yangfan/tools/wkhtmltox";
    22. private static final Integer PAGE_HEIGHT = 60;
    23. private static final Integer PAGE_WIDTH = 100;
    24. public static void main(String[] args) {
    25. testWkPdf(getHtml(), PAGE_HEIGHT, PAGE_WIDTH);
    26. }
    27. public static void testWkPdf(String html, Integer pageHeight, Integer pageWidth) {
    28. byte[] bytes = html2pdf(html, pageHeight, pageWidth).toByteArray();
    29. storagePdf(bytes, FILEDIR_PATH, RANDOM.nextInt() + SUFIX_PDF);
    30. }
    31. /**
    32. * 存储pdf文件
    33. *
    34. * @param bfile pdf字节流
    35. * @param filePath 文件路径
    36. * @param fileName 文件名称
    37. */
    38. public static void storagePdf(byte[] bfile, String filePath, String fileName) {
    39. BufferedOutputStream bos = null;
    40. FileOutputStream fos = null;
    41. File file = null;
    42. try {
    43. File dir = new File(filePath);
    44. if ((!dir.exists()) && (dir.isDirectory())) {
    45. boolean mkdirs = dir.mkdirs();
    46. }
    47. file = new File(filePath + "/" + fileName);
    48. fos = new FileOutputStream(file);
    49. bos = new BufferedOutputStream(fos);
    50. bos.write(bfile);
    51. } catch (Exception e) {
    52. e.printStackTrace();
    53. } finally {
    54. if (bos != null) {
    55. try {
    56. bos.close();
    57. } catch (IOException e1) {
    58. e1.printStackTrace();
    59. }
    60. }
    61. if (fos != null) {
    62. try {
    63. fos.close();
    64. } catch (IOException e1) {
    65. e1.printStackTrace();
    66. }
    67. }
    68. }
    69. }
    70. /**
    71. * 将传入的页面转换成pdf,返回pdf字节数组
    72. * 默认自动随机生成文件名称
    73. *
    74. * @param html html页面信息
    75. * @return byte[] pdf字节流
    76. */
    77. public static ByteArrayOutputStream html2pdf(String html, Integer pageHeight, Integer pageWidth) {
    78. String fileName = System.currentTimeMillis() + RANDOM.nextInt() + "";
    79. String dest = FILEDIR_PATH;
    80. return doHtml2pdf(html, dest, pageHeight, pageWidth);
    81. }
    82. private static ByteArrayOutputStream doHtml2pdf(String html, String dest, Integer pageHeight, Integer pageWidth) {
    83. String wkhtmltopdf = findExecutable();
    84. //将内存中的html文件存储到一个临时地方
    85. File htmlTempFile = createFile(PREFIX, SUFIX_HTML, dest);
    86. FileUtil.writeString(html, htmlTempFile, CharsetUtil.UTF_8);
    87. //wk转换pdf之后的pdf存储文件地址
    88. File wkpdfDestTempFile = createFile(PREFIX, SUFIX_PDF, dest);
    89. if (StrUtil.isBlank(wkhtmltopdf)) {
    90. log.info("no wkhtmltopdf found!");
    91. throw new RuntimeException("html转换pdf出错了,未找到wkHtml工具");
    92. }
    93. String srcAbsolutePath = htmlTempFile.getAbsolutePath();
    94. String destAbsolutePath = wkpdfDestTempFile.getAbsolutePath();
    95. File parent = wkpdfDestTempFile.getParentFile();
    96. if (!parent.exists()) {
    97. boolean dirsCreation = parent.mkdirs();
    98. log.info("create dir for new file,{}", dirsCreation);
    99. }
    100. String finalCmd = buildCmdParam(srcAbsolutePath, destAbsolutePath, pageHeight, pageWidth);
    101. return doProcess(finalCmd, htmlTempFile, wkpdfDestTempFile);
    102. }
    103. /**
    104. * 执行wkHtmltox命令,读取生成的的pdf文件,输出执行结果,最后删除由于执行wk命令生成的零时的pdf文件和html文件
    105. *
    106. * @param finalCmd cmd命令
    107. * @param htmlTempFile html零时文件
    108. * @param wkpdfDestTempFile 生成的pdf文件
    109. * @return byte[] pdf字节流
    110. */
    111. private static ByteArrayOutputStream doProcess(String finalCmd, File htmlTempFile, File wkpdfDestTempFile) {
    112. InputStream is = null;
    113. try {
    114. Process proc = Runtime.getRuntime().exec(finalCmd);
    115. new Thread(new ProcessStreamHandler(proc.getInputStream())).start();
    116. new Thread(new ProcessStreamHandler(proc.getErrorStream())).start();
    117. proc.waitFor();
    118. ByteArrayOutputStream baos = new ByteArrayOutputStream();
    119. is = new FileInputStream(wkpdfDestTempFile);
    120. byte[] buf = new byte[1024];
    121. while (is.read(buf, 0, buf.length) != -1) {
    122. baos.write(buf, 0, buf.length);
    123. }
    124. return baos;
    125. } catch (IOException | InterruptedException e) {
    126. log.error("html转换pdf出错", e);
    127. throw new RuntimeException("html转换pdf出错了");
    128. } finally {
    129. if (htmlTempFile != null) {
    130. boolean delete = htmlTempFile.delete();
    131. }
    132. if (wkpdfDestTempFile != null) {
    133. boolean delete = wkpdfDestTempFile.delete();
    134. }
    135. if (is != null) {
    136. try {
    137. is.close();
    138. } catch (IOException e) {
    139. e.printStackTrace();
    140. }
    141. }
    142. }
    143. }
    144. public static File createFile(String prefix, String sufix, String fileDirPath) {
    145. File file = null;
    146. File fileDir = new File(fileDirPath);
    147. try {
    148. file = File.createTempFile(prefix, sufix, fileDir);
    149. } catch (IOException e) {
    150. log.info("创建文件失败:", e.getCause());
    151. }
    152. return file;
    153. }
    154. private static String buildCmdParam(String srcAbsolutePath, String destAbsolutePath, Integer pageHeight, Integer pageWidth) {
    155. StringBuilder cmd = new StringBuilder();
    156. cmd.append(findExecutable()).append(space)
    157. .append("--margin-left").append(space)
    158. .append("0").append(space)
    159. .append("--margin-right").append(space)
    160. .append("0").append(space)
    161. .append("--margin-top").append(space)
    162. .append("0").append(space)
    163. .append("--margin-bottom").append(space)
    164. .append("0").append(space)
    165. .append("--page-height").append(space)
    166. .append(pageHeight).append(space)
    167. .append("--page-width").append(space)
    168. .append(pageWidth).append(space)
    169. .append(srcAbsolutePath).append(space)
    170. // .append("--footer-center").append(space)
    171. // .append("[page]").append(space)
    172. // .append("--footer-font-size").append(space)
    173. // .append("14").append(space)
    174. //
    175. // .append("--disable-smart-shrinking").append(space)
    176. // .append("--load-media-error-handling").append(space)
    177. // .append("ignore").append(space)
    178. // .append("--load-error-handling").append(space)
    179. // .append("ignore").append(space)
    180. // .append("--footer-right").append(space)
    181. // .append("WanG提供技术支持").append(space)
    182. .append(destAbsolutePath);
    183. return cmd.toString();
    184. }
    185. /**
    186. * 获取当前系统的可执行命令
    187. *
    188. * @return
    189. */
    190. public static String findExecutable() {
    191. Process p;
    192. try {
    193. String osname = System.getProperty("os.name").toLowerCase();
    194. String cmd = osname.contains("windows") ? "where wkhtmltopdf" : "which wkhtmltopdf";
    195. p = Runtime.getRuntime().exec(cmd);
    196. new Thread(new ProcessStreamHandler(p.getErrorStream())).start();
    197. p.waitFor();
    198. return IOUtils.toString(p.getInputStream(), Charset.defaultCharset());
    199. } catch (IOException e) {
    200. log.info("根据当前系统返回 wkhtmltopdf 执行命令失败,IO异常:", e);
    201. } catch (InterruptedException e) {
    202. log.info("根据当前系统返回 wkhtmltopdf 执行命令失败,中断异常:", e);
    203. }
    204. return "";
    205. }
    206. private static class ProcessStreamHandler implements Runnable {
    207. private InputStream is;
    208. public ProcessStreamHandler(InputStream is) {
    209. this.is = is;
    210. }
    211. @Override
    212. public void run() {
    213. BufferedReader reader = null;
    214. try {
    215. InputStreamReader isr = new InputStreamReader(is, StandardCharsets.UTF_8);
    216. reader = new BufferedReader(isr);
    217. String line;
    218. while ((line = reader.readLine()) != null) {
    219. log.debug("---++++++++++--->" + line);
    220. }
    221. } catch (IOException e) {
    222. e.printStackTrace();
    223. } finally {
    224. if (reader != null) {
    225. try {
    226. reader.close();
    227. } catch (IOException e) {
    228. e.printStackTrace();
    229. }
    230. }
    231. }
    232. }
    233. }
    234. }

  • 相关阅读:
    “第五十一天”
    使用Django开发一款竞争对手产品监控系统
    .NET源码解读kestrel服务器及创建HttpContext对象流程
    HJ3 明明的随机数
    nodejs+vue校园跑腿系统elementui
    设计模式_解释器模式
    计算机网络---TCP/UDP
    Cenots7 离线安装部署PostgreSQL
    js 正则表达式
    刷题笔记22——二叉搜索树BST
  • 原文地址:https://blog.csdn.net/weixin_38829588/article/details/127433450