Springboot結(jié)合thymeleaf模板生成pdf文件的代碼示例
1. 導(dǎo)入依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>com.github.librepdf</groupId>
<artifactId>openpdf</artifactId>
<version>1.3.30</version>
</dependency>
<dependency>
<groupId>org.xhtmlrenderer</groupId>
<artifactId>flying-saucer-pdf-openpdf</artifactId>
<version>9.4.0</version>
</dependency>
2. 創(chuàng)建Html模板
在resource/template下創(chuàng)建模板文件,需要注意的是樣式必須寫在style標(biāo)簽內(nèi),不能寫在標(biāo)簽內(nèi)部,否則無(wú)效。
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="zh-CN">
<head>
<meta charset="UTF-8"/>
<title>處方簽</title>
<style>
@page {
size: 148mm 210mm;
margin: 0;
}
body {
font-family: "SimSun", sans-serif;
font-size: 13px;
line-height: 1.2;
margin: 0;
padding: 5mm;
width: 138mm;
height: 200mm;
-fs-pdf-font-embed: embed;
-fs-pdf-font-encoding: Identity-H;
}
.prescription-header {
text-align: center;
margin-bottom: 8mm;
font-size: 24px;
font-family: "KaiTi", sans-serif;
font-weight: bold;
}
.hospital-name {
margin-bottom: 1mm;
}
.rp-header {
font-size: 26px;
margin: 2mm 0;
}
.medicine-table {
width: 100%;
border-collapse: collapse;
margin: 2mm 0;
}
.medicine-table th,
.medicine-table td {
padding: 0.5mm;
text-align: left;
}
.medicine-table th {
font-weight: bold;
}
.signature-area {
margin-top: 4mm;
padding-top: 2mm;
}
.signature-table {
width: 100%;
border-collapse: collapse;
}
.signature-table td {
text-align: left;
vertical-align: top;
padding: 1mm;
}
.signature-title {
margin-bottom: 2mm;
}
.signature-image {
width: 40px;
height: 20px;
}
.dynamic-field {
font-weight: normal;
}
.border-bottom{
border-bottom: 1px solid #000;
margin-bottom: 1mm;
padding-bottom: 1mm;
}
.margin-top2{
margin-top: 2mm;
}
.margin-left20 {
margin-left: 20px;
}
.margin-left60 {
margin-left: 60px;
}
.thw{
width: 100px;
}
</style>
</head>
<body>
<div class="prescription-header">
<div class="hospital-name" th:text="${prescriptionInfo.hospitalName}">中山大學(xué)附屬第三醫(yī)院肇慶醫(yī)院</div>
<div class="prescription-title">互聯(lián)網(wǎng)醫(yī)院處方箋</div>
</div>
<div>
<div class="border-bottom">
<span>費(fèi)別:</span><span th:text="${prescriptionInfo.paymentType}">自費(fèi)</span>
<span class="margin-left20">醫(yī)??ㄌ?hào):</span><span class="txt-label-end" th:text="${prescriptionInfo.medicalInsuranceNo}">XXXXXXXXXX</span>
<span class="margin-left20">處方編號(hào):</span><span class="txt-label-end" th:text="${prescriptionInfo.prescriptionNo}">XXXXXXXXXXXX</span>
</div>
<div>
<span>姓名:</span><span class="txt-label-end" th:text="${patientInfo.name}">張三三</span>
<span class="margin-left60">性別:</span><span class="txt-label-end" th:text="${patientInfo.gender}">男</span>
<span class="margin-left60">年齡:</span><span class="txt-label-end" th:text="${patientInfo.age+ '歲'}">40歲</span>
<div class="margin-top2">
<span>門診號(hào):</span>
<span class="txt-label-end" th:text="${patientInfo.medicalRecordNo}">XXXXXXXXXXXXX</span>
<span class="margin-left60">科別:</span>
<span class="txt-label-end" th:text="${prescriptionInfo.department}">內(nèi)科互聯(lián)網(wǎng)醫(yī)院門診</span>
</div>
<div class="margin-top2">
<span>臨床診斷:</span><span th:text="${prescriptionInfo.clinicalDiagnosis}">發(fā)燒,肺炎,肺部感染</span>
</div>
<div class="margin-top2">
<span>開(kāi)具日期:</span><span th:text="${prescriptionInfo.issueDate}">2026-01-12 19:11:01</span>
</div>
<div class="margin-top2 border-bottom">
<span>住址/電話:</span><span th:text="${patientInfo.address + ' / ' + patientInfo.phone}">廣東省廣州市天河區(qū)鳳凰街道1號(hào) / 1300000000</span>
</div>
</div>
</div>
<div class="rp-header">Rp</div>
<table class="medicine-table">
<thead>
<tr>
<th>藥品名稱</th>
<th>規(guī)格</th>
<th>單次劑量</th>
<th>用法</th>
<th>頻次</th>
<th>總量</th>
<th>單價(jià)(元)</th>
<th>總價(jià)(元)</th>
</tr>
</thead>
<tbody>
<tr th:each="item : ${medicineItems}">
<td class="dynamic-field thw" th:text="${item.medicineName}">(基)氯硝西泮片(氯硝安定)(恩華)(精二)(甲)2mgx100片/瓶</td>
<td class="dynamic-field" th:text="${item.specification}">228mg * 24粒</td>
<td class="dynamic-field" th:text="${item.dosage}">2粒</td>
<td class="dynamic-field" th:text="${item.usage}">隨餐服用</td>
<td class="dynamic-field" th:text="${item.frequency}">每天三次</td>
<td class="dynamic-field" th:text="${item.quantity}">4盒</td>
<td class="dynamic-field" th:text="${item.unitPrice}">30.96</td>
<td class="dynamic-field" th:text="${item.totalPrice}">123.84</td>
</tr>
</tbody>
</table>
<div class="signature-area">
<table class="signature-table">
<tr>
<td>
<div class="signature-title">
<span class="doctor-label">醫(yī) 師:</span>
<img th:if="${medicalStaff.physician.signatureImage}" class="signature-image" th:src="'data:image/png;base64,' + ${medicalStaff.physician.signatureImage}" alt=""/>
</div>
</td>
<td class="signature-title">
<span style="margin-left: 60px;">藥品金額(元):</span>
<span class="dynamic-field" th:text="${totalAmount}">206.16</span>
</td>
</tr>
</table>
<table class="signature-table border-bottom">
<tr>
<td>
<div class="signature-title">
審核藥師:
<img th:if="${medicalStaff.physician.signatureImage}" class="signature-image" th:src="'data:image/png;base64,' + ${medicalStaff.reviewer.signatureImage}" alt=""/>
</div>
</td>
<td>
<div class="signature-title">
配藥藥師:
<img th:if="${medicalStaff.physician.signatureImage}" class="signature-image" th:src="'data:image/png;base64,' + ${medicalStaff.dispenser.signatureImage}" alt=""/>
</div>
</td>
<td>
<div class="signature-title">
核對(duì)、發(fā)藥藥師:
<img th:if="${medicalStaff.physician.signatureImage}" class="signature-image" th:src="'data:image/png;base64,' + ${medicalStaff.issuer.signatureImage}" alt=""/>
</div>
</td>
</tr>
</table>
</div>
<div>
<span>※注:處方金額以收費(fèi)時(shí)為準(zhǔn)!</span>
<span>藥師提示:</span>
<span> 1.請(qǐng)遵醫(yī)囑服藥;2.請(qǐng)繳費(fèi)后到藥房窗口取藥并點(diǎn)清藥品;3.處方當(dāng)日有效;4.發(fā)出藥品如無(wú)質(zhì)量問(wèn)題不予退換 </span>
</div>
</body>
</html>
3. 渲染模板并轉(zhuǎn)為pdf
package com.chinaunicom.medical.ihm.pub.service;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Validator;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.chinaunicom.medical.ihm.baseservice.api.dict.models.Base64FileVO;
import com.chinaunicom.medical.ihm.baseservice.service.FileService;
import com.chinaunicom.medical.ihm.core.utils.DesensitizedUtils;
import com.chinaunicom.medical.ihm.obs.healthcare.repository.model.Prescription;
import com.chinaunicom.medical.ihm.pub.mapper.PubPrescriptionMapper;
import com.chinaunicom.medical.ihm.pub.model.oo.*;
import com.chinaunicom.medical.ihm.pub.utils.MetaObjectUtil;
import com.lowagie.text.DocumentException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestParam;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
import org.xhtmlrenderer.pdf.ITextRenderer;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
@Slf4j
@Service
@RequiredArgsConstructor
public class PrescriptionPdfService {
private final TemplateEngine templateEngine;
private final PubPrescriptionMapper pubPrescriptionMapper;
private final FileService fileService;
public String generatePrescriptionPdf(@RequestParam Long prescriptionId) {
try {
log.info("開(kāi)始生成處方簽PDF ,處方編號(hào):{}", prescriptionId);
PrescriptionDTO prescriptionDTO = getPrintData(prescriptionId);
byte[] pdfBytes = generatePrescriptionPdf(prescriptionDTO);
String pdfFileId = fileService.upload(pdfBytes, prescriptionId + ".pdf", MediaType.APPLICATION_PDF_VALUE, SpringUtil.getApplicationName(), "prescription-pdf", null);
log.info("處方簽PDF生成成功,處方id:{} 文件id:{},文件大?。簕}字節(jié)", prescriptionId, pdfFileId, pdfBytes.length);
//設(shè)置文件id
pubPrescriptionMapper.update(new Prescription(), Wrappers.lambdaUpdate(Prescription.class).set(Prescription::getPdfFileId, pdfFileId).eq(Prescription::getId, prescriptionId));
return pdfFileId;
} catch (Exception e) {
log.error("生成處方簽PDF失敗", e);
}
return "";
}
public PrescriptionDTO getPrintData(Long prescriptionId){
//查詢處方
PrescriptionSlipDTO prescriptionSlipDTO = pubPrescriptionMapper.getPrescriptionSlipInfoById(prescriptionId);
Validator.validateTrue(Objects.nonNull(prescriptionSlipDTO), "處方不存在");
//查詢藥品
List<PrescriptionSlipDrugDTO> prescriptionSlipDrugInfoList = pubPrescriptionMapper.getPrescriptionSlipDrugInfoById(prescriptionId);
//查詢頻次編碼
List<FrequencyDict> frequencyDict = MetaObjectUtil.listByCode("Frequency_dict", FrequencyDict.class);
Map<String, String> freqMap = CollUtil.emptyIfNull(frequencyDict).stream().collect(Collectors.toMap(FrequencyDict::getFrequencyCode, FrequencyDict::getFrequencyName, (oldValue, newValue) -> newValue));
//查詢用法編碼
List<DrugUsageDict> drugUsageDict = MetaObjectUtil.listByCode("Drug_usage_code", DrugUsageDict.class);
Map<String, String> drugUsageMap = CollUtil.emptyIfNull(drugUsageDict).stream().collect(Collectors.toMap(DrugUsageDict::getDrugUsageCode, DrugUsageDict::getDrugUsageName, (oldValue, newValue) -> newValue));
PrescriptionDTO prescriptionDTO = new PrescriptionDTO();
//設(shè)置患者信息
PrescriptionDTO.PatientInfo patientInfo = new PrescriptionDTO.PatientInfo();
{
patientInfo.setName(StrUtil.emptyIfNull(prescriptionSlipDTO.getPatientName()));
if(Objects.nonNull(prescriptionSlipDTO.getPatientGender())){
patientInfo.setGender(StrUtil.emptyIfNull(prescriptionSlipDTO.getPatientGender().getDesc()));
}
patientInfo.setAge(prescriptionSlipDTO.getPatientAge());
patientInfo.setMedicalRecordNo(prescriptionSlipDTO.getTreatmentNo());
patientInfo.setAddress(prescriptionSlipDTO.getPatientAddress());
if(StrUtil.isNotBlank(prescriptionSlipDTO.getPatientPhone())){
patientInfo.setPhone(DesensitizedUtils.decrypt(prescriptionSlipDTO.getPatientPhone()));
}
}
//設(shè)置處方信息
PrescriptionDTO.PrescriptionInfo prescriptionInfo = new PrescriptionDTO.PrescriptionInfo();
{
//醫(yī)院名稱
prescriptionInfo.setHospitalName(prescriptionSlipDTO.getHospitalName());
//費(fèi)別
if(prescriptionSlipDTO.getTreatmentType() != null){
prescriptionInfo.setPaymentType(StrUtil.emptyIfNull(prescriptionSlipDTO.getTreatmentType().getDesc()));
}else {
prescriptionInfo.setPaymentType("");
}
//醫(yī)保卡號(hào)
prescriptionInfo.setMedicalInsuranceNo("");
//處方編號(hào)
prescriptionInfo.setPrescriptionNo(prescriptionSlipDTO.getPrescriptionNo());
//科室名稱
prescriptionInfo.setDepartment(prescriptionSlipDTO.getDepartmentName());
//臨床診斷
prescriptionInfo.setClinicalDiagnosis(prescriptionSlipDTO.getDiagnoseName());
//開(kāi)方日期
prescriptionInfo.setIssueDate(prescriptionSlipDTO.getCreateTime());
}
//設(shè)置藥品信息
List<PrescriptionDTO.MedicineItem> medicineItems = prescriptionSlipDrugInfoList.stream().map(drug -> {
PrescriptionDTO.MedicineItem info = new PrescriptionDTO.MedicineItem();
info.setMedicineName(StrUtil.emptyIfNull(drug.getDrugName()));
info.setSpecification(StrUtil.emptyIfNull(drug.getDrugSpec()));
//單次劑量
info.setDosage(StrUtil.join("",drug.getSingleDose(),drug.getDosageUnitCode()));
//用法
info.setUsage(StrUtil.emptyIfNull(drugUsageMap.get(drug.getDrugUsageCode())));
//頻次
info.setFrequency(StrUtil.emptyIfNull(freqMap.getOrDefault(drug.getFrequencyCode(),drug.getFrequencyCode())));
info.setQuantity(drug.getAmount());
info.setUnitPrice(ObjectUtil.defaultIfNull(drug.getPrice(), BigDecimal.ZERO));
info.setTotalPrice(ObjectUtil.defaultIfNull(drug.getTotalPrice(),BigDecimal.ZERO));
return info;
}).toList();
//設(shè)置患者信息
prescriptionDTO.setPatientInfo(patientInfo);
//設(shè)置處方信息
prescriptionDTO.setPrescriptionInfo(prescriptionInfo);
//總金額
prescriptionDTO.setTotalAmount(prescriptionSlipDTO.getTotalAmount());
prescriptionDTO.setMedicineItems(medicineItems);
PrescriptionDTO.MedicalStaff medicalStaff = new PrescriptionDTO.MedicalStaff();
//醫(yī)生簽名
PrescriptionDTO.StaffInfo physician = new PrescriptionDTO.StaffInfo();
medicalStaff.setPhysician(physician);
try {
Base64FileVO doctorFileBase64 = fileService.getFileBase64(prescriptionSlipDTO.getDoctorFileId(),null,null);
if(Objects.nonNull(doctorFileBase64)){
physician.setSignatureImage(doctorFileBase64.getBase64Str());
}
} catch (Exception e) {
}
//審核藥師簽名
PrescriptionDTO.StaffInfo reviewer = new PrescriptionDTO.StaffInfo();
medicalStaff.setReviewer(reviewer);
try {
if(StrUtil.isNotBlank(prescriptionSlipDTO.getAuditFileId())){
Base64FileVO reViewFileBase64 = fileService.getFileBase64(prescriptionSlipDTO.getAuditFileId(),null,null);
if(Objects.nonNull(reViewFileBase64)){
reviewer.setSignatureImage(reViewFileBase64.getBase64Str());
}
}
} catch (Exception e) {
}
PrescriptionDTO.StaffInfo dispenser = new PrescriptionDTO.StaffInfo();
dispenser.setSignatureImage("");
medicalStaff.setDispenser(dispenser);
PrescriptionDTO.StaffInfo issuer = new PrescriptionDTO.StaffInfo();
issuer.setSignatureImage("");
medicalStaff.setIssuer(issuer);
prescriptionDTO.setMedicalStaff(medicalStaff);
return prescriptionDTO;
}
/**
* 生成處方簽PDF
* @param prescriptionDTO 處方數(shù)據(jù)
* @return PDF字節(jié)數(shù)組
*/
public byte[] generatePrescriptionPdf(PrescriptionDTO prescriptionDTO) {
try {
// 渲染HTML模板
String htmlContent = renderHtmlTemplate(prescriptionDTO);
// 將HTML轉(zhuǎn)換為PDF
return convertHtmlToPdf(htmlContent);
} catch (Exception e) {
log.error("生成處方簽PDF失敗", e);
throw new RuntimeException("生成處方簽PDF失敗: " + e.getMessage(), e);
}
}
/**
* 渲染HTML模板
* @param prescriptionDTO 處方數(shù)據(jù)
* @return HTML內(nèi)容
*/
private String renderHtmlTemplate(PrescriptionDTO prescriptionDTO) {
Context context = new Context();
// 設(shè)置模板變量
context.setVariable("patientInfo", prescriptionDTO.getPatientInfo());
context.setVariable("prescriptionInfo", prescriptionDTO.getPrescriptionInfo());
context.setVariable("medicineItems", prescriptionDTO.getMedicineItems());
context.setVariable("medicalStaff", prescriptionDTO.getMedicalStaff());
context.setVariable("totalAmount", prescriptionDTO.getTotalAmount());
// 渲染模板
return templateEngine.process("prescription-template", context);
}
/**
* 將HTML轉(zhuǎn)換為PDF
* @param htmlContent HTML內(nèi)容
* @return PDF字節(jié)數(shù)組
*/
private byte[] convertHtmlToPdf(String htmlContent) throws DocumentException, IOException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try {
ITextRenderer renderer = new ITextRenderer();
// 設(shè)置字體支持
setFonts(renderer);
// 設(shè)置頁(yè)面大小
renderer.setDocumentFromString(htmlContent);
renderer.layout();
// 生成PDF
renderer.createPDF(outputStream);
} finally {
if (outputStream != null) {
outputStream.close();
}
}
return outputStream.toByteArray();
}
/**
* 設(shè)置中文字體支持
* @param renderer PDF渲染器
*/
private void setFonts(ITextRenderer renderer) {
List<String> fontNames = Arrays.asList("simsun.ttc", "simkai.ttf");
for (String fontName : fontNames) {
try {
// 獲取字體文件路徑
java.net.URL fontUrl = getClass().getClassLoader().getResource("font/"+fontName);
if (fontUrl != null) {
//注冊(cè)字體
log.info("加載字體: {}", fontName);
renderer.getFontResolver().addFont(fontUrl.getPath(), com.lowagie.text.pdf.BaseFont.IDENTITY_H, com.lowagie.text.pdf.BaseFont.NOT_EMBEDDED);
}
} catch (Exception e) {
log.error("加載字體失敗: {}", e.getMessage());
}
}
}
}
總結(jié)
到此這篇關(guān)于Springboot結(jié)合thymeleaf模板生成pdf文件的文章就介紹到這了,更多相關(guān)Springboot thymeleaf生成pdf文件內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringMVC結(jié)合Jcrop實(shí)現(xiàn)圖片裁剪
這篇文章主要介紹了SpringMVC結(jié)合Jcrop實(shí)現(xiàn)圖片裁剪的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-12-12
IntelliJ IDEA 報(bào)錯(cuò):找不到包或者找不到符號(hào)的問(wèn)題及解決方案
這篇文章主要介紹了IntelliJ IDEA 報(bào)錯(cuò):找不到包或者找不到符號(hào)的問(wèn)題及解決方案,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-08-08
淺析Spring boot 中 logback 配置<springPropert
這篇文章主要介紹了淺析Spring boot 中 logback 配置<springProperty> 讀取application.properties 中的屬性,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-02-02
Java遍歷輸出指定目錄、樹(shù)形結(jié)構(gòu)所有文件包括子目錄下的文件
這篇文章主要介紹了Java遍歷輸出指定目錄、樹(shù)形結(jié)構(gòu)下的所有文件包括子目錄中的文件,需要的朋友可以參考下2015-07-07
java中JSONObject轉(zhuǎn)換為HashMap(方法+main方法調(diào)用實(shí)例)
這篇文章主要介紹了java中JSONObject轉(zhuǎn)換為HashMap(方法+main方法調(diào)用實(shí)例),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11
RestTemplate使用不當(dāng)引發(fā)的問(wèn)題及解決
這篇文章主要介紹了RestTemplate使用不當(dāng)引發(fā)的問(wèn)題及解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10

