From 13392606c16362d23478e84e0d43e869813d3095 Mon Sep 17 00:00:00 2001
From: liujie <1793218484@qq.com>
Date: 星期六, 24 一月 2026 18:36:13 +0800
Subject: [PATCH] update

---
 admin-server/src/main/java/com/estate/expert/admin/controller/stat/ReportController.java |  628 +++++++++++++++++++++++++++++++++++++++++++++++++++++----
 1 files changed, 583 insertions(+), 45 deletions(-)

diff --git a/admin-server/src/main/java/com/estate/expert/admin/controller/stat/ReportController.java b/admin-server/src/main/java/com/estate/expert/admin/controller/stat/ReportController.java
index d5786ba..4292b8a 100644
--- a/admin-server/src/main/java/com/estate/expert/admin/controller/stat/ReportController.java
+++ b/admin-server/src/main/java/com/estate/expert/admin/controller/stat/ReportController.java
@@ -5,24 +5,38 @@
 import com.estate.expert.admin.service.CheckTaskService;
 import com.estate.expert.admin.service.ReportService;
 import com.estate.expert.admin.service.ReportTemplateService;
+import com.estate.expert.admin.utils.WordToPdfConverter;
 import com.estate.expert.common.auth.IgnoreAuth;
+import com.estate.expert.common.rpc.Result;
+import fr.opensagres.poi.xwpf.converter.pdf.PdfConverter;
+import fr.opensagres.poi.xwpf.converter.pdf.PdfOptions;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiParam;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.rendering.ImageType;
+import org.apache.pdfbox.rendering.PDFRenderer;
+import org.apache.poi.xwpf.usermodel.XWPFDocument;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Controller;
 import org.springframework.validation.annotation.Validated;
-import org.springframework.web.bind.annotation.CrossOrigin;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.*;
 
 import javax.annotation.Resource;
+import javax.imageio.ImageIO;
 import javax.servlet.http.HttpServletResponse;
-import java.io.BufferedOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
+import java.awt.image.BufferedImage;
+import java.io.*;
+import java.net.HttpURLConnection;
+import java.net.URL;
 import java.net.URLEncoder;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.ArrayList;
+import java.util.List;
 
 @Api(tags = "管理后台/统计/报告下载")
 @Slf4j
@@ -41,14 +55,18 @@
     @Resource
     private ReportTemplateService reportTemplateService;
 
+    @Value("${file.upload.dir}/reports")
+    private String REPORT_UPLOAD_DIR;
+
     @ApiOperation("模糊评价报告下载")
     @GetMapping("/fuzzyReport")
     @IgnoreAuth
     public void fuzzyReport(@ApiParam("检查任务ID") @RequestParam(value = "checkTaskId") Integer checkTaskId,
-                          @ApiParam("报告模版ID") @RequestParam(value = "templateId") Integer templateId,
-                          HttpServletResponse response) throws IOException {
+                              @ApiParam("报告模版ID") @RequestParam(value = "templateId") Integer templateId,
+                              @ApiParam("uuid") @RequestParam(value = "uuid",required = false) String uuid,
+                              HttpServletResponse response) throws IOException {
         try (var doc = reportService.fuzzyReport(checkTaskId, templateId)) {
-            download(doc, response, "模糊评价", checkTaskId, templateId);
+            download(doc, response, "模糊评价", checkTaskId, templateId,uuid);
         }
     }
 
@@ -59,7 +77,7 @@
                           @ApiParam("报告模版ID") @RequestParam(value = "templateId") Integer templateId,
                           HttpServletResponse response) throws IOException {
         try (var doc = reportService.scoreReport(checkTaskId, templateId)) {
-            download(doc, response, "打分评价", checkTaskId, templateId);
+            download(doc, response, "打分评价", checkTaskId, templateId,null);
         }
     }
 
@@ -69,9 +87,30 @@
     public void example(@ApiParam("报告模版ID") @RequestParam(value = "templateId") Integer templateId,
                           HttpServletResponse response) throws IOException {
         try (var doc = reportService.fuzzyReport(1, templateId)) {
-            download(doc, response, "示例",1, templateId);
+            download(doc, response, "示例",1, templateId,null);
         }
     }
+
+    @Autowired
+    private WordToPdfConverter wordToPdfConverter;
+
+
+    @ApiOperation("模糊评价报告PDF下载(新接口)- 通过Word链接转换")
+    @GetMapping("/fuzzyReportPdfNew")
+    @IgnoreAuth
+    public void fuzzyReportPdfNew(@ApiParam("Word文档URL") @RequestParam(value = "wordUrl") String wordUrl,
+                                  @ApiParam("文件名(可选)") @RequestParam(value = "fileName", required = false) String fileName,
+                                  HttpServletResponse response) throws Exception {
+        downloadPdfFromWordUrl(wordUrl, fileName, response);
+
+        try {
+            wordToPdfConverter.convertUrlToPdf(wordUrl,"C:\\Users\\Admin\\Desktop\\test.pdf");
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+
+    }
+
 
     @ApiOperation("模糊评价报告PDF下载")
     @GetMapping("/fuzzyReportPdf")
@@ -91,53 +130,552 @@
         downloadPdf(checkTaskId, templateId, response, "打分评价", false);
     }
 
+    @ApiOperation("获取报告图片列表")
+    @GetMapping("/getReportImages")
+    @ResponseBody
+    @IgnoreAuth
+    public Result<List<String>> getReportImages(@ApiParam("文件UUID") @RequestParam(value = "uuid") String uuid) {
+        try {
+            List<String> imageFiles = new ArrayList<>();
+            Path imageDir = Path.of(REPORT_UPLOAD_DIR, "images").toAbsolutePath().normalize();
+
+            if (!Files.exists(imageDir)) {
+                return Result.success(imageFiles);
+            }
+
+            // 查找所有匹配的图片文件
+            try (var stream = Files.list(imageDir)) {
+                stream.filter(path -> {
+                    String fileName = path.getFileName().toString();
+                    return fileName.startsWith(uuid + "_page_") && fileName.endsWith(".png");
+                })
+                .sorted((p1, p2) -> {
+                    // 按页码排序
+                    String name1 = p1.getFileName().toString();
+                    String name2 = p2.getFileName().toString();
+                    int page1 = extractPageNumber(name1);
+                    int page2 = extractPageNumber(name2);
+                    return Integer.compare(page1, page2);
+                })
+                .forEach(path -> imageFiles.add(path.getFileName().toString()));
+            }
+
+            log.info("查询报告图片列表: uuid={}, 共{}张图片", uuid, imageFiles.size());
+            return Result.success(imageFiles);
+
+        } catch (Exception e) {
+            log.error("查询报告图片列表失败: uuid={}", uuid, e);
+            return Result.error("查询图片列表失败: " + e.getMessage());
+        }
+    }
+
+    @ApiOperation("检查报告转换状态")
+    @GetMapping("/checkConversionStatus")
+    @ResponseBody
+    @IgnoreAuth
+    public Result<ConversionStatus> checkConversionStatus(@ApiParam("文件UUID") @RequestParam(value = "uuid") String uuid) {
+        try {
+            ConversionStatus status = new ConversionStatus();
+            status.setUuid(uuid);
+
+            // 检查Word文件是否存在
+            Path wordPath = Path.of(REPORT_UPLOAD_DIR).resolve(uuid + ".docx");
+            status.setWordFileExists(Files.exists(wordPath));
+
+            // 检查图片文件
+            Path imageDir = Path.of(REPORT_UPLOAD_DIR, "images");
+            if (Files.exists(imageDir)) {
+                try (var stream = Files.list(imageDir)) {
+                    long imageCount = stream.filter(path -> {
+                        String fileName = path.getFileName().toString();
+                        return fileName.startsWith(uuid + "_page_") && fileName.endsWith(".png");
+                    }).count();
+                    status.setImageCount((int) imageCount);
+                    status.setConversionCompleted(imageCount > 0);
+                }
+            } else {
+                status.setImageCount(0);
+                status.setConversionCompleted(false);
+            }
+
+            return Result.success(status);
+
+        } catch (Exception e) {
+            log.error("检查转换状态失败: uuid={}", uuid, e);
+            return Result.error("检查转换状态失败: " + e.getMessage());
+        }
+    }
+
     private void download(XWPFTemplate doc, HttpServletResponse response, String typeName,
-                          Integer checkTaskId, Integer templateId) throws IOException {
-        OutputStream out;
-        BufferedOutputStream bos;
+                          Integer checkTaskId, Integer templateId,String uuid) throws IOException {
+        ByteArrayOutputStream docOutputStream = null;
+        OutputStream out = null;
+        BufferedOutputStream bos = null;
 
-        var checkTask = checkTaskService.get(checkTaskId, true);
-        var reportTemplate = reportTemplateService.get(templateId);
-        BizResultCode.TASK_NOT_FOUND.check(checkTask == null);
+        try {
+            var checkTask = checkTaskService.get(checkTaskId, true);
+            var reportTemplate = reportTemplateService.get(templateId);
+            BizResultCode.TASK_NOT_FOUND.check(checkTask == null);
 
-        var docName = checkTask.getProject().getName()+ "_" + reportTemplate.getName() + "_" + typeName ;
-        String encodedFileName = URLEncoder.encode(docName, "UTF-8");
-        response.setContentType("application/octet-stream");
-        response.setHeader("Content-disposition", "attachment;filename=\"" + encodedFileName + ".docx\"");
+            var docName = checkTask.getProject().getName() + "_" + reportTemplate.getName() + "_" + typeName;
+            String encodedFileName = URLEncoder.encode(docName, "UTF-8");
+            response.setContentType("application/octet-stream");
+            response.setHeader("Content-disposition", "attachment;filename=\"" + encodedFileName + ".docx\"");
 
-        // HttpServletResponse response
-        out = response.getOutputStream();
-        bos = new BufferedOutputStream(out);
-        doc.write(bos);
-        bos.flush();
-        out.flush();
+            // 先将文档写入字节数组
+            docOutputStream = new ByteArrayOutputStream();
+            doc.write(docOutputStream);
+            byte[] docBytes = docOutputStream.toByteArray();
+
+            // 写入响应流供用户下载
+            out = response.getOutputStream();
+            bos = new BufferedOutputStream(out);
+            bos.write(docBytes);
+            bos.flush();
+            out.flush();
+
+            // 保存文件到服务器
+            if(uuid!=null && !uuid.isEmpty()){
+                saveFileToServer(docBytes, docName + ".docx",uuid);
+            }
+        } catch (Exception e) {
+            log.error("文档下载失败", e);
+            throw new IOException("文档下载失败: " + e.getMessage(), e);
+        } finally {
+            if (docOutputStream != null) {
+                try {
+                    docOutputStream.close();
+                } catch (Exception e) {
+                    log.warn("关闭输出流失败", e);
+                }
+            }
+        }
     }
 
     private void downloadPdf(Integer checkTaskId, Integer templateId, HttpServletResponse response,
                              String typeName, boolean isFuzzy) throws IOException {
-        OutputStream out;
-        BufferedOutputStream bos;
+        ByteArrayOutputStream pdfOutputStream = null;
+        OutputStream out = null;
+        BufferedOutputStream bos = null;
 
-        var checkTask = checkTaskService.get(checkTaskId, true);
-        var reportTemplate = reportTemplateService.get(templateId);
-        BizResultCode.TASK_NOT_FOUND.check(checkTask == null);
+        try {
+            var checkTask = checkTaskService.get(checkTaskId, true);
+            var reportTemplate = reportTemplateService.get(templateId);
+            BizResultCode.TASK_NOT_FOUND.check(checkTask == null);
 
-        var docName = checkTask.getProject().getName() + "_" + reportTemplate.getName() + "_" + typeName;
-        String encodedFileName = URLEncoder.encode(docName, "UTF-8");
-        response.setContentType("application/pdf");
-        response.setHeader("Content-disposition", "attachment;filename=\"" + encodedFileName + ".pdf\"");
+            var docName = checkTask.getProject().getName() + "_" + reportTemplate.getName() + "_" + typeName;
+            String encodedFileName = URLEncoder.encode(docName, "UTF-8");
+            response.setContentType("application/pdf");
+            response.setHeader("Content-disposition", "attachment;filename=\"" + encodedFileName + ".pdf\"");
 
-        out = response.getOutputStream();
-        bos = new BufferedOutputStream(out);
+            // 先将PDF写入字节数组
+            pdfOutputStream = new ByteArrayOutputStream();
+            if (isFuzzy) {
+                reportService.fuzzyReportPdf(checkTaskId, templateId, pdfOutputStream);
+            } else {
+                reportService.scoreReportPdf(checkTaskId, templateId, pdfOutputStream);
+            }
+            byte[] pdfBytes = pdfOutputStream.toByteArray();
 
-        if (isFuzzy) {
-            reportService.fuzzyReportPdf(checkTaskId, templateId, bos);
-        } else {
-            reportService.scoreReportPdf(checkTaskId, templateId, bos);
+            // 写入响应流供用户下载
+            out = response.getOutputStream();
+            bos = new BufferedOutputStream(out);
+            bos.write(pdfBytes);
+            bos.flush();
+            out.flush();
+
+            // 保存文件到服务器
+            saveFileToServer(pdfBytes, docName + ".pdf",null);
+
+        } catch (Exception e) {
+            log.error("PDF下载失败", e);
+            throw new IOException("PDF下载失败: " + e.getMessage(), e);
+        } finally {
+            if (pdfOutputStream != null) {
+                try {
+                    pdfOutputStream.close();
+                } catch (Exception e) {
+                    log.warn("关闭输出流失败", e);
+                }
+            }
+        }
+    }
+
+    /**
+     * 将Word文档转换为PDF并下载
+     * @param doc Word文档模板
+     * @param response HTTP响应
+     * @param typeName 报告类型名称
+     * @param checkTaskId 检查任务ID
+     * @param templateId 模板ID
+     * @throws IOException IO异常
+     */
+    private void downloadAsPdf(XWPFTemplate doc, HttpServletResponse response, String typeName,
+                               Integer checkTaskId, Integer templateId) throws IOException {
+        ByteArrayOutputStream docOutputStream = null;
+        ByteArrayInputStream docInputStream = null;
+        ByteArrayOutputStream pdfOutputStream = null;
+        XWPFDocument document = null;
+        OutputStream out = null;
+        BufferedOutputStream bos = null;
+
+        try {
+            var checkTask = checkTaskService.get(checkTaskId, true);
+            var reportTemplate = reportTemplateService.get(templateId);
+            BizResultCode.TASK_NOT_FOUND.check(checkTask == null);
+
+            var docName = checkTask.getProject().getName() + "_" + reportTemplate.getName() + "_" + typeName;
+            String encodedFileName = URLEncoder.encode(docName, "UTF-8");
+            response.setContentType("application/pdf");
+            response.setHeader("Content-disposition", "attachment;filename=\"" + encodedFileName + ".pdf\"");
+
+            // 将Word模板写入字节数组
+            docOutputStream = new ByteArrayOutputStream();
+            doc.write(docOutputStream);
+
+            // 从字节数组读取Word文档
+            docInputStream = new ByteArrayInputStream(docOutputStream.toByteArray());
+            document = new XWPFDocument(docInputStream);
+
+            // 配置PDF选项
+            PdfOptions options = PdfOptions.create();
+            options.fontEncoding("UTF-8");
+
+            // 先将PDF转换到字节数组
+            pdfOutputStream = new ByteArrayOutputStream();
+            PdfConverter.getInstance().convert(document, pdfOutputStream, options);
+            byte[] pdfBytes = pdfOutputStream.toByteArray();
+
+            // 写入响应流供用户下载
+            out = response.getOutputStream();
+            bos = new BufferedOutputStream(out);
+            bos.write(pdfBytes);
+            bos.flush();
+            out.flush();
+
+            // 保存文件到服务器
+            saveFileToServer(pdfBytes, docName + ".pdf",null);
+
+        } catch (Exception e) {
+            log.error("Word转PDF失败", e);
+            throw new IOException("Word转PDF失败: " + e.getMessage(), e);
+        } finally {
+            // 关闭资源
+            if (document != null) {
+                try {
+                    document.close();
+                } catch (Exception e) {
+                    log.warn("关闭Word文档失败", e);
+                }
+            }
+            if (pdfOutputStream != null) {
+                try {
+                    pdfOutputStream.close();
+                } catch (Exception e) {
+                    log.warn("关闭PDF输出流失败", e);
+                }
+            }
+            if (docInputStream != null) {
+                try {
+                    docInputStream.close();
+                } catch (Exception e) {
+                    log.warn("关闭输入流失败", e);
+                }
+            }
+            if (docOutputStream != null) {
+                try {
+                    docOutputStream.close();
+                } catch (Exception e) {
+                    log.warn("关闭输出流失败", e);
+                }
+            }
+        }
+    }
+
+    /**
+     * 保存文件到服务器
+     * @param fileBytes 文件字节数组
+     * @param fileName 文件名
+     */
+    private String saveFileToServer(byte[] fileBytes, String fileName,String uuid) {
+        try {
+            // 确保目录存在
+            Path reportDir = Path.of(REPORT_UPLOAD_DIR).toAbsolutePath().normalize();
+            if (!Files.exists(reportDir)) {
+                Files.createDirectories(reportDir);
+            }
+            //文件名随机
+            fileName =uuid+".docx";
+            // 保存文件
+            Path filePath = reportDir.resolve(fileName);
+            Files.write(filePath, fileBytes, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
+
+            log.info("文件已保存到服务器: {}", filePath.toString());
+            return fileName;
+        } catch (Exception e) {
+            log.error("保存文件到服务器失败: {}", fileName, e);
+            // 不抛出异常,避免影响用户下载
+        }
+        return null;
+    }
+
+    /**
+     * 将Word文档转换为图片并保存到服务器
+     * @param docBytes Word文档字节数组
+     * @param uuid 文件唯一标识
+     */
+    private void convertWordToImagesAndSave(byte[] docBytes, String uuid) {
+        ByteArrayInputStream docInputStream = null;
+        XWPFDocument document = null;
+        ByteArrayOutputStream pdfOutputStream = null;
+        PDDocument pdfDocument = null;
+
+        try {
+            // 1. 将Word文档转换为PDF
+            docInputStream = new ByteArrayInputStream(docBytes);
+            document = new XWPFDocument(docInputStream);
+
+            pdfOutputStream = new ByteArrayOutputStream();
+            PdfOptions options = PdfOptions.create();
+            options.fontEncoding("UTF-8");
+            PdfConverter.getInstance().convert(document, pdfOutputStream, options);
+
+            byte[] pdfBytes = pdfOutputStream.toByteArray();
+
+            // 2. 将PDF转换为图片
+            ByteArrayInputStream pdfInputStream = new ByteArrayInputStream(pdfBytes);
+            pdfDocument = PDDocument.load(pdfInputStream);
+            PDFRenderer pdfRenderer = new PDFRenderer(pdfDocument);
+
+            // 确保图片目录存在
+            Path imageDir = Path.of(REPORT_UPLOAD_DIR, "images").toAbsolutePath().normalize();
+            if (!Files.exists(imageDir)) {
+                Files.createDirectories(imageDir);
+            }
+
+            // 转换每一页为图片
+            int pageCount = pdfDocument.getNumberOfPages();
+            log.info("开始转换Word为图片,共{}页", pageCount);
+
+            for (int i = 0; i < pageCount; i++) {
+                // 渲染PDF页面为图片,DPI设置为300以获得高质量图片
+                BufferedImage image = pdfRenderer.renderImageWithDPI(i, 300, ImageType.RGB);
+
+                // 保存图片
+                String imageFileName = uuid + "_page_" + (i + 1) + ".png";
+                Path imagePath = imageDir.resolve(imageFileName);
+                ImageIO.write(image, "PNG", imagePath.toFile());
+
+                log.info("页面{}已转换为图片: {}", i + 1, imagePath.toString());
+            }
+
+            log.info("Word文档已成功转换为{}张图片", pageCount);
+
+        } catch (Exception e) {
+            log.error("Word转图片失败: uuid={}", uuid, e);
+            // 不抛出异常,避免影响用户下载
+        } finally {
+            // 关闭资源
+            if (pdfDocument != null) {
+                try {
+                    pdfDocument.close();
+                } catch (Exception e) {
+                    log.warn("关闭PDF文档失败", e);
+                }
+            }
+            if (document != null) {
+                try {
+                    document.close();
+                } catch (Exception e) {
+                    log.warn("关闭Word文档失败", e);
+                }
+            }
+            if (pdfOutputStream != null) {
+                try {
+                    pdfOutputStream.close();
+                } catch (Exception e) {
+                    log.warn("关闭PDF输出流失败", e);
+                }
+            }
+            if (docInputStream != null) {
+                try {
+                    docInputStream.close();
+                } catch (Exception e) {
+                    log.warn("关闭文档输入流失败", e);
+                }
+            }
+        }
+    }
+
+    /**
+     * 从文件名中提取页码
+     * @param fileName 文件名,格式:uuid_page_N.png
+     * @return 页码
+     */
+    private int extractPageNumber(String fileName) {
+        try {
+            // 文件名格式:uuid_page_N.png
+            String[] parts = fileName.replace(".png", "").split("_page_");
+            if (parts.length == 2) {
+                return Integer.parseInt(parts[1]);
+            }
+        } catch (Exception e) {
+            log.warn("解析页码失败: {}", fileName, e);
+        }
+        return 0;
+    }
+
+    /**
+     * 通过Word文档URL转换为PDF并下载
+     * @param wordUrl Word文档的URL地址
+     * @param fileName 文件名(可选)
+     * @param response HTTP响应
+     * @throws IOException IO异常
+     */
+    private void downloadPdfFromWordUrl(String wordUrl, String fileName, HttpServletResponse response) throws IOException {
+        InputStream wordInputStream = null;
+        XWPFDocument document = null;
+        ByteArrayOutputStream pdfOutputStream = null;
+        OutputStream out = null;
+        BufferedOutputStream bos = null;
+        HttpURLConnection connection = null;
+
+        try {
+            log.info("开始从URL下载Word文档: {}", wordUrl);
+
+            // 1. 从URL下载Word文档
+            URL url = new URL(wordUrl);
+            connection = (HttpURLConnection) url.openConnection();
+            connection.setRequestMethod("GET");
+            connection.setConnectTimeout(30000); // 30秒连接超时
+            connection.setReadTimeout(60000);    // 60秒读取超时
+            connection.setRequestProperty("User-Agent", "Mozilla/5.0");
+
+            int responseCode = connection.getResponseCode();
+            if (responseCode != HttpURLConnection.HTTP_OK) {
+                throw new IOException("下载Word文档失败,HTTP状态码: " + responseCode);
+            }
+
+            wordInputStream = connection.getInputStream();
+            log.info("Word文档下载成功,开始转换为PDF");
+
+            // 2. 读取Word文档
+            document = new XWPFDocument(wordInputStream);
+
+            // 3. 转换为PDF
+            pdfOutputStream = new ByteArrayOutputStream();
+            PdfOptions options = PdfOptions.create();
+            options.fontEncoding("UTF-8");
+
+            // 确保图表不丢失的关键配置
+            PdfConverter.getInstance().convert(document, pdfOutputStream, options);
+
+            byte[] pdfBytes = pdfOutputStream.toByteArray();
+            log.info("PDF转换成功,大小: {} bytes", pdfBytes.length);
+
+            // 4. 设置响应头(修复中文乱码)
+            String downloadFileName = fileName != null && !fileName.isEmpty()
+                ? fileName
+                : "report_" + System.currentTimeMillis();
+
+            if (!downloadFileName.toLowerCase().endsWith(".pdf")) {
+                downloadFileName += ".pdf";
+            }
+
+            // 使用RFC 5987规范处理中文文件名
+            String encodedFileName = URLEncoder.encode(downloadFileName, "UTF-8").replaceAll("\\+", "%20");
+
+            response.setContentType("application/pdf");
+            response.setCharacterEncoding("UTF-8");
+            response.setHeader("Content-Disposition",
+                "attachment; filename=\"" + encodedFileName + "\"; filename*=UTF-8''" + encodedFileName);
+            response.setContentLength(pdfBytes.length);
+
+            // 5. 写入响应流
+            out = response.getOutputStream();
+            bos = new BufferedOutputStream(out);
+            bos.write(pdfBytes);
+            bos.flush();
+            out.flush();
+
+            log.info("PDF文档下载成功: {}", downloadFileName);
+
+        } catch (Exception e) {
+            log.error("从Word URL转换PDF失败: url={}", wordUrl, e);
+            throw new IOException("从Word URL转换PDF失败: " + e.getMessage(), e);
+        } finally {
+            // 关闭资源
+            if (document != null) {
+                try {
+                    document.close();
+                } catch (Exception e) {
+                    log.warn("关闭Word文档失败", e);
+                }
+            }
+            if (pdfOutputStream != null) {
+                try {
+                    pdfOutputStream.close();
+                } catch (Exception e) {
+                    log.warn("关闭PDF输出流失败", e);
+                }
+            }
+            if (wordInputStream != null) {
+                try {
+                    wordInputStream.close();
+                } catch (Exception e) {
+                    log.warn("关闭Word输入流失败", e);
+                }
+            }
+            if (connection != null) {
+                try {
+                    connection.disconnect();
+                } catch (Exception e) {
+                    log.warn("断开HTTP连接失败", e);
+                }
+            }
+        }
+    }
+
+    /**
+     * 转换状态内部类
+     */
+    public static class ConversionStatus {
+        private String uuid;
+        private boolean wordFileExists;
+        private int imageCount;
+        private boolean conversionCompleted;
+
+        public String getUuid() {
+            return uuid;
         }
 
-        bos.flush();
-        out.flush();
+        public void setUuid(String uuid) {
+            this.uuid = uuid;
+        }
+
+        public boolean isWordFileExists() {
+            return wordFileExists;
+        }
+
+        public void setWordFileExists(boolean wordFileExists) {
+            this.wordFileExists = wordFileExists;
+        }
+
+        public int getImageCount() {
+            return imageCount;
+        }
+
+        public void setImageCount(int imageCount) {
+            this.imageCount = imageCount;
+        }
+
+        public boolean isConversionCompleted() {
+            return conversionCompleted;
+        }
+
+        public void setConversionCompleted(boolean conversionCompleted) {
+            this.conversionCompleted = conversionCompleted;
+        }
     }
 
 }

--
Gitblit v1.7.1