zzw 1 месяц назад
Родитель
Сommit
7db08116cd

+ 27 - 0
assistMg/pom.xml

@@ -90,6 +90,33 @@
             <version>8.3.8</version>
         </dependency>
 
+        <!-- Aspose.Words - Word文档处理 -->
+        <dependency>
+            <groupId>com.aspose</groupId>
+            <artifactId>aspose-words</artifactId>
+            <version>23.1</version>
+            <scope>system</scope>
+            <systemPath>${project.basedir}/../lib-3rd/aspose-words-23.1.jar</systemPath>
+        </dependency>
+
+        <!-- Aspose.Cells - Excel文档处理 -->
+        <dependency>
+            <groupId>com.aspose</groupId>
+            <artifactId>aspose-cells</artifactId>
+            <version>23.1</version>
+            <scope>system</scope>
+            <systemPath>${project.basedir}/../lib-3rd/aspose-cells-23.1.jar</systemPath>
+        </dependency>
+
+        <!-- Aspose.PDF - PDF文档处理 -->
+        <dependency>
+            <groupId>com.aspose</groupId>
+            <artifactId>aspose-pdf</artifactId>
+            <version>23.1</version>
+            <scope>system</scope>
+            <systemPath>${project.basedir}/../lib-3rd/aspose-pdf-23.1.jar</systemPath>
+        </dependency>
+
     </dependencies>
     <!-- 用于生成jar文件-->
     <build>

+ 332 - 266
assistMg/src/main/java/com/hotent/project/service/ArchiveTest.java

@@ -1,22 +1,18 @@
 package com.hotent.project.service;
 
-import org.docx4j.Docx4J;
-import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
-import org.docx4j.openpackaging.parts.WordprocessingML.FooterPart;
-import org.docx4j.openpackaging.parts.WordprocessingML.MainDocumentPart;
-import org.docx4j.relationships.Relationship;
-import org.docx4j.wml.*;
-
-import javax.xml.bind.JAXBElement;
+import com.aspose.words.*;
+import com.aspose.cells.Workbook;
+import com.aspose.cells.PdfSaveOptions;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
 import java.io.File;
-import java.io.FileInputStream;
-import java.math.BigInteger;
 import java.util.ArrayList;
 import java.util.List;
 
 /**
- * 卷宗生成测试类(使用docx4j实现节分隔符控制页码
- * 测试封面、目录、正文、封底的合并效果
+ * 卷宗生成测试类(使用Aspose实现多格式合并
+ * 支持:Word(.docx/.doc)、Excel(.xlsx/.xls)、PDF(.pdf)
  *
  * 页码规则:
  * - 封面:无页码
@@ -26,10 +22,8 @@ import java.util.List;
  */
 public class ArchiveTest {
 
-    private static ObjectFactory factory = new ObjectFactory();
-
     public static void main(String[] args) throws Exception {
-        System.out.println("========== 卷宗生成测试(docx4j版)==========\n");
+        System.out.println("========== 卷宗生成测试(Aspose版)==========\n");
 
         // 测试文件路径
         String basePath = "D:\\fx\\cc\\";
@@ -42,28 +36,21 @@ public class ArchiveTest {
         List<String> catalogFiles = new ArrayList<>();
         catalogFiles.add(basePath + "卷内目录20251225155255833.docx");
 
-        // 正文文件(14个资料类别
+        // 正文文件(支持Word、Excel、PDF混合
         List<String> contentFiles = new ArrayList<>();
-        contentFiles.add(basePath + "政府定价成本监审结论报告20251225151207150.docx");
-        contentFiles.add(basePath + "成本监审通知书20251225142953160.docx");
-        contentFiles.add(basePath + "成本监审通知书20251225143004173.docx");
+        // Excel文件
+        contentFiles.add(basePath + "小店区-幼儿教育成本监审表(封面)_176664431290620251225143152602.xlsx");
+        contentFiles.add(basePath + "小店区-幼儿教育成本监审表(固定资产) _176664457050920251225143610719.xlsx");
+        // 更多Word文件
         contentFiles.add(basePath + "成本监审提取资料登记表20251225154211987.docx");
         contentFiles.add(basePath + "报送资料文件20251225143050824.docx");
-        contentFiles.add(basePath + "报送资料文件20251225143055127.docx");
-        contentFiles.add(basePath + "成本监审补充资料通知书20251225150624580.docx");
-        contentFiles.add(basePath + "成本审核初步意见告知书20251225150642481.docx");
-        contentFiles.add(basePath + "成本审核初步意见表20251225153700986.docx");
-        contentFiles.add(basePath + "成本监审集体审议记录122520251225154018971.docx");
-        contentFiles.add(basePath + "成本监审工作底稿20251225153205828.docx");
-        contentFiles.add(basePath + "中止定价成本监审通知书20251225150552396.docx");
-        contentFiles.add(basePath + "中止定价成本监审通知书20251225150608304.docx");
 
         // 封底文件
         List<String> backCoverFiles = new ArrayList<>();
         backCoverFiles.add(basePath + "案卷封底20251225155301766.docx");
 
         // 输出路径
-        String outputPath = basePath + "卷宗_docx4j_" + System.currentTimeMillis() + ".docx";
+        String outputPath = basePath + "卷宗_Aspose_" + System.currentTimeMillis() + ".docx";
 
         // 合并文档
         mergeDocuments(coverFiles, catalogFiles, contentFiles, backCoverFiles, outputPath);
@@ -73,7 +60,7 @@ public class ArchiveTest {
     }
 
     /**
-     * 合并多个Word文档,支持分节控制页码
+     * 合并多个文档(支持Word、Excel、PDF)
      *
      * @param coverFiles     封面文件列表
      * @param catalogFiles   目录文件列表
@@ -82,14 +69,13 @@ public class ArchiveTest {
      * @param outputPath     输出路径
      */
     public static void mergeDocuments(List<String> coverFiles, List<String> catalogFiles,
-                                List<String> contentFiles, List<String> backCoverFiles,
-                                String outputPath) throws Exception {
-        System.out.println("开始使用docx4j合并文档...");
+                                      List<String> contentFiles, List<String> backCoverFiles,
+                                      String outputPath) throws Exception {
+        System.out.println("开始使用Aspose合并文档...");
 
-        // 创建主文档
-        WordprocessingMLPackage mainDoc = WordprocessingMLPackage.createPackage();
-        MainDocumentPart mainPart = mainDoc.getMainDocumentPart();
-        Body body = mainPart.getContents().getBody();
+        // 创建主文档(不使用ensureMinimum,避免产生空白页)
+        Document mainDoc = new Document();
+        mainDoc.removeAllChildren();
 
         boolean isFirst = true;
 
@@ -99,11 +85,9 @@ public class ArchiveTest {
             for (String filePath : coverFiles) {
                 if (filePath != null && new File(filePath).exists()) {
                     System.out.println("   ✓ " + extractFileName(filePath));
-                    isFirst = mergeWordFile(mainDoc, body, filePath, isFirst);
+                    isFirst = appendDocument(mainDoc, filePath, isFirst, SectionStart.NEW_PAGE);
                 }
             }
-            // 封面后添加节分隔符(无页脚)
-            addSectionBreak(body, false, null);
         }
 
         // 2. 合并目录(无页码节)
@@ -112,303 +96,385 @@ public class ArchiveTest {
             for (String filePath : catalogFiles) {
                 if (filePath != null && new File(filePath).exists()) {
                     System.out.println("   ✓ " + extractFileName(filePath));
-                    isFirst = mergeWordFile(mainDoc, body, filePath, isFirst);
+                    isFirst = appendDocument(mainDoc, filePath, isFirst, SectionStart.NEW_PAGE);
                 }
             }
-            // 目录后添加节分隔符(准备进入有页码的正文)
-            addSectionBreak(body, false, null);
         }
 
-        // 3. 创建正文的页脚(有页码)
-        FooterPart footerPart = null;
-        Relationship footerRel = null;
-        if (contentFiles != null && !contentFiles.isEmpty()) {
-            footerPart = createFooterWithPageNumber(mainDoc);
-            footerRel = mainDoc.getMainDocumentPart().addTargetPart(footerPart);
-        }
+        // 记录正文开始的节索引
+        int contentStartSectionIndex = mainDoc.getSections().getCount();
 
-        // 4. 合并正文(有页码节)
+        // 3. 合并正文(有页码节)
         if (contentFiles != null && !contentFiles.isEmpty()) {
             System.out.println("\n3. 合并正文(有页码)...");
             for (String filePath : contentFiles) {
                 if (filePath != null && new File(filePath).exists()) {
                     System.out.println("   ✓ " + extractFileName(filePath));
-                    isFirst = mergeWordFile(mainDoc, body, filePath, isFirst);
+                    isFirst = appendDocument(mainDoc, filePath, isFirst, SectionStart.NEW_PAGE);
                 }
             }
-            // 正文后添加节分隔符(带页脚引用,然后进入无页码的封底)
-            if (backCoverFiles != null && !backCoverFiles.isEmpty()) {
-                addSectionBreak(body, true, footerRel);
-            }
         }
 
-        // 5. 合并封底(无页码节)
+        // 记录正文结束的节索引
+        int contentEndSectionIndex = mainDoc.getSections().getCount() - 1;
+
+        // 4. 合并封底(无页码节)
         if (backCoverFiles != null && !backCoverFiles.isEmpty()) {
             System.out.println("\n4. 合并封底...");
             for (String filePath : backCoverFiles) {
                 if (filePath != null && new File(filePath).exists()) {
                     System.out.println("   ✓ " + extractFileName(filePath));
-                    isFirst = mergeWordFile(mainDoc, body, filePath, isFirst);
+                    isFirst = appendDocument(mainDoc, filePath, isFirst, SectionStart.NEW_PAGE);
                 }
             }
         }
 
-        // 设置文档最后一节的属性(封底无页脚)
-        setLastSectionProperties(body, false, null);
+        // 5. 计算正文总页数并设置页码
+        System.out.println("\n5. 设置页码...");
+        int contentTotalPages = calculateContentPages(mainDoc, contentStartSectionIndex, contentEndSectionIndex);
+        System.out.println("   正文总页数: " + contentTotalPages);
+        setupPageNumbers(mainDoc, contentStartSectionIndex, contentEndSectionIndex, contentTotalPages);
 
         // 保存文档
         File outputFile = new File(outputPath);
-        if (!outputFile.getParentFile().exists()) {
+        if (outputFile.getParentFile() != null && !outputFile.getParentFile().exists()) {
             outputFile.getParentFile().mkdirs();
         }
-        Docx4J.save(mainDoc, outputFile);
+        mainDoc.save(outputPath);
         System.out.println("\n文档保存成功:" + outputPath);
     }
 
     /**
-     * 合并单个Word文件
+     * 计算正文部分的总页数
      */
-    private static boolean mergeWordFile(WordprocessingMLPackage mainDoc, Body body,
-                                   String filePath, boolean isFirst) throws Exception {
-        File file = new File(filePath);
-        if (!file.exists()) {
-            System.out.println("   [警告] 文件不存在:" + filePath);
-            return isFirst;
-        }
+    private static int calculateContentPages(Document doc, int contentStartSectionIndex, int contentEndSectionIndex) throws Exception {
+        // 更新文档布局以获取准确的页数
+        doc.updatePageLayout();
 
-        String lowerPath = filePath.toLowerCase();
-        if (!lowerPath.endsWith(".docx") && !lowerPath.endsWith(".doc")) {
-            System.out.println("   [警告] 不支持的文件格式:" + filePath);
-            return isFirst;
-        }
+        int totalPages = 0;
+        SectionCollection sections = doc.getSections();
 
-        // 添加分页符(非第一个文档)
-        if (!isFirst) {
-            addPageBreak(body);
+        for (int i = contentStartSectionIndex; i <= contentEndSectionIndex && i < sections.getCount(); i++) {
+            Section section = sections.get(i);
+            // 获取该节的页数
+            int sectionPages = doc.getPageCount();  // 这是整个文档的页数,需要另外计算
         }
 
-        // 加载源文档
-        WordprocessingMLPackage sourceDoc = WordprocessingMLPackage.load(new FileInputStream(file));
-        List<Object> sourceContent = sourceDoc.getMainDocumentPart().getContent();
-
-        // 复制内容到主文档
-        for (Object obj : sourceContent) {
-            // 跳过节属性(SectPr),避免干扰
-            if (obj instanceof JAXBElement) {
-                JAXBElement<?> element = (JAXBElement<?>) obj;
-                if (element.getValue() instanceof SectPr) {
-                    continue;
+        // 使用LayoutCollector获取精确页数
+        LayoutCollector collector = new LayoutCollector(doc);
+        int contentStartPage = Integer.MAX_VALUE;
+        int contentEndPage = 0;
+
+        for (int i = contentStartSectionIndex; i <= contentEndSectionIndex && i < sections.getCount(); i++) {
+            Section section = sections.get(i);
+            // 获取节的第一个段落所在页
+            Node firstNode = section.getBody().getFirstChild();
+            Node lastNode = section.getBody().getLastChild();
+
+            if (firstNode != null) {
+                int startPage = collector.getStartPageIndex(firstNode);
+                if (startPage > 0 && startPage < contentStartPage) {
+                    contentStartPage = startPage;
                 }
             }
-            if (obj instanceof SectPr) {
-                continue;
+            if (lastNode != null) {
+                int endPage = collector.getEndPageIndex(lastNode);
+                if (endPage > contentEndPage) {
+                    contentEndPage = endPage;
+                }
             }
+        }
 
-            // 深拷贝并添加到主文档
-            Object cloned = org.docx4j.XmlUtils.deepCopy(obj);
-            body.getContent().add(cloned);
+        if (contentStartPage != Integer.MAX_VALUE && contentEndPage > 0) {
+            totalPages = contentEndPage - contentStartPage + 1;
         }
 
-        return false;
+        return totalPages;
     }
 
     /**
-     * 添加分页符
+     * 追加文档(自动识别格式)
      */
-    private static void addPageBreak(Body body) {
-        P p = factory.createP();
-        R r = factory.createR();
-        Br br = factory.createBr();
-        br.setType(STBrType.PAGE);
-        r.getContent().add(br);
-        p.getContent().add(r);
-        body.getContent().add(p);
+    private static boolean appendDocument(Document mainDoc, String filePath, boolean isFirst, int sectionStart) throws Exception {
+        String lowerPath = filePath.toLowerCase();
+        Document docToAppend;
+
+        if (lowerPath.endsWith(".docx") || lowerPath.endsWith(".doc")) {
+            // Word文档直接加载
+            docToAppend = new Document(filePath);
+        } else if (lowerPath.endsWith(".xlsx") || lowerPath.endsWith(".xls")) {
+            // Excel转Word
+            docToAppend = convertExcelToWord(filePath);
+        } else if (lowerPath.endsWith(".pdf")) {
+            // PDF转Word
+            docToAppend = convertPdfToWord(filePath);
+        } else {
+            System.out.println("   [警告] 不支持的文件格式:" + filePath);
+            return isFirst;
+        }
+
+        if (docToAppend != null && docToAppend.getSections().getCount() > 0) {
+            if (!isFirst) {
+                // 设置第一节的分节符类型
+                docToAppend.getFirstSection().getPageSetup().setSectionStart(sectionStart);
+            }
+
+            // 追加到主文档
+            mainDoc.appendDocument(docToAppend, ImportFormatMode.KEEP_SOURCE_FORMATTING);
+            return false;
+        }
+
+        return isFirst;
     }
 
     /**
-     * 添加节分隔符
-     *
-     * @param body          文档Body
-     * @param withFooter    是否带页脚
-     * @param footerRel     页脚关系(如果withFooter为true)
+     * Excel转Word文档
      */
-    private static void addSectionBreak(Body body, boolean withFooter, Relationship footerRel) {
-        P p = factory.createP();
-        PPr pPr = factory.createPPr();
-        SectPr sectPr = factory.createSectPr();
-
-        // 设置节类型为下一页
-        SectPr.Type type = factory.createSectPrType();
-        type.setVal("nextPage");
-        sectPr.setType(type);
-
-        // 如果需要页脚,添加页脚引用
-        if (withFooter && footerRel != null) {
-            FooterReference footerRef = factory.createFooterReference();
-            footerRef.setType(HdrFtrRef.DEFAULT);
-            footerRef.setId(footerRel.getId());
-            sectPr.getEGHdrFtrReferences().add(footerRef);
-
-            // 页码从1开始
-            CTPageNumber pgNumType = factory.createCTPageNumber();
-            pgNumType.setStart(BigInteger.valueOf(1));
-            sectPr.setPgNumType(pgNumType);
+    private static Document convertExcelToWord(String excelPath) throws Exception {
+        // 加载Excel
+        Workbook workbook = new Workbook(excelPath);
+
+        // 获取第一个工作表
+        com.aspose.cells.Worksheet worksheet = workbook.getWorksheets().get(0);
+        com.aspose.cells.Cells cells = worksheet.getCells();
+
+        // 获取实际使用的行数
+        int maxRow = cells.getMaxDataRow() + 1;  // getMaxDataRow返回的是索引,+1得到行数
+
+        // 如果只有三行或更少,转换为普通文本格式
+        if (maxRow <= 3) {
+            return convertExcelToText(workbook);
         }
 
-        pPr.setSectPr(sectPr);
-        p.setPPr(pPr);
-        body.getContent().add(p);
+        // 先转为PDF(保持格式)
+        ByteArrayOutputStream pdfStream = new ByteArrayOutputStream();
+        com.aspose.cells.PdfSaveOptions pdfOptions = new com.aspose.cells.PdfSaveOptions();
+        pdfOptions.setOnePagePerSheet(false);  // 允许多页
+        pdfOptions.setAllColumnsInOnePagePerSheet(true);  // 所有列在一页
+        workbook.save(pdfStream, pdfOptions);
+
+        // PDF转Word
+        ByteArrayInputStream pdfInput = new ByteArrayInputStream(pdfStream.toByteArray());
+        com.aspose.pdf.Document pdfDoc = new com.aspose.pdf.Document(pdfInput);
+
+        ByteArrayOutputStream wordStream = new ByteArrayOutputStream();
+        pdfDoc.save(wordStream, com.aspose.pdf.SaveFormat.DocX);
+
+        // 加载Word文档
+        ByteArrayInputStream wordInput = new ByteArrayInputStream(wordStream.toByteArray());
+        Document doc = new Document(wordInput);
+
+        return doc;
     }
 
     /**
-     * 设置文档最后一节的属性
+     * Excel转为普通文本格式的Word文档(适用于只有两三行的情况)
+     * 第一行为标题,第二行为表头,第三行为数据
+     * 展示格式:表头: 数据
      */
-    private static void setLastSectionProperties(Body body, boolean withFooter, Relationship footerRel) {
-        SectPr sectPr = body.getSectPr();
-        if (sectPr == null) {
-            sectPr = factory.createSectPr();
-            body.setSectPr(sectPr);
+    private static Document convertExcelToText(Workbook workbook) throws Exception {
+        Document doc = new Document();
+        DocumentBuilder builder = new DocumentBuilder(doc);
+
+        // 获取第一个工作表
+        com.aspose.cells.Worksheet worksheet = workbook.getWorksheets().get(0);
+        com.aspose.cells.Cells cells = worksheet.getCells();
+
+        int maxRow = cells.getMaxDataRow() + 1;
+        int maxCol = cells.getMaxDataColumn() + 1;
+
+        // 第一行是标题,单独输出(居中、加粗、大字号)
+        if (maxRow >= 1) {
+            for (int col = 0; col < maxCol; col++) {
+                com.aspose.cells.Cell cell = cells.get(0, col);
+                String value = cell.getStringValue();
+                if (value != null && !value.trim().isEmpty()) {
+                    builder.getParagraphFormat().setAlignment(ParagraphAlignment.CENTER);
+                    builder.getParagraphFormat().setSpaceAfter(12);
+                    builder.getFont().setName("宋体");
+                    builder.getFont().setSize(16);
+                    builder.getFont().setBold(true);
+                    builder.writeln(value.trim());
+                    builder.getFont().setBold(false);
+                    break;  // 标题通常只有一个
+                }
+            }
         }
 
-        // 清除现有页脚引用
-        sectPr.getEGHdrFtrReferences().clear();
+        // 第二行是表头,第三行是数据,配对输出
+        if (maxRow >= 3) {
+            // 重置段落格式
+            builder.getParagraphFormat().setAlignment(ParagraphAlignment.LEFT);
+            builder.getParagraphFormat().setSpaceAfter(6);
+            builder.getParagraphFormat().setFirstLineIndent(24);  // 首行缩进
+            builder.getFont().setSize(12);
+
+            for (int col = 0; col < maxCol; col++) {
+                com.aspose.cells.Cell headerCell = cells.get(1, col);  // 表头
+                com.aspose.cells.Cell dataCell = cells.get(2, col);    // 数据
 
-        if (withFooter && footerRel != null) {
-            FooterReference footerRef = factory.createFooterReference();
-            footerRef.setType(HdrFtrRef.DEFAULT);
-            footerRef.setId(footerRel.getId());
-            sectPr.getEGHdrFtrReferences().add(footerRef);
+                String header = headerCell.getStringValue();
+                String data = dataCell.getStringValue();
+
+                if (header != null && !header.trim().isEmpty()) {
+                    String line = header.trim() + ":" + (data != null ? data.trim() : "");
+                    builder.writeln(line);
+                }
+            }
+        } else if (maxRow == 2) {
+            // 只有两行:第一行标题,第二行数据
+            builder.getParagraphFormat().setAlignment(ParagraphAlignment.LEFT);
+            builder.getParagraphFormat().setSpaceAfter(6);
+            builder.getFont().setSize(12);
+
+            for (int col = 0; col < maxCol; col++) {
+                com.aspose.cells.Cell cell = cells.get(1, col);
+                String value = cell.getStringValue();
+                if (value != null && !value.trim().isEmpty()) {
+                    builder.writeln(value.trim());
+                }
+            }
         }
-    }
 
-    /**
-     * 创建带页码的页脚
-     * 格式:第 X 页 / 共 Y 页
-     */
-    private static FooterPart createFooterWithPageNumber(WordprocessingMLPackage wordPackage) throws Exception {
-        FooterPart footerPart = new FooterPart();
-        footerPart.setPackage(wordPackage);
-
-        Ftr footer = factory.createFtr();
-
-        // 创建段落
-        P p = factory.createP();
-
-        // 设置段落居中
-        PPr pPr = factory.createPPr();
-        Jc jc = factory.createJc();
-        jc.setVal(JcEnumeration.CENTER);
-        pPr.setJc(jc);
-        p.setPPr(pPr);
-
-        // 创建字体属性
-        RPr rPr = createFontRPr("宋体", "20");
-
-        // "第 "
-        R r1 = factory.createR();
-        r1.setRPr(rPr);
-        Text t1 = factory.createText();
-        t1.setValue("第 ");
-        t1.setSpace("preserve");
-        r1.getContent().add(t1);
-        p.getContent().add(r1);
-
-        // PAGE 字段(当前页码)
-        addFieldToRun(p, "PAGE", rPr);
-
-        // " 页 / 共 "
-        R r2 = factory.createR();
-        r2.setRPr(rPr);
-        Text t2 = factory.createText();
-        t2.setValue(" 页 / 共 ");
-        t2.setSpace("preserve");
-        r2.getContent().add(t2);
-        p.getContent().add(r2);
-
-        // NUMPAGES 字段(总页数)
-        addFieldToRun(p, "NUMPAGES", rPr);
-
-        // " 页"
-        R r3 = factory.createR();
-        r3.setRPr(rPr);
-        Text t3 = factory.createText();
-        t3.setValue(" 页");
-        r3.getContent().add(t3);
-        p.getContent().add(r3);
-
-        footer.getContent().add(p);
-        footerPart.setContents(footer);
-
-        return footerPart;
+        return doc;
     }
 
+
+
     /**
-     * 添加字段到段落
+     * PDF转Word文档
      */
-    private static void addFieldToRun(P p, String fieldName, RPr rPr) {
-        // 字段开始
-        R rBegin = factory.createR();
-        rBegin.setRPr(rPr);
-        FldChar fldCharBegin = factory.createFldChar();
-        fldCharBegin.setFldCharType(STFldCharType.BEGIN);
-        rBegin.getContent().add(fldCharBegin);
-        p.getContent().add(rBegin);
-
-        // 字段指令
-        R rInstr = factory.createR();
-        rInstr.setRPr(rPr);
-        Text instrText = factory.createText();
-        instrText.setValue(" " + fieldName + " ");
-        instrText.setSpace("preserve");
-        JAXBElement<Text> instrTextElement = factory.createRInstrText(instrText);
-        rInstr.getContent().add(instrTextElement);
-        p.getContent().add(rInstr);
-
-        // 字段分隔
-        R rSep = factory.createR();
-        rSep.setRPr(rPr);
-        FldChar fldCharSep = factory.createFldChar();
-        fldCharSep.setFldCharType(STFldCharType.SEPARATE);
-        rSep.getContent().add(fldCharSep);
-        p.getContent().add(rSep);
-
-        // 默认值
-        R rDefault = factory.createR();
-        rDefault.setRPr(rPr);
-        Text defaultText = factory.createText();
-        defaultText.setValue("1");
-        rDefault.getContent().add(defaultText);
-        p.getContent().add(rDefault);
-
-        // 字段结束
-        R rEnd = factory.createR();
-        rEnd.setRPr(rPr);
-        FldChar fldCharEnd = factory.createFldChar();
-        fldCharEnd.setFldCharType(STFldCharType.END);
-        rEnd.getContent().add(fldCharEnd);
-        p.getContent().add(rEnd);
+    private static Document convertPdfToWord(String pdfPath) throws Exception {
+        // 加载PDF
+        com.aspose.pdf.Document pdfDoc = new com.aspose.pdf.Document(pdfPath);
+
+        // 转为Word
+        ByteArrayOutputStream wordStream = new ByteArrayOutputStream();
+        pdfDoc.save(wordStream, com.aspose.pdf.SaveFormat.DocX);
+
+        // 加载Word文档
+        ByteArrayInputStream wordInput = new ByteArrayInputStream(wordStream.toByteArray());
+        return new Document(wordInput);
     }
 
     /**
-     * 创建字体属性
+     * 设置页码(只有正文部分显示页码)
+     *
+     * @param doc                     文档
+     * @param contentStartSectionIndex 正文开始的节索引
+     * @param contentEndSectionIndex   正文结束的节索引
+     * @param contentTotalPages        正文总页数
      */
-    private static RPr createFontRPr(String fontName, String fontSize) {
-        RPr rPr = factory.createRPr();
-
-        // 字体
-        RFonts rFonts = factory.createRFonts();
-        rFonts.setAscii(fontName);
-        rFonts.setEastAsia(fontName);
-        rFonts.setHAnsi(fontName);
-        rPr.setRFonts(rFonts);
-
-        // 字号
-        HpsMeasure sz = factory.createHpsMeasure();
-        sz.setVal(new BigInteger(fontSize));
-        rPr.setSz(sz);
-        rPr.setSzCs(sz);
-
-        return rPr;
+    private static void setupPageNumbers(Document doc, int contentStartSectionIndex, int contentEndSectionIndex, int contentTotalPages) throws Exception {
+        SectionCollection sections = doc.getSections();
+
+        for (int i = 0; i < sections.getCount(); i++) {
+            Section section = sections.get(i);
+            HeaderFooterCollection headerFooters = section.getHeadersFooters();
+
+            // 取消链接到上一节(使每节页眉页脚独立)
+            headerFooters.linkToPrevious(false);
+
+            // 清除现有页脚
+            HeaderFooter existingFooter = headerFooters.getByHeaderFooterType(HeaderFooterType.FOOTER_PRIMARY);
+            if (existingFooter != null) {
+                existingFooter.remove();
+            }
+
+            // 清除现有页眉
+            HeaderFooter existingHeader = headerFooters.getByHeaderFooterType(HeaderFooterType.HEADER_PRIMARY);
+            if (existingHeader != null) {
+                existingHeader.remove();
+            }
+
+            // 只有正文部分添加页码和页眉编号
+            if (i >= contentStartSectionIndex && i <= contentEndSectionIndex) {
+                // 页码设置:只有正文第一节从1开始,后续节继续累加
+                if (i == contentStartSectionIndex) {
+                    // 正文第一节:页码从1开始
+                    section.getPageSetup().setRestartPageNumbering(true);
+                    section.getPageSetup().setPageStartingNumber(1);
+                } else {
+                    // 正文后续节:页码继续累加,不重新开始
+                    section.getPageSetup().setRestartPageNumbering(false);
+                }
+
+                // ========== 创建页眉(右上角编号 001、002、003...)==========
+                HeaderFooter header = new HeaderFooter(doc, HeaderFooterType.HEADER_PRIMARY);
+                headerFooters.add(header);
+
+                // 创建页眉段落(右对齐)
+                Paragraph headerPara = new Paragraph(doc);
+                headerPara.getParagraphFormat().setAlignment(ParagraphAlignment.RIGHT);
+                // 设置右缩进,增加与右侧边距的距离
+                headerPara.getParagraphFormat().setRightIndent(20);
+                // 去掉页眉下边框线
+                headerPara.getParagraphFormat().getBorders().getBottom().setLineStyle(LineStyle.NONE);
+
+                // 先将段落添加到header中
+                header.appendChild(headerPara);
+
+                // 使用DocumentBuilder插入带格式的页码字段
+                DocumentBuilder builder = new DocumentBuilder(doc);
+                builder.moveTo(headerPara);
+
+                // 设置字体(加粗)
+                builder.getFont().setName("宋体");
+                builder.getFont().setSize(12);
+                builder.getFont().setBold(true);
+
+                // 插入带格式的PAGE字段,格式为3位数字
+                builder.insertField("PAGE \\# \"000\"", "001");
+
+                // 设置页眉距离
+                section.getPageSetup().setHeaderDistance(20);
+
+                // ========== 创建页脚(第 X 页 / 共 Y 页)==========
+                HeaderFooter footer = new HeaderFooter(doc, HeaderFooterType.FOOTER_PRIMARY);
+                headerFooters.add(footer);
+
+                // 创建页脚段落
+                Paragraph footerPara = new Paragraph(doc);
+                footerPara.getParagraphFormat().setAlignment(ParagraphAlignment.CENTER);
+                footerPara.getParagraphFormat().setSpaceBefore(12);
+
+                // 设置页脚距离页面底部的距离
+                section.getPageSetup().setFooterDistance(30);
+
+                // 添加页码文本:第 X 页 / 共 Y 页
+                Run run1 = new Run(doc, "第 ");
+                run1.getFont().setName("宋体");
+                run1.getFont().setSize(10.5);
+                footerPara.appendChild(run1);
+
+                // 当前页码字段
+                footerPara.appendField(FieldType.FIELD_PAGE, true);
+
+                Run run2 = new Run(doc, " 页 / 共 ");
+                run2.getFont().setName("宋体");
+                run2.getFont().setSize(10.5);
+                footerPara.appendChild(run2);
+
+                // 使用计算出的正文总页数(固定值)
+                Run runTotal = new Run(doc, String.valueOf(contentTotalPages));
+                runTotal.getFont().setName("宋体");
+                runTotal.getFont().setSize(10.5);
+                footerPara.appendChild(runTotal);
+
+                Run run3 = new Run(doc, " 页");
+                run3.getFont().setName("宋体");
+                run3.getFont().setSize(10.5);
+                footerPara.appendChild(run3);
+
+                footer.appendChild(footerPara);
+            } else {
+                // 封面、目录、封底:不添加页眉页脚,且不重新开始页码
+                section.getPageSetup().setRestartPageNumbering(false);
+            }
+        }
+
+        // 更新所有字段,使页码格式生效
+        doc.updateFields();
     }
 
     /**

+ 527 - 0
assistMg/src/main/java/com/hotent/project/service/AsposeArchiveService.java

@@ -0,0 +1,527 @@
+package com.hotent.project.service;
+
+import com.aspose.words.*;
+import com.aspose.cells.Workbook;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.util.List;
+
+/**
+ * 卷宗生成服务(使用Aspose实现多格式合并)
+ * 支持:Word(.docx/.doc)、Excel(.xlsx/.xls)、PDF(.pdf)
+ *
+ * 页码规则:
+ * - 封面:无页码
+ * - 目录:无页码
+ * - 正文:有页码(第 X 页 / 共 Y 页)
+ * - 封底:无页码
+ */
+@Service
+public class AsposeArchiveService {
+
+    private static final Logger logger = LoggerFactory.getLogger(AsposeArchiveService.class);
+
+    /**
+     * 合并多个文档(支持Word、Excel、PDF)
+     *
+     * @param coverFiles     封面文件列表
+     * @param catalogFiles   目录文件列表
+     * @param contentFiles   正文文件列表
+     * @param backCoverFiles 封底文件列表
+     * @param outputPath     输出路径
+     */
+    public void mergeDocuments(List<String> coverFiles, List<String> catalogFiles,
+                               List<String> contentFiles, List<String> backCoverFiles,
+                               String outputPath) throws Exception {
+        logger.info("开始使用Aspose合并文档...");
+
+        // 创建主文档(不使用ensureMinimum,避免产生空白页)
+        Document mainDoc = new Document();
+        mainDoc.removeAllChildren();
+
+        boolean isFirst = true;
+
+        // 1. 合并封面(无页码节)
+        if (coverFiles != null && !coverFiles.isEmpty()) {
+            logger.info("1. 合并封面...");
+            for (String filePath : coverFiles) {
+                if (filePath != null && new File(filePath).exists()) {
+                    logger.info("   ✓ {}", extractFileName(filePath));
+                    isFirst = appendDocument(mainDoc, filePath, isFirst, SectionStart.NEW_PAGE);
+                }
+            }
+        }
+
+        // 2. 合并目录(无页码节)
+        if (catalogFiles != null && !catalogFiles.isEmpty()) {
+            logger.info("2. 合并目录...");
+            for (String filePath : catalogFiles) {
+                if (filePath != null && new File(filePath).exists()) {
+                    logger.info("   ✓ {}", extractFileName(filePath));
+                    isFirst = appendDocument(mainDoc, filePath, isFirst, SectionStart.NEW_PAGE);
+                }
+            }
+        }
+
+        // 记录正文开始的节索引
+        int contentStartSectionIndex = mainDoc.getSections().getCount();
+
+        // 3. 合并正文(有页码节)
+        if (contentFiles != null && !contentFiles.isEmpty()) {
+            logger.info("3. 合并正文(有页码)...");
+            for (String filePath : contentFiles) {
+                if (filePath != null && new File(filePath).exists()) {
+                    logger.info("   ✓ {}", extractFileName(filePath));
+                    isFirst = appendDocument(mainDoc, filePath, isFirst, SectionStart.NEW_PAGE);
+                }
+            }
+        }
+
+        // 记录正文结束的节索引
+        int contentEndSectionIndex = mainDoc.getSections().getCount() - 1;
+
+        // 4. 合并封底(无页码节)
+        if (backCoverFiles != null && !backCoverFiles.isEmpty()) {
+            logger.info("4. 合并封底...");
+            for (String filePath : backCoverFiles) {
+                if (filePath != null && new File(filePath).exists()) {
+                    logger.info("   ✓ {}", extractFileName(filePath));
+                    isFirst = appendDocument(mainDoc, filePath, isFirst, SectionStart.NEW_PAGE);
+                }
+            }
+        }
+
+        // 5. 计算正文总页数并设置页码
+        logger.info("5. 设置页码...");
+        int contentTotalPages = calculateContentPages(mainDoc, contentStartSectionIndex, contentEndSectionIndex);
+        logger.info("   正文总页数: {}", contentTotalPages);
+        setupPageNumbers(mainDoc, contentStartSectionIndex, contentEndSectionIndex, contentTotalPages);
+
+        // 保存文档
+        File outputFile = new File(outputPath);
+        if (outputFile.getParentFile() != null && !outputFile.getParentFile().exists()) {
+            outputFile.getParentFile().mkdirs();
+        }
+        mainDoc.save(outputPath);
+        logger.info("文档保存成功:{}", outputPath);
+    }
+
+    /**
+     * 计算正文部分的总页数
+     */
+    private int calculateContentPages(Document doc, int contentStartSectionIndex, int contentEndSectionIndex) throws Exception {
+        // 更新文档布局以获取准确的页数
+        doc.updatePageLayout();
+
+        SectionCollection sections = doc.getSections();
+
+        // 使用LayoutCollector获取精确页数
+        LayoutCollector collector = new LayoutCollector(doc);
+        int contentStartPage = Integer.MAX_VALUE;
+        int contentEndPage = 0;
+
+        for (int i = contentStartSectionIndex; i <= contentEndSectionIndex && i < sections.getCount(); i++) {
+            Section section = sections.get(i);
+            // 获取节的第一个段落所在页
+            Node firstNode = section.getBody().getFirstChild();
+            Node lastNode = section.getBody().getLastChild();
+
+            if (firstNode != null) {
+                int startPage = collector.getStartPageIndex(firstNode);
+                if (startPage > 0 && startPage < contentStartPage) {
+                    contentStartPage = startPage;
+                }
+            }
+            if (lastNode != null) {
+                int endPage = collector.getEndPageIndex(lastNode);
+                if (endPage > contentEndPage) {
+                    contentEndPage = endPage;
+                }
+            }
+        }
+
+        int totalPages = 0;
+        if (contentStartPage != Integer.MAX_VALUE && contentEndPage > 0) {
+            totalPages = contentEndPage - contentStartPage + 1;
+        }
+
+        return totalPages;
+    }
+
+    /**
+     * 追加文档(自动识别格式)
+     */
+    private boolean appendDocument(Document mainDoc, String filePath, boolean isFirst, int sectionStart) throws Exception {
+        String lowerPath = filePath.toLowerCase();
+        Document docToAppend;
+
+        if (lowerPath.endsWith(".docx") || lowerPath.endsWith(".doc")) {
+            // Word文档直接加载
+            docToAppend = new Document(filePath);
+        } else if (lowerPath.endsWith(".xlsx") || lowerPath.endsWith(".xls")) {
+            // Excel转Word
+            docToAppend = convertExcelToWord(filePath);
+        } else if (lowerPath.endsWith(".pdf")) {
+            // PDF转Word
+            docToAppend = convertPdfToWord(filePath);
+        } else {
+            logger.warn("   [警告] 不支持的文件格式:{}", filePath);
+            return isFirst;
+        }
+
+        if (docToAppend != null && docToAppend.getSections().getCount() > 0) {
+            if (!isFirst) {
+                // 设置第一节的分节符类型
+                docToAppend.getFirstSection().getPageSetup().setSectionStart(sectionStart);
+            }
+
+            // 追加到主文档
+            mainDoc.appendDocument(docToAppend, ImportFormatMode.KEEP_SOURCE_FORMATTING);
+            return false;
+        }
+
+        return isFirst;
+    }
+
+    /**
+     * Excel转Word文档
+     */
+    private Document convertExcelToWord(String excelPath) throws Exception {
+        // 加载Excel
+        Workbook workbook = new Workbook(excelPath);
+
+        // 获取第一个工作表
+        com.aspose.cells.Worksheet worksheet = workbook.getWorksheets().get(0);
+        com.aspose.cells.Cells cells = worksheet.getCells();
+
+        // 获取实际使用的行数
+        int maxRow = cells.getMaxDataRow() + 1;  // getMaxDataRow返回的是索引,+1得到行数
+
+        // 如果只有三行或更少,转换为普通文本格式
+        if (maxRow <= 3) {
+            return convertExcelToText(workbook);
+        }
+
+        // 先转为PDF(保持格式)
+        ByteArrayOutputStream pdfStream = new ByteArrayOutputStream();
+        com.aspose.cells.PdfSaveOptions pdfOptions = new com.aspose.cells.PdfSaveOptions();
+        pdfOptions.setOnePagePerSheet(false);  // 允许多页
+        pdfOptions.setAllColumnsInOnePagePerSheet(true);  // 所有列在一页
+        workbook.save(pdfStream, pdfOptions);
+
+        // PDF转Word
+        ByteArrayInputStream pdfInput = new ByteArrayInputStream(pdfStream.toByteArray());
+        com.aspose.pdf.Document pdfDoc = new com.aspose.pdf.Document(pdfInput);
+
+        ByteArrayOutputStream wordStream = new ByteArrayOutputStream();
+        pdfDoc.save(wordStream, com.aspose.pdf.SaveFormat.DocX);
+
+        // 加载Word文档
+        ByteArrayInputStream wordInput = new ByteArrayInputStream(wordStream.toByteArray());
+        Document doc = new Document(wordInput);
+
+        return doc;
+    }
+
+    /**
+     * Excel转为普通文本格式的Word文档(适用于只有两三行的情况)
+     * 第一行为标题,第二行为表头,第三行为数据
+     * 展示格式:表头: 数据
+     */
+    private Document convertExcelToText(Workbook workbook) throws Exception {
+        Document doc = new Document();
+        DocumentBuilder builder = new DocumentBuilder(doc);
+
+        // 获取第一个工作表
+        com.aspose.cells.Worksheet worksheet = workbook.getWorksheets().get(0);
+        com.aspose.cells.Cells cells = worksheet.getCells();
+
+        int maxRow = cells.getMaxDataRow() + 1;
+        int maxCol = cells.getMaxDataColumn() + 1;
+
+        // 第一行是标题,单独输出(居中、加粗、大字号)
+        if (maxRow >= 1) {
+            for (int col = 0; col < maxCol; col++) {
+                com.aspose.cells.Cell cell = cells.get(0, col);
+                String value = cell.getStringValue();
+                if (value != null && !value.trim().isEmpty()) {
+                    builder.getParagraphFormat().setAlignment(ParagraphAlignment.CENTER);
+                    builder.getParagraphFormat().setSpaceAfter(12);
+                    builder.getFont().setName("宋体");
+                    builder.getFont().setSize(16);
+                    builder.getFont().setBold(true);
+                    builder.writeln(value.trim());
+                    builder.getFont().setBold(false);
+                    break;  // 标题通常只有一个
+                }
+            }
+        }
+
+        // 第二行是表头,第三行是数据,配对输出
+        if (maxRow >= 3) {
+            // 重置段落格式
+            builder.getParagraphFormat().setAlignment(ParagraphAlignment.LEFT);
+            builder.getParagraphFormat().setSpaceAfter(6);
+            builder.getParagraphFormat().setFirstLineIndent(24);  // 首行缩进
+            builder.getFont().setSize(12);
+
+            for (int col = 0; col < maxCol; col++) {
+                com.aspose.cells.Cell headerCell = cells.get(1, col);  // 表头
+                com.aspose.cells.Cell dataCell = cells.get(2, col);    // 数据
+
+                String header = headerCell.getStringValue();
+                String data = dataCell.getStringValue();
+
+                if (header != null && !header.trim().isEmpty()) {
+                    String line = header.trim() + ":" + (data != null ? data.trim() : "");
+                    builder.writeln(line);
+                }
+            }
+        } else if (maxRow == 2) {
+            // 只有两行:第一行标题,第二行数据
+            builder.getParagraphFormat().setAlignment(ParagraphAlignment.LEFT);
+            builder.getParagraphFormat().setSpaceAfter(6);
+            builder.getFont().setSize(12);
+
+            for (int col = 0; col < maxCol; col++) {
+                com.aspose.cells.Cell cell = cells.get(1, col);
+                String value = cell.getStringValue();
+                if (value != null && !value.trim().isEmpty()) {
+                    builder.writeln(value.trim());
+                }
+            }
+        }
+
+        return doc;
+    }
+
+    /**
+     * PDF转Word文档
+     */
+    private Document convertPdfToWord(String pdfPath) throws Exception {
+        // 加载PDF
+        com.aspose.pdf.Document pdfDoc = new com.aspose.pdf.Document(pdfPath);
+
+        // 转为Word
+        ByteArrayOutputStream wordStream = new ByteArrayOutputStream();
+        pdfDoc.save(wordStream, com.aspose.pdf.SaveFormat.DocX);
+
+        // 加载Word文档
+        ByteArrayInputStream wordInput = new ByteArrayInputStream(wordStream.toByteArray());
+        return new Document(wordInput);
+    }
+
+    /**
+     * 设置页码(只有正文部分显示页码)
+     *
+     * @param doc                      文档
+     * @param contentStartSectionIndex 正文开始的节索引
+     * @param contentEndSectionIndex   正文结束的节索引
+     * @param contentTotalPages        正文总页数
+     */
+    private void setupPageNumbers(Document doc, int contentStartSectionIndex, int contentEndSectionIndex, int contentTotalPages) throws Exception {
+        SectionCollection sections = doc.getSections();
+
+        for (int i = 0; i < sections.getCount(); i++) {
+            Section section = sections.get(i);
+            HeaderFooterCollection headerFooters = section.getHeadersFooters();
+
+            // 取消链接到上一节(使每节页眉页脚独立)
+            headerFooters.linkToPrevious(false);
+
+            // 清除现有页脚
+            HeaderFooter existingFooter = headerFooters.getByHeaderFooterType(HeaderFooterType.FOOTER_PRIMARY);
+            if (existingFooter != null) {
+                existingFooter.remove();
+            }
+
+            // 清除现有页眉
+            HeaderFooter existingHeader = headerFooters.getByHeaderFooterType(HeaderFooterType.HEADER_PRIMARY);
+            if (existingHeader != null) {
+                existingHeader.remove();
+            }
+
+            // 只有正文部分添加页码和页眉编号
+            if (i >= contentStartSectionIndex && i <= contentEndSectionIndex) {
+                // 页码设置:只有正文第一节从1开始,后续节继续累加
+                if (i == contentStartSectionIndex) {
+                    // 正文第一节:页码从1开始
+                    section.getPageSetup().setRestartPageNumbering(true);
+                    section.getPageSetup().setPageStartingNumber(1);
+                } else {
+                    // 正文后续节:页码继续累加,不重新开始
+                    section.getPageSetup().setRestartPageNumbering(false);
+                }
+
+                // ========== 创建页眉(右上角编号 001、002、003...)==========
+                HeaderFooter header = new HeaderFooter(doc, HeaderFooterType.HEADER_PRIMARY);
+                headerFooters.add(header);
+
+                // 创建页眉段落(右对齐)
+                Paragraph headerPara = new Paragraph(doc);
+                headerPara.getParagraphFormat().setAlignment(ParagraphAlignment.RIGHT);
+                // 设置右缩进,增加与右侧边距的距离
+                headerPara.getParagraphFormat().setRightIndent(20);
+                // 去掉页眉下边框线
+                headerPara.getParagraphFormat().getBorders().getBottom().setLineStyle(LineStyle.NONE);
+
+                // 先将段落添加到header中
+                header.appendChild(headerPara);
+
+                // 使用DocumentBuilder插入带格式的页码字段
+                DocumentBuilder builder = new DocumentBuilder(doc);
+                builder.moveTo(headerPara);
+
+                // 设置字体(加粗)
+                builder.getFont().setName("宋体");
+                builder.getFont().setSize(12);
+                builder.getFont().setBold(true);
+
+                // 插入带格式的PAGE字段,格式为3位数字
+                builder.insertField("PAGE \\# \"000\"", "001");
+
+                // 设置页眉距离
+                section.getPageSetup().setHeaderDistance(20);
+
+                // ========== 创建页脚(第 X 页 / 共 Y 页)==========
+                HeaderFooter footer = new HeaderFooter(doc, HeaderFooterType.FOOTER_PRIMARY);
+                headerFooters.add(footer);
+
+                // 创建页脚段落
+                Paragraph footerPara = new Paragraph(doc);
+                footerPara.getParagraphFormat().setAlignment(ParagraphAlignment.CENTER);
+                footerPara.getParagraphFormat().setSpaceBefore(12);
+
+                // 设置页脚距离页面底部的距离
+                section.getPageSetup().setFooterDistance(30);
+
+                // 添加页码文本:第 X 页 / 共 Y 页
+                Run run1 = new Run(doc, "第 ");
+                run1.getFont().setName("宋体");
+                run1.getFont().setSize(10.5);
+                footerPara.appendChild(run1);
+
+                // 当前页码字段
+                footerPara.appendField(FieldType.FIELD_PAGE, true);
+
+                Run run2 = new Run(doc, " 页 / 共 ");
+                run2.getFont().setName("宋体");
+                run2.getFont().setSize(10.5);
+                footerPara.appendChild(run2);
+
+                // 使用计算出的正文总页数(固定值)
+                Run runTotal = new Run(doc, String.valueOf(contentTotalPages));
+                runTotal.getFont().setName("宋体");
+                runTotal.getFont().setSize(10.5);
+                footerPara.appendChild(runTotal);
+
+                Run run3 = new Run(doc, " 页");
+                run3.getFont().setName("宋体");
+                run3.getFont().setSize(10.5);
+                footerPara.appendChild(run3);
+
+                footer.appendChild(footerPara);
+            } else {
+                // 封面、目录、封底:不添加页眉页脚,且不重新开始页码
+                section.getPageSetup().setRestartPageNumbering(false);
+            }
+        }
+
+        // 更新所有字段,使页码格式生效
+        doc.updateFields();
+    }
+
+    /**
+     * 计算文件页数(支持Word、Excel、PDF)
+     *
+     * @param filePath 文件路径
+     * @return 页数
+     */
+    public int calculateFilePageCount(String filePath) {
+        try {
+            if (filePath == null || filePath.isEmpty()) {
+                return 0;
+            }
+
+            String lowerPath = filePath.toLowerCase();
+
+            if (lowerPath.endsWith(".docx") || lowerPath.endsWith(".doc")) {
+                return getWordPageCount(filePath);
+            } else if (lowerPath.endsWith(".xlsx") || lowerPath.endsWith(".xls")) {
+                return getExcelPageCount(filePath);
+            } else if (lowerPath.endsWith(".pdf")) {
+                return getPdfPageCount(filePath);
+            } else {
+                return 1;
+            }
+        } catch (Exception e) {
+            logger.error("计算文件页数失败,文件:{},错误:{}", filePath, e.getMessage(), e);
+            return 1;
+        }
+    }
+
+    /**
+     * 获取Word文档页数
+     */
+    private int getWordPageCount(String filePath) {
+        try {
+            Document doc = new Document(filePath);
+            doc.updatePageLayout();
+            return doc.getPageCount();
+        } catch (Exception e) {
+            logger.error("读取Word页数失败,文件:{},错误:{}", filePath, e.getMessage());
+            return 1;
+        }
+    }
+
+    /**
+     * 获取Excel文档页数(转PDF后计算)
+     */
+    private int getExcelPageCount(String filePath) {
+        try {
+            Workbook workbook = new Workbook(filePath);
+            // 转为PDF计算页数
+            ByteArrayOutputStream pdfStream = new ByteArrayOutputStream();
+            com.aspose.cells.PdfSaveOptions pdfOptions = new com.aspose.cells.PdfSaveOptions();
+            pdfOptions.setOnePagePerSheet(false);
+            pdfOptions.setAllColumnsInOnePagePerSheet(true);
+            workbook.save(pdfStream, pdfOptions);
+
+            ByteArrayInputStream pdfInput = new ByteArrayInputStream(pdfStream.toByteArray());
+            com.aspose.pdf.Document pdfDoc = new com.aspose.pdf.Document(pdfInput);
+            return pdfDoc.getPages().size();
+        } catch (Exception e) {
+            logger.error("读取Excel页数失败,文件:{},错误:{}", filePath, e.getMessage());
+            return 1;
+        }
+    }
+
+    /**
+     * 获取PDF文档页数
+     */
+    private int getPdfPageCount(String filePath) {
+        try {
+            com.aspose.pdf.Document pdfDoc = new com.aspose.pdf.Document(filePath);
+            return pdfDoc.getPages().size();
+        } catch (Exception e) {
+            logger.error("读取PDF页数失败,文件:{},错误:{}", filePath, e.getMessage());
+            return 1;
+        }
+    }
+
+    /**
+     * 提取文件名
+     */
+    private String extractFileName(String url) {
+        if (url == null || url.isEmpty()) {
+            return "";
+        }
+        int lastSlash = Math.max(url.lastIndexOf('/'), url.lastIndexOf('\\'));
+        return lastSlash >= 0 ? url.substring(lastSlash + 1) : url;
+    }
+}

+ 43 - 556
assistMg/src/main/java/com/hotent/project/service/AsyncMaterialSummaryService.java

@@ -16,8 +16,6 @@ import com.hotent.uc.util.ContextUtil;
 import com.hotent.util.FileUploadUtil;
 import com.hotent.util.wordexcelutils.CompleteTemplateProcessor;
 import com.hotent.util.wordexcelutils.SmartTemplateWriter;
-import org.apache.pdfbox.pdmodel.PDDocument;
-import org.apache.poi.xwpf.model.XWPFHeaderFooterPolicy;
 import org.apache.poi.xwpf.usermodel.*;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -25,7 +23,7 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 
-import java.io.FileOutputStream;
+import java.io.File;
 import java.time.LocalDateTime;
 import java.util.Comparator;
 import java.util.List;
@@ -93,7 +91,7 @@ public class AsyncMaterialSummaryService {
     private CostProjectProccessManager costProjectProccessManager;
 
     @Autowired
-    private Docx4jArchiveService docx4jArchiveService;
+    private AsposeArchiveService asposeArchiveService;
 
 
     // 从配置文件读取模板路径
@@ -907,8 +905,8 @@ public class AsyncMaterialSummaryService {
             String outputPath = FileUploadUtil.generateSavePath(
                     EipConfig.getUploadPath(), fileName, "docx");
 
-            // 使用docx4j合并文档
-            docx4jArchiveService.mergeDocuments(coverFiles, catalogFiles, contentFiles, backCoverFiles, outputPath);
+            // 使用Aspose合并文档
+            asposeArchiveService.mergeDocuments(coverFiles, catalogFiles, contentFiles, backCoverFiles, outputPath);
 
             // 生成访问URL
             archiveUrl = FileUploadUtil.getPathFileName(outputPath, fileName);
@@ -938,494 +936,6 @@ public class AsyncMaterialSummaryService {
         }
     }
 
-    /**
-     * 将URL转换为本地文件路径
-     * 支持格式:
-     * 1. /profile/upload/xxx.docx
-     * 2. http://xxx/profile/upload/xxx.docx
-     * 3. 本地绝对路径
-     */
-    private String convertToLocalPath(String fileUrl) {
-        if (StringUtil.isEmpty(fileUrl)) {
-            return null;
-        }
-
-        // http或https开头,提取/profile部分
-        if (fileUrl.startsWith("http://") || fileUrl.startsWith("https://")) {
-            int profileIndex = fileUrl.indexOf("/profile/");
-            if (profileIndex > 0) {
-                String relativePath = fileUrl.substring(profileIndex);
-                return EipConfig.getProfile() + relativePath.substring(8);
-            }
-            return null;
-        }
-
-        // /profile开头
-        if (fileUrl.startsWith("/profile")) {
-            return EipConfig.getProfile() + fileUrl.substring(8);
-        }
-
-        // 本地绝对路径
-        return fileUrl;
-    }
-
-    /**
-     * 合并单个文档文件到主文档(支持Word、PDF、Excel等多种格式)
-     *
-     * @param document        主文档
-     * @param fileUrl         文件URL(如:/profile/upload/20251116/xxx.docx)
-     * @param isFirstDocument 是否是第一个文档
-     * @return 是否是第一个文档(用于下次调用)
-     */
-    private boolean mergeDocumentFile(XWPFDocument document, String fileUrl, boolean isFirstDocument) {
-        try {
-            String filePath = convertToLocalPath(fileUrl);
-            if (filePath == null) {
-                logger.warn("无法解析文件路径:{}", fileUrl);
-                return isFirstDocument;
-            }
-
-            java.io.File file = new java.io.File(filePath);
-            if (!file.exists()) {
-                logger.warn("文件不存在:{}(原始路径:{})", filePath, fileUrl);
-                return isFirstDocument;
-            }
-
-            String lowerCasePath = filePath.toLowerCase();
-
-            // 添加分页符(非第一个文档)
-            if (!isFirstDocument) {
-                XWPFParagraph pageBreakPara = document.createParagraph();
-                XWPFRun pageBreakRun = pageBreakPara.createRun();
-                pageBreakRun.addBreak(BreakType.PAGE);
-            }
-
-            // 根据文件类型处理
-            if (lowerCasePath.endsWith(".docx") || lowerCasePath.endsWith(".doc")) {
-                mergeWordDocument(document, filePath);
-            } else if (lowerCasePath.endsWith(".pdf")) {
-                mergePdfDocument(document, filePath);
-            } else if (lowerCasePath.endsWith(".xlsx") || lowerCasePath.endsWith(".xls")) {
-                mergeExcelDocument(document, filePath);
-            } else {
-                logger.warn("不支持的文件格式:{}", fileUrl);
-                return isFirstDocument;
-            }
-
-            return false;
-
-        } catch (Exception e) {
-            logger.error("合并文件失败:{},错误:{}", fileUrl, e.getMessage(), e);
-            return isFirstDocument;
-        }
-    }
-
-    /**
-     * 合并Word文档
-     */
-    private void mergeWordDocument(XWPFDocument document, String filePath) {
-        java.io.FileInputStream fis = null;
-        XWPFDocument sourceDoc = null;
-        try {
-            fis = new java.io.FileInputStream(filePath);
-            sourceDoc = new XWPFDocument(fis);
-
-            org.openxmlformats.schemas.wordprocessingml.x2006.main.CTBody srcBody = sourceDoc.getDocument().getBody();
-            org.openxmlformats.schemas.wordprocessingml.x2006.main.CTBody destBody = document.getDocument().getBody();
-
-            for (org.apache.xmlbeans.XmlObject obj : srcBody.selectPath("./*")) {
-                if (obj instanceof org.openxmlformats.schemas.wordprocessingml.x2006.main.CTP) {
-                    org.openxmlformats.schemas.wordprocessingml.x2006.main.CTP newP = destBody.addNewP();
-                    newP.set(obj.copy());
-                } else if (obj instanceof org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTbl) {
-                    org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTbl newTbl = destBody.addNewTbl();
-                    newTbl.set(obj.copy());
-                }
-            }
-        } catch (Exception e) {
-            logger.error("合并Word失败:{},错误:{}", filePath, e.getMessage());
-            XWPFParagraph para = document.createParagraph();
-            XWPFRun run = para.createRun();
-            run.setText("[Word文件:" + new java.io.File(filePath).getName() + " - 无法嵌入]");
-        } finally {
-            try {
-                if (sourceDoc != null) sourceDoc.close();
-                if (fis != null) fis.close();
-            } catch (Exception e) {
-                logger.warn("关闭文件流失败:{}", e.getMessage());
-            }
-        }
-    }
-
-    /**
-     * 合并PDF文档(优化版:根据PDF实际尺寸自适应,保持宽高比)
-     */
-    private void mergePdfDocument(XWPFDocument document, String filePath) {
-        PDDocument pdfDoc = null;
-        try {
-            pdfDoc = PDDocument.load(new java.io.File(filePath));
-            org.apache.pdfbox.rendering.PDFRenderer renderer = new org.apache.pdfbox.rendering.PDFRenderer(pdfDoc);
-            int pageCount = pdfDoc.getNumberOfPages();
-
-            // Word页面可用宽度(A4纸,左右边距各2.5cm,约15cm可用)
-            double wordPageWidth = 450; // 点(约15.9cm)
-            double wordPageHeight = 650; // 点(约22.9cm)
-
-            for (int i = 0; i < pageCount; i++) {
-                // 获取PDF页面尺寸
-                org.apache.pdfbox.pdmodel.PDPage pdfPage = pdfDoc.getPage(i);
-                org.apache.pdfbox.pdmodel.common.PDRectangle mediaBox = pdfPage.getMediaBox();
-                float pdfWidth = mediaBox.getWidth();
-                float pdfHeight = mediaBox.getHeight();
-
-                // 计算缩放比例,使图片适应Word页面
-                double scaleX = wordPageWidth / pdfWidth;
-                double scaleY = wordPageHeight / pdfHeight;
-                double scale = Math.min(scaleX, scaleY); // 取较小值,保证不超出页面
-
-                // 计算最终图片尺寸(保持宽高比)
-                int imgWidthPt = (int) (pdfWidth * scale);
-                int imgHeightPt = (int) (pdfHeight * scale);
-
-                // 使用较高DPI渲染以获得清晰图片
-                int dpi = 150; // 平衡清晰度和文件大小
-                java.awt.image.BufferedImage image = renderer.renderImageWithDPI(i, dpi);
-
-                // 转为PNG
-                java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
-                javax.imageio.ImageIO.write(image, "png", baos);
-                byte[] imageBytes = baos.toByteArray();
-                baos.close();
-
-                // 创建段落并居中
-                XWPFParagraph para = document.createParagraph();
-                para.setAlignment(ParagraphAlignment.CENTER);
-                para.setSpacingBefore(0);
-                para.setSpacingAfter(0);
-
-                XWPFRun run = para.createRun();
-                java.io.ByteArrayInputStream bais = new java.io.ByteArrayInputStream(imageBytes);
-                run.addPicture(bais, org.apache.poi.xwpf.usermodel.Document.PICTURE_TYPE_PNG,
-                        "pdf_page_" + i + ".png",
-                        org.apache.poi.util.Units.toEMU(imgWidthPt),
-                        org.apache.poi.util.Units.toEMU(imgHeightPt));
-                bais.close();
-
-                // 每页PDF后添加分页符(最后一页除外)
-                if (i < pageCount - 1) {
-                    XWPFParagraph pageBreak = document.createParagraph();
-                    XWPFRun pageBreakRun = pageBreak.createRun();
-                    pageBreakRun.addBreak(BreakType.PAGE);
-                }
-            }
-            logger.info("PDF文件已合并:{}", filePath);
-        } catch (Exception e) {
-            logger.error("合并PDF失败:{},错误:", filePath, e.getMessage());
-            XWPFParagraph para = document.createParagraph();
-            XWPFRun run = para.createRun();
-            run.setText("[PDF文件:" + new java.io.File(filePath).getName() + " - 无法嵌入]");
-        } finally {
-            try {
-                if (pdfDoc != null) pdfDoc.close();
-            } catch (Exception e) {
-                logger.warn("关闭PDDocument失败:{}", e.getMessage());
-            }
-        }
-    }
-
-    /**
-     * 合并Excel文档(优化版:转图片方式,保持原有格式)
-     */
-    private void mergeExcelDocument(XWPFDocument document, String filePath) {
-        org.apache.poi.ss.usermodel.Workbook workbook = null;
-        try {
-            workbook = org.apache.poi.ss.usermodel.WorkbookFactory.create(new java.io.File(filePath));
-
-            for (int sheetIndex = 0; sheetIndex < workbook.getNumberOfSheets(); sheetIndex++) {
-                org.apache.poi.ss.usermodel.Sheet sheet = workbook.getSheetAt(sheetIndex);
-
-                if (sheet.getPhysicalNumberOfRows() == 0) continue;
-
-                logger.info("正在转换Sheet: {}", sheet.getSheetName());
-
-                // 将Excel Sheet转为图片
-                byte[] imageBytes = excelSheetToImage(workbook, sheetIndex);
-
-                if (imageBytes != null && imageBytes.length > 0) {
-                    XWPFParagraph para = document.createParagraph();
-                    para.setAlignment(ParagraphAlignment.CENTER);
-                    XWPFRun run = para.createRun();
-
-                    java.io.ByteArrayInputStream bais = new java.io.ByteArrayInputStream(imageBytes);
-                    // 设置图片宽度为页面宽度(约16cm = 453pt),高度自适应
-                    run.addPicture(bais, org.apache.poi.xwpf.usermodel.Document.PICTURE_TYPE_PNG,
-                            "excel_sheet_" + sheetIndex + ".png",
-                            org.apache.poi.util.Units.toEMU(450),
-                            org.apache.poi.util.Units.toEMU(600));
-                    bais.close();
-                } else {
-                    // 图片转换失败,使用表格方式
-                    logger.warn("Excel转图片失败,使用表格方式");
-                    mergeExcelAsTable(document, sheet);
-                }
-
-                if (sheetIndex < workbook.getNumberOfSheets() - 1) {
-                    XWPFParagraph pageBreak = document.createParagraph();
-                    XWPFRun pageBreakRun = pageBreak.createRun();
-                    pageBreakRun.addBreak(BreakType.PAGE);
-                }
-            }
-            logger.info("Excel文件已合并:{}", filePath);
-        } catch (Exception e) {
-            logger.error("合并Excel失败:{},错误:{}", filePath, e.getMessage());
-            XWPFParagraph para = document.createParagraph();
-            XWPFRun run = para.createRun();
-            run.setText("[Excel文件:" + new java.io.File(filePath).getName() + " - 无法嵌入]");
-        } finally {
-            try {
-                if (workbook != null) workbook.close();
-            } catch (Exception e) {
-                logger.warn("关闭Workbook失败:{}", e.getMessage());
-            }
-        }
-    }
-
-    /**
-     * 将Excel Sheet转为PNG图片(优化版)
-     */
-    private byte[] excelSheetToImage(org.apache.poi.ss.usermodel.Workbook workbook, int sheetIndex) {
-        try {
-            org.apache.poi.ss.usermodel.Sheet sheet = workbook.getSheetAt(sheetIndex);
-
-            // 计算表格区域
-            int lastRowNum = sheet.getLastRowNum();
-            int maxColNum = 0;
-            for (int i = 0; i <= lastRowNum; i++) {
-                org.apache.poi.ss.usermodel.Row row = sheet.getRow(i);
-                if (row != null && row.getLastCellNum() > maxColNum) {
-                    maxColNum = row.getLastCellNum();
-                }
-            }
-
-            if (lastRowNum < 0 || maxColNum <= 0) {
-                return null;
-            }
-
-            // 计算每列的最佳宽度(根据内容)
-            int[] colWidths = new int[maxColNum];
-            java.awt.Font measureFont = new java.awt.Font("宋体", java.awt.Font.PLAIN, 14);
-            java.awt.image.BufferedImage tempImg = new java.awt.image.BufferedImage(1, 1, java.awt.image.BufferedImage.TYPE_INT_RGB);
-            java.awt.Graphics2D tempG = tempImg.createGraphics();
-            tempG.setFont(measureFont);
-            java.awt.FontMetrics fm = tempG.getFontMetrics();
-
-            for (int colIdx = 0; colIdx < maxColNum; colIdx++) {
-                int maxWidth = 60; // 最小宽度
-                for (int rowIdx = 0; rowIdx <= lastRowNum; rowIdx++) {
-                    org.apache.poi.ss.usermodel.Row row = sheet.getRow(rowIdx);
-                    if (row != null) {
-                        org.apache.poi.ss.usermodel.Cell cell = row.getCell(colIdx);
-                        if (cell != null) {
-                            String value = getCellValueAsString(cell);
-                            int width = fm.stringWidth(value) + 30; // 加边距
-                            maxWidth = Math.max(maxWidth, Math.min(width, 200)); // 限制最大宽度
-                        }
-                    }
-                }
-                colWidths[colIdx] = maxWidth;
-            }
-            tempG.dispose();
-
-            // 计算图片尺寸
-            int cellHeight = 35;  // 行高
-            int titleHeight = 45; // 标题行高
-            int padding = 40;     // 边距
-
-            int totalWidth = padding * 2;
-            for (int w : colWidths) {
-                totalWidth += w;
-            }
-
-            int imgHeight = padding + titleHeight + (lastRowNum + 1) * cellHeight + padding;
-
-            // 限制最大尺寸
-            totalWidth = Math.min(totalWidth, 1800);
-            imgHeight = Math.min(imgHeight, 2500);
-
-            // 创建图片
-            java.awt.image.BufferedImage image = new java.awt.image.BufferedImage(
-                    totalWidth, imgHeight, java.awt.image.BufferedImage.TYPE_INT_RGB);
-            java.awt.Graphics2D g2d = image.createGraphics();
-
-            // 设置抗锯齿
-            g2d.setRenderingHint(java.awt.RenderingHints.KEY_ANTIALIASING,
-                    java.awt.RenderingHints.VALUE_ANTIALIAS_ON);
-            g2d.setRenderingHint(java.awt.RenderingHints.KEY_TEXT_ANTIALIASING,
-                    java.awt.RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
-
-            // 白色背景
-            g2d.setColor(java.awt.Color.WHITE);
-            g2d.fillRect(0, 0, totalWidth, imgHeight);
-
-            // 字体
-            java.awt.Font titleFont = new java.awt.Font("宋体", java.awt.Font.BOLD, 16);
-            java.awt.Font headerFont = new java.awt.Font("宋体", java.awt.Font.BOLD, 14);
-            java.awt.Font contentFont = new java.awt.Font("宋体", java.awt.Font.PLAIN, 13);
-
-            int startX = padding;
-            int startY = padding;
-
-            // 绘制表格
-            for (int rowIdx = 0; rowIdx <= lastRowNum; rowIdx++) {
-                org.apache.poi.ss.usermodel.Row row = sheet.getRow(rowIdx);
-                int currentHeight = (rowIdx == 0) ? titleHeight : cellHeight;
-                int y = startY + (rowIdx == 0 ? 0 : titleHeight + (rowIdx - 1) * cellHeight);
-
-                int x = startX;
-                for (int colIdx = 0; colIdx < maxColNum; colIdx++) {
-                    int cellWidth = colWidths[colIdx];
-
-                    // 获取单元格值
-                    String cellValue = "";
-                    if (row != null) {
-                        org.apache.poi.ss.usermodel.Cell cell = row.getCell(colIdx);
-                        if (cell != null) {
-                            cellValue = getCellValueAsString(cell);
-                        }
-                    }
-
-                    // 绘制单元格背景
-                    if (rowIdx == 0) {
-                        // 标题行 - 深色背景
-                        g2d.setColor(new java.awt.Color(64, 64, 64));
-                        g2d.fillRect(x, y, cellWidth, currentHeight);
-                        g2d.setFont(titleFont);
-                        g2d.setColor(java.awt.Color.WHITE);
-                    } else if (rowIdx == 1) {
-                        // 表头行 - 浅灰背景
-                        g2d.setColor(new java.awt.Color(220, 220, 220));
-                        g2d.fillRect(x, y, cellWidth, currentHeight);
-                        g2d.setFont(headerFont);
-                        g2d.setColor(java.awt.Color.BLACK);
-                    } else {
-                        // 数据行 - 交替颜色
-                        if (rowIdx % 2 == 0) {
-                            g2d.setColor(new java.awt.Color(245, 245, 245));
-                        } else {
-                            g2d.setColor(java.awt.Color.WHITE);
-                        }
-                        g2d.fillRect(x, y, cellWidth, currentHeight);
-                        g2d.setFont(contentFont);
-                        g2d.setColor(java.awt.Color.BLACK);
-                    }
-
-                    // 绘制边框
-                    g2d.setColor(new java.awt.Color(180, 180, 180));
-                    g2d.drawRect(x, y, cellWidth, currentHeight);
-
-                    // 绘制文字(居中)
-                    if (rowIdx == 0) {
-                        g2d.setColor(java.awt.Color.WHITE);
-                    } else {
-                        g2d.setColor(java.awt.Color.BLACK);
-                    }
-                    java.awt.FontMetrics metrics = g2d.getFontMetrics();
-                    int textX = x + (cellWidth - metrics.stringWidth(cellValue)) / 2;
-                    int textY = y + (currentHeight + metrics.getAscent() - metrics.getDescent()) / 2;
-                    g2d.drawString(cellValue, Math.max(textX, x + 5), textY);
-
-                    x += cellWidth;
-                }
-            }
-
-            // 绘制外边框(加粗)
-            g2d.setColor(new java.awt.Color(100, 100, 100));
-            g2d.setStroke(new java.awt.BasicStroke(2));
-            int tableWidth = 0;
-            for (int w : colWidths) tableWidth += w;
-            int tableHeight = titleHeight + lastRowNum * cellHeight;
-            g2d.drawRect(startX, startY, tableWidth, tableHeight);
-
-            g2d.dispose();
-
-            // 转为PNG字节数组
-            java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
-            javax.imageio.ImageIO.write(image, "png", baos);
-            return baos.toByteArray();
-
-        } catch (Exception e) {
-            logger.error("Excel转图片失败: {}", e.getMessage());
-            return null;
-        }
-    }
-
-    /**
-     * 获取单元格值(字符串形式)
-     */
-    private String getCellValueAsString(org.apache.poi.ss.usermodel.Cell cell) {
-        if (cell == null) return "";
-
-        switch (cell.getCellType()) {
-            case STRING:
-                return cell.getStringCellValue();
-            case NUMERIC:
-                if (org.apache.poi.ss.usermodel.DateUtil.isCellDateFormatted(cell)) {
-                    return new java.text.SimpleDateFormat("yyyy-MM-dd").format(cell.getDateCellValue());
-                }
-                double numValue = cell.getNumericCellValue();
-                if (numValue == Math.floor(numValue)) {
-                    return String.valueOf((long) numValue);
-                }
-                return String.valueOf(numValue);
-            case BOOLEAN:
-                return String.valueOf(cell.getBooleanCellValue());
-            case FORMULA:
-                try {
-                    return String.valueOf(cell.getNumericCellValue());
-                } catch (Exception e) {
-                    try {
-                        return cell.getStringCellValue();
-                    } catch (Exception e2) {
-                        return "";
-                    }
-                }
-            default:
-                return "";
-        }
-    }
-
-    /**
-     * 以表格方式合并Excel(备用方案)
-     */
-    private void mergeExcelAsTable(XWPFDocument document, org.apache.poi.ss.usermodel.Sheet sheet) {
-        int rows = sheet.getPhysicalNumberOfRows();
-        int cols = 0;
-        for (int i = 0; i < rows; i++) {
-            org.apache.poi.ss.usermodel.Row row = sheet.getRow(i);
-            if (row != null && row.getPhysicalNumberOfCells() > cols) {
-                cols = row.getPhysicalNumberOfCells();
-            }
-        }
-
-        if (cols == 0) cols = 1;
-
-        XWPFTable table = document.createTable(rows, cols);
-
-        for (int i = 0; i < rows; i++) {
-            org.apache.poi.ss.usermodel.Row excelRow = sheet.getRow(i);
-            XWPFTableRow wordRow = table.getRow(i);
-
-            if (excelRow != null) {
-                for (int j = 0; j < cols; j++) {
-                    org.apache.poi.ss.usermodel.Cell cell = excelRow.getCell(j);
-                    String cellValue = cell != null ? getCellValueAsString(cell) : "";
-
-                    XWPFTableCell wordCell = wordRow.getCell(j);
-                    wordCell.setText(cellValue);
-                }
-            }
-        }
-    }
 
     /**
      * 更新单个资料归纳主表的总页数
@@ -1477,7 +987,7 @@ public class AsyncMaterialSummaryService {
     }
 
     /**
-     * 统计每个资料归纳主表的总页数(所有明细的页数之和)
+     * 统计每个资料归纳主表的总页数
      *
      * @param taskId 任务ID
      */
@@ -1530,7 +1040,8 @@ public class AsyncMaterialSummaryService {
     }
 
     /**
-     * 计算文件页数(公共方法,可被外部调用)
+     * 计算文件页数
+     * 使用Aspose计算,支持Word、Excel、PDF
      *
      * @param fileUrl 文件URL或路径
      * @return 页数
@@ -1541,75 +1052,21 @@ public class AsyncMaterialSummaryService {
                 return 0;
             }
 
-            String lowerCaseUrl = fileUrl.toLowerCase();
-
-            if (lowerCaseUrl.endsWith(".doc") || lowerCaseUrl.endsWith(".docx")) {
-                return getWordPageCount(fileUrl);
-            } else {
-                return 1;
-            }
-
-        } catch (Exception e) {
-            logger.error("计算文件页数失败,文件:{},错误:{}", fileUrl, e.getMessage(), e);
-            return 1;
-        }
-    }
-
-    /**
-     * 获取Word文档的页数
-     *
-     * @param fileUrl 文件URL或路径(如:/profile/upload/20251116/xxx.docx)
-     * @return 页数
-     */
-    private int getWordPageCount(String fileUrl) {
-        java.io.FileInputStream fis = null;
-        XWPFDocument document = null;
-
-        try {
+            // 转换为本地路径
             String filePath = fileUrl;
             if (filePath.startsWith("/profile")) {
                 filePath = EipConfig.getProfile() + filePath.substring(8);
             }
 
-            fis = new java.io.FileInputStream(filePath);
-            document = new XWPFDocument(fis);
-
-            // 获取页数(可能为null或0)
-            try {
-                int pageCount = document.getProperties().getExtendedProperties().getUnderlyingProperties().getPages();
-                if (pageCount > 0) {
-                    return pageCount;
-                }
-            } catch (Exception e) {
-                logger.debug("无法读取Word文档页数属性:{}", e.getMessage());
-            }
-
-            // 如果页数属性不可用,通过段落数估算(每页约50行)
-            int paragraphCount = document.getParagraphs().size();
-            int tableCount = document.getTables().size();
-            int estimatedPages = Math.max(1, (paragraphCount + tableCount * 10) / 50);
-
-            return estimatedPages;
+            // 使用Aspose服务计算页数
+            return asposeArchiveService.calculateFilePageCount(filePath);
 
         } catch (Exception e) {
-            logger.error("读取Word页数失败,文件:{},错误:{}", fileUrl, e.getMessage(), e);
+            logger.error("计算文件页数失败,文件:{},错误:{}", fileUrl, e.getMessage(), e);
             return 1;
-        } finally {
-            try {
-                if (document != null) {
-                    document.close();
-                }
-                if (fis != null) {
-                    fis.close();
-                }
-            } catch (Exception e) {
-                logger.warn("关闭文件流失败:{}", e.getMessage());
-            }
         }
     }
 
-    // ==================== 卷宗校对文书生成功能 ====================
-
 
     /**
      * 生成卷宗文书(封面/目录/封底)
@@ -1670,7 +1127,7 @@ public class AsyncMaterialSummaryService {
         }
 
         // 检查模板文件是否存在
-        java.io.File templateFile = new java.io.File(templatePath);
+        File templateFile = new java.io.File(templatePath);
         if (!templateFile.exists()) {
             throw new RuntimeException("模板文件不存在:" + templatePath);
         }
@@ -1942,7 +1399,7 @@ public class AsyncMaterialSummaryService {
     }
 
     /**
-     * 生成卷内目录(只填充表格,不替换占位符)
+     * 生成卷内目录
      */
     private void generateCatalogDocument(XWPFDocument document, Map<String, String> replacements, String taskId) {
         // 获取资料归纳列表,生成目录表格
@@ -2063,4 +1520,34 @@ public class AsyncMaterialSummaryService {
         paragraph.setAlignment(ParagraphAlignment.CENTER);
     }
 
+    /**
+     * 将URL转换为本地文件路径
+     * 支持格式:
+     * 1. /profile/upload/xxx.docx
+     * 2. http://xxx/profile/upload/xxx.docx
+     * 3. 本地绝对路径
+     */
+    private String convertToLocalPath(String fileUrl) {
+        if (StringUtil.isEmpty(fileUrl)) {
+            return null;
+        }
+
+        // http或https开头,提取/profile部分
+        if (fileUrl.startsWith("http://") || fileUrl.startsWith("https://")) {
+            int profileIndex = fileUrl.indexOf("/profile/");
+            if (profileIndex > 0) {
+                String relativePath = fileUrl.substring(profileIndex);
+                return EipConfig.getProfile() + relativePath.substring(8);
+            }
+            return null;
+        }
+
+        // /profile开头
+        if (fileUrl.startsWith("/profile")) {
+            return EipConfig.getProfile() + fileUrl.substring(8);
+        }
+
+        // 本地绝对路径
+        return fileUrl;
+    }
 }

BIN
lib-3rd/aspose-cells-23.1.jar


BIN
lib-3rd/aspose-pdf-23.1.jar


BIN
lib-3rd/aspose-words-23.1.jar