在企业级应用开发中,PDF生成与打印是常见的业务需求。本文将详细介绍一个完整的后端生成PDF、前端接收并打印的实现方案,基于Java Spring Boot后端和React前端的实际项目代码。
这个方案需要解决以下几个关键问题:
从提供的代码来看,这是一个财务系统的打印模块,专门处理"存出保证金"等财务单据的打印需求。系统采用了前后端分离架构,后端使用Java Spring Boot,前端使用React。
后端采用典型的Spring MVC三层架构,首先看控制器层的实现:
java复制@RestController
@RequestMapping("/smartoc/financial")
@Slf4j
public class FinancialPrintController {
@Resource
private FinancialPrintTemplate financialPrintTemplate;
@GetMapping("/printDepositGuarantee/{recordId}/{objectType}")
public void printDepositGuarantee(
@PathVariable("recordId") String recordId,
@PathVariable("objectType") String objectType,
HttpServletResponse response) throws IOException {
log.info("打印请求参数 - recordId: {}, objectType: {}", recordId, objectType);
financialPrintTemplate.streamPdfToResponse(recordId, objectType, response);
}
}
关键设计要点:
@RestController标注这是一个RESTful控制器/smartoc/financial/printDepositGuarantee/{recordId}/{objectType},符合RESTful规范HttpServletResponse输出流,避免不必要的中间转换业务逻辑集中在FinancialPrintTemplate类中,主要完成以下功能:
java复制public void streamPdfToResponse(String recordId, String objectType, HttpServletResponse response) throws IOException {
// 1. 生成PDF文件实体
CmonFileEntity pdfFileEntity = generatePdf(recordId, objectType);
// 2. 使用文件存储服务读取PDF流
try (InputStream pdfStream = fileStoreService.openInputStream(pdfFileEntity);
OutputStream out = response.getOutputStream()) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = pdfStream.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
out.flush();
}
}
PDF生成的核心流程:
提示:这里使用了try-with-resources语法确保流正确关闭,避免了资源泄漏问题。
PDF生成采用了以下技术方案:
XWPFTemplate和自定义注解@TocTemplateSection实现Word模板的数据填充java复制@TocTemplateSection(name = "存出保证金", variables = DepositInGuaranteeData.class, path = DEPOSIT_IN_GUARANTEE_DOC_PATH)
public XWPFTemplate depositInGuaranteeDocTemplate(InputStream tplIn,
@RequestParam("objectId") String recordId,
@RequestParam("objectType") String objectType) {
DepositInGuaranteeData businessData = threadLocalData.get();
XWPFTemplate template = WPFTemplateKit.buildBody(tplIn, businessData);
threadLocalData.remove();
return template;
}
Word转PDF:通过cmonFileService.wordToPdf()方法实现转换,底层可能使用了Apache PDFBox或iText等库
数据获取:使用BeanCruder执行SQL查询获取业务数据,然后填充到DepositInGuaranteeData等DTO对象中
InputStream和OutputStream直接传输,避免内存中保存完整文件ThreadLocal存储业务数据,避免多线程问题前端使用React实现,核心打印代码如下:
javascript复制const printReceipt = async (event, btn) => {
if (!recordId || !paymentExternalType) {
Message.error('缺少必要参数,无法打印');
return;
}
btn.setLoading(true);
try {
// 1. 构建精确的PDF URL
const pdfUrl = `/smartoc/financial/printDepositGuarantee/${recordId}/${paymentExternalType}`;
// 2. 使用原生 fetch API 获取PDF二进制数据
const response = await fetch(pdfUrl, {
method: 'GET',
headers: {
'Accept': 'application/pdf'
}
});
// 3. 验证HTTP状态和内容类型
if (!response.ok) {
const errorText = await response.text();
console.error('后端返回错误:', errorText);
Message.error(`HTTP错误!状态: ${response.status}`);
}
const contentType = response.headers.get('content-type');
if (!contentType || !contentType.includes('application/pdf')) {
const errorText = await response.text();
console.error('响应内容类型错误:', contentType, errorText);
Message.error('后端返回的不是PDF文件');
}
// 4. 创建Blob并触发打印
const blob = await response.blob();
const blobUrl = URL.createObjectURL(blob);
// 5. 使用iframe实现静默打印
const printFrame = document.createElement('iframe');
printFrame.style.display = 'none';
printFrame.src = blobUrl;
document.body.appendChild(printFrame);
printFrame.onload = function() {
printFrame.contentWindow.print();
setTimeout(() => {
URL.revokeObjectURL(blobUrl);
document.body.removeChild(printFrame);
}, 2000);
};
} catch (err) {
Message.error(`打印失败:${err.message || '未知错误'}`);
} finally {
btn.setLoading(false);
}
};
Accept: application/pdf确保获取PDF格式问题1:生成的PDF内容不正确
可能原因:
解决方案:
DEPOSIT_IN_GUARANTEE_DOC_PATH是否正确DepositInGuaranteeData中的数据是否正确填充问题2:PDF生成性能差
优化建议:
问题1:打印对话框不弹出
可能原因:
解决方案:
问题2:大文件下载慢
优化建议:
这个方案在实际项目中运行稳定,能够满足企业级应用的PDF打印需求。通过前后端的合理设计和各种优化措施,既保证了功能完整性,又兼顾了性能和用户体验。