本站消息

站长简介/公众号

  出租广告位,需要合作请联系站长


+关注
已关注

分类  

暂无分类

标签  

暂无标签

日期归档  

暂无数据

WKHtmltoPdf

发布于2023-01-18 23:00     阅读(801)     评论(0)     点赞(4)     收藏(4)


踩过的坑

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

  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]... <output file>

在命令行上可通过 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. }




所属网站分类: 技术文章 > 博客

作者:Jjxj

链接:http://www.qianduanheidong.com/blog/article/494767/b9bbc172b7b6306f6d93/

来源:前端黑洞网

任何形式的转载都请注明出处,如有侵权 一经发现 必将追究其法律责任

4 0
收藏该文
已收藏

评论内容:(最多支持255个字符)