Spring Boot + iTextPdf:3步实现HTML模板生成pdf

Spring Boot + iTextPdf:3步实现HTML模板生成pdf

1. 简介

在企业级应用中,PDF生成是常见的文档处理需求。无论是订单确认、发票开具、合同签署,还是报表导出,用户普遍期望获得格式规范、内容完整且可打印的PDF文件作为正式凭证或存档依据。相比HTML或Excel,PDF具有跨平台一致性、防篡改性强、布局固定等优势,尤其适用于金融、电商、政务等对文档合规性要求较高的场景。

本篇文章将通过通过itextpdf + html模板生成pdf文档(支持外部资源)。

2.实战案例

2.1 环境准备

<dependency>
  <groupId>com.itextpdf</groupId>
  <artifactId>kernel</artifactId>
  <version>9.3.0</version>
</dependency>
<dependency>
  <groupId>com.itextpdf</groupId>
  <artifactId>html2pdf</artifactId>
  <version>6.2.1</version>
</dependency>
<dependency>
  <groupId>ognl</groupId>
  <artifactId>ognl</artifactId>
  <version>3.4.7</version>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

我们将通过thymeleaf模板技术生成PDF。thymeleaf支持各种强劲的表达式,超级适合生成各种复杂的文档。

配置thymeleaf

spring:
  thymeleaf:
    mode: HTML
    encoding: UTF-8
    prefix: classpath:/templates/
    suffix: .html
    cache: false

2.2 准备数据 & 模板

public class Receipt {
  // 收单机构信息
  private String scope;
  // 商户信息
  private String merchantName;
  private String merchantId;
  private String terminalId;
  private String merchantCity;
  // 交易基本信息
  private String stan; // 系统跟踪号
  private String transactionDate; // 交易日期
  private String transactionType; // 交易类型
  private BigDecimal requestAmount; // 交易金额
  // 交易详情
  private String mcc; // 商户类别码
  private String scheme; // 卡组织
  private String maskedPan; // 掩码卡号
  private String acquirer; // 收单机构BIN
  // 系统信息
  private String approvalNumber; // 授权号
  private String processingCode; // 处理代码
  private String responseCode; // 响应码
  private String retrievalNumber; // 检索参考号
  private String displayMessage; // 交易状态信息
  // getters, setters
}

模板定义

<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>收据副本</title>
  <link href="/google.css" rel='stylesheet' type='text/css' />
  <style>
    html, body {
      font-family: "Microsoft YaHei", sans-serif;;
    }
    .wrapper {
      width: 440px;
      height: 1024px;
    }
    .bolded {
      font-weight: bolder;
    }
    .content {
      width: 45vh;
      padding: 20px 40px;
    }
    .section {
      border-bottom: 2px dashed black;
      padding: 20px 0px;
    }
    .section-item {
      margin-bottom: 20px;
      overflow-wrap: break-word;
    }
    .section-item:last-child {
      margin-bottom: 0px;
    }
    small:last-child {
      text-align: right;
      float: right;
    }
  </style>
</head>
<body>
<div class="wrapper">
  <div class="content">
    <h1 style="text-align: center; border-bottom: 2px dashed black; padding-bottom: 20px;">** 收据副本 **<img src="/seo.png" width="32" height="32"/></h1>
    <div class="section">
      <div class="section-item">
        <small>收单机构名称:</small>
        <small style="word-wrap: anywhere" th:text="${receipt.scope}"></small>
      </div>
      <div class="section-item">
        <small>商户名称:</small>
        <small style="word-wrap: anywhere" th:text="${receipt.merchantName}"></small>
      </div>
      <div class="section-item">
        <small>商户ID:</small>
        <small th:text="${receipt.merchantId}"></small>
      </div>
      <div class="section-item">
        <small>终端ID:</small>
        <small th:text="${receipt.terminalId}"></small>
      </div>
      <div class="section-item">
        <small>商户城市:</small>
        <small th:text="${receipt.merchantCity}"></small>
      </div>
    </div>
    <div class="section">
      <div class="section-item">
        <small>系统跟踪号(STAN):</small>
        <small th:text="${receipt.stan}"></small>
      </div>
      <div class="section-item">
        <small>交易日期:</small>
        <small th:text="${receipt.transactionDate}"></small>
      </div>
    </div>
    <div class="section">
      <div class="section-item">
        <small>交易类型:</small>
        <small th:text="${receipt.transactionType}"></small>
      </div>
      <div class="section-item">
        <small>交易金额:</small>
        <small th:text="'NGN ' + ${receipt.requestAmount}"><b></b></small>
      </div>
    </div>
    <div class="section">
      <div class="section-item">
        <small>商户类别码(MCC):</small>
        <small th:text="${receipt.mcc}"></small>
      </div>
      <div class="section-item">
        <small>卡组织:</small>
        <small th:text="${receipt.scheme}"></small>
      </div>
      <div class="section-item">
        <small>卡号:</small>
        <small th:text="${receipt.maskedPan}"></small>
      </div>
      <div class="section-item">
        <small>收单机构识别码(BIN)</small>
        <small th:text="${receipt.acquirer}"></small>
      </div>
    </div>
    <div class="section">
      <div class="section-item">
        <small>授权号:</small>
        <small th:text="${receipt.approvalNumber}"></small>
      </div>
      <div class="section-item">
        <small>处理代码:</small>
        <small th:text="${receipt.processingCode}"></small>
      </div>
      <div class="section-item">
        <small>响应码:</small>
        <small th:text="${receipt.responseCode}"></small>
      </div>
      <div class="section-item">
        <small>检索参考号(RRN):</small>
        <small th:text="${receipt.retrievalNumber}"></small>
      </div>
      <div class="section-item">
        <small>状态</small>
        <small th:text="${receipt.displayMessage}"></small>
      </div>
    </div>
    <h3 style="text-align: center; padding-bottom: 20px;">感谢您的光临!</h3>
  </div>
</div>
</body>
</html>

在该模板中,我们使用了外部的css资源以及图片资源。

如下是我们需要准备的资源

Spring Boot + iTextPdf:3步实现HTML模板生成pdf

2.3 Controller接口定义

@RestController
@RequestMapping("/html2pdf")
public class Html2PdfController {
  private final ITemplateEngine templateEngine ;
  public Html2PdfController(ITemplateEngine templateEngine) {
    this.templateEngine = templateEngine;
  }
  @GetMapping("/download")
  public ResponseEntity<byte[]> downloadEJournalFile( HttpServletRequest request,
      HttpServletResponse response) throws Exception {
    // 1.准备数据
    Map<String, Object> variables = Map.of("receipt", getData()) ;
    // 2.创建上下文并添加变量(模板需要的数据)
    Context context = new Context();
    context.setVariables(variables);
    // 2.1.获取解析后的模板内容
    String receiptTemplate = templateEngine.process("receipt", context);
    // 3.配置模板中使用的基础资源信息
    ConverterProperties converterProperties = new ConverterProperties();
    // 如果你的模板中引用了样式,那么你需要设置
    converterProperties.setBaseUri("http://localhost:8080") ;
    // 3.1.设置字体
    FontProvider fontProvider = new FontProvider();
    // 加载系统所有字体(最简单方式)
    fontProvider.addSystemFonts() ;
    converterProperties.setFontProvider(fontProvider) ;
    // 3.2.HTML到PDF转换
    ByteArrayOutputStream target = new ByteArrayOutputStream();
    HtmlConverter.convertToPdf(receiptTemplate, target, converterProperties);
    // 4.设置下载信息
    String fileName = URLEncoder.encode("收据明细.pdf", "UTF-8");
    HttpHeaders header = new HttpHeaders();
    header.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + fileName);
    header.add("Cache-Control", "no-cache, no-store, must-revalidate") ;
    header.add("Pragma", "no-cache") ;
    header.add("Expires", "0") ;
    return ResponseEntity.ok()
        .headers(header)
        .contentType(MediaType.APPLICATION_PDF)
        .body(target.toByteArray()) ;
  }
  public Receipt getData() {
    return ... ;
  }
}

该接口中已经详细的说明关键代码的作用。

2.4 测试

调用上面接口后,生成的pdf如下:

Spring Boot + iTextPdf:3步实现HTML模板生成pdf

图片、样式都正确的加载。

© 版权声明

相关文章

暂无评论

none
暂无评论...