Spring Java使用OpenPDF生成PDF仅IDM可下载问题求解
问题根因
- 响应体冲突:当前方法既通过
response.getOutputStream()写入了PDF字节流,又返回了带字符串内容的ResponseEntity,导致最终响应内容是PDF字节+额外字符串,整体格式不合法,浏览器无法识别为可下载的PDF文件。 - 响应头缺失:缺少缓存控制头、跨域头暴露配置,部分浏览器无法准确判断响应内容类型,无法触发下载逻辑。
- 文件名编码问题:原文件名未做标准化编码,含特殊字符时会导致
Content-Disposition头失效。 - 导出方法流处理不完整:仅关闭了Document对象,未关闭PdfWriter,可能存在PDF字节未完全写入响应流的问题。
- 前端调用方式错误:如果是AJAX调用接口未指定blob响应类型,浏览器会默认按文本解析响应,直接输出字节码。
可行解决方案
1. 修复Controller层代码
@CrossOrigin(origins = "*", exposedHeaders = "Content-Disposition") // 跨域场景下暴露文件头给前端 @ApiOperation(value = "Generate Customer Request PDF ") @PostMapping(path = { "/customer/generatePDF" }, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_PDF_VALUE) @ApiResponses(value = { @ApiResponse(code = 200, message = "Successful"), @ApiResponse(code = 400, message = "Bad Request, Validation Errors, ...", response = BadRequest.class), @ApiResponse(code = 401, message = "Unauthorized", response = Unauthorized.class), @ApiResponse(code = 500, message = "Internal server error", response = InternalError.class) }) // 改为void,不要返回额外的ResponseEntity内容,避免响应体冲突 public void generateCustomerRequestPDF(@RequestBody CustomerRequestDto customersRequestDto, HttpServletResponse response) throws BusinessException, DocumentException, IOException, ParseException, TransformerException{ DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); // create PDF object List<CustomerRequestPDF> requests = new ArrayList<>(); CustomerRequestPDF c = new CustomerRequestPDF(); c.setName(customersRequestDto.getCusName()); c.setAmount(customersRequestDto.getAmount()+" "+customersRequestDto.getCurrencySymbol()); c.setPaymentDate( customersRequestDto.getCompleteAt().format(formatter) ); c.setPaymentType(customersRequestDto.getOperationTypeName()); c.setPaymentReference(customersRequestDto.getRrn()); c.setPaymentNature(customersRequestDto.getGroupStatus()); requests.add(c); CustomerPDFExporter exporter = new CustomerPDFExporter(requests); // 先重置响应,避免之前的缓存内容干扰 response.reset(); // 标准化编码文件名,兼容所有浏览器 String fileName = URLEncoder.encode("recu_" + c.getPaymentReference() + "_"+ LocalDateTime.now() + ".pdf", StandardCharsets.UTF_8.name()); response.setContentType("application/pdf"); response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + fileName); // 添加缓存控制头 response.setHeader("Pragma", "no-cache"); response.setHeader("Cache-Control", "no-cache"); exporter.export(response); // 手动刷新输出流,保证所有字节写入响应 response.getOutputStream().flush(); }
2. 修复导出方法的流处理逻辑
public void export(HttpServletResponse response) throws DocumentException, IOException { Document document = new Document(PageSize.A4); // 持有PdfWriter引用后续关闭 PdfWriter writer = PdfWriter.getInstance(document, response.getOutputStream()); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); document.open(); Font font = FontFactory.getFont(FontFactory.HELVETICA_BOLD); font.setSize(25); font.setColor(Color.BLUE); // titre du recu Paragraph p = new Paragraph("Recu du paiement client", font); p.setAlignment(Paragraph.ALIGN_CENTER); document.add(p); // date du recu font.setSize(12); font.setColor(Color.BLACK); Paragraph p2 = new Paragraph("Recu le : "+LocalDateTime.now().format(formatter), font); p2.setAlignment(Paragraph.ALIGN_LEFT); p2.setSpacingBefore(50); document.add(p2); String nature = ""; String nom = ""; String montant = ""; String date = ""; String type = ""; String rrn = ""; for(CustomerRequestPDF c : requests) { if(c.getPaymentNature().equals("L")) { nature = "Paiement effectué en masse"; } else { nature = "Paiement effectué individuel"; } nom = c.getName(); montant = c.getAmount(); date = c.getPaymentDate(); type = c.getPaymentType(); rrn = c.getPaymentReference(); } Paragraph p3 = new Paragraph(nature, font); p3.setAlignment(Paragraph.ALIGN_LEFT); document.add(p3); // informations du paiement font.setSize(13); Paragraph p4 = new Paragraph("Nom Client : "+nom, font); p4.setSpacingBefore(50); document.add(p4); Paragraph p5 = new Paragraph("Montant : "+montant, font); document.add(p5); Paragraph p6 = new Paragraph("Date Paiement : "+date, font); document.add(p6); Paragraph p7 = new Paragraph("Nature Paiement : "+type, font); document.add(p7); Paragraph p8 = new Paragraph("Reference Paiement : "+rrn, font); document.add(p8); // 按顺序关闭资源,保证所有字节写入 document.close(); writer.close(); }
3. 前端AJAX调用适配(非表单直接提交场景)
如果是通过axios/fetch等异步方式调用接口,需指定blob响应类型并手动触发下载:
axios.post('/customer/generatePDF', requestParams, { responseType: 'blob' // 必须指定响应类型为blob,否则浏览器会按文本解析 }).then(res => { // 从响应头提取文件名 const disposition = res.headers['content-disposition'] let fileName = 'receipt.pdf' if (disposition) { const match = disposition.match(/filename\*=UTF-8''(.+)/) if (match) fileName = decodeURIComponent(match[1]) } // 生成临时下载链接触发下载 const blob = new Blob([res.data], { type: 'application/pdf' }) const url = window.URL.createObjectURL(blob) const a = document.createElement('a') a.href = url a.download = fileName a.click() window.URL.revokeObjectURL(url) })
内容的提问来源于stack exchange,提问作者MK S2M
相关产品推荐
相关产品推荐

