Angular前端传HTML至Spring Boot生成PDF并下载方案问询
解决方案:Spring Boot接收前端HTML并生成可下载的PDF
我明白你的需求:之前用PHP mPDF把前端渲染好的测验结果HTML转成PDF,现在迁移到Spring Boot,已经搞定了本地模板生成PDF的功能,现在要实现接收前端传来的已填充数据的HTML,生成PDF并让用户直接下载。下面是完整的实现步骤和代码:
1. 核心思路调整
你之前的代码是用Thymeleaf渲染后端模板,但现在场景是前端已经把测验结果渲染成完整的HTML了,所以我们需要:
- 接收前端POST过来的HTML字符串
- 把HTML转成Flying Saucer支持的XHTML
- 用Flying Saucer把XHTML转成PDF字节流
- 通过Spring MVC返回PDF字节流,设置响应头让浏览器触发下载
2. 依赖准备
确保你的pom.xml里有这些依赖(Flying Saucer、JTidy、Spring Web):
<!-- Flying Saucer for PDF generation --> <dependency> <groupId>org.xhtmlrenderer</groupId> <artifactId>flying-saucer-pdf-itext5</artifactId> <version>9.1.22</version> </dependency> <!-- JTidy for HTML to XHTML conversion --> <dependency> <groupId>net.sf.jtidy</groupId> <artifactId>jtidy</artifactId> <version>r938</version> </dependency> <!-- Spring Web (如果你还没加的话) --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
3. 修改PdfService:处理传入的HTML并返回PDF字节流
把原来的服务改成专注于处理HTML转PDF的逻辑,不再依赖Thymeleaf模板:
import org.springframework.stereotype.Service; import org.xhtmlrenderer.pdf.ITextRenderer; import org.xhtmlrenderer.pdf.ITextFontResolver; import com.lowagie.text.pdf.BaseFont; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.UnsupportedEncodingException; import org.w3c.tidy.Tidy; @Service public class PdfService { private static final String UTF_8 = "UTF-8"; // 接收前端传来的HTML字符串,返回PDF字节数组 public byte[] generatePdfFromHtml(String htmlContent) throws Exception { // 1. 把普通HTML转成XHTML(Flying Saucer只支持XHTML) String xHtml = convertToXhtml(htmlContent); // 2. 初始化Flying Saucer渲染器 ITextRenderer renderer = new ITextRenderer(); // 如果需要自定义字体,可以在这里添加(比如你之前的Code39.ttf) ITextFontResolver fontResolver = renderer.getFontResolver(); fontResolver.addFont("classpath:Code39.ttf", BaseFont.IDENTITY_H, BaseFont.EMBEDDED); // 3. 设置要渲染的XHTML内容,baseUrl可以处理HTML里的相对资源(如果有的话) // 如果前端HTML里的资源是绝对路径或base64,这里可以传null renderer.setDocumentFromString(xHtml, null); renderer.layout(); // 4. 把PDF写入字节流(不写入本地文件,直接返回给前端) ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); renderer.createPDF(outputStream); outputStream.close(); return outputStream.toByteArray(); } // HTML转XHTML的工具方法 private String convertToXhtml(String html) throws UnsupportedEncodingException { Tidy tidy = new Tidy(); tidy.setInputEncoding(UTF_8); tidy.setOutputEncoding(UTF_8); tidy.setXHTML(true); tidy.setShowWarnings(false); // 关闭警告,避免日志冗余 tidy.setQuiet(true); ByteArrayInputStream inputStream = new ByteArrayInputStream(html.getBytes(UTF_8)); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); tidy.parseDOM(inputStream, outputStream); return outputStream.toString(UTF_8); } }
4. 创建Controller:接收前端请求并返回可下载的PDF
写一个POST接口,接收前端传来的HTML,调用服务生成PDF,然后设置响应头让浏览器下载:
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import java.util.UUID; @RestController public class PdfController { private final PdfService pdfService; // 构造注入服务 public PdfController(PdfService pdfService) { this.pdfService = pdfService; } @PostMapping("/api/generate-quiz-pdf") public ResponseEntity<byte[]> generateQuizPdf(@RequestBody String htmlContent) { try { // 调用服务生成PDF字节数组 byte[] pdfBytes = pdfService.generatePdfFromHtml(htmlContent); // 设置响应头,让浏览器触发下载 HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_PDF); // 生成唯一文件名,避免重复 String fileName = "quiz-result-" + UUID.randomUUID() + ".pdf"; headers.setContentDispositionFormData("attachment", fileName); headers.setContentLength(pdfBytes.length); return new ResponseEntity<>(pdfBytes, headers, HttpStatus.OK); } catch (Exception e) { e.printStackTrace(); return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); } } }
5. 前端(Angular)调用示例
在Angular里,你可以这样发送渲染好的HTML并触发下载:
import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Injectable } from '@angular/core'; @Injectable({ providedIn: 'root' }) export class PdfService { constructor(private http: HttpClient) {} downloadQuizPdf(htmlContent: string): void { const headers = new HttpHeaders({ 'Content-Type': 'text/plain' }); this.http.post('/api/generate-quiz-pdf', htmlContent, { headers, responseType: 'blob' }).subscribe(blob => { // 创建下载链接 const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'quiz-result.pdf'; document.body.appendChild(a); a.click(); // 清理资源 window.URL.revokeObjectURL(url); document.body.removeChild(a); }); } }
关键注意事项
- HTML规范:Flying Saucer对HTML的规范性要求较高,尽量让前端生成的HTML是标准的(比如闭合标签、正确的嵌套),JTidy会帮你修复一些问题,但复杂的HTML可能需要手动调整。
- 资源处理:如果你的HTML里有图片或外部CSS,建议把它们转成base64嵌入HTML,或者确保后端能访问到这些资源的绝对路径,否则PDF里会显示缺失。
- 字体支持:如果需要特殊字体(比如你之前的Code39条码字体),要把字体文件放在
src/main/resources下,然后通过classpath:路径引用。
内容的提问来源于stack exchange,提问作者Malek Anas
相关产品推荐
相关产品推荐

