wkhtmltopdf 使用本地文件生成 PDF
一般使用命令 wkhtmltopdf URL pdfPath
生成 PDF 文件,其中 URL 为 GET 请求地址。
但是笔者在做的项目是一个模板中心服务(后续代码整理好会上传到 Gitee),实现的功能是可以上传 Thymeleaf 的 HTML 模板文件,然后调用 GET 接口传入模板数据(Map 类型)后,直接返回生成的 PDF。既然使用 Map 传送模板数据,就不能使用 GET 请求了,一方面拼接 Map 比较麻烦,另一方面如果 Map 数据量过大可能超过 URL 长度限制。
印象中 wkhtmltopdf 可以通过本地文件转 PDF,遂尝试先调用 POST 接口将数据填充到模板中,拿到返回的 HTML 后写入到临时文件中,然后通过 wkhtmltopdf HTML pdfPath
将 HTML 文件转换为 PDF 文件。
但是程序怎么弄都不行,就是会报错:
2022-12-13 16:36:59,873 INFO [TID: N/A] [pool-2-thread-1] utils.FileUtils: HtmlToPdfInterceptor ===> Loading page (1/2)
2022-12-13 16:36:59,873 INFO [TID: N/A] [pool-2-thread-1] utils.FileUtils: HtmlToPdfInterceptor ===> [> ] 0%
2022-12-13 16:36:59,873 INFO [TID: N/A] [pool-2-thread-1] utils.FileUtils: HtmlToPdfInterceptor ===> [======> ] 10%
2022-12-13 16:37:02,427 INFO [TID: N/A] [pool-2-thread-1] utils.FileUtils: HtmlToPdfInterceptor ===> Error: Failed to load http://d/Source/secp-template-center/secp-template-service/target/classes/templates/thymeleaf/Seeds-Pro/test.html, with network status code 3 and http status code 0 - Host d not found
2022-12-13 16:37:02,428 INFO [TID: N/A] [pool-2-thread-1] utils.FileUtils: HtmlToPdfInterceptor ===> Error: Failed loading page http:///D:/Source/secp-template-center/secp-template-service/target/classes/templates/thymeleaf/Seeds-Pro/test.html (sometimes it will work just to ignore this error with --load-error-handling ignore)
2022-12-13 16:37:02,428 INFO [TID: N/A] [pool-2-thread-1] utils.FileUtils: HtmlToPdfInterceptor ===> Exit with code 1 due to network error: HostNotFoundError
但是同样的命令,直接在 cmd 里面就可以执行:
搜索无果,官方 GitHub 也没找到有用信息,含同事来看也没发现问题,后来他在自己的 Mac 上拉代码跑了遍,发现正常,而我用的是公司发的 ThinkPad。。。
事后分析,是路径分隔符的问题,wkhtmltopdf 对 Windows 下的路径分隔符兼容性不好,通过 Path.of(first, more)
静态方法结合 File.separator
构造路径就没有问题了。
OpenFeign 不支持 HttpServletResponse
问题
模板中心服务返回的 HttpServletResponse
,OpenFeign 不支持,只能将文件转为 Base64 返回,另外的方法是使用 RestTemplate
或者 HttpClient
。
使用方 getOutputStream() has already been called for this response 错误
使用方通过 GET 请求获取 PDF 的时候,返回值必须是 void,因为返回文件使用的是 HttpServletResponse
,而 SpringMVC 返回的时候也会使用 response 的 getOutputStream()
方法,而该方法只能调用一次。