当前导出PDF工具
java后端如何导出像前端用echarts那么漂亮而且还有带有图表的PDF呢,或者图片。目前后端导出word或者excel都是有县城的jar,导出简单的图表也有的简单的jfreechart
总结一下用java生成PDF的方法:
A、itext-PdfStamper pdfStamper(俗称抠模板):代码简单 模板要先提供,且字段长度固定、不灵活
B、itext-Document document(正常代码撰写):模板可根据代码调整、但样式不如C灵活 要维护的后台代码较多,整个过程全手工代码插入表格
C、wkhtmltopdf(使用工具):模板样式可根据前端随意调整,要维护的前台代码较多,原理是后端命令行启动无窗webkit,中文需要依赖字体库,html渲染完成后导出文件,
D、phantomjs和C类似,只是它的侧重点除了导出文件,还可以通过webkit执行js脚本,进行定制化操作,但是目前已经不再更新,一般用于爬虫python中较多。
C,D的原理相同都是使用了webkit渲染html后导出文件
需要依赖的jar和参考文档
A/B: itext-pdfa-5.5.6.jar、itext-xtra-5.5.6.jar、itext-5.5.6.jar、itext-asian.jar 简单模板和表格
C: wkhtmltopdf-0.12.4、python2.7.14、pdfkit 参考文档:https://www.jianshu.com/p/4d65857ffe5e/
wkhtmltopdf --page-height 420mm --page-width 297mm /path/src/edit.html /path/src/edit.pdf
D:https://phantomjs.org/download
#可以直接命令行执行js脚本
phantomjs -v
phantomjs导出PDF
流程如下
- 编写html文件
- 编写phantomjs 的js脚本,主要是打开该文件,并导出图片或者PDF
- 命令行执行没问题后,可以在Java中执行cmd命令
/***
* echarts-convert.js
* https://phantomjs.org/download
* https://echarts.apache.org/zh/builder.html
*官网下载http://phantomjs.org/download.html 国内镜像http://npm.taobao.org/dist/phantomjs/
* EChartsConvert: eharts to PDF
*auhtor :zhanghl@deepbulue.com
*time:2020-12-31 14:26
*/
var system = require('system');
var fs = require('fs');
var config = {
// define the location of js files
VUE: 'vue.min.js',
JQUERY: 'jquery-1.9.1.min.js',
ECHARTS: 'echarts.min.js',
DEEPBLUE: 'deepblue.js',
DEFAULT_WIDTH: '740',
DEFAULT_HEIGHT: 'auto'
}, parseParams, render;
var webPage = require('webpage');
var page = webPage.create();
page.viewportSize = { width: 640,height: 768 };
// page.paperSize = {format: 'A4', orientation: 'portrait',width: '740px', height: "500px", margin: '0px' }
page.paperSize = {
width: '8.5in',
height: '11in',
margin: {
top: '5px',
left: '20px'
},
header: {
height: "1cm",
contents: phantom.callback(function(pageNum, numPages) {
return "<h5> <span style='color: #93a1a1;float:right'>deepblueAI-" + pageNum + " / " + numPages + "</span></h5>";
})
},
footer: {
height: "1cm",
contents: phantom.callback(function(pageNum, numPages) {
return "<h5> <span style='color: #93a1a1;float:right'>deepblueAI-" + pageNum + " / " + numPages + "</span></h5>";
})
}
}
page.onLoadFinished = function () {
console.log("html dom onLoadFinished");
}
var usage = function () {
console.log("\nUsage: phantomjs echarts-convert.js -url xxx.html -outfile filename -options options" +
"\n-outfile save file path" +
"\n-options json str \n");
};
var getParams = function () {
console.log("getParams for cmd \n");
var map = {}, i, key;
if (system.args.length < 2) {
usage();
phantom.exit();
}
for (i = 0; i < system.args.length; i += 1) {
if (system.args[i].charAt(0) === '-') {
key = system.args[i].substr(1, i.length);
var vals = system.args[i + 1];
console.log(key + " = " + vals);
if (key === 'url') {
try {
map[key] = vals;
} catch (e) {
console.log('Error: cannot find url, ' + system.args[i + 1]);
phantom.exit();
}
}else if (key === 'outfile') {
try {
map[key] = vals;
} catch (e) {
console.log('Error: cannot find outfile, ' + system.args[i + 1]);
phantom.exit();
}
} else {
map[key] = vals;
}
}
}
console.log("options obj = " + map[key]);
return map;
};
function createChart(params) {
var options = eval('(' + params + ')');
deepblue.init(options);
return {height: jQuery(document).height(),width : jQuery(document).width(),container : jQuery("#container").width()};
};
//getParams
var paramMap =getParams();
page.open(paramMap.url , function(status) {
page.injectJs(config.JQUERY);
page.injectJs(config.ECHARTS);
page.injectJs(config.DEEPBLUE);
console.log("http status:"+status);
// get the dom height
var viewRect= page.evaluate(createChart, paramMap.options);
console.log('width ='+viewRect.width+ ' height='+viewRect.height + ' container='+viewRect.container );
// page.paperSize.height= height+"px";
if( status == 'success' ) {
setTimeout(function(){
page.clipRect = {
top : 0,
left : 0,
width : 840,
height : viewRect.height
};
// page.render('test/example1.pdf');
page.render(paramMap.outfile, {format: 'pdf', quality: '50'});
console.log('success' );
page.close();
phantom.exit();
}, 500);
}else {
console.log('render failed' );
// exit
phantom.exit();
}
});
phantomjs /ads-monitor/html/echarts-convert.js -url file:ads-monitor/html/contractDashboard.html -outfile /ads-monitor/pdf/PDF-10-20210104165711.pdf -options {\"groupListData\":[{\"groupId\":22,\"groupName\":\"虹桥机场T1一楼屏幕\",\"materialUrlList\":[{\"materialName\":\"000\",\"materialTags\":\"5555\",\"materialType\":\"picture\",\"materialUrl\":\"http://degao-oss.oss-cn-shanghai.aliyuncs.com/materials/dev/16081164972335.jpg\",\"rid\":72}],\"palanResult\":{\"days\":16,\"nubPerDay\":1,\"totalNub\":24},\"pieData\":[{\"type\":\"normal\",\"value\":\"833.33\"},{\"type\":\"abnormal\",\"value\":\"-733.33\"}],\"planTimeIntervalList\":[{\"endTime\":1606705735000,\"purchaseCount\":10,\"rid\":24,\"startTime\":1605841734000},{\"endTime\":1608267599000,\"purchaseCount\":10,\"rid\":30,\"startTime\":1607961601000},{\"endTime\":1609394916000,\"purchaseCount\":4,\"rid\":37,\"startTime\":1609135712000}],\"progress\":\"12.50\",\"realResult\":{\"abnormalNub\":25,\"abnormalRate\":\"100.00\",\"days\":3,\"normalNub\":0,\"normalRate\":\"0.00\",\"nubPerDay\":0,\"totalNub\":3},\"taskTimeIntervalList\":[{\"endTime\":1608009134000,\"rid\":36,\"startTime\":1608098231000},{\"endTime\":1608220804000,\"rid\":42,\"startTime\":1608015979000},{\"endTime\":1607961600000,\"rid\":50,\"startTime\":1608134399000},{\"endTime\":1607961600000,\"rid\":52,\"startTime\":1608134399000},{\"endTime\":1607961600000,\"rid\":53,\"startTime\":1608134399000},{\"endTime\":1608134400000,\"rid\":54,\"startTime\":1608134399000},{\"endTime\":1607961600000,\"rid\":55,\"startTime\":1608134399000},{\"endTime\":1607961600000,\"rid\":56,\"startTime\":1608134399000},{\"endTime\":1608267599000,\"rid\":60,\"startTime\":1607961601000},{\"endTime\":1609394916000,\"rid\":66,\"startTime\":1609135712000}]},{\"groupId\":21,\"groupName\":\"浦东机场T1一楼屏幕\",\"materialUrlList\":[{\"materialName\":\"iphone12-白色\",\"materialTags\":\"白色|iphone\",\"materialType\":\"picture\",\"materialUrl\":\"https://degao-oss.oss-cn-shanghai.aliyuncs.com/2b13b64ded702c2c.jpg?Expires=1921200484&OSSAccessKeyId=LTAI4GDBhXMfswkafRVMiybj&Signature=dB3GKh9XQ3nVp515NoKOyP1fkVc%3D\",\"rid\":34},{\"materialName\":\"iphone12-黑色\",\"materialTags\":\"黑色|iphone12\",\"materialType\":\"picture\",\"materialUrl\":\"https://degao-oss.oss-cn-shanghai.aliyuncs.com/681fe3b14009da2f.jpg?Expires=1921200515&OSSAccessKeyId=LTAI4GDBhXMfswkafRVMiybj&Signature=gCgDFY%2B40K7NZW00gf7tDmOgr%2FU%3D\",\"rid\":35},{\"materialName\":\"testbyhanhao\",\"materialTags\":\"test\",\"materialType\":\"picture\",\"materialUrl\":\"https://degao-oss.oss-cn-shanghai.aliyuncs.com/ct5%E7%94%B7%E5%AD%A9.jpg?Expires=1922168178&OSSAccessKeyId=LTAI4GDBhXMfswkafRVMiybj&Signature=x8rtpIeQqF7XcydyrFhgp0I2M6I%3D\",\"rid\":44},{\"materialName\":\"花屏广告测试1\",\"materialTags\":\"8888\",\"materialType\":\"video\",\"materialUrl\":\"https://degao-oss.oss-cn-shanghai.aliyuncs.com/materials/dev/160811557329912.mp4\",\"rid\":70},{\"materialName\":\"videoTest测试名称长度测试名称长度测试名称长度测试名称长度测试名称长度测试名称长度\",\"materialTags\":\"333\",\"materialType\":\"video\",\"materialUrl\":\"https://degao-oss.oss-cn-shanghai.aliyuncs.com/materials/dev/160914457154710.mp4\",\"rid\":103},{\"materialName\":\"videoTest测试名称长度测试名称长度测试名称长度测试名称长度测试名称长度测试名称长度测试名称长度测试名称长度测试名称长度测试名称长度测试名称长度测试名称长度\",\"materialTags\":\"444\",\"materialType\":\"video\",\"materialUrl\":\"https://degao-oss.oss-cn-shanghai.aliyuncs.com/materials/dev/160914461256915.mp4\",\"rid\":104},{\"materialName\":\"03\",\"materialTags\":\"test\",\"materialType\":\"video\",\"materialUrl\":\"https://degao-oss.oss-cn-shanghai.aliyuncs.com/materials/dev/160861441998817.mp4\",\"rid\":98},{\"materialName\":\"testbyhanhao888\",\"materialTags\":\"888\",\"materialType\":\"picture\",\"materialUrl\":\"https://degao-oss.oss-cn-shanghai.aliyuncs.com/materials/dev/ct5%E7%94%B7%E5%AD%A9.jpg?Expires=1923121164&OSSAccessKeyId=LTAI4GDBhXMfswkafRVMiybj&Signature=SWFYT0qF4imIEd7FiBDDPVClkt4%3D\",\"rid\":52},{\"materialName\":\"花屏广告测试1\",\"materialTags\":\"6666\",\"materialType\":\"video\",\"materialUrl\":\"https://degao-oss.oss-cn-shanghai.aliyuncs.com/materials/dev/160811543745915.mp4\",\"rid\":69},{\"materialName\":\"360-胡宁\",\"materialTags\":\"360\",\"materialType\":\"video\",\"materialUrl\":\"https://degao-oss.oss-cn-shanghai.aliyuncs.com/materials/dev/160852502804915.mp4\",\"rid\":94},{\"materialName\":\"Linux先行者-冯晓焰\",\"materialTags\":\"linux\",\"materialType\":\"video\",\"materialUrl\":\"https://degao-oss.oss-cn-shanghai.aliyuncs.com/materials/dev/160851738926817.mp4\",\"rid\":93},{\"materialName\":\"面霸-左耳朵耗子\",\"materialTags\":\"左耳朵耗子\",\"materialType\":\"video\",\"materialUrl\":\"https://degao-oss.oss-cn-shanghai.aliyuncs.com/materials/dev/16085169580984.mp4\",\"rid\":92},{\"materialName\":\"花屏广告测试1\",\"materialTags\":\"444444444444444444444444444444|88888888888888888888888888888888888\",\"materialType\":\"video\",\"materialUrl\":\"https://degao-oss.oss-cn-shanghai.aliyuncs.com/materials/dev/16091450261533.mp4\",\"rid\":105}],\"palanResult\":{\"days\":16,\"nubPerDay\":1,\"totalNub\":24},\"planTimeIntervalList\":[{\"endTime\":1606705735000,\"purchaseCount\":10,\"rid\":24,\"startTime\":1605841734000},{\"endTime\":1608267599000,\"purchaseCount\":10,\"rid\":30,\"startTime\":1607961601000},{\"endTime\":1609394916000,\"purchaseCount\":4,\"rid\":37,\"startTime\":1609135712000}],\"progress\":\"0\",\"realResult\":{\"abnormalNub\":0,\"abnormalRate\":\"0\",\"days\":0,\"normalNub\":0,\"normalRate\":\"0\",\"nubPerDay\":0,\"totalNub\":0},\"taskTimeIntervalList\":[{\"endTime\":1608009134000,\"rid\":36,\"startTime\":1608098231000},{\"endTime\":1608220804000,\"rid\":42,\"startTime\":1608015979000},{\"endTime\":1607961600000,\"rid\":50,\"startTime\":1608134399000},{\"endTime\":1607961600000,\"rid\":52,\"startTime\":1608134399000},{\"endTime\":1607961600000,\"rid\":53,\"startTime\":1608134399000},{\"endTime\":1608134400000,\"rid\":54,\"startTime\":1608134399000},{\"endTime\":1607961600000,\"rid\":55,\"startTime\":1608134399000},{\"endTime\":1607961600000,\"rid\":56,\"startTime\":1608134399000},{\"endTime\":1608267599000,\"rid\":60,\"startTime\":1607961601000},{\"endTime\":1609394916000,\"rid\":66,\"startTime\":1609135712000}]}],\"headData\":{\"brandZnName\":\"淘宝\",\"consumerZnName\":\"测试创建1\",\"contractNo\":\"100001\",\"hqAriportPosition\":{\"lcdNub\":0,\"ledNub\":3,\"positionTotal\":3},\"pdAriportPosition\":{\"lcdNub\":1,\"ledNub\":7,\"positionTotal\":8},\"positionTotal\":11,\"rid\":10},\"playData\":{\"barOptionList\":[{\"time\":\"20201221\",\"type\":\"normal\",\"value\":0},{\"time\":\"20201221\",\"type\":\"abnormal\",\"value\":11},{\"time\":\"20201225\",\"type\":\"normal\",\"value\":0},{\"time\":\"20201225\",\"type\":\"abnormal\",\"value\":2}],\"lineOptionList\":[{\"time\":\"20201221\",\"value\":10},{\"time\":\"20201225\",\"value\":10}]}}
导出PDF样图如下
后续
过了一年多了吧,再次看到该文想补充下后续。
虽然导出的PDF样式很漂亮,但是放在服务器上强依赖安装webkit和字体库,对服务器软件有强依赖且导出会影响服务器性能。由于这是在项目中的一种尝试,特别是我们的项目在使用K8S时如果对安装基础软件有依赖,基础镜像都需要安装这些没必要都会使用的依赖,因此在后面的落地中并没有使用该方案。强制前端使用浏览器打印功能导出PDF,避免不必要的性能繁琐和性能开销。[更新于2022-4-15]