Showing
2 changed files
with
89 additions
and
15 deletions
xingyun-sc/src/main/java/com/lframework/xingyun/sc/controller/order/PurchaseOrderInfoController.java
| ... | ... | @@ -49,15 +49,15 @@ import javax.annotation.Resource; |
| 49 | 49 | import javax.servlet.http.HttpServletResponse; |
| 50 | 50 | import javax.validation.Valid; |
| 51 | 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 | 53 | import java.math.BigDecimal; |
| 56 | 54 | import java.math.RoundingMode; |
| 57 | 55 | import java.net.URLEncoder; |
| 56 | +import java.nio.file.Files; | |
| 58 | 57 | import java.time.LocalDate; |
| 59 | 58 | import java.time.format.DateTimeFormatter; |
| 60 | 59 | import java.util.*; |
| 60 | +import java.util.concurrent.TimeUnit; | |
| 61 | 61 | import java.util.stream.Collectors; |
| 62 | 62 | |
| 63 | 63 | /** |
| ... | ... | @@ -358,13 +358,12 @@ public class PurchaseOrderInfoController extends DefaultBaseController { |
| 358 | 358 | public void printPurchaseOrder(@NotBlank(message = "id不能为空") String id, HttpServletResponse response) throws IOException { |
| 359 | 359 | PurchaseOrderInfo data = purchaseOrderInfoService.findById(id); |
| 360 | 360 | |
| 361 | - // 设置响应头 | |
| 362 | - setupResponse(response, data.getOrderNo() + "-订货单打印.xlsx"); | |
| 363 | - | |
| 364 | 361 | Wrapper<PurchaseOrderLine> purchaseOrderLineWrapper = Wrappers.lambdaQuery(PurchaseOrderLine.class) |
| 365 | 362 | .eq(PurchaseOrderLine::getPurchaseOrderId, id); |
| 366 | 363 | List<PurchaseOrderLine> purchaseOrderLineList = purchaseOrderLineService.list(purchaseOrderLineWrapper); |
| 367 | 364 | |
| 365 | + File tempExcel = null; | |
| 366 | + File pdfFile = null; | |
| 368 | 367 | |
| 369 | 368 | try { |
| 370 | 369 | // 加载模板文件 |
| ... | ... | @@ -467,21 +466,96 @@ public class PurchaseOrderInfoController extends DefaultBaseController { |
| 467 | 466 | |
| 468 | 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 | 475 | } finally { |
| 474 | 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 | 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 | ... | ... |
No preview for this file type