Browse Source

--催收单批量打印待修改

wangbo 4 months ago
parent
commit
2b107d57db

+ 14 - 0
zhsw-common/src/main/java/com/rongwei/zhsw/system/service/SwCollectionNoticeService.java

@@ -0,0 +1,14 @@
+package com.rongwei.zhsw.system.service;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+public interface SwCollectionNoticeService {
+
+//    void printCollectionBill(List<Map<String, String>> data);
+
+    void printCollectionBill(List<Map<String, String>> data, HttpServletResponse response) throws IOException;
+
+}

+ 322 - 0
zhsw-common/src/main/java/com/rongwei/zhsw/system/service/impl/SwCollectionNoticeServiceImpl.java

@@ -0,0 +1,322 @@
+package com.rongwei.zhsw.system.service.impl;
+
+import com.rongwei.rwcommon.utils.StringUtils;
+import com.rongwei.rwcommoncomponent.file.service.SysFileItemService;
+import com.rongwei.rwcommonentity.commonservers.domain.SysFileItemDo;
+import com.rongwei.zhsw.system.service.SwCollectionNoticeService;
+import org.apache.poi.util.IOUtils;
+import org.apache.poi.util.Units;
+import org.apache.poi.xwpf.usermodel.*;
+import org.openxmlformats.schemas.drawingml.x2006.main.CTPositiveSize2D;
+import org.openxmlformats.schemas.drawingml.x2006.wordprocessingDrawing.*;
+import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTDrawing;
+import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTR;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+// 必须导入的类(POI 4.1.1)
+import org.openxmlformats.schemas.drawingml.x2006.wordprocessingDrawing.CTPosH;
+import org.openxmlformats.schemas.drawingml.x2006.wordprocessingDrawing.CTPosV;
+import org.openxmlformats.schemas.drawingml.x2006.wordprocessingDrawing.STRelFromH;
+import org.openxmlformats.schemas.drawingml.x2006.wordprocessingDrawing.STRelFromV;
+import javax.servlet.http.HttpServletResponse;
+import java.io.*;
+import java.util.List;
+import java.util.Map;
+
+@Service
+public class SwCollectionNoticeServiceImpl implements SwCollectionNoticeService {
+    @Autowired
+    private SysFileItemService sysFileItemService;
+
+    @Override
+    public void printCollectionBill(List<Map<String, String>> userList,HttpServletResponse response) {
+
+
+        // 1. 获取模板文件
+        final String templateId = "eec8b33cc8db4ed3b04a632f6c6f1e6f";
+        SysFileItemDo templateFile = sysFileItemService.getById(templateId);
+        if (templateFile == null) {
+            throw new RuntimeException("模板文件未找到");
+
+        }
+
+        // 2. 验证模板路径
+        String templatePath = templateFile.getFullpath();
+        if (StringUtils.isBlank(templatePath)) {
+            throw new RuntimeException("模板文件路径为空");
+
+        }
+
+        File templateFileObj = new File(templatePath);
+        if (!templateFileObj.exists() || !templateFileObj.isFile()) {
+            throw new RuntimeException("模板文件不存在");
+
+        }
+
+        // 3. 预加载模板到内存(提升性能)
+        byte[] templateBytes;
+        try (FileInputStream fis = new FileInputStream(templateFileObj)) {
+            templateBytes = IOUtils.toByteArray(fis);
+        } catch (IOException e) {
+
+            throw new RuntimeException("模板加载失败");
+
+        }
+
+        // 4. 创建输出目录
+        String outputDirectory = "output" + File.separator;
+        File dir = new File(outputDirectory);
+        if (!dir.exists() && !dir.mkdirs()) {
+
+            throw new RuntimeException("目录创建失败");
+        }
+
+        // 5. 生成合并文档
+        String outputFileName = outputDirectory + "催收单合并文档_" + System.currentTimeMillis() + ".docx";
+        String pdfFilePath = outputFileName.replace(".docx", ".pdf");
+        try (XWPFDocument mergedDocument = new XWPFDocument();
+             FileOutputStream out = new FileOutputStream(outputFileName)) {
+
+//             预加载模板图片到合并文档(避免重复复制)
+            try (InputStream is = new ByteArrayInputStream(templateBytes);
+                 XWPFDocument templateDoc = new XWPFDocument(is)) {
+                copyPictures(templateDoc, mergedDocument);
+            }
+
+            for (int i = 0; i < userList.size(); i++) {
+                Map<String, String> user = userList.get(i);
+                try (InputStream is = new ByteArrayInputStream(templateBytes);
+                     XWPFDocument document = new XWPFDocument(is)) {
+
+                    replaceTextSafely(document, "${USERNUMBER}", user.get("USERNUMBER"));
+                    replaceTextSafely(document, "${USERNAME}", user.get("USERNAME"));
+                    replaceTextSafely(document, "${YEAR}", user.get("YEAR"));
+// 遍历源文档中的段落
+                    for (XWPFParagraph srcPara : document.getParagraphs()) {
+                        XWPFParagraph newPara = mergedDocument.createParagraph();
+
+                        copyParagraphStyle(srcPara,newPara);
+
+
+                        for (XWPFRun srcRun : srcPara.getRuns()) {
+                            if (!srcRun.getEmbeddedPictures().isEmpty()) {
+                                for (XWPFPicture srcPic : srcRun.getEmbeddedPictures()) {
+                                    XWPFPictureData picData = srcPic.getPictureData();
+                                    byte[] data = picData.getData();
+                                    int picType = picData.getPictureType();
+                                    XWPFRun newRun = newPara.createRun();
+
+                                    // 插入浮动式图片(关键修改)
+                                    int widthEMU = Units.toEMU(200);
+                                    int heightEMU = Units.toEMU(110);
+                                    newRun.addPicture(new ByteArrayInputStream(data), picType, "image", widthEMU, heightEMU);
+
+                                    // 强制设置为浮动式并配置环绕
+                                    CTR ctr = newRun.getCTR();
+                                    CTDrawing drawing = ctr.getDrawingArray(0);
+                                    CTInline inline = drawing.getInlineArray(0);
+                                    CTAnchor anchor = convertInlineToAnchor(inline);
+                                    drawing.setAnchorArray(new CTAnchor[]{anchor});
+                                    configureAnchor(anchor, newPara);
+                                }
+                            }
+                            else {
+                                // 复制普通文本
+                                XWPFRun newRun = newPara.createRun();
+                                String text = srcRun.getText(0);
+                                if (text != null) {
+                                    newRun.setText(text, 0);
+                                }
+                                copyRunStyle(srcRun, newRun);
+
+                            }
+                        }
+
+                    }
+                    // 分页符
+                    if (i < userList.size() -   1) {
+                        mergedDocument.createParagraph().createRun().addBreak(BreakType.PAGE);
+                    }
+                }
+            }
+
+            mergedDocument.write(out);
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new RuntimeException("生成文件失败", e);
+        }
+        try {
+            com.aspose.words.Document doc = new com.aspose.words.Document(outputFileName);
+            doc.save(pdfFilePath, com.aspose.words.SaveFormat.PDF);
+        } catch (Exception e) {
+            throw new RuntimeException("Word 转 PDF 失败");
+        }
+        File pdfFile = new File(pdfFilePath);
+        if (!pdfFile.exists()) {
+            throw new RuntimeException("PDF 文件未生成");
+        }
+        try (FileInputStream fis = new FileInputStream(pdfFile);
+             OutputStream os = response.getOutputStream()) {
+
+            response.setContentType("application/pdf");
+            response.setHeader("Content-Disposition", "inline; filename=催收单.pdf");
+            IOUtils.copy(fis, os);
+            os.flush();
+        } catch (IOException e) {
+            throw new RuntimeException("PDF 预览失败");
+        }
+    }
+
+    private void setPictureToTightWrapLeft(XWPFRun run, XWPFParagraph paragraph) {
+        CTR ctr = run.getCTR();
+        if (ctr.sizeOfDrawingArray() > 0) {
+            CTDrawing drawing = ctr.getDrawingArray(0);
+
+            // 处理浮动式图片(CTAnchor)
+            if (drawing.sizeOfAnchorArray() > 0) {
+                CTAnchor anchor = drawing.getAnchorArray(0);
+                configureAnchor(anchor, paragraph);
+            }
+            // 处理嵌入式图片(CTInline)转换为浮动式
+            else if (drawing.sizeOfInlineArray() > 0) {
+                CTInline inline = drawing.getInlineArray(0);
+                CTAnchor anchor = convertInlineToAnchor(inline);
+                drawing.setAnchorArray(new CTAnchor[]{anchor});
+                configureAnchor(anchor, paragraph);
+            }
+        }
+    }
+    private void configureAnchor(CTAnchor anchor, XWPFParagraph paragraph) {
+        // 尺寸
+        CTPositiveSize2D extent = anchor.getExtent();
+        extent.setCx(Units.toEMU(200));
+        extent.setCy(Units.toEMU(110));
+
+        // 水平位置(相对于段落)
+        CTPosH posH = anchor.addNewPositionH();
+        posH.setRelativeFrom(STRelFromH.Enum.forString("character")); // 字符串匹配
+        posH.setPosOffset(Units.toEMU(0));
+
+
+        CTPosV posV = anchor.addNewPositionV();
+        posV.setRelativeFrom(STRelFromV.Enum.forString("line"));
+        posV.setPosOffset(Units.toEMU(0));
+
+        // 环绕
+        anchor.addNewWrapTight();
+
+        // 间距
+        anchor.setDistT(0);
+        anchor.setDistB(0);
+        anchor.setDistL(0);
+        anchor.setDistR(Units.toEMU(10));
+
+        // 层级
+        anchor.setBehindDoc(false);
+    }
+
+    // 嵌入式转浮动式(保持不变)
+    private CTAnchor convertInlineToAnchor(CTInline inline) {
+        CTAnchor anchor = CTAnchor.Factory.newInstance();
+        anchor.setDocPr(inline.getDocPr());
+        anchor.setGraphic(inline.getGraphic());
+        anchor.setExtent(inline.getExtent());
+        anchor.setDistT(0);
+        anchor.setDistB(0);
+        anchor.setDistL(0);
+        anchor.setDistR(0);
+        anchor.setLocked(false);
+        anchor.setRelativeHeight(0);
+        return anchor;
+    }
+
+    /**
+     * 安全替换文本(跳过含图片的段落)
+     */
+    private void replaceTextSafely(XWPFDocument document, String placeholder, String replacement) {
+        if (document == null) {
+            throw new IllegalArgumentException("Document cannot be null.");
+        }
+        if (placeholder == null || placeholder.isEmpty()) {
+            throw new IllegalArgumentException("Placeholder cannot be null or empty.");
+        }
+        if (replacement == null) {
+            throw new IllegalArgumentException("Replacement cannot be null.");
+        }
+        for (XWPFParagraph para : document.getParagraphs()) {
+            // 跳过包含图片的段落(保护布局)
+            if (para.getRuns().stream().anyMatch(run -> !run.getEmbeddedPictures().isEmpty())) {
+                continue;
+            }
+
+            // 手动实现文本替换
+            String fullText = para.getText(); // 获取段落全文
+            if (fullText != null && fullText.contains(placeholder)) {
+                // 清空原有 Runs
+                List<XWPFRun> runs = para.getRuns();
+                for (XWPFRun run : runs) {
+                    run.setText("", 0);
+                }
+
+                // 创建新 Run 并设置替换后的文本
+                XWPFRun newRun = para.createRun();
+                newRun.setText(fullText.replace(placeholder, replacement));
+                copyRunStyle(runs.get(0), newRun); // 继承第一个 Run 的样式
+            }
+        }
+    }
+
+    private void copyRunStyle(XWPFRun source, XWPFRun target) {
+        target.setColor(source.getColor());
+        target.setBold(source.isBold());
+        target.setItalic(source.isItalic());
+        target.setFontFamily(source.getFontFamily());
+        target.setFontSize(source.getFontSize());
+        target.setUnderline(source.getUnderline());
+        target.setStrikeThrough(source.isStrikeThrough());
+        target.setSubscript(source.getSubscript());
+        target.setTextPosition(source.getTextPosition());
+    }
+
+
+    private void copyParagraphStyle(XWPFParagraph srcPara, XWPFParagraph newPara) {
+        // 仅复制段落级样式(对齐、缩进、间距等)
+        newPara.setAlignment(srcPara.getAlignment());
+        newPara.setVerticalAlignment(srcPara.getVerticalAlignment());
+        newPara.setSpacingAfter(srcPara.getSpacingAfter());
+        newPara.setSpacingBefore(srcPara.getSpacingBefore());
+        newPara.setIndentationFirstLine(srcPara.getIndentationFirstLine());
+        newPara.setIndentationLeft(srcPara.getIndentationLeft());
+        newPara.setIndentationRight(srcPara.getIndentationRight());
+        if (srcPara.getStyleID() != null) newPara.setStyle(srcPara.getStyleID());
+    }
+
+    private void copyPictures(XWPFDocument sourceDoc, XWPFDocument targetDoc) {
+        for (XWPFPictureData sourcePic : sourceDoc.getAllPictures()) {
+            try {
+                byte[] data = sourcePic.getData();
+                int picType = sourcePic.getPictureType();
+
+                // 直接添加图片并自动管理引用
+                targetDoc.addPictureData(data, picType);
+            } catch (Exception e) {
+                throw new RuntimeException("图片复制失败", e);
+            }
+        }
+    }
+
+
+}
+
+
+
+
+
+
+
+
+
+
+
+
+

+ 13 - 9
zhsw-server/pom.xml

@@ -12,6 +12,7 @@
     <artifactId>zhsw-server</artifactId>
 
     <dependencies>
+
         <dependency>
             <groupId>com.rongwei</groupId>
             <artifactId>zhsw-common</artifactId>
@@ -76,19 +77,22 @@
             <version>1.1.10</version>
         </dependency>
 
-        <dependency>
-            <groupId>org.apache.httpcomponents</groupId>
-            <artifactId>httpclient</artifactId>
-            <version>4.5.5</version>
-        </dependency>
+            <dependency>
+                <groupId>org.apache.httpcomponents</groupId>
+                <artifactId>httpclient</artifactId>
+                <version>4.5.5</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-starter-mail</artifactId>
+            </dependency>
+
 
-        <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-mail</artifactId>
-        </dependency>
 
     </dependencies>
 
+
     <build>
         <plugins>
             <plugin>

+ 34 - 0
zhsw-server/src/main/java/com/rongwei/zhsw/system/controller/SwCollectionNoticeController.java

@@ -0,0 +1,34 @@
+package com.rongwei.zhsw.system.controller;
+
+import com.rongwei.zhsw.system.service.SwCollectionNoticeService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
+import java.util.Map;
+
+@RestController
+@RequestMapping("/swCollectionNotice")
+public class SwCollectionNoticeController {
+    @Autowired
+    SwCollectionNoticeService swCollectionNoticeService;
+
+
+@PostMapping("/receiveData")
+public void receiveData(@RequestBody List<Map<String, String>> data, HttpServletResponse response) throws Exception {
+    swCollectionNoticeService.printCollectionBill(data, response);
+}
+
+}
+
+
+
+