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;
|
|
}
|
|
}
|
|
}
|