java利用模板渲染PDF报告

今天研究一下如何根据pdf模板渲染生成pdf文件,例如:导出公司资金年度报告。废话不多说,我先把代码端上来。

引入依赖

<?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.test</groupId>
<artifactId>pdf-template</artifactId>
<version>1.0-SNAPSHOT</version>
  <properties>
  <maven.compiler.source>8</maven.compiler.source>
  <maven.compiler.target>8</maven.compiler.target>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>
  <dependencies>
    <!-- FastJSON -->
      <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>fastjson</artifactId>
      <version>1.2.83</version>
      </dependency>
      <!-- OpenHTML to PDF -->
        <dependency>
        <groupId>com.openhtmltopdf</groupId>
        <artifactId>openhtmltopdf-core</artifactId>
        <version>1.0.10</version>
        </dependency>
        <dependency>
        <groupId>com.openhtmltopdf</groupId>
        <artifactId>openhtmltopdf-pdfbox</artifactId>
        <version>1.0.10</version>
        </dependency>
        <!-- 用于HTML解析和模板处理 -->
          <dependency>
          <groupId>org.jsoup</groupId>
          <artifactId>jsoup</artifactId>
          <version>1.15.3</version>
          </dependency>
          <!-- 工具类 -->
            <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.12.0</version>
            </dependency>
            <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.11.0</version>
            </dependency>
          </dependencies>
          <build>
            <plugins>
              <plugin>
              <groupId>org.apache.maven.plugins</groupId>
              <artifactId>maven-compiler-plugin</artifactId>
              <version>3.8.1</version>
                <configuration>
                <source>1.8</source>
                <target>1.8</target>
                </configuration>
              </plugin>
            </plugins>
          </build>
        </project>

创建报告类

import com.alibaba.fastjson.JSONObject;
public class ReportData {
// 公司名
private String companyName;
// 年度
private int fiscalYear;
// 总收入
private double totalRevenue;
// 总支出
private double totalExpenses;
// 净利润
private double netProfit;
// 总资产
private double assets;
// 总负债
private double liabilities;
// 所有者权益
private double equity;
public static ReportData fromJSONObject(JSONObject json) {
ReportData data = new ReportData();
data.setCompanyName(json.getString("companyName"));
data.setFiscalYear(json.getInteger("fiscalYear"));
data.setTotalRevenue(json.getDouble("totalRevenue"));
data.setTotalExpenses(json.getDouble("totalExpenses"));
data.setNetProfit(json.getDouble("netProfit"));
data.setAssets(json.getDouble("assets"));
data.setLiabilities(json.getDouble("liabilities"));
data.setEquity(json.getDouble("equity"));
return data;
}
// Getter和Setter方法
public String getCompanyName() { return companyName; }
public void setCompanyName(String companyName) { this.companyName = companyName; }
public int getFiscalYear() { return fiscalYear; }
public void setFiscalYear(int fiscalYear) { this.fiscalYear = fiscalYear; }
public double getTotalRevenue() { return totalRevenue; }
public void setTotalRevenue(double totalRevenue) { this.totalRevenue = totalRevenue; }
public double getTotalExpenses() { return totalExpenses; }
public void setTotalExpenses(double totalExpenses) { this.totalExpenses = totalExpenses; }
public double getNetProfit() { return netProfit; }
public void setNetProfit(double netProfit) { this.netProfit = netProfit; }
public double getAssets() { return assets; }
public void setAssets(double assets) { this.assets = assets; }
public double getLiabilities() { return liabilities; }
public void setLiabilities(double liabilities) { this.liabilities = liabilities; }
public double getEquity() { return equity; }
public void setEquity(double equity) { this.equity = equity; }
}

创建pdf渲染类

import com.openhtmltopdf.pdfboxout.PdfRendererBuilder;
import com.openhtmltopdf.extend.FSSupplier;
import com.openhtmltopdf.outputdevice.helper.BaseRendererBuilder;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import java.io.*;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class PdfGenerator {
public void generatePdf(ReportData data, String outputPath) throws Exception {
// 读取HTML模板
InputStream templateStream = getClass().getClassLoader()
.getResourceAsStream("template/report-template.html");
if (templateStream == null) {
throw new FileNotFoundException("HTML模板文件未找到");
}
String htmlTemplate = IOUtils.toString(templateStream, "UTF-8");
// 替换模板中的变量
String processedHtml = processTemplate(htmlTemplate, data);
// 使用Jsoup清理HTML,确保格式正确
Document doc = Jsoup.parse(processedHtml);
doc.outputSettings().syntax(Document.OutputSettings.Syntax.xml);
String cleanHtml = doc.html();
// 使用OpenHTMLToPDF生成PDF,配置中文字体
OutputStream os = null;
try {
os = new FileOutputStream(outputPath);
PdfRendererBuilder builder = new PdfRendererBuilder();
// 配置中文字体,如果字体没有则需要下载
InputStream fontStream = getClass().getClassLoader()
.getResourceAsStream("fonts/simsun.ttf");
if (fontStream != null) {
byte[] fontBytes = IOUtils.toByteArray(fontStream);
FSSupplier<InputStream> fontSupplier = () -> new ByteArrayInputStream(fontBytes);
  builder.useFont(fontSupplier, "SimSun", 400, BaseRendererBuilder.FontStyle.NORMAL, true);
  builder.useFont(fontSupplier, "SimSun", 400, BaseRendererBuilder.FontStyle.ITALIC, true);
  builder.useFont(fontSupplier, "SimSun", 700, BaseRendererBuilder.FontStyle.OBLIQUE, true);
  }
  builder.withHtmlContent(cleanHtml, null);
  builder.useFastMode();
  builder.toStream(os);
  builder.run();
  } finally {
  if (os != null) {
  try {
  os.close();
  } catch (IOException e) {
  e.printStackTrace();
  }
  }
  }
  }
  private String processTemplate(String html, ReportData data) {
  // 创建数字格式化器
  DecimalFormat df = new DecimalFormat("#,##0.00");
  // 准备替换变量
  Map<String, String> variables = new HashMap<String, String>();
    variables.put("companyName", data.getCompanyName());
    variables.put("fiscalYear", String.valueOf(data.getFiscalYear()));
    variables.put("totalRevenueFormatted", df.format(data.getTotalRevenue()));
    variables.put("totalExpensesFormatted", df.format(data.getTotalExpenses()));
    variables.put("netProfitFormatted", df.format(data.getNetProfit()));
    variables.put("assetsFormatted", df.format(data.getAssets()));
    variables.put("liabilitiesFormatted", df.format(data.getLiabilities()));
    variables.put("equityFormatted", df.format(data.getEquity()));
    // 计算财务比率
    double profitMargin = (data.getNetProfit() / data.getTotalRevenue()) * 100;
    double debtToAssetRatio = (data.getLiabilities() / data.getAssets()) * 100;
    double returnOnEquity = (data.getNetProfit() / data.getEquity()) * 100;
    variables.put("profitMargin", String.format("%.2f", profitMargin));
    variables.put("debtToAssetRatio", String.format("%.2f", debtToAssetRatio));
    variables.put("returnOnEquity", String.format("%.2f", returnOnEquity));
    // 根据数值正负设置CSS类
    variables.put("netProfitClass", data.getNetProfit() >= 0 ? "positive" : "negative");
    variables.put("profitMarginClass", profitMargin >= 0 ? "positive" : "negative");
    variables.put("returnOnEquityClass", returnOnEquity >= 0 ? "positive" : "negative");
    // 添加生成日期
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    variables.put("generationDate", sdf.format(new Date()));
    // 替换所有变量
    String result = html;
    for (Map.Entry<String, String> entry : variables.entrySet()) {
      String value = StringUtils.defaultString(entry.getValue(), "");
      result = result.replace("${" + entry.getKey() + "}", value);
      }
      return result;
      }
      }

创建Main测试渲染pdf

import com.alibaba.fastjson.JSONObject;
import com.test.builder.PdfGenerator;
import com.test.builder.ReportData;
import java.io.File;
public class Main {
public static void main(String[] args) {
try {
// 示例JSON数据
JSONObject jsonData = new JSONObject();
jsonData.put("companyName", "示例科技有限公司");
jsonData.put("fiscalYear", 2023);
jsonData.put("totalRevenue", 12500000.75);
jsonData.put("totalExpenses", 8500000.50);
jsonData.put("netProfit", 4000000.25);
jsonData.put("assets", 8500000.00);
jsonData.put("liabilities", 3000000.00);
jsonData.put("equity", 5500000.00);
// 从JSON创建数据对象
ReportData reportData = ReportData.fromJSONObject(jsonData);
// 生成PDF
PdfGenerator generator = new PdfGenerator();
String outputPath = new File("").getAbsolutePath() + "/企业资金年度报告.pdf";
generator.generatePdf(reportData, outputPath);
System.out.println("PDF生成成功,保存路径: " + outputPath);
} catch (Exception e) {
e.printStackTrace();
}
}
}

最重要的模板文件

<!DOCTYPE html>
  <html>
    <head>
      <meta charset="UTF-8" />
      <style>
        @page {
        size: A4;
        margin: 2cm;
        }
        body {
        font-family: SimSun, Arial, sans-serif;
        line-height: 1.6;
        color: #333;
        margin: 0;
        padding: 0;
        }
        .header {
        text-align: center;
        margin-bottom: 30px;
        border-bottom: 2px solid #1a5276;
        padding-bottom: 20px;
        }
        .company-name {
        font-size: 24pt;
        font-weight: bold;
        color: #1a5276;
        margin-bottom: 10px;
        }
        .report-title {
        font-size: 18pt;
        margin-bottom: 5px;
        }
        .fiscal-year {
        font-size: 14pt;
        }
        .section {
        margin-bottom: 25px;
        }
        .section-title {
        font-size: 16pt;
        font-weight: bold;
        color: #1a5276;
        border-left: 5px solid #1a5276;
        padding-left: 10px;
        margin: 20px 0 15px 0;
        }
        .financial-table {
        width: 100%;
        border-collapse: collapse;
        margin: 15px 0;
        font-family: SimSun, Arial, sans-serif;
        }
        .financial-table th, .financial-table td {
        border: 1px solid #ddd;
        padding: 10px;
        text-align: right;
        }
        .financial-table th {
        background-color: #f2f2f2;
        text-align: left;
        font-weight: bold;
        }
        .financial-table tr:nth-child(even) {
        background-color: #f9f9f9;
        }
        .highlight {
        font-weight: bold;
        color: #1a5276;
        }
        .footer {
        margin-top: 50px;
        text-align: right;
        font-size: 10pt;
        color: #666;
        }
        .signature-area {
        margin-top: 60px;
        border-top: 1px dashed #999;
        padding-top: 10px;
        }
        .positive {
        color: #28a745;
        }
        .negative {
        color: #dc3545;
        }
      </style>
    </head>
    <body>
        <div class="header">
      <div class="company-name">${companyName}</div>
      <div class="report-title">企业资金年度报告</div>
      <div class="fiscal-year">财政年度: ${fiscalYear}</div>
      </div>
        <div class="section">
      <div class="section-title">财务概要</div>
          <table class="financial-table">
          <tr>
          <th>指标</th>
          <th>金额 (元)</th>
          </tr>
          <tr>
          <td>总收入</td>
          <td>${totalRevenueFormatted}</td>
          </tr>
          <tr>
          <td>总支出</td>
          <td>${totalExpensesFormatted}</td>
          </tr>
            <tr class="highlight ${netProfitClass}">
          <td>净利润</td>
          <td>${netProfitFormatted}</td>
          </tr>
        </table>
      </div>
        <div class="section">
      <div class="section-title">资产负债表</div>
          <table class="financial-table">
          <tr>
          <th>项目</th>
          <th>金额 (元)</th>
          </tr>
          <tr>
          <td>总资产</td>
          <td>${assetsFormatted}</td>
          </tr>
          <tr>
          <td>总负债</td>
          <td>${liabilitiesFormatted}</td>
          </tr>
            <tr class="highlight">
          <td>所有者权益</td>
          <td>${equityFormatted}</td>
          </tr>
        </table>
      </div>
        <div class="section">
      <div class="section-title">财务分析</div>
      <p>基于${fiscalYear}年度的财务数据,${companyName}的财务状况如下:</p>
      <p>净利润率为: <span class="${profitMarginClass}">${profitMargin}%</span></p>
      <p>资产负债率为: ${debtToAssetRatio}%</p>
      <p>权益回报率为: <span class="${returnOnEquityClass}">${returnOnEquity}%</span></p>
      </div>
        <div class="signature-area">
      <p>总经理签字: ________________________</p>
      <p>财务总监签字: ________________________</p>
      <p>日期: ________________________</p>
      </div>
        <div class="footer">
        本报告生成时间: ${generationDate}
      </div>
    </body>
  </html>

我在网上找过很多种通过生成pdf的方式,但是要么先下载一个软件定义pdf模板文件然后通过代码实现模板渲染,要么就是通过工具如iText硬写,实在复杂。这个方式是我找了多种实现方式以及询问AI,类比这些实现方式得到的我觉得我最喜欢的方式。因为我也会写一点前端代码,而且这个方式不算太复杂,也不需要下载什么软件就先去定义一个pdf模板文件,希望对各位有所帮助。

posted @ 2025-12-04 22:21  clnchanpin  阅读(44)  评论(0)    收藏  举报