1.项目需求
项目中有个需求,需要将页面的结果导出为pdf,但是页面结果是html,类似于下图:
报告结果页面(部分)
有两种思路可以解决这个问题:
1.将结果整理成表格形式,使用pdf表单和表格配置成模板,然后使用java操作这个模板可以导出结果,但是只能输出表格形式的pdf,不能导出和页面一样的结果;
2.获取页面的html,利用框架将html转化成pdf,可以得到与页面一模一样的结果
项目中需要导出和页面一样的结果,所以只能采用第二种方案
2.需求难点
html页面是动态的,只有显示的时候才能获取到完整的页面,如果只是导出单个页面,可以在用户详情显示页面加导出按钮导出单个报告的pdf,但是如果要导出多个页面的报告,同时没法显示多个报告详情,所以需要我们按照动态操作html模板(根据报告详情),然后将操作完成的html页面导出。
3.步骤
1.采用html2pdf+jsoup框架实现,增加依赖
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>html2pdf</artifactId>
<version>2.0.2</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>font-asian</artifactId>
<version>7.1.2</version>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.6.2</version>
</dependency>
框架介绍:
jsoup工具介绍:参考https://www.open-open.com/jsoup/
html2pdf工具介绍:参考https://www.jianshu.com/p/62d80d1205f0
2.编写html模板(部分,report-pdf.html)
...
<ul>
<li class="comp-ScrollWrapper-basic-item"><span class="before"></span>身份信息核验<span class="page-order-result-basic-text">验证一致</span></li>
<li class="comp-ScrollWrapper-basic-item"><span class="before"></span>司法信息检索<span class="page-order-result-basic-text danger">司法信息检索</span></li>
<li class="comp-ScrollWrapper-basic-item"><span class="before"></span>司法信息检索<span class="page-order-result-basic-text">低风险</span></li>
</ul>
<div class="comp-ScrollWrapper-declare grey">本报告仅用于入职人员背调信息查询</div>
</div>
<dl class="page-order-resultPanel-verify">
<dt class="page-order-resultPanel-verify-title">
身份信息核验
</dt>
<dd class="page-order-resultPanel-verify-body">
<ul class="page-order-resultKey noBottom">
<li class="page-order-resultKey-item">
<span class="page-order-resultKey-label ">姓名</span>
<span class="page-order-resultItem-item-text baseText">
*佳佳
</span>
</li>
<li class="page-order-resultKey-item">
<span class="page-order-resultKey-label ">身份证号</span>
<span class="page-order-resultItem-item-text baseText">
110103********6789
</span>
</li>
</ul>
</dd>
</dl>
<dl class="page-order-resultPanel-verify">
<dt class="page-order-resultPanel-verify-title">
社会行为规范性评估
</dt>
<dd class="page-order-resultPanel-verify-body">
<ul class="page-order-resultKey noBottom">
<li class="page-order-resultKey-item">
<span class="page-order-resultKey-label">风险得分</span>
<span class="page-order-resultItem-item-text green">
<!-- 低green 中yellow 高red-->
低风险
</span>
</li>
<li class="page-order-resultKey-item">
<span class="page-order-resultKey-label">核实时间</span>
<span class="page-order-resultItem-item-text">
2019年9月2日
</span>
</li>
</ul>
</dd>
</dl>
<dl class="page-order-resultPanel-verify">
<dt class="page-order-resultPanel-verify-title">
社会行为规范性评估
</dt>
<dd class="page-order-resultPanel-verify-body">
<ul>
<li>
<dl>
<dt class="page-order-resultItem-item title others block">
<div class="page-order-resultItem-item-label">
网贷黑名单检索
</div>
<div class="page-order-resultItem-item-text">
暂无相关信息
</div>
</dt>
</dl>
</li>
<li>
<dl>
<dt class="page-order-resultItem-item title others block">
<div class="page-order-resultItem-item-label">
失信公告检索
</div>
<div class="page-order-resultItem-item-text diffText">
存在相关差异
</div>
</dt>
<dd class="page-order-resultItem-item-wrapper">
<ul class="page-order-resultKey noBottom">
<li class="page-order-resultKey-item">
<span class="page-order-resultKey-label">立案时间</span>
<span class="page-order-resultKey-item-text">
2016年8月11日
</span>
</li>
<li class="page-order-resultKey-item">
<span class="page-order-resultKey-label">概要</span>
<span class="page-order-resultKey-item-text">
其他有履行能力而拒不履行生效法...
</span>
</li>
</ul>
<div class="page-order-resultKey-matchRatio-warp">
<div class="page-order-resultKey-matchRatio">
<div class="page-order-resultKey-assist-icon"></div>
信息匹配度: 30%
</div>
</div>
</dd>
</dl>
</li>
<li>
<dl>
<dt class="page-order-resultItem-item title others block">
<div class="page-order-resultItem-item-label">
执行公告检索
</div>
<div class="page-order-resultItem-item-text diffText">
存在相关差异
</div>
</dt>
</dl>
</li>
</ul>
</dd>
</dl>
...
导入html模板到内存(该模板是全局共用)
/**
* html静态模板
*/
private StringBuilder htmlTemplate;
/**
* 转换配置
*/
private ConverterProperties cp;
@Override
public void afterPropertiesSet() throws Exception {
htmlTemplate = new StringBuilder();
BufferedReader br = null;
try {
InputStream inputStream = this.getClass().getResourceAsStream("report-pdf.html");
br = new BufferedReader(new InputStreamReader(inputStream));
//建立一个对象,它把文件内容转成计算机能读懂的语言
String line = null;
while ((line = br.readLine()) != null) {
htmlTemplate.append(line);
htmlTemplate.append("\r\n");
}
cp = new ConverterProperties();
//导入字体
cp.setFontProvider(new DefaultFontProvider(true, true, true));
cp.setCharset("utf-8");
} catch (Exception ex) {
logger.error("PdfExportUtil afterPropertiesSet error,",ex);
} finally {
try {
if (br != null) {
br.close();
}
} finally {
}
}
}
3.导入字体和初始化水印
因为该框架默认不支持中文字符,需要导入中文字体
/**
* 生成pdf
* @param referReportVO
* @param fileName
* @return
*/
public Boolean generateBgiReportPdf(ReferReportVO referReportVO, String fileName) {
Document document = null;
PdfDocument pd = null;
//生产文件夹
int index = fileName.lastIndexOf("/");
String pathInfo = fileName.substring(0, index + 1);
File file = new File(pathInfo);
if (!file.exists()){
if (!file.mkdir()){
return false;
}
}
try {
pd = new PdfDocument(new PdfWriter(fileName));
document = new Document(pd);
document.setFont(PdfFontFactory.createFont("STSong-Light", "UniGB-UCS2-H", false));
WaterMark waterMark = new WaterMark("仅供"+referReportVO.getCorpName()+"公司内部使用");
pd.addEventHandler(PdfDocumentEvent.END_PAGE, waterMark);
StringBuilder autoHtml = autoMakeHtml(referReportVO);
//设置页面边距 必须先设置边距,再添加内容,否则页边距无效
List<IElement> list = HtmlConverter.convertToElements(autoHtml.toString(), this.cp);
for (IElement ie : list) {
if (ie instanceof HtmlPageBreak) {
document.add((HtmlPageBreak)ie);
//普通块级元素
} else {
document.add((IBlockElement)ie);
}
}
} catch (Exception ex) {
logger.error("generateBgiReportPdf error,corpId={},reportNo={}", referReportVO.getCorpId(),
referReportVO.getReportNo(), ex);
return false;
} finally {
if (document != null) {
try {
document.close();
}
catch (Exception ex) {
logger.error("生成文件失败",ex);
return false;
}
}
}
return true;
}
水印类
/**加水印
* @Author: ding
* @Date: 2019-09-17 15:21
*/
public class WaterMark implements IEventHandler {
private static final Logger logger = LoggerFactory.getLogger(PdfExportUtil.class);
private String waterText;
public WaterMark(String waterText) {
this.waterText = waterText;
}
@Override
public void handleEvent(Event event){
PdfDocumentEvent documentEvent = (PdfDocumentEvent) event;
PdfDocument document = documentEvent.getDocument();
PdfPage page = documentEvent.getPage();
PdfCanvas pdfCanvas = new PdfCanvas(page.getLastContentStream(), page.getResources(), document);
Rectangle rectangle = page.getPageSize();
PdfFont pdfFont = null;
try {
pdfFont = PdfFontFactory.createFont("STSong-Light", "UniGB-UCS2-H", false);
} catch (Exception ex){
logger.error("创建字体失败",ex);
return;
}
Canvas canvas = new Canvas(pdfCanvas, document, page.getPageSize())
.setFontColor(new DeviceRgb(227,228,228))
.setFontSize(12)
.setFont(pdfFont);
float initX = rectangle.getWidth()/2;
float initY = rectangle.getHeight()/2;
Random rd = new Random(System.currentTimeMillis());
for (int i = -1; i<= 1; i++) {
for (int j=-2; j<=2; j=j+1) {
int nextRd = rd.nextInt(50);
if (nextRd > 20 && nextRd < 45) {
canvas.showTextAligned(this.waterText, initX+i*(100+nextRd), initY+j*(150+nextRd),
TextAlignment.CENTER, 170);
}
}
}
}
}
4.使用jsoup根据报告数据操作html(部分代码)
...
/**
* 动态生成html
* @param referReportVO
* @return
*/
private StringBuilder autoMakeHtml(ReferReportVO referReportVO) throws Exception{
org.jsoup.nodes.Document document = Jsoup.parse(this.htmlTemplate.toString(),"utf-8");
String userName = referReportVO.getRespondentName();
//用户名称图片展示
Element userNameImgEle = document.getElementById("bg-user-name-1");
userNameImgEle.text(userName);
//用户名称
Element userNameStrEle = document.getElementById("bg-user-name-2");
userNameStrEle.text(userName);
//企业名称
Element corpNameStrEle = document.getElementById("bg-corp-name");
corpNameStrEle.text(referReportVO.getCorpName());
List<ItemReportVO> reportVOList = referReportVO.getReferItemReportList();
if (CollectionUtils.isEmpty(reportVOList)) {
return new StringBuilder(document.toString());
}
//差异图片渲染显示
Element diffImgEle = null;
Integer reportLevel = referReportVO.getReportLevel();
if (reportLevel.equals(ReportLevelEnum.HAVE_DEFFERENCE.getCode().intValue())) {
diffImgEle = document.getElementById("bg-img-has-difference");
} else if (reportLevel.equals(ReportLevelEnum.PART_DEFFERENCE.getCode().intValue())) {
diffImgEle = document.getElementById("bgi-img-part-difference");
} else {
diffImgEle = document.getElementById("bg-img-no-difference");
}
diffImgEle.attr("style", "display:block");
//结果概述
Element conItemListEle = document.getElementById("bgi-ul-conclusion-list");
String conLi = null;
for (ItemReportVO itemReportVO : reportVOList) {
String conclusion = itemReportVO.getConclusion();
String conclusionDesc = itemReportVO.getConclusionDesc();
conclusionDesc = conclusionDesc.replaceAll("x","*").replaceAll("X","*");
if (conclusion.equalsIgnoreCase("0")) {
conLi = "<li class=\"comp-ScrollWrapper-basic-item\"><span class=\"before\"></span><span>"+itemReportVO.getItemName()+
"</span><span class=\"page-order-result-basic-text\">"+conclusionDesc + "</span></li>";
} else {
conLi = "<li class=\"comp-ScrollWrapper-basic-item\"><span class=\"before\"></span><span>"+itemReportVO.getItemName()+
"</span><span class=\"page-order-result-basic-text-difftext\">"+conclusionDesc + "</span></li>";
}
conItemListEle.append(conLi);
}
Map<String, ItemReportVO> itemNameToReportVOMap = reportVOList.stream().collect(
Collectors.toMap(ItemReportVO::getItemName, s -> s, (k1, k2) -> k1));
//身份信息校验
ItemReportVO itemReportVO = itemNameToReportVOMap.get("身份信息核验");
if (itemReportVO == null) {
logger.error("报告内容存在问题,具体内容为:{}", JSON.toJSONString(referReportVO));
return null;
}
Element identityCheck = document.getElementById("bgi-ul-identity-check");
generateItemFields(identityCheck, itemReportVO.getFiledList());
Element allProductsEle = document.getElementById("all-products-contents");
//社会行为规范性评估
itemReportVO = itemNameToReportVOMap.get("社会行为规范性评估");
if (itemReportVO != null) {
generateProductItem(allProductsEle, itemReportVO.getItemName(), "bgi-ul-social-item");
Element socialCheckUl = document.getElementById("bgi-ul-social-item");
generateItemFields(socialCheckUl, itemReportVO.getFiledList());
}
//司法信息检索
itemReportVO = itemNameToReportVOMap.get("司法信息检索");
if (itemReportVO != null) {
generateProductItem(allProductsEle, itemReportVO.getItemName(), "bgi-ul-law-item");
Element lawCheckUl = document.getElementById("bgi-ul-law-item");
generateProductFieldItems(itemReportVO.getProductNo(), lawCheckUl, itemReportVO.getFiledList(), "bgi-dl-law-search", true);
}
//商业利益冲突核验
itemReportVO = itemNameToReportVOMap.get("商业利益冲突核验");
if (itemReportVO != null) {
generateProductItem(allProductsEle, itemReportVO.getItemName(), "bgi-ul-busi-item");
Element busiCheckUl = document.getElementById("bgi-ul-busi-item");
generateProductFieldItems(itemReportVO.getProductNo(), busiCheckUl, itemReportVO.getFiledList(), "bgi-dl-busi-search", false);
}
//职业资格证书核验
itemReportVO = itemNameToReportVOMap.get("职业资格证书核验");
if (itemReportVO != null) {
generateProductItem(allProductsEle, itemReportVO.getItemName(), "bgi-ul-position-item");
Element positionCheckUl = document.getElementById("bgi-ul-position-item");
generateProductFieldItems(itemReportVO.getProductNo(), positionCheckUl, itemReportVO.getFiledList(), "bgi-dl-position-search", false);
}
return new StringBuilder(document.toString());
}
private void generateProductItem(Element parentEle, String productName, String idName) {
String productStr = "<dl class=\"page-order-resultPanel-verify\">\n" +
"<dt class=\"page-order-resultPanel-verify-title\">\n" + productName+
"</dt>\n<dd class=\"page-order-resultPanel-verify-body\">\n" +
"<ul class=\"page-order-resultKey noBottom\" id=\""+idName+"\">\n" +
"</ul>\n</dd>\n</dl>";
parentEle.append(productStr);
}
private void generateItemFields(Element parentEle, List<ItemFieldVO> filedList) {
String identityLi = null;
if (!CollectionUtils.isEmpty(filedList)) {
for (ItemFieldVO itemFieldVO : filedList) {
identityLi = "<li class=\"page-order-resultKey-item\">\n<span class=\"page-order-resultKey-label \">"+ itemFieldVO.getFieldName()+
"</span>\n<span class=\"page-order-resultItem-item-text baseText\">\n" + itemFieldVO.getFieldValue()+ "\n</span>\n</li>";
parentEle.append(identityLi);
}
}
}
...
5.查看导出的结果
pdf导出结果
基本上与html页面相差无几
有不理解的欢迎私信联系我
网友评论