xiaochan 6 miesięcy temu
rodzic
commit
8c4af085af

+ 9 - 3
thyy-archive/src/main/java/org/thyy/archive/contorller/EmrArchiveController.java

@@ -45,9 +45,9 @@ public class EmrArchiveController {
 
         String id = info.getInpatientNo() + "_" + info.getAdmissTimes() + "_" + data.getReqTimes();
         String path = config.getEmrPath() + "/" + info.getInpatientNo()
-                + "/" + info.getAdmissTimes()
-                + ArchiveType.HUI_ZHEN.getDirName()
-                + "/" + id + ".pdf";
+                      + "/" + info.getAdmissTimes()
+                      + ArchiveType.HUI_ZHEN.getDirName()
+                      + "/" + id + ".pdf";
 
         if (FileUtil.exist(path) && data.getCover()) {
             Archivehuizhen.createPdf(info, path);
@@ -90,4 +90,10 @@ public class EmrArchiveController {
         return R.ok(service.getAllLog(patNo, times));
     }
 
+    @PostMapping("/createMergedPDF")
+    public ResultVo<String> createMergedPDF(@RequestParam("patNo") String patNo,
+                                            @RequestParam("times") Integer times) {
+        return service.createMergedPDF(patNo, times);
+    }
+
 }

+ 76 - 2
thyy-archive/src/main/java/org/thyy/archive/service/archive/task/ArchiveService.java

@@ -7,8 +7,12 @@ import cn.hutool.core.thread.ExecutorBuilder;
 import cn.hutool.core.thread.ThreadFactoryBuilder;
 import cn.hutool.core.util.IdUtil;
 import cn.hutool.core.util.ReflectUtil;
+import cn.hutool.core.util.StrUtil;
 import cn.hutool.extra.spring.SpringUtil;
 import cn.hutool.json.JSONArray;
+import com.alibaba.fastjson2.JSONObject;
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
 import org.reflections.Reflections;
 import org.reflections.scanners.Scanners;
 import org.reflections.util.ConfigurationBuilder;
@@ -24,7 +28,9 @@ import org.thyy.archive.data.archive.ArchiveTask;
 import org.thyy.archive.data.archive.PatientArchive;
 import org.thyy.archive.data.archive.TaskPatient;
 import org.thyy.archive.enumtype.ArchiveType;
+import org.thyy.archive.service.socket.SocketApi;
 import org.thyy.archive.utils.TokenUtil;
+import org.thyy.utils.TreeUtilV2;
 import org.thyy.utils.exception.BizException;
 import org.thyy.utils.exception.ExceptionEnum;
 import org.thyy.utils.result.R;
@@ -39,6 +45,7 @@ import java.util.*;
 import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
 
+@Slf4j
 @Service
 public class ArchiveService {
     private final ArchiveDao dao;
@@ -48,7 +55,6 @@ public class ArchiveService {
     private final ArchiveTaskDao taskDao;
     private final ArchiveConfig config;
 
-
     public ArchiveService(ArchiveDao dao, ArchiveTaskDao taskDao, ArchiveConfig config) {
         this.dao = dao;
         this.taskDao = taskDao;
@@ -136,7 +142,7 @@ public class ArchiveService {
             }
             // 判断是否跳过任务
             if (data.getPassTaskName() != null &&
-                    data.getPassTaskName().contains(archiveMethod.getName())) {
+                data.getPassTaskName().contains(archiveMethod.getName())) {
                 log.warn("跳过任务:{}", archiveMethod.getName());
                 continue;
             }
@@ -329,4 +335,72 @@ public class ArchiveService {
         }
     }
 
+    @Data
+    public static class PdfMergerInfo {
+        private long total = 0;
+        private long current = 0;
+
+        public void addTotal() {
+            total++;
+        }
+
+        public void addCurrent() {
+            current++;
+        }
+    }
+
+    public ResultVo<String> createMergedPDF(String patNo, Integer times) {
+        String path = StrUtil.format("{}/{}/{}/{}_{}.pdf", config.getEmrPath(), patNo, times, patNo, times);
+        List<PatientArchive> data = dao.getPatientList(patNo, times);
+
+        PdfMergerInfo info = new PdfMergerInfo();
+
+        data.forEach(item -> {
+            if (item.getPath() != null) {
+                item.setPath(config.getPath() + item.getPath());
+                info.addTotal();
+            }
+        });
+
+        List<PatientArchive> treeData = TreeUtilV2.create(data)
+                .sort((a) -> {
+                    if (a.getSort() == null)
+                        return Integer.MAX_VALUE;
+                    return a.getSort();
+                })
+                .execute();
+
+        if (treeData.isEmpty()) {
+            return R.fail(ExceptionEnum.LOGICAL_ERROR, "请先生成pdf");
+        }
+        PatientArchive ignoreDir = null;
+
+
+        // 检查未整理文件夹下是否有文件
+        for (PatientArchive item : treeData) {
+            if ("未整理".equals(item.getName())) {
+                ignoreDir = item;
+                if (item.getChildren() != null && !item.getChildren().isEmpty()) {
+                    return R.fail(ExceptionEnum.LOGICAL_ERROR, "还有文件未整理,请先整理文件");
+                }
+            }
+        }
+        treeData.remove(ignoreDir);
+
+        PdfMerger.mergeWithBookmarks(path, treeData, (value) -> {
+            info.addCurrent();
+            String log = StrUtil.format("""
+                    {
+                        "roomCode": "{}",
+                        "msg": {
+                            "code": "pdfMerger",
+                            "data": {}
+                        }
+                    }
+                    """, patNo + "_" + times, JSONObject.toJSONString(info));
+            SocketApi.sendArchiveRoomMsg(log);
+        });
+        return R.ok(ExceptionEnum.SUCCESS_AND_EL_MESSAGE, "合并成功。");
+    }
+
 }

+ 152 - 0
thyy-archive/src/main/java/org/thyy/archive/service/archive/task/PdfMerger.java

@@ -0,0 +1,152 @@
+package org.thyy.archive.service.archive.task;
+
+import com.itextpdf.text.Document;
+import com.itextpdf.text.pdf.*;
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.util.StrUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.thyy.archive.data.archive.PatientArchive;
+
+import java.io.FileOutputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+
+@Slf4j
+public class PdfMerger {
+
+    /**
+     * 合并PDF文件并添加可跳转的书签
+     *
+     * @param outputPath 输出PDF路径
+     * @param nodes      树状结构的节点列表
+     */
+    public static void mergeWithBookmarks(String outputPath, List<PatientArchive> nodes, Consumer<?> consumer) {
+        if (StrUtil.isBlank(outputPath) || nodes == null || nodes.isEmpty()) {
+            return;
+        }
+
+        outputPath = FileUtil.normalize(outputPath);
+
+        if (FileUtil.exist(outputPath)) {
+            FileUtil.del(outputPath);
+        }
+
+        FileUtil.mkParentDirs(outputPath);
+
+        Document document = null;
+        PdfCopy copy = null;
+
+        try {
+            document = new Document();
+            copy = new PdfCopy(document, new FileOutputStream(outputPath));
+            document.open();
+
+            // 收集所有PDF文件并计算页码偏移量
+            Map<String, Integer> pageStartMap = new HashMap<>();
+            int totalPages = 0;
+
+            for (PatientArchive node : nodes) {
+                List<PdfFileInfo> pdfFiles = collectPdfFiles(node);
+                for (PdfFileInfo pdfFile : pdfFiles) {
+
+                    PdfReader reader = new PdfReader(pdfFile.filePath());
+
+                    int pageCount = reader.getNumberOfPages();
+                    copy.addDocument(reader);
+
+                    // 记录起始页码
+                    pageStartMap.put(pdfFile.bookmarkId(), totalPages + 1);
+                    totalPages += pageCount;
+
+                    reader.close();
+                    consumer.accept(null);
+                }
+            }
+
+            // 创建书签
+            PdfOutline rootOutline = copy.getRootOutline();
+            for (PatientArchive node : nodes) {
+                createBookmarks(node, rootOutline, pageStartMap, copy);
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        } finally {
+            if (document != null) {
+                document.close();
+            }
+            if (copy != null) {
+                copy.close();
+            }
+        }
+    }
+
+    private record PdfFileInfo(String filePath, String bookmarkId) {
+
+    }
+
+    /**
+     * 收集所有PDF文件信息
+     */
+    private static List<PdfFileInfo> collectPdfFiles(PatientArchive node) {
+        List<PdfFileInfo> pdfFiles = new ArrayList<>();
+        collectPdfFilesRecursive(node, pdfFiles, null);
+        return pdfFiles;
+    }
+
+    private static void collectPdfFilesRecursive(PatientArchive node, List<PdfFileInfo> pdfFiles, String parentId) {
+        if (node == null) {
+            return;
+        }
+
+        String currentId = node.getId();
+
+        if (!node.getIsDir() && StrUtil.isNotBlank(node.getPath())) {
+            String normalizedPath = FileUtil.normalize(node.getPath());
+            pdfFiles.add(new PdfFileInfo(normalizedPath, currentId));
+        }
+
+        if (node.getChildren() != null && !node.getChildren().isEmpty()) {
+            for (PatientArchive child : node.getChildren()) {
+                collectPdfFilesRecursive(child, pdfFiles, currentId);
+            }
+        }
+    }
+
+    /**
+     * 递归创建书签
+     */
+    private static void createBookmarks(PatientArchive node, PdfOutline parentOutline,
+                                        Map<String, Integer> pageStartMap, PdfCopy copy) {
+        if (node == null) {
+            return;
+        }
+
+        String bookmarkId = node.getId();
+        int pageNumber = 1; // 默认指向第一页
+
+        if (node.getIsDir()) {
+            if (node.getChildren() != null && !node.getChildren().isEmpty()) {
+                PatientArchive patientArchive = node.getChildren().getFirst();
+                pageNumber = pageStartMap.getOrDefault(patientArchive.getId(), 1);
+            }
+        } else if (StrUtil.isNotBlank(node.getPath())) {
+            pageNumber = pageStartMap.getOrDefault(bookmarkId, 1);
+        }
+
+
+        // 创建书签动作 - 确保可以跳转
+        PdfDestination destination = new PdfDestination(PdfDestination.FIT);
+        PdfAction action = PdfAction.gotoLocalPage(pageNumber, destination, copy);
+        PdfOutline outline = new PdfOutline(parentOutline, action, node.getName());
+
+        // 递归处理子节点
+        if (node.getChildren() != null && !node.getChildren().isEmpty()) {
+            for (PatientArchive child : node.getChildren()) {
+                createBookmarks(child, outline, pageStartMap, copy);
+            }
+        }
+    }
+}

+ 6 - 2
thyy-archive/src/main/java/org/thyy/archive/service/socket/SocketApi.java

@@ -11,11 +11,15 @@ public class SocketApi {
     public static String URL = SpringUtil.getBean(ArchiveConfig.class)
             .getSocketUrl();
 
-    public static void sendArchiveRoomMsg(JSONObject js) {
+    public static void sendArchiveRoomMsg(String js) {
         TryUtil.ignoreErr(() -> HttpRequest.post(URL + "/send/archive")
                 .header("Content-Type", "application/json")
-                .body(js.toJSONString(0))
+                .body(js)
                 .setConnectionTimeout(3000)
                 .execute());
     }
+
+    public static void sendArchiveRoomMsg(JSONObject js) {
+        TryUtil.ignoreErr(() -> sendArchiveRoomMsg(js.toJSONString(0)));
+    }
 }

+ 175 - 0
thyy-utils/src/main/java/org/thyy/utils/TreeUtilV2.java

@@ -0,0 +1,175 @@
+package org.thyy.utils;
+
+import cn.hutool.core.util.ReflectUtil;
+import cn.hutool.core.util.StrUtil;
+
+import java.util.*;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+public class TreeUtilV2<E> {
+
+    public interface TowFunction<T, T1> {
+        void apply(T t, T1 t1);
+    }
+
+    private String id = "id";
+    private String parentId = "parentId";
+    private String children = "children";
+    private String sort = null;
+    private Function<E, Integer> sortFunction = null;
+    private TowFunction<E, E> towFunction = null;
+    private final List<E> data;
+
+    /**
+     * 字符转成大写
+     *
+     * @param c 需要转化的字符
+     */
+    public static char toUpperCase(char c) {
+        if (97 <= c && c <= 122) {
+            c ^= 32;
+        }
+        return c;
+    }
+
+
+    /**
+     * 首字母大写(进行字母的ascii编码前移,效率是最高的)
+     *
+     * @param fieldName 需要转化的字符串
+     */
+    public static String capitalizeTheFirstLetter(String fieldName) {
+        char[] chars = fieldName.toCharArray();
+        chars[0] = toUpperCase(chars[0]);
+        return String.valueOf(chars);
+    }
+
+    public static String getMethodName(String fieldName) {
+        return "get" + capitalizeTheFirstLetter(fieldName);
+    }
+
+    public static String setMethodName(String fieldName) {
+        return "set" + capitalizeTheFirstLetter(fieldName);
+    }
+
+    private static <T> T get(Object obj, String value, Object... args) {
+        return ReflectUtil.invoke(obj, getMethodName(value), args);
+    }
+
+    private void set(Object obj, String value, Object... args) {
+        ReflectUtil.invoke(obj, setMethodName(value), args);
+    }
+
+    public TreeUtilV2(List<E> list) {
+        this.data = list;
+    }
+
+    public static <E> TreeUtilV2<E> create(List<E> list) {
+        return new TreeUtilV2<>(list);
+    }
+
+    public TreeUtilV2<E> id(String id) {
+        this.id = id;
+        return this;
+    }
+
+    public TreeUtilV2<E> parentId(String parentId) {
+        this.parentId = parentId;
+        return this;
+    }
+
+    public TreeUtilV2<E> children(String children) {
+        this.children = children;
+        return this;
+    }
+
+    public TreeUtilV2<E> sort(String sort) {
+        this.sort = sort;
+        return this;
+    }
+
+    public TreeUtilV2<E> sort(Function<E, Integer> sortFunction) {
+        this.sortFunction = sortFunction;
+        return this;
+    }
+
+    public TreeUtilV2<E> fatherSonNode(TowFunction<E, E> towFunction) {
+        this.towFunction = towFunction;
+        return this;
+    }
+
+    public List<E> execute() {
+        return run();
+    }
+
+    private void addCallback(E parent, E item) {
+        if (towFunction == null) return;
+        towFunction.apply(parent, item);
+    }
+
+    private List<E> handelSort(List<E> resultList) {
+        if (sortFunction != null) {
+            return runSort(resultList, sortFunction);
+        }
+
+        if (StrUtil.isNotBlank(sort)) {
+            return runSort(resultList, (a) -> get(a, sort));
+        }
+        return resultList;
+    }
+
+    private List<E> runSort(List<E> resultList, Function<E, Integer> sortFunction) {
+        Stream<E> sorted = resultList
+                .stream()
+                .sorted((Comparator.comparing(a -> {
+                            List<E> child = get(a, children);
+                            if (child != null && !child.isEmpty()) {
+                                set(a, children, runSort(child, sortFunction));
+                            }
+                            Integer sort = sortFunction.apply(a);
+                            return sort == null ? 0 : sort;
+                        }
+                )));
+        return sorted.collect(Collectors.toList());
+    }
+
+    private List<E> run() {
+        List<E> resultList = new ArrayList<>();
+        Map<Object, E> treeMap = new HashMap<>(data.size());
+
+        for (E item : data) {
+            Object key = get(item, id);
+            Object parent = get(item, parentId);
+            treeMap.put(key, item);
+            if (parent == null) {
+                addCallback(null, item);
+                resultList.add(item);
+            }
+        }
+
+        for (E item : data) {
+            Object parentKey = get(item, parentId);
+            if (parentKey == null) continue;
+            E tree = treeMap.get(parentKey);
+            if (tree != null) {
+                List<E> child = get(tree, children);
+                if (child == null) {
+                    set(tree, children, new ArrayList<>());
+                    child = get(tree, children);
+                }
+                addCallback(tree, item);
+                child.add(item);
+            } else {
+                // 如果没有找到父节点就直接添加到主要的节点上
+                resultList.add(item);
+                addCallback(null, item);
+            }
+        }
+
+        return handelSort(resultList);
+    }
+
+}
+