From 8be8b3ab626d769cc1745b6b3ec5506fd489831c Mon Sep 17 00:00:00 2001
From: liujie <1793218484@qq.com>
Date: 星期一, 26 一月 2026 11:02:31 +0800
Subject: [PATCH] update

---
 admin-server/src/main/java/com/estate/expert/admin/utils/wordtopdf/PdfUtils.java         |  432 ++++++++++++++++++++++++++++
 admin-server/src/main/java/com/estate/expert/admin/controller/stat/ReportController.java |   55 ---
 admin-server/src/main/java/com/estate/expert/admin/utils/wordtopdf/WordUtil.java         |  336 ++++++++++++++++++++++
 admin-server/pom.xml                                                                     |   27 +
 4 files changed, 805 insertions(+), 45 deletions(-)

diff --git a/admin-server/pom.xml b/admin-server/pom.xml
index e304b9c..aae340b 100644
--- a/admin-server/pom.xml
+++ b/admin-server/pom.xml
@@ -22,6 +22,29 @@
 
     <dependencies>
 
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>com.documents4j</groupId>
+            <artifactId>documents4j-local</artifactId>
+            <version>1.0.3</version>
+
+        </dependency>
+        <dependency>
+            <groupId>com.documents4j</groupId>
+            <artifactId>documents4j-transformer-msoffice-word</artifactId>
+            <version>1.0.3</version>
+        </dependency>
+        <dependency>
+            <groupId>org.freemarker</groupId>
+            <artifactId>freemarker</artifactId>
+            <version>2.3.33</version>
+        </dependency>
+
         <!-- JODConverter for LibreOffice integration -->
         <dependency>
             <groupId>org.jodconverter</groupId>
@@ -197,6 +220,10 @@
             <artifactId>mybatis-plus-boot-starter</artifactId>
             <version>3.5.2</version>
         </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-test</artifactId>
+        </dependency>
     </dependencies>
 
     <build>
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 4292b8a..a3793cd 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
@@ -6,6 +6,8 @@
 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.admin.utils.wordtopdf.PdfUtils;
+import com.estate.expert.admin.utils.wordtopdf.WordUtil;
 import com.estate.expert.common.auth.IgnoreAuth;
 import com.estate.expert.common.rpc.Result;
 import fr.opensagres.poi.xwpf.converter.pdf.PdfConverter;
@@ -36,7 +38,9 @@
 import java.nio.file.Path;
 import java.nio.file.StandardOpenOption;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 @Api(tags = "管理后台/统计/报告下载")
 @Slf4j
@@ -92,7 +96,7 @@
     }
 
     @Autowired
-    private WordToPdfConverter wordToPdfConverter;
+    private PdfUtils pdfUtils;
 
 
     @ApiOperation("模糊评价报告PDF下载(新接口)- 通过Word链接转换")
@@ -101,10 +105,10 @@
     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");
+            String test = pdfUtils.test("1769239688785.docx");
+
         } catch (Exception e) {
             throw new RuntimeException(e);
         }
@@ -128,45 +132,6 @@
                                @ApiParam("报告模版ID") @RequestParam(value = "templateId") Integer templateId,
                                HttpServletResponse response) throws IOException {
         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("检查报告转换状态")
@@ -235,9 +200,9 @@
             out.flush();
 
             // 保存文件到服务器
-            if(uuid!=null && !uuid.isEmpty()){
-                saveFileToServer(docBytes, docName + ".docx",uuid);
-            }
+//            if(uuid!=null && !uuid.isEmpty()){
+//                saveFileToServer(docBytes, docName + ".docx",uuid);
+//            }
         } catch (Exception e) {
             log.error("文档下载失败", e);
             throw new IOException("文档下载失败: " + e.getMessage(), e);
diff --git a/admin-server/src/main/java/com/estate/expert/admin/utils/wordtopdf/PdfUtils.java b/admin-server/src/main/java/com/estate/expert/admin/utils/wordtopdf/PdfUtils.java
new file mode 100644
index 0000000..b7a1039
--- /dev/null
+++ b/admin-server/src/main/java/com/estate/expert/admin/utils/wordtopdf/PdfUtils.java
@@ -0,0 +1,432 @@
+package com.estate.expert.admin.utils.wordtopdf;
+
+import com.documents4j.api.DocumentType;
+import com.documents4j.api.IConverter;
+import com.documents4j.job.LocalConverter;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.mock.web.MockMultipartFile;
+import org.springframework.stereotype.Component;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.*;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+@Slf4j
+@Component
+public class PdfUtils {
+
+    public  String wordToPdf(String url,String filePath, String fileName) {
+        try {
+            DocumentType documentType = DocumentType.DOC;
+            if(url.contains(".docx")){
+                documentType = DocumentType.DOCX;
+            }
+            if(url.contains(".doc")){
+                documentType = DocumentType.DOC;
+            }
+            if(url.contains(".xlsx")){
+                documentType = DocumentType.XLSX;
+            }else {
+                if(url.contains(".xls")){
+                    documentType = DocumentType.XLS;
+                }
+            }
+            InputStream inputStream = new URL(url).openStream();
+            ByteArrayOutputStream stream = new ByteArrayOutputStream();
+            IConverter converter = LocalConverter.builder().build();
+            converter.convert(inputStream)
+                    .as(documentType)
+                    .to(stream)
+                    .as(DocumentType.PDF).execute();
+
+            //上传图片
+            byte2File(stream.toByteArray(),filePath + "/pdf",fileName.substring(0,fileName.lastIndexOf(".")) + ".pdf");
+            MultipartFile multipartFile = convertToMultipartFile(stream,fileName.substring(0,fileName.lastIndexOf(".")) );
+
+
+            String s = saveFileToServer(multipartFile.getBytes(), fileName, "1123121");
+            stream.close();
+            inputStream.close();
+            return s;
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+    @Value("${file.upload.dir}/reports")
+    private String REPORT_UPLOAD_DIR;
+    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 转 pdf
+     *
+     */
+    public String wordToPdf(String filePath, String fileName) {
+        try {
+            // 确保路径正确性
+            String inputFile = new File(filePath, fileName).getAbsolutePath();
+            String outputDir = new File(filePath, "pdf").getAbsolutePath();
+
+            log.info("开始转换文件:{}", inputFile);
+            // 创建输出目录
+            new File(outputDir).mkdirs();
+
+            // 使用完整的转换参数
+            List<String> command = Arrays.asList(
+                    "/usr/bin/libreoffice",  // 使用完整路径
+                    "--headless",
+                    "--norestore",
+                    "--convert-to",
+                    "pdf:writer_pdf_Export:PDFExport{" +
+                            "EmbedStandardFonts=1;" +
+                            "EmbedFonts=1;" +
+                            "EmbedOnlyUsedFonts=0;" +
+                            "UseTaggedPDF=1" +
+                            "}",
+                    "--outdir",
+                    outputDir,
+                    inputFile
+            );
+
+            // 创建进程构建器
+            ProcessBuilder pb = new ProcessBuilder(command);
+            log.info("执行命令:{}", command);
+
+            // 设置环境变量
+            Map<String, String> env = pb.environment();
+            env.put("LC_ALL", "zh_CN.UTF-8");
+            env.put("LANG", "zh_CN.UTF-8");
+            env.put("LANGUAGE", "zh_CN.UTF-8");
+            log.info("环境变量:{}", env);
+            // 重定向错误流到标准输出
+            pb.redirectErrorStream(true);
+            log.info("启动进程");
+            // 启动进程
+            Process process = pb.start();
+            log.info("等待进程完成");
+            // 读取输出
+            StringBuilder output = new StringBuilder();
+            try (BufferedReader reader = new BufferedReader(
+                    new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))) {
+                String line;
+                while ((line = reader.readLine()) != null) {
+                    output.append(line).append("\n");
+                }
+            }
+            log.info("进程完成,输出:{}", output);
+            // 等待进程完成,设置超时
+            if (!process.waitFor(120, TimeUnit.SECONDS)) {
+                process.destroyForcibly();
+                throw new RuntimeException("转换超时");
+            }
+            log.info("进程完成,退出码");
+            int exitCode = process.exitValue();
+            if (exitCode != 0) {
+                throw new RuntimeException("转换失败,退出码:" + exitCode + "\n输出:" + output);
+            }
+            log.info("进程完成,退出码:{}", exitCode);
+            // 检查生成的PDF文件
+            String pdfFileName = fileName.substring(0, fileName.lastIndexOf(".")) + ".pdf";
+            File pdfFile = new File(outputDir, pdfFileName);
+            log.info("PDF文件:{}", pdfFile);
+            if (!pdfFile.exists() || pdfFile.length() == 0) {
+                throw new RuntimeException("PDF文件未生成或为空");
+            }
+            String absolutePath = pdfFile.getAbsolutePath();
+            log.info("PDF文件绝对路径:{}", absolutePath);
+            MultipartFile file = convertFileToMultipartFile(pdfFile);
+            log.info("PDF文件转换完成");
+//            String s = tencentCosUtil.upLoadFile(multipartFile,"/wordToPdf");
+//            String url = ObsUploadUtil.obsUpload(file);
+            log.info("PDF文件上传完成");
+            return "";
+
+        } catch (Exception e) {
+            throw new RuntimeException("PDF转换失败: " + e.getMessage(), e);
+        }
+    }
+
+    public MultipartFile convertFileToMultipartFile(File file) throws IOException {
+        // 读取文件内容到字节数组
+        byte[] fileContent = Files.readAllBytes(file.toPath());
+
+        // 创建 MultipartFile 对象
+        MultipartFile multipartFile = new MockMultipartFile(
+                file.getName(),       // 文件名
+                file.getName(),       // 原始文件名
+                "application/pdf",  // 内容类型,根据实际情况调整
+                fileContent           // 文件内容
+        );
+
+        return multipartFile;
+    }
+
+    // 在使用前检查和配置环境
+    public static void setupEnvironment() {
+        try {
+            // 1. 检查LibreOffice安装
+            checkLibreOffice();
+
+            // 2. 检查和安装字体
+            installFonts();
+
+            // 3. 配置字体
+            configureFonts();
+
+            // 4. 验证环境变量
+            checkEnvironment();
+
+        } catch (Exception e) {
+            throw new RuntimeException("环境设置失败: " + e.getMessage(), e);
+        }
+    }
+
+    private static void checkLibreOffice() throws IOException, InterruptedException {
+        Process process = Runtime.getRuntime().exec("which libreoffice");
+        if (process.waitFor() != 0) {
+            throw new RuntimeException("LibreOffice未安装");
+        }
+    }
+
+    private static void installFonts() throws IOException, InterruptedException {
+        // 创建字体安装脚本
+        String scriptContent =
+                "#!/bin/bash\n" +
+                        "apt-get update\n" +
+                        "apt-get install -y fonts-wqy-zenhei fonts-wqy-microhei fonts-arphic-ukai fonts-arphic-uming\n" +
+                        "fc-cache -fv\n";
+
+        File script = new File("/tmp/install_fonts.sh");
+        Files.write(script.toPath(), scriptContent.getBytes());
+        script.setExecutable(true);
+
+        // 执行脚本
+        Process process = Runtime.getRuntime().exec("sudo /tmp/install_fonts.sh");
+        process.waitFor();
+
+        // 清理脚本
+        script.delete();
+    }
+
+    private static void configureFonts() throws IOException {
+        // 创建字体配置文件
+        String fontConfig =
+                "<?xml version=\"1.0\"?>\n" +
+                        "<!DOCTYPE fontconfig SYSTEM \"fonts.dtd\">\n" +
+                        "<fontconfig>\n" +
+                        "  <match target=\"pattern\">\n" +
+                        "    <test name=\"family\"><string>serif</string></test>\n" +
+                        "    <edit name=\"family\" mode=\"prepend\">\n" +
+                        "      <string>WenQuanYi Zen Hei</string>\n" +
+                        "    </edit>\n" +
+                        "  </match>\n" +
+                        "  <match target=\"pattern\">\n" +
+                        "    <test name=\"family\"><string>sans-serif</string></test>\n" +
+                        "    <edit name=\"family\" mode=\"prepend\">\n" +
+                        "      <string>WenQuanYi Zen Hei</string>\n" +
+                        "    </edit>\n" +
+                        "  </match>\n" +
+                        "</fontconfig>";
+
+        // 写入配置文件
+        File configFile = new File(System.getProperty("user.home") + "/.fonts.conf");
+        Files.write(configFile.toPath(), fontConfig.getBytes());
+    }
+
+    private static void checkEnvironment() {
+        // 检查环境变量
+        String[] requiredVars = {"LANG", "LC_ALL", "LANGUAGE"};
+        for (String var : requiredVars) {
+            String value = System.getenv(var);
+            if (value == null || !value.contains("zh_CN")) {
+                System.err.println("警告: " + var + " 环境变量未正确设置");
+            }
+        }
+    }
+
+    public static MultipartFile convertToMultipartFile(ByteArrayOutputStream baos, String fileName) throws IOException {
+        // 创建一个临时文件
+        File tempFile = File.createTempFile(fileName, ".pdf");
+
+        // 将ByteArrayOutputStream中的数据写入临时文件
+        try (FileOutputStream fos = new FileOutputStream(tempFile)) {
+            baos.writeTo(fos);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+
+        // 创建一个MultipartFile对象
+        return new MockMultipartFile(
+                fileName + ".pdf",          // 参数名称
+                fileName + ".pdf", // 文件名
+                "application/pdf", // 内容类型
+                Files.readAllBytes(tempFile.toPath()) // 文件内容
+        );
+    }
+
+    /**
+     * file转byte
+     */
+    public static byte[] file2byte(File file){
+        byte[] buffer = null;
+        try{
+            FileInputStream fis = new FileInputStream(file);
+            ByteArrayOutputStream bos = new ByteArrayOutputStream();
+            byte[] b = new byte[1024];
+            int n;
+            while ((n = fis.read(b)) != -1)
+            {
+                bos.write(b, 0, n);
+            }
+            fis.close();
+            bos.close();
+            buffer = bos.toByteArray();
+        }catch (FileNotFoundException e){
+            e.printStackTrace();
+        }
+        catch (IOException e){
+            e.printStackTrace();
+        }
+        return buffer;
+    }
+
+    /**
+     * byte 转file
+     */
+    public static File byte2File(byte[] buf, String filePath, String fileName){
+        BufferedOutputStream bos = null;
+        FileOutputStream fos = null;
+        OutputStreamWriter osw = null;
+        File file = null;
+        try{
+            File dir = new File(filePath+"/");
+            if (!dir.exists()){
+                dir.mkdirs();
+            }
+            file = new File(filePath +File.separator + fileName);
+            fos = new FileOutputStream(file);
+            bos = new BufferedOutputStream(fos);
+//            osw = new OutputStreamWriter(fos, StandardCharsets.UTF_8);
+            bos.write(buf);
+        }catch (Exception e){
+            e.printStackTrace();
+        }
+        finally{
+            if (bos != null){
+                try{
+                    bos.close();
+                }catch (IOException e){
+                    e.printStackTrace();
+                }
+            }
+            if (fos != null){
+                try{
+                    fos.close();
+                }catch (IOException e){
+                    e.printStackTrace();
+                }
+            }
+        }
+        return file;
+    }
+    /**
+     * multipartFile转File
+     **/
+    public static File multipartFile2File(MultipartFile multipartFile){
+        File file = null;
+        if (multipartFile != null){
+            try {
+                file= File.createTempFile("tmp", null);
+                multipartFile.transferTo(file);
+                System.gc();
+                file.deleteOnExit();
+            }catch (Exception e){
+                e.printStackTrace();
+                log.warn("multipartFile转File发生异常:"+e);
+            }
+        }
+        return file;
+    }
+
+
+    public static void main(String[] args) {
+//        String url = "file:///E:\\qiyeweixin\\WXWork\\1688855207501340\\Cache\\File\\2024-09\\专业技术工作总结.docx";
+//        String filePath = "E:\\qiyeweixin\\WXWork\\1688855207501340\\Cache\\File\\2024-09";
+//        String fileName = "专业技术工作总结.docx";
+//        String s = wordToPdf(url, filePath, fileName);
+//        System.err.println(s);
+
+//        String url = "file:///F:\\测试动态列表Word.doc";
+////        String filePath = "E:\\qiyeweixin\\WXWork\\1688855207501340\\Cache\\File\\2024-09";
+////        String fileName = "专业技术工作总结.docx";4
+//        String filePath = "F:\\";
+//
+//        String s = wordToPdf(url, filePath, "测试动态列表Word.doc");
+//        System.err.println(s);
+
+
+
+        // TODO Auto-generated method stub
+
+
+//        HashMap<String, Object> paramMap = new HashMap<>();
+//        paramMap.put("CorpID", "SCZT006959");
+//        paramMap.put("Pwd", "123456");
+//        paramMap.put("Mobile", "19522115070");
+//        paramMap.put("Cell", "");
+//        paramMap.put("SendTime", "");
+//        paramMap.put("Content", java.net.URLEncoder.encode("你好,这是测试短信发送。【职评网】"));
+//        String result3 = HttpUtil.post("http://sdk2.028lk.com/sdk2/BatchSend2.aspx", paramMap);
+//        if (result3 == null) {
+//            result3 = "";
+//        }
+//        result3 = StrUtil.trim(result3);
+//        log.info("返回结果:" + result3);
+//
+//        if (result3.matches("^\\d+$") && !result3.equals("0")) {
+//            return;
+//        }
+
+    }
+
+    public String test(String fileName){
+        // windows
+        String url = "file:///F:\\file\\estate\\reports\\"+fileName;
+        String filePath = "F:\\file\\estate\\reports";
+        String result = wordToPdf(url,filePath, fileName);
+        // linux
+//        String filePath = "F:\\file\\estate\\reports\\1769239688785.docx";
+//        String result = wordToPdf(filePath, fileName);
+        log.info("word转pdf返回结果:" + result);
+        return result;
+    }
+
+}
diff --git a/admin-server/src/main/java/com/estate/expert/admin/utils/wordtopdf/WordUtil.java b/admin-server/src/main/java/com/estate/expert/admin/utils/wordtopdf/WordUtil.java
new file mode 100644
index 0000000..50f3631
--- /dev/null
+++ b/admin-server/src/main/java/com/estate/expert/admin/utils/wordtopdf/WordUtil.java
@@ -0,0 +1,336 @@
+package com.estate.expert.admin.utils.wordtopdf;
+
+import freemarker.cache.ClassTemplateLoader;
+import freemarker.template.Configuration;
+import freemarker.template.Template;
+import freemarker.template.TemplateException;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.poi.xwpf.usermodel.*;
+import org.springframework.mock.web.MockMultipartFile;
+import org.springframework.stereotype.Component;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.annotation.Resource;
+import java.io.*;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+@Slf4j
+@Component
+public class WordUtil {
+
+    /**
+     * 基于模板生成 Word 文档
+     *
+     * @param basePackagePath  resources 目录下模板所在包路径
+     * @param templateFileName 模板文件名
+     * @param templateParam    模板参数
+     * @param fileName         文件名
+     */
+    @Resource
+    private PdfUtils pdfUtils;
+    public static void fillTemplate(String templatePath, String outputPath,Map<String, Object> dataMap) {
+        try (FileInputStream fis = new FileInputStream(templatePath)) {
+            // 设置默认编码为UTF-8
+            System.setProperty("file.encoding", "UTF-8");
+
+            XWPFDocument document = new XWPFDocument(fis);
+            XWPFParagraph pic = document.createParagraph();
+            Base64.Decoder decoder = Base64.getDecoder();
+            // 处理段落
+            for (XWPFParagraph paragraph : document.getParagraphs()) {
+                replaceParagraph(paragraph, dataMap);
+            }
+
+            // 处理表格
+            for (XWPFTable table : document.getTables()) {
+                for (XWPFTableRow row : table.getRows()) {
+                    for (XWPFTableCell cell : row.getTableCells()) {
+                        for (XWPFParagraph paragraph : cell.getParagraphs()) {
+                            replaceParagraph(paragraph, dataMap);
+                        }
+                    }
+                }
+            }
+
+            // 使用UTF-8编码保存文件
+            try (FileOutputStream fos = new FileOutputStream(outputPath)) {
+                document.write(fos);
+            }
+
+            System.err.println("模板填充完成!文件保存在: " + outputPath);
+
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    private static void replaceParagraph(XWPFParagraph paragraph, Map<String, Object> dataMap) {
+        // 获取段落中所有runs
+        List<XWPFRun> runs = paragraph.getRuns();
+        if (runs == null || runs.isEmpty()) return;
+
+        // 首先合并所有runs的文本,以便正确识别占位符
+        StringBuilder fullText = new StringBuilder();
+        for (XWPFRun run : runs) {
+            String text = run.getText(0);
+            if (text != null) {
+                fullText.append(text);
+            }
+        }
+
+        String paragraphText = fullText.toString();
+
+        // 使用正则表达式查找所有占位符,包括括号内的
+        Pattern pattern = Pattern.compile("\\$\\{[^}]+\\}|\\([^)]*\\$\\{[^}]+\\}[^)]*\\)");
+        Matcher matcher = pattern.matcher(paragraphText);
+
+        List<ReplacementInfo> replacements = new ArrayList<>();
+
+        // 收集所有需要替换的信息
+        while (matcher.find()) {
+            String matched = matcher.group();
+            int start = matcher.start();
+            int end = matcher.end();
+
+            // 找出涉及到的runs
+            int startRun = -1;
+            int endRun = -1;
+            int currentPos = 0;
+
+            for (int i = 0; i < runs.size(); i++) {
+                XWPFRun run = runs.get(i);
+                String runText = run.getText(0);
+                if (runText == null) continue;
+
+                int runLength = runText.length();
+                if (startRun == -1 && currentPos + runLength > start) {
+                    startRun = i;
+                }
+                if (currentPos + runLength >= end) {
+                    endRun = i;
+                    break;
+                }
+                currentPos += runLength;
+            }
+
+            if (startRun != -1 && endRun != -1) {
+                // 处理括号内的占位符
+                String replacement = processPlaceholder(matched, dataMap);
+                replacements.add(new ReplacementInfo(startRun, endRun, matched, replacement));
+            }
+        }
+
+        // 从后向前替换,避免位置变化影响
+        Collections.sort(replacements, (a, b) -> b.startRun - a.startRun);
+
+        for (ReplacementInfo info : replacements) {
+            replaceRunRange(paragraph, info);
+        }
+    }
+
+    private static String processPlaceholder(String text, Map<String, Object> dataMap) {
+        // 处理括号内的占位符
+        Pattern placeholderPattern = Pattern.compile("\\$\\{([^}]+)\\}");
+        Matcher matcher = placeholderPattern.matcher(text);
+
+        StringBuffer result = new StringBuffer();
+        while (matcher.find()) {
+            String placeholder = matcher.group(0); // 完整的占位符
+            String key = matcher.group(1); // 占位符中的键
+            String replacement = Objects.nonNull(dataMap.get("${" + key + "}"))?String.valueOf(dataMap.get("${" + key + "}")):"";
+
+            if (replacement != null) {
+                // 如果在括号内,保留括号
+                if (text.startsWith("(") && text.endsWith(")")) {
+                    matcher.appendReplacement(result, replacement);
+                } else {
+                    matcher.appendReplacement(result, Matcher.quoteReplacement(replacement));
+                }
+            }
+        }
+        matcher.appendTail(result);
+
+        return result.toString();
+    }
+
+    private static void replaceRunRange(XWPFParagraph paragraph, ReplacementInfo info) {
+        List<XWPFRun> runs = paragraph.getRuns();
+
+        // 保存第一个run的样式
+        XWPFRun styleRun = runs.get(info.startRun);
+        RunStyle style = new RunStyle(styleRun);
+
+        // 删除范围内的所有runs
+        for (int i = info.endRun; i >= info.startRun; i--) {
+            paragraph.removeRun(i);
+        }
+
+        // 创建新的run并设置文本
+        XWPFRun newRun = paragraph.insertNewRun(info.startRun);
+        // 如果有回车符,则插入一个空行
+        if (info.replacementText.contains("\n") || info.replacementText.contains("\r") || info.replacementText.contains("\r\n")) {
+            log.info("info.originalText=================: {}", info.replacementText);
+            // 每一个\n,\r,\r\n都插入一个空行,并且最后的文本进行换行,为一整个文本
+            String[] s = info.replacementText.split("\r\n");
+            for (int i = 0; i < s.length; i++) {
+                if (i == 0) {
+                    newRun.setText(s[i] + "       ");
+                }else {
+//                    newRun.addCarriageReturn();
+                    newRun.setText(s[i] + "       ");
+                }
+            }
+        }else {
+            newRun.setText(info.replacementText);
+        }
+
+        // 应用样式
+        style.applyStyle(newRun);
+    }
+
+
+    /**
+     * 生成PDF文件
+     * @param basePackagePath 模板文件所在的包路径
+     * @param templateFileName 模板文件名
+     * @param templateParam 模板参数
+     * @param fileName  填充参数后的word文件名
+     * @param saveDirectory 填充参数后的word保存文件目录
+     * @return
+     */
+    public String generatePdf(String basePackagePath, String templateFileName, Map<String,Object> templateParam, String fileName, String saveDirectory) {
+        try {
+            fillTemplate(basePackagePath+templateFileName, saveDirectory+fileName+".docx", templateParam);
+            String filePath = saveDirectory + fileName + ".docx";
+            File file = new File(filePath);
+
+            // 检查文件是否存在
+            if (!file.exists()) {
+                throw new FileNotFoundException("文件不存在: " + filePath);
+            }
+
+            // 读取文件内容
+            byte[] fileContent = new byte[(int) file.length()];
+            try (FileInputStream fis = new FileInputStream(file)) {
+                fis.read(fileContent);
+            }
+
+            String test = pdfUtils.test(fileName + ".docx");
+//            MultipartFile mockMultipartFile = new MockMultipartFile(encodedFileName+".doc", fileContent);
+//            String s = ObsUploadUtil.obsUpload(mockMultipartFile);
+            return test;
+        } catch (IOException e) {
+            log.error("生成pdf异常,异常原因:{}", e.getMessage(), e);
+            throw new RuntimeException("生成pdf异常,异常原因:" + e.getMessage());
+        }
+    }
+
+    private static class ReplacementInfo {
+        int startRun;
+        int endRun;
+        String originalText;
+        String replacementText;
+
+        ReplacementInfo(int startRun, int endRun, String originalText, String replacementText) {
+            this.startRun = startRun;
+            this.endRun = endRun;
+            this.originalText = originalText;
+            this.replacementText = replacementText;
+        }
+    }
+
+    // 用于保存和恢复运行样式的辅助类
+    private static class RunStyle {
+        String fontFamily;
+        int fontSize;
+        boolean bold;
+        boolean italic;
+        String color;
+        UnderlinePatterns underline;
+
+        RunStyle(XWPFRun run) {
+            this.fontFamily = run.getFontFamily();
+            this.fontSize = run.getFontSize();
+            this.bold = run.isBold();
+            this.italic = run.isItalic();
+            this.color = run.getColor();
+            this.underline = run.getUnderline();
+        }
+
+        void applyStyle(XWPFRun run) {
+            if (fontFamily != null) {
+                run.setFontFamily(fontFamily);
+                run.setFontFamily(fontFamily, XWPFRun.FontCharRange.eastAsia);
+            }
+            if (fontSize != -1) {
+                run.setFontSize(fontSize);
+            }
+            run.setBold(bold);
+            run.setItalic(italic);
+            if (color != null) {
+                run.setColor(color);
+            }
+            if (underline != null) {
+                run.setUnderline(underline);
+            }
+        }
+    }
+
+//    public String generatePdf(String basePackagePath, String templateFileName, Object templateParam, String fileName, String saveDirectory) {
+//        try {
+//            // 创建 Freemarker 的 Configuration 对象,设置默认的不兼容改进选项
+//            Configuration configuration = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
+//            configuration.setDefaultEncoding("utf-8");
+//            // 设置模板加载器,加载模板文件
+//            configuration.setTemplateLoader(new ClassTemplateLoader(getClass(), basePackagePath));
+//            Template t = configuration.getTemplate(templateFileName, "utf-8");
+//
+//            // 使用 URLEncoder 对文件名进行编码,以防止中文文件名在不同浏览器和操作系统下出现乱码问题
+////            String encodedFileName = URLEncoder.encode(fileName + "_" + System.currentTimeMillis(), "utf-8");
+//            String encodedFileName =fileName ;
+//
+//            // 定义保存文件的路径
+//            File saveDir = new File(saveDirectory);
+//            if (!saveDir.exists()) {
+//                saveDir.mkdirs();
+//            }
+//
+//            // 定义文件名
+//            String filePath = saveDir.getAbsolutePath() + File.separator + encodedFileName + ".doc";
+//
+//            // 创建 Writer 对象,用于将生成的文档写到文件中,缓存区大小设为 10KB
+//            Writer out = new BufferedWriter(new FileWriter(filePath), 10240);
+//
+//            // 将模型数据与模板结合生成 Word 文档,写入到 Writer 对象中
+//            t.process(templateParam, out);
+//            out.close();
+//
+//            File file = new File(filePath);
+//
+//            // 检查文件是否存在
+//            if (!file.exists()) {
+//                throw new FileNotFoundException("文件不存在: " + filePath);
+//            }
+//
+//            // 读取文件内容
+//            byte[] fileContent = new byte[(int) file.length()];
+//            try (FileInputStream fis = new FileInputStream(file)) {
+//                fis.read(fileContent);
+//            }
+//
+//            String test = pdfUtils.test(encodedFileName + ".doc");
+////            MultipartFile mockMultipartFile = new MockMultipartFile(encodedFileName+".doc", fileContent);
+////            String s = ObsUploadUtil.obsUpload(mockMultipartFile);
+//            return test;
+//        } catch (IOException | TemplateException e) {
+//            log.error("生成pdf异常,异常原因:{}", e.getMessage(), e);
+//            throw new RuntimeException("生成pdf异常,异常原因:" + e.getMessage());
+//        }
+//    }
+
+
+
+
+}

--
Gitblit v1.7.1