教程與原始碼:Spring Boot+Thymeleaf實現基於HTML發票/收據生成和下載功能
計費功能對於每個 SaaS 來說都是必不可少的,需要生成發票或收據。大多數架構都傾向於透過 API 呼叫來實現此功能,以獲得單一實現、一致性和減少客戶端負載等好處。使用 HTML 模板可以更輕鬆地堅持產品品牌。
在本文中,我將展示如何使用 Java Spring Boot 和 Thymeleaf 模板引擎從 HTML 模板生成 pdf 格式的訂閱收據。
需要開發的關鍵要求包括:
- 從 PDF 格式的 HTML 模板生成訂閱收據。
- .在單個 PDF 檔案中包含一個或多個收據。
- 建立 API 以下載收據。
控制器(端點)下載生成的收據。
@RestController @RequestMapping(<font>"api/v1/subscription") public class SubscriptionController { private final SubscriptionService subscriptionService; public SubscriptionController(SubscriptionService subscriptionService) { this.subscriptionService = subscriptionService; } @GetMapping("/receipt-binary") public ResponseEntity<byte[]> downloadSubscriptionReceipt() { return subscriptionService.downloadSubscriptionReceipts(); } }
|
讓我們定義缺失的 Subscription 和 SubscriptionService 類
public class Subscription implements Serializable { private Integer id; private String subscriptionPlan; private LocalDate startDate; private LocalDate endDate; private String description; private Double price;
<font>//removed constructor, getters and setters due to length<i> } @Service public class SubscriptionService {
//Converts HTML string to byte array<i> private byte[] generatePdfFromHtml(String html){ ByteArrayOutputStream output = new ByteArrayOutputStream(); HtmlConverter.convertToPdf(html, output); return output.toByteArray(); } }
|
我們將從基礎開始構建 SubscriptionService 類,首先是輔助私人函式。 PDF 以位元組陣列的形式返回,我們需要使用 generatePdfFromHtml 函式將 HTML 字串轉換為位元組陣列。 在資原始檔夾中建立名為 templates 的子資料夾,並新增名為 SubscriptionReceiptTemplate.html 的 HTML 模板。 在此下載模板 在需求 2 中,如果有多個訂閱物件,我們需要確保每個收據都顯示在一個新的 PDF 頁面上。
我發現最好的解決辦法是讓模板接受訂閱物件列表,即使只需要或只可用一個訂閱(而不是分別建立收據並將它們合併到一個 PDF 檔案中)。
<div class=<font>"invoice-box" data-th-each="subscription, iteration : ${subscriptions}"> . . . <!-- Reduced due to length Please download the file from Github --> <div data-th-if="${iteration.index + 1 < subscriptions.size()}" style="page-break-after: always;"></div>
|
在上面的 Thymeleaf HTML 模板中,data-th-each 用於迴圈遍歷訂閱物件。 HTML 程式碼段的最後一行使用了條件內聯 CSS,如果不是最後一次迭代,則每次迭代後都會中斷頁面。
我們首先需要將 Thymeleaf 模板轉換為字串,然後再轉換為位元組陣列。 在 SubscriptionService 類中新增以下程式碼,將模板轉換為字串。
private String parseSubscriptionTemplate(List<Subscription> subscriptions){ ClassLoaderTemplateResolver templateResolver = new ClassLoaderTemplateResolver(); templateResolver.setTemplateMode(TemplateMode.HTML); templateResolver.setSuffix(<font>".html");
Context context = new Context(); Map<String, Object> templateVariables = Map.of( "subscriptions", subscriptions, "userFirstName", "Foo", "userLastName","Bar", "userCompanyName", "BarFoo Services Ltd"); context.setVariables(templateVariables); SpringTemplateEngine templateEngine = new SpringTemplateEngine(); templateEngine.setTemplateResolver(templateResolver); return templateEngine.process("templates/SubscriptionReceiptTemplate", context); }
|
在上面的程式碼中,我們傳遞了一個 Subscription 物件和使用者值的列表,該列表將在執行時被模板使用。
ClassLoaderTemplateResolver 是 TemplateResolver 的一種型別,它有助於確定如何以及在何處查詢模板。 在這種情況下,模板必須位於 classpath 中(即資原始檔夾下)。 其他選項包括 FileTemplateResolver(如果需要將模板檔案儲存在其他地方)或 StringTemplateResolver(如果需要直接從資料庫等來源傳遞字串)。
現在,我們需要在 SubscriptionService 中新增以下程式碼,以完成 SubscriptionController 中引用的 downloadSubscriptionReceipts() 方法。
public ResponseEntity<byte[]> downloadSubscriptionReceipts(){ List<Subscription> subscriptions = new ArrayList<>() {}; subscriptions.add( new Subscription(1, <font>"Gold Package", LocalDate.of(2024, 7, 9), LocalDate.of(2025, 7,9), "Annual Subscription", 20000.0 )); String subscriptionPdfHtml = parseSubscriptionTemplate(subscriptions); byte[] pdf = generatePdfFromHtml(subscriptionPdfHtml);
String fileName = "subscription_receipt.pdf"; 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(pdf); }
|
最後,讓我們執行解決方案並測試終端。
原始碼:Github repository