风控之眼,风险预警平台
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

89 lines
3.6 KiB

package com.fkzy.warn.service;
import com.microsoft.playwright.options.Margin;
import com.microsoft.playwright.options.WaitUntilState;
import org.springframework.stereotype.Service;
import com.microsoft.playwright.*;
import org.springframework.stereotype.Service;
import java.util.regex.Pattern;
/**
* @author zhangjing
* @date 2026/01/13 10:33
* @description
*/
@Service
public class PdfService {
// 可选:限制只允许访问你自己的域名(安全)
private static final Pattern ALLOWED_URL_PATTERN =
Pattern.compile("^https://your-domain\\.com/report/.*");
public byte[] generatePdfFromUrl(String reportUrl) {
// 安全校验:防止 SSRF 攻击
// if (!ALLOWED_URL_PATTERN.matcher(reportUrl).matches()) {
// throw new IllegalArgumentException("Invalid report URL");
// }
try (Playwright playwright = Playwright.create()) {
Browser browser = playwright.chromium().launch();
Page page = browser.newPage();
// 1. 导航到前端报表页面
page.navigate(reportUrl, new Page.NavigateOptions()
.setWaitUntil(WaitUntilState.NETWORKIDLE)); // 等待网络空闲
// 2. 【关键】等待 ECharts 渲染完成
// 方法一:前端在图表加载完成后添加特定 class(推荐)
page.waitForSelector("body.report-ready", new Page.WaitForSelectorOptions()
.setTimeout(30_000)); // 最多等 30 秒
// 方法二(备选):等待某个图表容器有内容
// page.waitForFunction("() => document.querySelector('#chart').children.length > 0");
// 3. 【可选】注入水印和 Logo(如果前端没做)
// 注意:如果前端已包含水印/Logo,此步可跳过
// page.addStyleTag(new Page.AddStyleTagOptions().setContent("""
// .playwright-watermark {
// position: fixed;
// top: 50%;
// left: 50%;
// transform: translate(-50%, -50%) rotate(-45deg);
// font-size: 80px;
// color: rgba(0, 0, 0, 0.08);
// pointer-events: none;
// z-index: 9999;
// white-space: nowrap;
// }
// """));
page.evaluate("() => { " +
"const wm = document.createElement('div');" +
"wm.className = 'playwright-watermark';" +
"wm.innerText = '机密';" +
"document.body.appendChild(wm);" +
"}");
// 4. 生成 PDF
Margin margin = new Margin();
margin.top="2cm";
margin.bottom="2cm";
margin.left="1.5cm";
margin.right="1.5cm";
byte[] pdfBytes = page.pdf(new Page.PdfOptions()
.setFormat("A4")
.setPrintBackground(true)
.setMargin(margin)
.setDisplayHeaderFooter(true)
.setHeaderTemplate("<div style='font-size:10px; text-align:center; width:100%;'>公司名称</div>")
.setFooterTemplate(
"<div style='font-size:9px; width:100%;'>" +
"<span style='float:left;'>© 2026 MyCompany</span>" +
"<span style='float:right;'>第 <span class='pageNumber'></span> 页 / 共 <span class='totalPages'></span> 页</span>" +
"</div>")
);
browser.close();
return pdfBytes;
}
}
}