diff --git a/pom.xml b/pom.xml index 0484aed..02bf02e 100644 --- a/pom.xml +++ b/pom.xml @@ -171,6 +171,13 @@ ${project.basedir}/lib/minio-7.0.2-all.jar + + + com.microsoft.playwright + playwright + 1.49.0 + + io.netty netty-all diff --git a/src/main/java/com/fkzy/warn/controller/PdfController.java b/src/main/java/com/fkzy/warn/controller/PdfController.java new file mode 100644 index 0000000..b0c4774 --- /dev/null +++ b/src/main/java/com/fkzy/warn/controller/PdfController.java @@ -0,0 +1,38 @@ +package com.fkzy.warn.controller; + +import com.fkzy.warn.model.Report; +import com.fkzy.warn.service.PdfService; +import io.swagger.annotations.Api; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +/** + * @author zhangjing + * @date 2026/01/13 10:40 + * @description + */ +@Api(tags = "生成报告相关") +@RestController +@RequestMapping("generateReport/") +@Slf4j +public class PdfController { + @Autowired + private PdfService pdfService; + + @PostMapping("download") + public ResponseEntity generateReport(@RequestBody String url) { + byte[] pdfBytes = pdfService.generatePdfFromUrl(url); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_PDF); + headers.setContentDispositionFormData("attachment", "report.pdf"); + + return ResponseEntity.ok() + .headers(headers) + .body(pdfBytes); + } +} diff --git a/src/main/java/com/fkzy/warn/service/PdfService.java b/src/main/java/com/fkzy/warn/service/PdfService.java new file mode 100644 index 0000000..84b66c8 --- /dev/null +++ b/src/main/java/com/fkzy/warn/service/PdfService.java @@ -0,0 +1,89 @@ +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("
公司名称
") + .setFooterTemplate( + "
" + + "© 2026 MyCompany" + + " 页 / 共 " + + "
") + ); + + browser.close(); + return pdfBytes; + } + } +} diff --git a/src/main/resources/bootstrap-dev.yml b/src/main/resources/bootstrap-dev.yml index 5b86776..ebf3692 100644 --- a/src/main/resources/bootstrap-dev.yml +++ b/src/main/resources/bootstrap-dev.yml @@ -5,7 +5,7 @@ spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driverClassName: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://182.151.8.209:3306/fkzy_operation?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2b8&allowMultiQueries=true + url: jdbc:mysql://182.151.8.209:33066/fkzy_operation?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2b8&allowMultiQueries=true username: root password: Zjtc!@#0804 druid: @@ -20,7 +20,7 @@ spring: #Redis redis: - host: 182.151.63.212 + host: 182.151.8.209 port: 63799 password: zjtc321! pool: @@ -44,11 +44,11 @@ mybatis-plus: call-setters-on-nulls: true minio: - host: http://182.151.8.209:9000 + host: http://182.151.8.209:9000 # endpoint MinIO服务所在地址 url: ${minio.host}/${minio.bucket}/ access-key: zjtc - secret-key: zjtc321! - bucket: cert + secret-key: Zjtc!@#0804 + bucket: fkzy ali: accessKeyId: http://182.151.8.209:8081/gas_apportal/publicApi/auth/logout @@ -59,7 +59,7 @@ file: #附件上传盘符,liunx服务器需要切换 fileUploadRootPath: D:/ fileUploadPath: /upload/ - preViewRealPath: http://182.151.8.209:9000/cert/. + preViewRealPath: http://182.151.8.209:9000/fkzy/