1. 将项目实施中物料和图片展示在小程序端,并可点击下载为pdf文件1. 开放多个接口,展示各类物料接口和下载pdf接口2. 重点讲解展示页面下载为pdf接口,因该接口实现方式导致OOM1. 根据Deepseek搜索比较和综合考量,选取容易上手且简易的实现方式:使用thymeleaf+saucer生成简单的PDF:html模版开发,入手快,便于学习,无需管理分页等事项。2.pom引入,使用jdk1.8+springboot2.7.18版本<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> <!-- <version>${spring-boot.version}</version>--> </dependency> <dependency> <groupId>org.xhtmlrenderer</groupId> <artifactId>flying-saucer-pdf</artifactId> <version>9.4.1</version> <!-- <version>9.1.22</version>--> <exclusions> <exclusion> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> </exclusion> <exclusion> <groupId>com.github.librepdf</groupId> <artifactId>openpdf</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>com.github.librepdf</groupId> <artifactId>openpdf</artifactId> <!-- <version>1.3.30</version>--> <version>1.3.43</version> </dependency>
thymeleaf: prefix: classpath:/template/ suffix: .html mode: HTML
<!DOCTYPE html><htmlxmlns:th="http://www.thymeleaf.org"><head> <metacharset="UTF-8"/> <title>indoor-services-2-pdf</title> <style> body { font-family: SimSun; } .title { align-content: center; text-align: center; } .signature { float: right } h1 { color: #2c3e50; text-align: center; } .content { margin: 20px 0; } .image-container { text-align: center; margin: 20px 0; } .image-container img { max-width: 100%; height: auto; } /*.rotate-90 {*/ /* -fs-pdf-image-rotation: 90; !* OpenPDF 专有属性*!*/ /*}*/ </style></head><body><h1th:text="${title}">title</h1><p>安装须知</p><p>1. 程序计数器、虚拟机栈、本地方法栈、堆、方法区;</p><p>2. 优先栈上分配(逃逸分析)→ Eden → Survivor → 老年代;</p><p>3. 标记-清除、标记-复制、标记-整理;</p><p>5. 注意事项:</p><p>(1)Region 分区、可预测停顿、并发标记、整理无碎片</p><p>项目信息</p><p>项目名称:<spanth:text="${projectName}">projectName</span></p><divth:if="${not #lists.isEmpty(pages)}"> <divth:each="page:${pages}"> <h2th:text="${page}+'信息-->Start'">信息-->Start</h2> <divth:if="${not #lists.isEmpty(floors)}"> <divth:each="floor:${floors}"> <h2th:text="${floor.name}">floorName</h2> <p>数量:<spanth:text="${floor.number}">number</span></p> <p>方式:<spanth:text="${floor.connectType}">connectType</span></p> <p>情况:<spanth:text="${floor.partitions}">partitions</span></p> </div> </div> </div></div><divth:if="${not #lists.isEmpty(projectImages)}"> <h2>照片</h2> <divth:each="image:${projectImages}"> <divstyle="text-align: center"> <imgth:src="${image}"style="max-height: 50%;object-fit: contain;"alt="projectImage"/> </div> </div></div></body></html>
@Beanpublic ITextRenderer createTextRenderer() throws DocumentException, IOException { ITextRenderer renderer = new ITextRenderer(); final ClassPathResource fonts = new ClassPathResource("fonts/simsun.ttf"); ITextFontResolver resolver = renderer.getFontResolver(); resolver.addFont(fonts.getPath(), BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED); return renderer;}
@Autowired private TemplateEngine templateEngine;@Autowired private ITextRenderer renderer;@GetMapping("downPdf")public ResponseEntity<ByteArrayResource> downIndoorServices2Pdf(@RequestParam(name = "count",defaultValue = "50") Integer count) throws Exception { Context context = new Context(); context.setVariable("title", "【" + 1223 + "】安装明细表"); context.setVariable("projectName", "天安门项目"); List<KanchaSingleFloorInfoVo> floors = Lists.newArrayList(); SingleFloorInfoVo floorVo = SingleFloorInfoVo.builder() .floorId(100L) .number(2) .name("1号楼层") .connectType("WIFI") .partitions("1号分区、2号分区、3号分区") .build(); SingleFloorInfoVo floorVo1 = SingleFloorInfoVo.builder() .floorId(1000L) .number(20) .name("2号楼层") .connectType("WIFI") .partitions("1号分区、2号分区、3号分区") .build(); floors.add(floorVo); floors.add(floorVo1); context.setVariable("floors", floors); List<String> projectImages = Lists.newArrayList(); // 28kb image 30000->6.1mb cost 2 min,image url是存储在minio上 // 8.2Mb*100->8.2Mb 8.2Mb*300->8.3Mb 8.2Mb*500->8.3Mb 8.2Mb*500->8.5Mb for (int i = 0; i < count; i++) { projectImages.add( "http://127.0.0.1:9000/work/96b62cff9e4844d4a13098fc33138c45?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=minioadmin%2F20260423%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20260423T103341Z&X-Amz-Expires=86400&X-Amz-SignedHeaders=host&X-Amz-Signature=2f60c1e598eaf22f8f91c059cdaed9c55002c2f035a2adae7b4807a2f6f7a280"); } context.setVariable("projectImages", projectImages); List<Integer> pages = Lists.newArrayList(); for (int i = 0; i < 10; i++) { pages.add(i); } context.setVariable("pages", pages); String html = templateEngine.process("indoor-service-2-pdf-back", context); HttpHeaders headers = new HttpHeaders(); headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=indoor-service.pdf"); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); renderer.setDocumentFromString(html); renderer.getSharedContext().setPrint(Boolean.TRUE); renderer.getSharedContext().setInteractive(false); renderer.layout(); renderer.createPDF(outputStream); //释放资源 renderer.finishPDF(); return ResponseEntity.ok() .headers(headers) .contentType(MediaType.APPLICATION_PDF) .body(new ByteArrayResource(outputStream.toByteArray()));}
-Xms256m -Xmx256m -XX:+HeapDumpOnOutOfMemoryError
7. 编写shell脚本多次下载,验证程序是否报OOM#/bin/bashfor i in {1..100}; do wget 'http://localhost:8080/pdf/downPdf?count=100'; done


Caused by: java.lang.OutOfMemoryError: Java heap spaceat java.util.Arrays.copyOf(Arrays.java:3236) ~[na:1.8.0_212]at java.io.ByteArrayOutputStream.grow(ByteArrayOutputStream.java:118) ~[na:1.8.0_212]at java.io.ByteArrayOutputStream.ensureCapacity(ByteArrayOutputStream.java:93) ~[na:1.8.0_212]at java.io.ByteArrayOutputStream.write(ByteArrayOutputStream.java:153) ~[na:1.8.0_212]at java.io.BufferedOutputStream.write(BufferedOutputStream.java:122) ~[na:1.8.0_212]//jdk8-211,没用到上述方法,openjdk version "1.8.0_212" 中的源码//java.io.FileOutputStream#writeBytes(FilterOutputStream.java:)at java.io.FilterOutputStream.write(FilterOutputStream.java:97) ~[na:1.8.0_212]at com.lowagie.text.pdf.OutputStreamCounter.write(OutputStreamCounter.java:111) ~[openpdf-1.3.35.jar!/:na]at com.lowagie.text.pdf.PdfStream.toPdf(PdfStream.java:361) ~[openpdf-1.3.35.jar!/:na]at com.lowagie.text.pdf.PdfIndirectObject.writeTo(PdfIndirectObject.java:164) ~[openpdf-1.3.35.jar!/:na]at com.lowagie.text.pdf.PdfWriter$PdfBody.add(PdfWriter.java:410) ~[openpdf-1.3.35.jar!/:na]at com.lowagie.text.pdf.PdfWriter$PdfBody.add(PdfWriter.java:390) ~[openpdf-1.3.35.jar!/:na]at com.lowagie.text.pdf.PdfWriter$PdfBody.add(PdfWriter.java:343) ~[openpdf-1.3.35.jar!/:na]at com.lowagie.text.pdf.PdfWriter.addToBody(PdfWriter.java:776) ~[openpdf-1.3.35.jar!/:na]at com.lowagie.text.pdf.PdfWriter.add(PdfWriter.java:3051) ~[openpdf-1.3.35.jar!/:na]at com.lowagie.text.pdf.PdfWriter.addDirectImageSimple(PdfWriter.java:3026) ~[openpdf-1.3.35.jar!/:na]at com.lowagie.text.pdf.PdfWriter.addDirectImageSimple(PdfWriter.java:2953) ~[openpdf-1.3.35.jar!/:na]at com.lowagie.text.pdf.PdfContentByte.addImage(PdfContentByte.java:1317) ~[openpdf-1.3.35.jar!/:na]at com.lowagie.text.pdf.PdfContentByte.addImage(PdfContentByte.java:1230) ~[openpdf-1.3.35.jar!/:na]at org.xhtmlrenderer.pdf.ITextOutputDevice.drawImage(ITextOutputDevice.java:858) ~[flying-saucer-pdf-9.4.1.jar!/:na]at org.xhtmlrenderer.pdf.ITextImageElement.paint(ITextImageElement.java:72) ~[flying-saucer-pdf-9.4.1.jar!/:na]at org.xhtmlrenderer.pdf.ITextOutputDevice.paintReplacedElement(ITextOutputDevice.java:195) ~[flying-saucer-pdf-9.4.1.jar!/:na]at org.xhtmlrenderer.layout.Layer.paintReplacedElement(Layer.java:522) ~[flying-saucer-core-9.4.1.jar!/:na]at org.xhtmlrenderer.layout.Layer.paintReplacedElements(Layer.java:479) ~[flying-saucer-core-9.4.1.jar!/:na]at org.xhtmlrenderer.layout.Layer.paintAsLayer(Layer.java:453) ~[flying-saucer-core-9.4.1.jar!/:na]at org.xhtmlrenderer.render.BlockBox.paintInline(BlockBox.java:268) ~[flying-saucer-core-9.4.1.jar!/:na]at org.xhtmlrenderer.layout.Layer.paintInlineContent(Layer.java:268) ~[flying-saucer-core-9.4.1.jar!/:na]at org.xhtmlrenderer.layout.Layer.paint(Layer.java:325) ~[flying-saucer-core-9.4.1.jar!/:na]at org.xhtmlrenderer.pdf.ITextRenderer.paintPage(ITextRenderer.java:456) ~[flying-saucer-pdf-9.4.1.jar!/:na]at org.xhtmlrenderer.pdf.ITextRenderer.writePDF(ITextRenderer.java:404) ~[flying-saucer-pdf-9.4.1.jar!/:na]at org.xhtmlrenderer.pdf.ITextRenderer.createPDF(ITextRenderer.java:357) ~[flying-saucer-pdf-9.4.1.jar!/:na]at org.xhtmlrenderer.pdf.ITextRenderer.createPDF(ITextRenderer.java:289) ~[flying-saucer-pdf-9.4.1.jar!/:na]at com.henry.work.rest.PdfController.downIndoorServices2Pdf(downIndoorServices2Pdf.java:122) ~[classes!/:na]
2.升级pom到稳定版本,并优化代码,释放资源,解决OOM问题,多次执行亦不报错。3.jdk1.8+springboot2.7.18+flying-saucer-pdf9.+openpdf1.3.x源码无法通过后端解决图片旋转角度问题,如签名之类的,需要前端进行旋转正向,前端可对上传的原始图片进行压缩,减小存储规格和后端导出pdf规格。4.中文字体无法显示问题,需要引入simsun.ttf等中文字体,放在resource/fonts下,因在docker运行时,运行在/ 路径下,不能找到该路径文件,将该文件同时放一份与dockerfile同文件夹下,通过dockerfile文件进行复制COPY simsun.ttf /fonts/simsun.ttf