Commit fee18ff49c3d444ddaf904b1e22987f7287dce56

Authored by 房远帅
1 parent b6779167

订货单:改成导出pdf

@@ -49,15 +49,15 @@ import javax.annotation.Resource; @@ -49,15 +49,15 @@ import javax.annotation.Resource;
49 import javax.servlet.http.HttpServletResponse; 49 import javax.servlet.http.HttpServletResponse;
50 import javax.validation.Valid; 50 import javax.validation.Valid;
51 import javax.validation.constraints.NotBlank; 51 import javax.validation.constraints.NotBlank;
52 -import java.io.FileNotFoundException;  
53 -import java.io.IOException;  
54 -import java.io.InputStream; 52 +import java.io.*;
55 import java.math.BigDecimal; 53 import java.math.BigDecimal;
56 import java.math.RoundingMode; 54 import java.math.RoundingMode;
57 import java.net.URLEncoder; 55 import java.net.URLEncoder;
  56 +import java.nio.file.Files;
58 import java.time.LocalDate; 57 import java.time.LocalDate;
59 import java.time.format.DateTimeFormatter; 58 import java.time.format.DateTimeFormatter;
60 import java.util.*; 59 import java.util.*;
  60 +import java.util.concurrent.TimeUnit;
61 import java.util.stream.Collectors; 61 import java.util.stream.Collectors;
62 62
63 /** 63 /**
@@ -358,13 +358,12 @@ public class PurchaseOrderInfoController extends DefaultBaseController { @@ -358,13 +358,12 @@ public class PurchaseOrderInfoController extends DefaultBaseController {
358 public void printPurchaseOrder(@NotBlank(message = "id不能为空") String id, HttpServletResponse response) throws IOException { 358 public void printPurchaseOrder(@NotBlank(message = "id不能为空") String id, HttpServletResponse response) throws IOException {
359 PurchaseOrderInfo data = purchaseOrderInfoService.findById(id); 359 PurchaseOrderInfo data = purchaseOrderInfoService.findById(id);
360 360
361 - // 设置响应头  
362 - setupResponse(response, data.getOrderNo() + "-订货单打印.xlsx");  
363 -  
364 Wrapper<PurchaseOrderLine> purchaseOrderLineWrapper = Wrappers.lambdaQuery(PurchaseOrderLine.class) 361 Wrapper<PurchaseOrderLine> purchaseOrderLineWrapper = Wrappers.lambdaQuery(PurchaseOrderLine.class)
365 .eq(PurchaseOrderLine::getPurchaseOrderId, id); 362 .eq(PurchaseOrderLine::getPurchaseOrderId, id);
366 List<PurchaseOrderLine> purchaseOrderLineList = purchaseOrderLineService.list(purchaseOrderLineWrapper); 363 List<PurchaseOrderLine> purchaseOrderLineList = purchaseOrderLineService.list(purchaseOrderLineWrapper);
367 364
  365 + File tempExcel = null;
  366 + File pdfFile = null;
368 367
369 try { 368 try {
370 // 加载模板文件 369 // 加载模板文件
@@ -467,21 +466,96 @@ public class PurchaseOrderInfoController extends DefaultBaseController { @@ -467,21 +466,96 @@ public class PurchaseOrderInfoController extends DefaultBaseController {
467 466
468 processTemplate(workbook, dataMap); 467 processTemplate(workbook, dataMap);
469 468
470 - // 写入响应流  
471 - workbook.write(response.getOutputStream());  
472 - response.getOutputStream().flush(); 469 + // === 4. 写入临时 .xlsx 文件 ===
  470 + tempExcel = File.createTempFile("purchase_order_" + data.getOrderNo(), ".xlsx");
  471 + try (FileOutputStream fos = new FileOutputStream(tempExcel)) {
  472 + workbook.write(fos);
  473 + }
  474 +
473 } finally { 475 } finally {
474 IOUtils.closeQuietly(workbook); 476 IOUtils.closeQuietly(workbook);
475 } 477 }
  478 + }
476 479
477 - } catch (FileNotFoundException e) {  
478 - throw new RuntimeException("模板文件不存在: templates/purchaseOrderTemplate.xlsx", e);  
479 - } catch (IOException e) {  
480 - throw new RuntimeException("无法读取模板文件: templates/purchaseOrderTemplate.xlsx", e); 480 + // === 5. 调用 LibreOffice 转 PDF ===
  481 + pdfFile = convertExcelToPdf(tempExcel);
  482 +
  483 + // === 6. 设置 PDF 响应头 ===
  484 + String fileName = data.getOrderNo() + "-订货单打印.pdf";
  485 + String encodedFileName;
  486 + try {
  487 + encodedFileName = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");
  488 + } catch (UnsupportedEncodingException e) {
  489 + // UTF-8 是标准编码,理论上不会抛出,但 Java 8 要求处理
  490 + throw new RuntimeException("UTF-8 encoding not supported", e);
  491 + }
  492 + response.setContentType("application/pdf");
  493 + response.setCharacterEncoding("UTF-8");
  494 + response.setHeader("Content-Disposition", "attachment;filename=" + encodedFileName);
  495 + response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
  496 +
  497 + // === 7. 输出 PDF 到浏览器 ===
  498 + try (InputStream pdfIn = new FileInputStream(pdfFile)) {
  499 + byte[] buffer = new byte[8192];
  500 + int len;
  501 + while ((len = pdfIn.read(buffer)) != -1) {
  502 + response.getOutputStream().write(buffer, 0, len);
  503 + }
  504 + response.getOutputStream().flush();
481 } 505 }
  506 +
482 } catch (Exception e) { 507 } catch (Exception e) {
483 - log.error("标准合同模版打印: {}", e.getMessage(), e);  
484 - throw e; 508 + log.error("订货单导出 PDF 失败: {}", e.getMessage(), e);
  509 + throw new RuntimeException("订货单导出失败,请联系管理员", e);
  510 + } finally {
  511 + // === 8. 清理临时文件 ===
  512 + deleteQuietly(tempExcel);
  513 + deleteQuietly(pdfFile);
  514 + }
  515 + }
  516 +
  517 + private File convertExcelToPdf(File excelFile) throws IOException, InterruptedException {
  518 + if (!excelFile.exists()) {
  519 + throw new IllegalArgumentException("Excel 文件不存在: " + excelFile.getAbsolutePath());
  520 + }
  521 +
  522 + String pdfPath = excelFile.getAbsolutePath().replaceAll("\\.xlsx$", ".pdf");
  523 + File pdfFile = new File(pdfPath);
  524 +
  525 + // 使用绝对路径 /usr/bin/libreoffice(CentOS 7 默认位置)
  526 + String command = String.format(
  527 + "/usr/bin/libreoffice --headless --convert-to pdf --outdir %s %s",
  528 + excelFile.getParent(),
  529 + excelFile.getAbsolutePath()
  530 + );
  531 +
  532 + Process process = Runtime.getRuntime().exec(command);
  533 + boolean finished = process.waitFor(30, TimeUnit.SECONDS); // 超时 30 秒
  534 +
  535 + if (!finished) {
  536 + process.destroyForcibly();
  537 + throw new RuntimeException("LibreOffice 转换超时(>30秒)");
  538 + }
  539 +
  540 + int exitCode = process.exitValue();
  541 + if (exitCode != 0) {
  542 + throw new RuntimeException("LibreOffice 转换失败,退出码: " + exitCode);
  543 + }
  544 +
  545 + if (!pdfFile.exists()) {
  546 + throw new RuntimeException("PDF 未生成: " + pdfPath);
  547 + }
  548 +
  549 + return pdfFile;
  550 + }
  551 +
  552 + private void deleteQuietly(File file) {
  553 + if (file != null && file.exists()) {
  554 + try {
  555 + Files.delete(file.toPath());
  556 + } catch (IOException e) {
  557 + log.warn("无法删除临时文件: {}", file.getAbsolutePath(), e);
  558 + }
485 } 559 }
486 } 560 }
487 561