|
|
@@ -1,10 +1,8 @@
|
|
|
package com.hotent.project.service;
|
|
|
|
|
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
|
|
-import org.apache.poi.xwpf.usermodel.XWPFDocument;
|
|
|
-import org.apache.poi.xwpf.usermodel.XWPFParagraph;
|
|
|
-import org.apache.poi.xwpf.usermodel.XWPFRun;
|
|
|
-import org.apache.poi.xwpf.usermodel.BreakType;
|
|
|
+import org.apache.poi.xwpf.model.XWPFHeaderFooterPolicy;
|
|
|
+import org.apache.poi.xwpf.usermodel.*;
|
|
|
|
|
|
import java.io.File;
|
|
|
import java.io.FileInputStream;
|
|
|
@@ -14,10 +12,13 @@ import java.util.List;
|
|
|
|
|
|
/**
|
|
|
* 卷宗生成测试类(不依赖Spring)
|
|
|
+ * 测试封面、目录、正文(带页码)、封底的生成效果
|
|
|
*/
|
|
|
public class ArchiveTest {
|
|
|
|
|
|
public static void main(String[] args) throws Exception {
|
|
|
+ System.out.println("========== 卷宗生成测试(含页码) ==========\n");
|
|
|
+
|
|
|
System.out.println("========== 文件名称提取 ==========");
|
|
|
List<String> fileUrls = extractFileNames();
|
|
|
|
|
|
@@ -34,11 +35,10 @@ public class ArchiveTest {
|
|
|
private static List<String> extractFileNames() {
|
|
|
String[] fileUrls = {
|
|
|
"D:\\fx\\cc\\7778、成本监审集体审议记录20251216101702575.docx",
|
|
|
- "D:\\fx\\cc\\成本审核模板_2025_12_16_14_34_56.xlsx",
|
|
|
+ "D:\\fx\\cc\\被跨固定表_17665464557132025122411205576.xlsx",
|
|
|
"D:\\fx\\cc\\文书文件20251216141325913.pdf"
|
|
|
};
|
|
|
|
|
|
-
|
|
|
List<String> validFiles = new ArrayList<>();
|
|
|
for (String url : fileUrls) {
|
|
|
String fileName = extractFileName(url);
|
|
|
@@ -60,7 +60,7 @@ public class ArchiveTest {
|
|
|
if (url == null || url.isEmpty()) {
|
|
|
return "";
|
|
|
}
|
|
|
- int lastSlash = url.lastIndexOf('/');
|
|
|
+ int lastSlash = Math.max(url.lastIndexOf('/'), url.lastIndexOf('\\'));
|
|
|
return lastSlash >= 0 ? url.substring(lastSlash + 1) : url;
|
|
|
}
|
|
|
|
|
|
@@ -136,19 +136,31 @@ public class ArchiveTest {
|
|
|
|
|
|
/**
|
|
|
* 生成卷宗
|
|
|
+ * 结构:封面 -> 目录 -> 正文(带页码) -> 封底
|
|
|
*/
|
|
|
private static void generateArchive(List<String> fileUrls) throws Exception {
|
|
|
XWPFDocument document = new XWPFDocument();
|
|
|
- boolean isFirst = true;
|
|
|
|
|
|
+ // 1. 生成封面
|
|
|
+ System.out.println("1. 生成封面...");
|
|
|
+ createCoverPage(document);
|
|
|
+
|
|
|
+ // 2. 添加分页符,生成目录
|
|
|
+ System.out.println("2. 生成目录...");
|
|
|
+ addPageBreak(document);
|
|
|
+ createCatalogPage(document);
|
|
|
+
|
|
|
+ // 3. 添加分页符并设置页码,合并正文文件
|
|
|
+ System.out.println("3. 合并正文文件(带页码)...");
|
|
|
+ addPageBreakWithPageNumber(document);
|
|
|
+
|
|
|
+ boolean isFirst = true;
|
|
|
for (String filePath : fileUrls) {
|
|
|
String fileName = extractFileName(filePath);
|
|
|
String lowerCase = filePath.toLowerCase();
|
|
|
|
|
|
if (!isFirst) {
|
|
|
- XWPFParagraph pageBreak = document.createParagraph();
|
|
|
- XWPFRun run = pageBreak.createRun();
|
|
|
- run.addBreak(BreakType.PAGE);
|
|
|
+ addPageBreak(document);
|
|
|
}
|
|
|
|
|
|
if (lowerCase.endsWith(".docx") || lowerCase.endsWith(".doc")) {
|
|
|
@@ -160,17 +172,274 @@ public class ArchiveTest {
|
|
|
}
|
|
|
|
|
|
isFirst = false;
|
|
|
- System.out.println("✓ 已添加: " + fileName);
|
|
|
+ System.out.println(" ✓ 已添加: " + fileName);
|
|
|
}
|
|
|
|
|
|
+ // 4. 添加分页符,生成封底
|
|
|
+ System.out.println("4. 生成封底...");
|
|
|
+ addPageBreak(document);
|
|
|
+ createBackCoverPage(document);
|
|
|
+
|
|
|
+ // 保存文档
|
|
|
String outputPath = "D:\\fx\\cc\\卷宗_" + System.currentTimeMillis() + ".docx";
|
|
|
FileOutputStream fos = new FileOutputStream(outputPath);
|
|
|
document.write(fos);
|
|
|
fos.close();
|
|
|
document.close();
|
|
|
|
|
|
- System.out.println("\n✓ 卷宗生成成功!");
|
|
|
+ System.out.println("\n========== 生成完成 ==========");
|
|
|
System.out.println("输出路径: " + outputPath);
|
|
|
+ System.out.println("\n页码说明:");
|
|
|
+ System.out.println("- 页码格式:第 X 页 / 共 Y 页");
|
|
|
+ System.out.println("- 页码位置:页脚居中");
|
|
|
+ System.out.println("- 注意:由于POI限制,页码会显示在所有页面(包括封面、目录、封底)");
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 创建封面页
|
|
|
+ */
|
|
|
+ private static void createCoverPage(XWPFDocument document) {
|
|
|
+ // 添加空行
|
|
|
+ for (int i = 0; i < 10; i++) {
|
|
|
+ document.createParagraph();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 标题
|
|
|
+ XWPFParagraph titlePara = document.createParagraph();
|
|
|
+ titlePara.setAlignment(ParagraphAlignment.CENTER);
|
|
|
+ XWPFRun titleRun = titlePara.createRun();
|
|
|
+ titleRun.setText("成本监审项目卷宗");
|
|
|
+ titleRun.setBold(true);
|
|
|
+ titleRun.setFontSize(36);
|
|
|
+ titleRun.setFontFamily("宋体");
|
|
|
+
|
|
|
+ // 空行
|
|
|
+ for (int i = 0; i < 5; i++) {
|
|
|
+ document.createParagraph();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 项目信息
|
|
|
+ String[] infos = {
|
|
|
+ "项目名称:XXXX成本监审项目",
|
|
|
+ "委托单位:XXXX发展和改革委员会",
|
|
|
+ "承办单位:XXXX会计师事务所",
|
|
|
+ "编制日期:2024年12月"
|
|
|
+ };
|
|
|
+
|
|
|
+ for (String info : infos) {
|
|
|
+ XWPFParagraph para = document.createParagraph();
|
|
|
+ para.setAlignment(ParagraphAlignment.CENTER);
|
|
|
+ XWPFRun run = para.createRun();
|
|
|
+ run.setText(info);
|
|
|
+ run.setFontSize(16);
|
|
|
+ run.setFontFamily("宋体");
|
|
|
+ }
|
|
|
+
|
|
|
+ System.out.println(" ✓ 封面生成完成");
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 创建目录页
|
|
|
+ */
|
|
|
+ private static void createCatalogPage(XWPFDocument document) {
|
|
|
+ // 标题
|
|
|
+ XWPFParagraph titlePara = document.createParagraph();
|
|
|
+ titlePara.setAlignment(ParagraphAlignment.CENTER);
|
|
|
+ XWPFRun titleRun = titlePara.createRun();
|
|
|
+ titleRun.setText("目 录");
|
|
|
+ titleRun.setBold(true);
|
|
|
+ titleRun.setFontSize(24);
|
|
|
+ titleRun.setFontFamily("宋体");
|
|
|
+
|
|
|
+ document.createParagraph();
|
|
|
+
|
|
|
+ // 目录内容
|
|
|
+ String[] items = {
|
|
|
+ "一、成本监审报告书",
|
|
|
+ "二、成本监审工作底稿",
|
|
|
+ "三、成本监审调查表",
|
|
|
+ "四、成本监审证据资料",
|
|
|
+ "五、成本监审审核意见",
|
|
|
+ "六、成本监审会议纪要",
|
|
|
+ "七、其他相关资料"
|
|
|
+ };
|
|
|
+
|
|
|
+ for (int i = 0; i < items.length; i++) {
|
|
|
+ XWPFParagraph para = document.createParagraph();
|
|
|
+ XWPFRun run = para.createRun();
|
|
|
+ run.setText(items[i] + " ........................ " + (i + 1));
|
|
|
+ run.setFontSize(14);
|
|
|
+ run.setFontFamily("宋体");
|
|
|
+ }
|
|
|
+
|
|
|
+ System.out.println(" ✓ 目录生成完成");
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 创建封底页
|
|
|
+ */
|
|
|
+ private static void createBackCoverPage(XWPFDocument document) {
|
|
|
+ // 添加空行
|
|
|
+ for (int i = 0; i < 15; i++) {
|
|
|
+ document.createParagraph();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 封底内容
|
|
|
+ XWPFParagraph para1 = document.createParagraph();
|
|
|
+ para1.setAlignment(ParagraphAlignment.CENTER);
|
|
|
+ XWPFRun run1 = para1.createRun();
|
|
|
+ run1.setText("(封底)");
|
|
|
+ run1.setFontSize(24);
|
|
|
+ run1.setFontFamily("宋体");
|
|
|
+
|
|
|
+ document.createParagraph();
|
|
|
+
|
|
|
+ XWPFParagraph para2 = document.createParagraph();
|
|
|
+ para2.setAlignment(ParagraphAlignment.CENTER);
|
|
|
+ XWPFRun run2 = para2.createRun();
|
|
|
+ run2.setText("XXXX会计师事务所");
|
|
|
+ run2.setFontSize(16);
|
|
|
+ run2.setFontFamily("宋体");
|
|
|
+
|
|
|
+ XWPFParagraph para3 = document.createParagraph();
|
|
|
+ para3.setAlignment(ParagraphAlignment.CENTER);
|
|
|
+ XWPFRun run3 = para3.createRun();
|
|
|
+ run3.setText("地址:XXXX市XXXX区XXXX路XXX号");
|
|
|
+ run3.setFontSize(12);
|
|
|
+ run3.setFontFamily("宋体");
|
|
|
+
|
|
|
+ XWPFParagraph para4 = document.createParagraph();
|
|
|
+ para4.setAlignment(ParagraphAlignment.CENTER);
|
|
|
+ XWPFRun run4 = para4.createRun();
|
|
|
+ run4.setText("电话:0XXX-XXXXXXXX");
|
|
|
+ run4.setFontSize(12);
|
|
|
+ run4.setFontFamily("宋体");
|
|
|
+
|
|
|
+ System.out.println(" ✓ 封底生成完成");
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 添加分页符
|
|
|
+ */
|
|
|
+ private static void addPageBreak(XWPFDocument document) {
|
|
|
+ XWPFParagraph paragraph = document.createParagraph();
|
|
|
+ XWPFRun run = paragraph.createRun();
|
|
|
+ run.addBreak(BreakType.PAGE);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 添加分页符并设置页码
|
|
|
+ * 页码格式:第 X 页 / 共 Y 页(居中显示)
|
|
|
+ */
|
|
|
+ private static void addPageBreakWithPageNumber(XWPFDocument document) {
|
|
|
+ try {
|
|
|
+ // 添加分页符
|
|
|
+ XWPFParagraph paragraph = document.createParagraph();
|
|
|
+ XWPFRun run = paragraph.createRun();
|
|
|
+ run.addBreak(BreakType.PAGE);
|
|
|
+
|
|
|
+ // 创建页脚并添加页码
|
|
|
+ XWPFHeaderFooterPolicy policy = document.getHeaderFooterPolicy();
|
|
|
+ if (policy == null) {
|
|
|
+ policy = document.createHeaderFooterPolicy();
|
|
|
+ }
|
|
|
+ XWPFFooter footer = policy.createFooter(XWPFHeaderFooterPolicy.DEFAULT);
|
|
|
+
|
|
|
+ // 创建页脚段落
|
|
|
+ XWPFParagraph footerPara = footer.createParagraph();
|
|
|
+ footerPara.setAlignment(ParagraphAlignment.CENTER);
|
|
|
+
|
|
|
+ // 使用复杂字段方式添加页码
|
|
|
+ org.openxmlformats.schemas.wordprocessingml.x2006.main.CTP ctp = footerPara.getCTP();
|
|
|
+
|
|
|
+ // 添加 "第 " 文本
|
|
|
+ org.openxmlformats.schemas.wordprocessingml.x2006.main.CTR r1 = ctp.addNewR();
|
|
|
+ org.openxmlformats.schemas.wordprocessingml.x2006.main.CTRPr rPr1 = r1.addNewRPr();
|
|
|
+ org.openxmlformats.schemas.wordprocessingml.x2006.main.CTFonts fonts1 = rPr1.addNewRFonts();
|
|
|
+ fonts1.setAscii("宋体");
|
|
|
+ fonts1.setEastAsia("宋体");
|
|
|
+ org.openxmlformats.schemas.wordprocessingml.x2006.main.CTHpsMeasure sz1 = rPr1.addNewSz();
|
|
|
+ sz1.setVal(java.math.BigInteger.valueOf(20)); // 10号字 = 20半磅
|
|
|
+ org.openxmlformats.schemas.wordprocessingml.x2006.main.CTText t1 = r1.addNewT();
|
|
|
+ t1.setStringValue("第 ");
|
|
|
+
|
|
|
+ // 添加 PAGE 字段开始
|
|
|
+ org.openxmlformats.schemas.wordprocessingml.x2006.main.CTR rBegin = ctp.addNewR();
|
|
|
+ org.openxmlformats.schemas.wordprocessingml.x2006.main.CTFldChar fldCharBegin = rBegin.addNewFldChar();
|
|
|
+ fldCharBegin.setFldCharType(org.openxmlformats.schemas.wordprocessingml.x2006.main.STFldCharType.BEGIN);
|
|
|
+
|
|
|
+ // 添加 PAGE 字段指令
|
|
|
+ org.openxmlformats.schemas.wordprocessingml.x2006.main.CTR rInstr = ctp.addNewR();
|
|
|
+ org.openxmlformats.schemas.wordprocessingml.x2006.main.CTText instrText = rInstr.addNewInstrText();
|
|
|
+ instrText.setStringValue(" PAGE ");
|
|
|
+
|
|
|
+ // 添加字段分隔符
|
|
|
+ org.openxmlformats.schemas.wordprocessingml.x2006.main.CTR rSep = ctp.addNewR();
|
|
|
+ org.openxmlformats.schemas.wordprocessingml.x2006.main.CTFldChar fldCharSep = rSep.addNewFldChar();
|
|
|
+ fldCharSep.setFldCharType(org.openxmlformats.schemas.wordprocessingml.x2006.main.STFldCharType.SEPARATE);
|
|
|
+
|
|
|
+ // 添加字段默认值
|
|
|
+ org.openxmlformats.schemas.wordprocessingml.x2006.main.CTR rDefault = ctp.addNewR();
|
|
|
+ org.openxmlformats.schemas.wordprocessingml.x2006.main.CTText tDefault = rDefault.addNewT();
|
|
|
+ tDefault.setStringValue("1");
|
|
|
+
|
|
|
+ // 添加字段结束
|
|
|
+ org.openxmlformats.schemas.wordprocessingml.x2006.main.CTR rEnd = ctp.addNewR();
|
|
|
+ org.openxmlformats.schemas.wordprocessingml.x2006.main.CTFldChar fldCharEnd = rEnd.addNewFldChar();
|
|
|
+ fldCharEnd.setFldCharType(org.openxmlformats.schemas.wordprocessingml.x2006.main.STFldCharType.END);
|
|
|
+
|
|
|
+ // 添加 " 页 / 共 " 文本
|
|
|
+ org.openxmlformats.schemas.wordprocessingml.x2006.main.CTR r2 = ctp.addNewR();
|
|
|
+ org.openxmlformats.schemas.wordprocessingml.x2006.main.CTRPr rPr2 = r2.addNewRPr();
|
|
|
+ org.openxmlformats.schemas.wordprocessingml.x2006.main.CTFonts fonts2 = rPr2.addNewRFonts();
|
|
|
+ fonts2.setAscii("宋体");
|
|
|
+ fonts2.setEastAsia("宋体");
|
|
|
+ org.openxmlformats.schemas.wordprocessingml.x2006.main.CTHpsMeasure sz2 = rPr2.addNewSz();
|
|
|
+ sz2.setVal(java.math.BigInteger.valueOf(20));
|
|
|
+ org.openxmlformats.schemas.wordprocessingml.x2006.main.CTText t2 = r2.addNewT();
|
|
|
+ t2.setStringValue(" 页 / 共 ");
|
|
|
+
|
|
|
+ // 添加 NUMPAGES 字段开始
|
|
|
+ org.openxmlformats.schemas.wordprocessingml.x2006.main.CTR rBegin2 = ctp.addNewR();
|
|
|
+ org.openxmlformats.schemas.wordprocessingml.x2006.main.CTFldChar fldCharBegin2 = rBegin2.addNewFldChar();
|
|
|
+ fldCharBegin2.setFldCharType(org.openxmlformats.schemas.wordprocessingml.x2006.main.STFldCharType.BEGIN);
|
|
|
+
|
|
|
+ // 添加 NUMPAGES 字段指令
|
|
|
+ org.openxmlformats.schemas.wordprocessingml.x2006.main.CTR rInstr2 = ctp.addNewR();
|
|
|
+ org.openxmlformats.schemas.wordprocessingml.x2006.main.CTText instrText2 = rInstr2.addNewInstrText();
|
|
|
+ instrText2.setStringValue(" NUMPAGES ");
|
|
|
+
|
|
|
+ // 添加字段分隔符
|
|
|
+ org.openxmlformats.schemas.wordprocessingml.x2006.main.CTR rSep2 = ctp.addNewR();
|
|
|
+ org.openxmlformats.schemas.wordprocessingml.x2006.main.CTFldChar fldCharSep2 = rSep2.addNewFldChar();
|
|
|
+ fldCharSep2.setFldCharType(org.openxmlformats.schemas.wordprocessingml.x2006.main.STFldCharType.SEPARATE);
|
|
|
+
|
|
|
+ // 添加字段默认值
|
|
|
+ org.openxmlformats.schemas.wordprocessingml.x2006.main.CTR rDefault2 = ctp.addNewR();
|
|
|
+ org.openxmlformats.schemas.wordprocessingml.x2006.main.CTText tDefault2 = rDefault2.addNewT();
|
|
|
+ tDefault2.setStringValue("1");
|
|
|
+
|
|
|
+ // 添加字段结束
|
|
|
+ org.openxmlformats.schemas.wordprocessingml.x2006.main.CTR rEnd2 = ctp.addNewR();
|
|
|
+ org.openxmlformats.schemas.wordprocessingml.x2006.main.CTFldChar fldCharEnd2 = rEnd2.addNewFldChar();
|
|
|
+ fldCharEnd2.setFldCharType(org.openxmlformats.schemas.wordprocessingml.x2006.main.STFldCharType.END);
|
|
|
+
|
|
|
+ // 添加 " 页" 文本
|
|
|
+ org.openxmlformats.schemas.wordprocessingml.x2006.main.CTR r3 = ctp.addNewR();
|
|
|
+ org.openxmlformats.schemas.wordprocessingml.x2006.main.CTRPr rPr3 = r3.addNewRPr();
|
|
|
+ org.openxmlformats.schemas.wordprocessingml.x2006.main.CTFonts fonts3 = rPr3.addNewRFonts();
|
|
|
+ fonts3.setAscii("宋体");
|
|
|
+ fonts3.setEastAsia("宋体");
|
|
|
+ org.openxmlformats.schemas.wordprocessingml.x2006.main.CTHpsMeasure sz3 = rPr3.addNewSz();
|
|
|
+ sz3.setVal(java.math.BigInteger.valueOf(20));
|
|
|
+ org.openxmlformats.schemas.wordprocessingml.x2006.main.CTText t3 = r3.addNewT();
|
|
|
+ t3.setStringValue(" 页");
|
|
|
+
|
|
|
+ System.out.println(" ✓ 页码设置完成");
|
|
|
+ } catch (Exception e) {
|
|
|
+ System.out.println(" ✗ 页码设置失败: " + e.getMessage());
|
|
|
+ e.printStackTrace();
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -198,7 +467,8 @@ public class ArchiveTest {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 合并PDF文档
|
|
|
+ * 合并PDF文档(优化版)
|
|
|
+ * 根据PDF实际尺寸自适应,保持宽高比
|
|
|
*/
|
|
|
private static void mergePdfDocument(XWPFDocument document, String filePath) throws Exception {
|
|
|
try {
|
|
|
@@ -207,24 +477,53 @@ public class ArchiveTest {
|
|
|
int pageCount = pdfDoc.getNumberOfPages();
|
|
|
System.out.println(" PDF页数: " + pageCount);
|
|
|
|
|
|
+ // Word页面可用宽度(A4纸,左右边距各2.5cm,约15cm可用)
|
|
|
+ double wordPageWidth = 450; // 点(约15.9cm)
|
|
|
+ double wordPageHeight = 650; // 点(约22.9cm)
|
|
|
+
|
|
|
for (int i = 0; i < pageCount; i++) {
|
|
|
- java.awt.image.BufferedImage image = renderer.renderImageWithDPI(i, 200);
|
|
|
System.out.println(" 正在转换第 " + (i + 1) + " 页...");
|
|
|
|
|
|
+ // 获取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(410), org.apache.poi.util.Units.toEMU(600));
|
|
|
+ run.addPicture(bais, Document.PICTURE_TYPE_PNG,
|
|
|
+ "pdf_page_" + i + ".png",
|
|
|
+ org.apache.poi.util.Units.toEMU(imgWidthPt),
|
|
|
+ org.apache.poi.util.Units.toEMU(imgHeightPt));
|
|
|
bais.close();
|
|
|
|
|
|
- para.createRun().addBreak();
|
|
|
-
|
|
|
+ // 每页PDF后添加分页符(最后一页除外)
|
|
|
if (i < pageCount - 1) {
|
|
|
XWPFParagraph pageBreak = document.createParagraph();
|
|
|
XWPFRun pageBreakRun = pageBreak.createRun();
|
|
|
@@ -244,7 +543,7 @@ public class ArchiveTest {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 合并Excel文档
|
|
|
+ * 合并Excel文档(转图片方式,保持原有格式)
|
|
|
*/
|
|
|
private static void mergeExcelDocument(XWPFDocument document, String filePath) throws Exception {
|
|
|
try {
|
|
|
@@ -255,33 +554,27 @@ public class ArchiveTest {
|
|
|
|
|
|
if (sheet.getPhysicalNumberOfRows() == 0) continue;
|
|
|
|
|
|
- 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;
|
|
|
-
|
|
|
- org.apache.poi.xwpf.usermodel.XWPFTable table = document.createTable(rows, cols);
|
|
|
- table.setStyleID("TableGrid");
|
|
|
-
|
|
|
- for (int i = 0; i < rows; i++) {
|
|
|
- org.apache.poi.ss.usermodel.Row excelRow = sheet.getRow(i);
|
|
|
- org.apache.poi.xwpf.usermodel.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 ? cell.toString() : "";
|
|
|
-
|
|
|
- org.apache.poi.xwpf.usermodel.XWPFTableCell wordCell = wordRow.getCell(j);
|
|
|
- wordCell.setText(cellValue);
|
|
|
- }
|
|
|
- }
|
|
|
+ System.out.println(" 正在转换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, 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 {
|
|
|
+ // 图片转换失败,使用表格方式
|
|
|
+ System.out.println(" 图片转换失败,使用表格方式");
|
|
|
+ mergeExcelAsTable(document, sheet);
|
|
|
}
|
|
|
|
|
|
if (sheetIndex < workbook.getNumberOfSheets() - 1) {
|
|
|
@@ -292,11 +585,251 @@ public class ArchiveTest {
|
|
|
}
|
|
|
|
|
|
workbook.close();
|
|
|
+ System.out.println(" Excel转换完成");
|
|
|
} catch (Exception e) {
|
|
|
System.out.println(" [警告] Excel合并失败: " + e.getMessage());
|
|
|
+ e.printStackTrace();
|
|
|
XWPFParagraph para = document.createParagraph();
|
|
|
XWPFRun run = para.createRun();
|
|
|
run.setText("[Excel文件: " + new File(filePath).getName() + " - 无法嵌入]");
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 将Excel Sheet转为PNG图片(优化版)
|
|
|
+ */
|
|
|
+ private static 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) {
|
|
|
+ System.out.println(" Excel转图片失败: " + e.getMessage());
|
|
|
+ e.printStackTrace();
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取单元格值(字符串形式)
|
|
|
+ */
|
|
|
+ private static 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 static 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);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|