You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 08:03:16