基于wkhtmltopdf实现html转pdf导出

html转pdf功能方案实现

一、html导出pdf方案优缺点比较

image-20250216093311591

二、html转pdf导出

导出效果是将定制化的页面报表html页面导出为pdf文件,根据需求可对html进一步排版,展示表格化的元素

1、知识介绍

1)freemarker

Freemarker是一款纯 Java编写的模板引擎软件,可以用来生成各种文本,包括但不限于:HTMLE-Mail以及各种源代码等等。

1
2
3
4
5
6
7
8
<html>
<head>
<title>index</title>
</head>
<body>
<p>你好,${userName}</p>
</body>
</html>

${userName}是 FTL 的插值语法,这个值由后端代码放到Model中

2)wkhtmltopdf

工具全名叫 “wkhtmltopdf” ,能够把html 文档转换成 pdf 文档 或 图片(image) 的命令行工具,可在win、linux等系统下运行

2、开发步骤

1)windows服务器

wkhtmltopdf直接解压即可,我的电脑配置wkhtml环境变量后,本地才可进行调试

2)linux服务器

组件装到平台上时,平台服务器需要安装wkhtmltopdf,才可以使用pdf导出功能

如何安装:

使用root用户,进入到/mnt/目录,mkdir wkhtmltopdf创建文件夹

解压wkhtmltopdf.zip压缩包,将压缩包里的内容上传到wkhtmltopdf文件夹

进入wkhtmltopdf文件夹,cd /mnt /wkhtmltopdf/ ,执行 sh install.sh命令安装wkhtmltopdf

安装完成后,打开当前目录的tmp文件,出现install rpm success,wkhtmltopdf则安装成功

image-20230531142557112

注意:一些高版本的JavaScript的语法,高版本的Jquery库,js库,Echarts中的一些高阶参数配置都会让wkhtmltopdf软件渲染异常。原因是wkhtmltopdf内部的webkit浏览器内核版本太低,导致某些语法解析不了,导致报错

3、代码示例

1)pdf下载接口示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
@ApiOperation("全量导出pdf")
@PostMapping(value = "/exportAllPdf")
public void exportAllPdf(@RequestBody ExportAllQo qo, @ApiIgnore HttpServletResponse response,
@ApiIgnore HttpServletRequest request) {

byte[] jsonByte = Base64Utils.encodeUrlSafe(JSONObject.toJSONString(qo).getBytes(StandardCharsets.UTF_8));
String jsonStgr = new String(jsonByte,StandardCharsets.UTF_8 );

String ipPort = HttpRequestUtil.getHost(request);

String newUrl = request.getScheme() + "://" + ipPort + "/" + CTM05SMARTPASS_COMPONENT_ID + EduStringUtil.fillingParameters(EXPORT_ALL_PDF, jsonStgr);

log.info("pdfUrl is {}",newUrl);

Pdf pdf = new Pdf();
pdf.addPageFromUrl(newUrl);
pdf.addParam(new Param("--enable-local-file-access"));
pdf.addParam(new Param("--page-size", "A4"));
pdf.addParam(new Param("--header-spacing", "3"));
pdf.addParam(new Param("--footer-spacing", "6"));

OutputStream os = null;
byte[] buff = new byte[1024];
BufferedInputStream bis = null;
try {
//先临时存储到本地
String pdfFileName = "ExportAll" + System.currentTimeMillis() + ".pdf";
File pdfFile = new File(ExportCommonManager.getUploadFileTempRootPath() + File.separator + pdfFileName);
log.info("filePath is {}",ExportCommonManager.getUploadFileTempRootPath() + File.separator + pdfFileName);
if(!pdfFile.exists()){
pdfFile.createNewFile();
}

//把pdf的html内容写入到本地临时文件中
pdf.saveAs(pdfFile);

//导出pdf文件
exportCommon(response, pdfFileName);

os = response.getOutputStream();
bis = new BufferedInputStream(new FileInputStream(pdfFile));
int i = 0;
while ((i = bis.read(buff)) != -1) {
os.write(buff, 0, i);
}
os.flush();

} catch (IOException | InterruptedException e) {
log.error("export pdf error {}", e);
} finally {
IOUtils.closeQuietly(bis);
IOUtils.closeQuietly(os);
}

}
2)ftl模板文件渲染接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@ApiOperation("全量导出html地址")
@GetMapping(value = "/resource/exportAllHtml/{base64Param}")
public ModelAndView exportAllHtml(@PathVariable String base64Param, @ApiIgnore ModelAndView modelAndView) {
//ModelAndView可以理解成ftl文件对象,这里设置指定ftl文件名称。
modelAndView.setViewName("exportAll");

//请求解析Base64编码参数
byte[] jsonByte = Base64Utils.decodeUrlSafe(base64Param.getBytes(StandardCharsets.UTF_8));
String decodeBase64Param = new String(jsonByte, StandardCharsets.UTF_8);
ExportAllQo qo = JsonUtil.fromJson(decodeBase64Param, ExportAllQo.class);

//封装model里的数据,填充到页面上
modelAndView = dataPdfService.exportAllHtml(qo,modelAndView);

//最终用户生成一个html页面
return modelAndView;
}

4、测试阶段

1、ftl模板渲染接口url+base64参数,直接放到浏览器中访问。查看页面是否能够正常访问。

2、如果页面能够正常访问,再测试下载接口,能否正常下载pdf

3、注意:导出pdf前后端开发存在一定的耦合性,一些高级js语法或者ftl语法的错误会导致导出报错,需要一定的时间联调。导出pdf后样式也会发生一定的改变,需要前端再一次修改