Bläddra i källkod

把项目合并在一起

xiaochan 11 månader sedan
incheckning
372ec9fbd9
72 ändrade filer med 4869 tillägg och 0 borttagningar
  1. 35 0
      .gitignore
  2. 61 0
      pom.xml
  3. 33 0
      thyy-archive/.gitignore
  4. 62 0
      thyy-archive/pom.xml
  5. 15 0
      thyy-archive/src/main/java/org/thyy/archive/ThyyArchiveApplication.java
  6. 56 0
      thyy-archive/src/main/java/org/thyy/archive/config/InterceptorConfig.java
  7. 39 0
      thyy-archive/src/main/java/org/thyy/archive/config/envionment/ArchiveConfig.java
  8. 34 0
      thyy-archive/src/main/java/org/thyy/archive/contorller/ArchiveController.java
  9. 93 0
      thyy-archive/src/main/java/org/thyy/archive/contorller/EmrArchiveController.java
  10. 87 0
      thyy-archive/src/main/java/org/thyy/archive/dao/OtherDao.java
  11. 59 0
      thyy-archive/src/main/java/org/thyy/archive/dao/archive/ArchiveDao.java
  12. 46 0
      thyy-archive/src/main/java/org/thyy/archive/dao/archive/ArchiveTaskDao.java
  13. 9 0
      thyy-archive/src/main/java/org/thyy/archive/dao/archive/ArchiveUploadDao.java
  14. 23 0
      thyy-archive/src/main/java/org/thyy/archive/dao/emr/EmrPatientDao.java
  15. 123 0
      thyy-archive/src/main/java/org/thyy/archive/data/CheckTheCallbacks.java
  16. 228 0
      thyy-archive/src/main/java/org/thyy/archive/data/Patient.java
  17. 9 0
      thyy-archive/src/main/java/org/thyy/archive/data/UserInfo.java
  18. 18 0
      thyy-archive/src/main/java/org/thyy/archive/data/archive/ArchiveClass.java
  19. 79 0
      thyy-archive/src/main/java/org/thyy/archive/data/archive/ArchiveTask.java
  20. 66 0
      thyy-archive/src/main/java/org/thyy/archive/data/archive/ArchiveUpload.java
  21. 114 0
      thyy-archive/src/main/java/org/thyy/archive/data/archive/PatientArchive.java
  22. 91 0
      thyy-archive/src/main/java/org/thyy/archive/data/archive/TaskPatient.java
  23. 184 0
      thyy-archive/src/main/java/org/thyy/archive/data/emr/EmrPatientData.java
  24. 11 0
      thyy-archive/src/main/java/org/thyy/archive/data/huizhen/CreateHuiZhen.java
  25. 87 0
      thyy-archive/src/main/java/org/thyy/archive/data/huizhen/JieShouHuiZhenPojo.java
  26. 172 0
      thyy-archive/src/main/java/org/thyy/archive/data/huizhen/YshHzRecord.java
  27. 83 0
      thyy-archive/src/main/java/org/thyy/archive/data/jianyan/QueryNormal.java
  28. 20 0
      thyy-archive/src/main/java/org/thyy/archive/enumtype/ArchiveType.java
  29. 81 0
      thyy-archive/src/main/java/org/thyy/archive/service/JianYanApi.java
  30. 91 0
      thyy-archive/src/main/java/org/thyy/archive/service/UploadService.java
  31. 18 0
      thyy-archive/src/main/java/org/thyy/archive/service/archive/task/ArchiveComponent.java
  32. 92 0
      thyy-archive/src/main/java/org/thyy/archive/service/archive/task/ArchiveLog.java
  33. 334 0
      thyy-archive/src/main/java/org/thyy/archive/service/archive/task/ArchiveService.java
  34. 178 0
      thyy-archive/src/main/java/org/thyy/archive/service/archive/task/start/ArchiveCreateFolder.java
  35. 206 0
      thyy-archive/src/main/java/org/thyy/archive/service/archive/task/start/ArchiveEmr.java
  36. 141 0
      thyy-archive/src/main/java/org/thyy/archive/service/archive/task/start/ArchiveJianCha.java
  37. 167 0
      thyy-archive/src/main/java/org/thyy/archive/service/archive/task/start/ArchiveJianYan.java
  38. 135 0
      thyy-archive/src/main/java/org/thyy/archive/service/archive/task/start/Archivehuizhen.java
  39. 79 0
      thyy-archive/src/main/java/org/thyy/archive/service/emr/EmrService.java
  40. 25 0
      thyy-archive/src/main/java/org/thyy/archive/service/socket/SocketApi.java
  41. 74 0
      thyy-archive/src/main/java/org/thyy/archive/utils/PdfUtil.java
  42. 45 0
      thyy-archive/src/main/java/org/thyy/archive/utils/TokenUtil.java
  43. 25 0
      thyy-archive/src/main/resources/application-prod.yml
  44. 25 0
      thyy-archive/src/main/resources/application.yml
  45. 123 0
      thyy-archive/src/main/resources/logback-spring.xml
  46. 33 0
      thyy-socket/.gitignore
  47. 39 0
      thyy-socket/pom.xml
  48. 13 0
      thyy-socket/src/main/java/org/thyy/socket/ThyySocketApplication.java
  49. 26 0
      thyy-socket/src/main/java/org/thyy/socket/config/CloseCodes.java
  50. 30 0
      thyy-socket/src/main/java/org/thyy/socket/config/CorsConfig.java
  51. 67 0
      thyy-socket/src/main/java/org/thyy/socket/config/SocketService.java
  52. 65 0
      thyy-socket/src/main/java/org/thyy/socket/config/WebSocketConfig.java
  53. 45 0
      thyy-socket/src/main/java/org/thyy/socket/controller/EmrController.java
  54. 43 0
      thyy-socket/src/main/java/org/thyy/socket/controller/PublicController.java
  55. 15 0
      thyy-socket/src/main/java/org/thyy/socket/data/CurrentDocument.java
  56. 14 0
      thyy-socket/src/main/java/org/thyy/socket/data/OutDocumentParams.java
  57. 11 0
      thyy-socket/src/main/java/org/thyy/socket/data/UserInfo.java
  58. 75 0
      thyy-socket/src/main/java/org/thyy/socket/service/Archive.java
  59. 34 0
      thyy-socket/src/main/java/org/thyy/socket/service/Business.java
  60. 51 0
      thyy-socket/src/main/java/org/thyy/socket/service/emr/EmrDocument.java
  61. 146 0
      thyy-socket/src/main/java/org/thyy/socket/service/emr/EmrEditor.java
  62. 15 0
      thyy-socket/src/main/java/org/thyy/socket/service/emr/EmrMapCenter.java
  63. 94 0
      thyy-socket/src/main/java/org/thyy/socket/service/emr/EmrRefresh.java
  64. 2 0
      thyy-socket/src/main/resources/application.yml
  65. 33 0
      thyy-utils/.gitignore
  66. 14 0
      thyy-utils/pom.xml
  67. 33 0
      thyy-utils/src/main/java/org/thyy/utils/exception/BizException.java
  68. 69 0
      thyy-utils/src/main/java/org/thyy/utils/exception/ExceptionEnum.java
  69. 58 0
      thyy-utils/src/main/java/org/thyy/utils/exception/GlobalExceptionHandler.java
  70. 32 0
      thyy-utils/src/main/java/org/thyy/utils/result/R.java
  71. 35 0
      thyy-utils/src/main/java/org/thyy/utils/result/ResultVo.java
  72. 1 0
      thyy-utils/src/main/resources/application.properties

+ 35 - 0
.gitignore

@@ -0,0 +1,35 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
+serverlog/
+.mvn/

+ 61 - 0
pom.xml

@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <version>3.4.1</version>
+        <relativePath/> <!-- lookup parent from repository -->
+    </parent>
+    <groupId>org.thyy.business</groupId>
+    <artifactId>thyy-business</artifactId>
+    <version>0.0.1</version>
+    <name>thyy-business</name>
+    <description>thyy-business</description>
+    <packaging>pom</packaging>
+    <properties>
+        <java.version>21</java.version>
+        <thyy.busuness>0.0.1</thyy.busuness>
+    </properties>
+
+    <modules>
+        <module>thyy-archive</module>
+        <module>thyy-utils</module>
+    </modules>
+    <dependencies>
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+            <version>5.8.34</version>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.thyy.business</groupId>
+            <artifactId>thyy-utils</artifactId>
+            <version>0.0.1</version>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba.fastjson2</groupId>
+            <artifactId>fastjson2</artifactId>
+            <version>2.0.52</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

+ 33 - 0
thyy-archive/.gitignore

@@ -0,0 +1,33 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/

+ 62 - 0
thyy-archive/pom.xml

@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.thyy.business</groupId>
+        <artifactId>thyy-business</artifactId>
+        <version>0.0.1</version>
+    </parent>
+    <artifactId>thyy-archive</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+    <name>thyy-archive</name>
+    <description>thyy-archive</description>
+    <properties>
+        <java.version>21</java.version>
+    </properties>
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter</artifactId>
+        </dependency>
+
+        <!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-generator -->
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
+            <version>3.5.9</version>
+        </dependency>
+
+
+        <!-- https://mvnrepository.com/artifact/com.itextpdf/itextpdf -->
+        <dependency>
+            <groupId>com.itextpdf</groupId>
+            <artifactId>itextpdf</artifactId>
+            <version>5.5.13.3</version>
+        </dependency>
+
+        <!-- https://mvnrepository.com/artifact/org.reflections/reflections -->
+        <dependency>
+            <groupId>org.reflections</groupId>
+            <artifactId>reflections</artifactId>
+            <version>0.10.2</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.microsoft.sqlserver</groupId>
+            <artifactId>mssql-jdbc</artifactId>
+            <version>8.2.2.jre8</version>
+        </dependency>
+
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

+ 15 - 0
thyy-archive/src/main/java/org/thyy/archive/ThyyArchiveApplication.java

@@ -0,0 +1,15 @@
+package org.thyy.archive;
+
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+@MapperScan("org.thyy.archive.dao")
+public class ThyyArchiveApplication {
+
+    public static void main(String[] args) {
+        SpringApplication.run(ThyyArchiveApplication.class, args);
+    }
+
+}

+ 56 - 0
thyy-archive/src/main/java/org/thyy/archive/config/InterceptorConfig.java

@@ -0,0 +1,56 @@
+package org.thyy.archive.config;
+
+import cn.hutool.crypto.asymmetric.KeyType;
+import cn.hutool.crypto.asymmetric.RSA;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.HandlerInterceptor;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+import org.thyy.utils.exception.BizException;
+import org.thyy.utils.exception.ExceptionEnum;
+
+@Configuration
+@Slf4j
+public class InterceptorConfig implements WebMvcConfigurer {
+
+    static String privateKey = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAJ1O6A6P3+GY4kh5cHsE5UqVzOqOFenqzty9ehKvGGWWAClk6K/Itqr/iGwLDSb7vTGPQY0gwjLwMUoqHPnPQN050/ChTAhvOYfxVl+BIzd6uJ19gnzCc/dc4uXPd1pfmPAPatw0AwZqv29uLiTuCeRsfu+VGm6N14nKGpwI40ozAgMBAAECgYAntZT3pBL4UE8Cy4D81OIKn2DwbriSaV8ZsszfbYrDjgdxa5MfWJPf+4xU9b82sYJzQb3i4buT/zT6C0JwPwSYzMfU5bRA+iUlq2HGRLG9/QD03/ewaS3Vf7pM2Ga0QZzlW2TToJZk2y76i4VAE8AG+Oh34q5Zgf7wEO+0s1BpwQJBAO+iKbkpC8AhY/6/CXEQh1zSGeryUJWsexQWJ1UjiFRfASQnXdVWdKgnws774K4LbIAaI5Nl3lx2lfKLzVWfeEECQQCoDVcA4LkZ7WU3zw6YTx7HIPxV5xgJfnGbKkcVE/ST5LllfiQf7N2+6YHEAVnEXiaTYtuqFfyjujExKmZjJwVzAkALIGl1in1cL3C4LGCg2laerba8XQH24SpZ1WmAH4U9/adM6XMG69vAzw/RkoptKmSgUhZiyrEXc/4kY+9l0WDBAkAdZM/t+GCH92z32QCdhWFFxTA9ukELEBHA6SErv6TM5XnEFc8twll61g77gJz56tnmd6kiU9wvqxaFmfv0pdOfAkEAnlrVdxNvhnDtyc1iLz+qIgKbIaOBPaPzpS/ovWvdPthTDoF1IwlJQKwjsgTD9mW278DjK5/A/VKmxx87eoZ26Q==";
+    static String publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCdTugOj9/hmOJIeXB7BOVKlczqjhXp6s7cvXoSrxhllgApZOivyLaq/4hsCw0m+70xj0GNIMIy8DFKKhz5z0DdOdPwoUwIbzmH8VZfgSM3eridfYJ8wnP3XOLlz3daX5jwD2rcNAMGar9vbi4k7gnkbH7vlRpujdeJyhqcCONKMwIDAQAB";
+    public static RSA rsa = new RSA(privateKey, publicKey);
+
+    @Override
+    public void addInterceptors(InterceptorRegistry registry) {
+        registry.addInterceptor(new AuthenticationInterceptor())
+                .addPathPatterns("/**");
+    }
+
+    static class AuthenticationInterceptor implements HandlerInterceptor {
+        @Override
+        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object object) throws Exception {
+            String key = request.getHeader("Authorization");
+            if (key == null) {
+                throw new BizException(ExceptionEnum.NETWORK_ERROR, "请求头 Authorization 不存在。");
+            }
+            String check = check(key);
+            log.info("请求来源:{}", request.getRemoteAddr());
+            log.info("请求路径: {}", request.getRequestURI());
+            if (check != null) {
+                log.info("访问:{}", check);
+                return true;
+            }
+            return false;
+        }
+    }
+
+    public static String check(String key) {
+        try {
+            return rsa.decryptStr(key, KeyType.PrivateKey);
+        } catch (Exception e) {
+            throw new BizException(ExceptionEnum.NETWORK_ERROR, "key错误。");
+        }
+    }
+
+
+}

+ 39 - 0
thyy-archive/src/main/java/org/thyy/archive/config/envionment/ArchiveConfig.java

@@ -0,0 +1,39 @@
+package org.thyy.archive.config.envionment;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+/**
+ * 归档配置
+ */
+@Component
+@Data
+@ConfigurationProperties(prefix = "thyy.archive")
+@NoArgsConstructor
+public class ArchiveConfig {
+    /**
+     * 生成的pdf在什么路径
+     */
+    private String path = System.getProperty("user.dir");
+    private String emrUrl = "http://172.16.32.125:8001";
+    private Boolean prod = false;
+    private String socketUrl;
+
+    public String getEmrPath() {
+        if (prod) {
+            return getPrdPath() + "/emr";
+        }
+        return getTestPath() + "/emr";
+    }
+
+    public String getPrdPath() {
+        return this.path + "/archive";
+    }
+
+    public String getTestPath() {
+        return this.path + "/archive-test";
+    }
+
+}

+ 34 - 0
thyy-archive/src/main/java/org/thyy/archive/contorller/ArchiveController.java

@@ -0,0 +1,34 @@
+package org.thyy.archive.contorller;
+
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+import org.thyy.archive.service.UploadService;
+import org.thyy.utils.result.R;
+import org.thyy.utils.result.ResultVo;
+
+
+@RestController
+public class ArchiveController {
+
+    private final UploadService service;
+
+    public ArchiveController(UploadService uploadService) {
+        this.service = uploadService;
+    }
+
+    @PostMapping(value = "/uploadFile")
+    public ResultVo<UploadService.UploadResult> uploadFile(@RequestParam("file") MultipartFile file,
+                                                           @RequestParam("path") String path,
+                                                           @RequestParam(value = "isTest", required = false) Boolean isTest) {
+        return R.ok(service.upload(file, path, isTest != null && isTest));
+    }
+
+    @PostMapping("/delUploadById/{id}")
+    public ResultVo<String> delUploadById(@PathVariable("id") String id) {
+        return service.delUploadById(id);
+    }
+
+}

+ 93 - 0
thyy-archive/src/main/java/org/thyy/archive/contorller/EmrArchiveController.java

@@ -0,0 +1,93 @@
+package org.thyy.archive.contorller;
+
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.json.JSONArray;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+import org.thyy.archive.config.envionment.ArchiveConfig;
+import org.thyy.archive.dao.OtherDao;
+import org.thyy.archive.data.archive.PatientArchive;
+import org.thyy.archive.data.archive.TaskPatient;
+import org.thyy.archive.data.huizhen.CreateHuiZhen;
+import org.thyy.archive.data.huizhen.JieShouHuiZhenPojo;
+import org.thyy.archive.enumtype.ArchiveType;
+import org.thyy.archive.service.archive.task.ArchiveService;
+import org.thyy.archive.service.archive.task.start.Archivehuizhen;
+import org.thyy.utils.result.R;
+import org.thyy.utils.result.ResultVo;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/emr")
+public class EmrArchiveController {
+
+    private final ArchiveService service;
+    private final OtherDao dao;
+    private final ArchiveConfig config;
+
+    public EmrArchiveController(ArchiveService service, OtherDao dao, ArchiveConfig config) {
+        this.service = service;
+        this.dao = dao;
+        this.config = config;
+    }
+
+    @PostMapping("/submitTask")
+    public ResultVo<String> submitTask(@RequestBody List<TaskPatient> data) {
+        service.submitTask(data);
+        return R.ok();
+    }
+
+    @PostMapping("/createHuiZhen")
+    public ResultVo<String> createHuiZhen(@RequestBody CreateHuiZhen data) {
+        JieShouHuiZhenPojo info =
+                dao.getHuanZheXinXi(data.getInpatientNo(), data.getAdmissTimes(), data.getReqTimes());
+
+        String id = info.getInpatientNo() + "_" + info.getAdmissTimes() + "_" + data.getReqTimes();
+        String path = config.getEmrPath() + "/" + info.getInpatientNo()
+                + "/" + info.getAdmissTimes()
+                + ArchiveType.HUI_ZHEN.getDirName()
+                + "/" + id + ".pdf";
+
+        if (FileUtil.exist(path) && data.getCover()) {
+            Archivehuizhen.createPdf(info, path);
+        }
+
+        return R.ok(path.replace(config.getPath(), ""));
+    }
+
+    @PostMapping("/upload")
+    public ResultVo<PatientArchive> upload(@RequestParam("file") MultipartFile file,
+                                           @RequestParam("patNo") String patNo,
+                                           @RequestParam("times") Integer times,
+                                           @RequestParam(value = "parent", required = false) String parent) {
+        return service.upload(file, patNo, times, parent);
+    }
+
+    @PostMapping("/getTaskNameList")
+    public ResultVo<List<String>> getTaskNameList() {
+        return R.ok(service.getTaskNameList());
+    }
+
+    @PostMapping("/addDir")
+    public ResultVo<String> addDir(@RequestBody PatientArchive archive) {
+        return service.addDir(archive);
+    }
+
+    @PostMapping("/delFile")
+    public ResultVo<String> delFile(@RequestBody PatientArchive archive) {
+        return service.delFile(archive);
+    }
+
+    @PostMapping("/rename")
+    public ResultVo<String> rename(@RequestParam("id") String id, @RequestParam("name") String name) {
+        return service.rename(id, name);
+    }
+
+    @PostMapping("/getAllLog")
+    public ResultVo<JSONArray> getAllLog(@RequestParam("patNo") String patNo,
+                                         @RequestParam("times") Integer times) {
+        return R.ok(service.getAllLog(patNo, times));
+    }
+
+}

+ 87 - 0
thyy-archive/src/main/java/org/thyy/archive/dao/OtherDao.java

@@ -0,0 +1,87 @@
+package org.thyy.archive.dao;
+
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+import org.thyy.archive.data.CheckTheCallbacks;
+import org.thyy.archive.data.huizhen.JieShouHuiZhenPojo;
+import org.thyy.archive.data.huizhen.YshHzRecord;
+
+import java.util.List;
+
+public interface OtherDao {
+
+    @Select("select inpatient_no,admiss_times,act_order_no, " +
+            "(select rtrim(name) from a_employee_mi with(nolock) where code = input_id) input_name," +
+            "(select rtrim(name) from zd_unit_code with(nolock) where code = a.dept_code) dept_name, " +
+            "(select rtrim(name) from zd_unit_code with(nolock) where  code = req_dept1) req_dept_name,hz_date," +
+            "(select rtrim(name) from a_employee_mi with(nolock) where code = hz_doctor1) hz_doctor1_name," +
+            "(select rtrim(name) from a_employee_mi with(nolock) where code = hz_doctor2) hz_doctor2_name, " +
+            "(select rtrim(name) from ysh_zd_hz_level where code = hz_level) as hz_level_name,hz_comment," +
+            "status_flag, " +
+            "input_date,req_times,hz_type " +
+            "from  ysh_hz_record a with(nolock) " +
+            "where  inpatient_no = #{inpatientNo} and admiss_times = #{admissTimes} " +
+            " order by req_times  ")
+    List<YshHzRecord> chaKanHuiZhenShenQing(@Param("inpatientNo") String inpatientNo,
+                                            @Param("admissTimes") Integer admissTimes);
+
+    @Select("select              rtrim(a.inpatient_no)                                                            as inpatient_no,\n" +
+            "                    a.admiss_times,\n" +
+            "                    b.admiss_date,\n" +
+            "                    rtrim(name)                                                                      as name,\n" +
+            "                    bed_no,\n" +
+            "                    sex,\n" +
+            "                    req_date,\n" +
+            "                    req_dept1,\n" +
+            "                    (select rtrim(name) name\n" +
+            "                     from zd_unit_code with (nolock)\n" +
+            "                     where code = req_dept1)                                                            req_dept1_name,\n" +
+            "                    (select birth_date\n" +
+            "                     from a_patient_mi with (nolock)\n" +
+            "                     where a.inpatient_no = a_patient_mi.inpatient_no)                                  birth_date,\n" +
+            "                    (select rtrim(name) name from zd_unit_code with (nolock) where code = req_dept2) as req_dept2,\n" +
+            "                    req_times,\n" +
+            "                    (select rtrim(name) name from a_employee_mi with (nolock) where input_id = code)    req_doctor,\n" +
+            "                    hz_type,\n" +
+            "                    (select rtrim(name) name\n" +
+            "                     from zd_unit_code with (nolock)\n" +
+            "                     where code = dept_code)                                                            dept_code_name,\n" +
+            "                    req_comment,\n" +
+            "                    hz_zd,\n" +
+            "                    hz_md,\n" +
+            "                    hz_comment,\n" +
+            "                    hz_doctor2,\n" +
+            "                    status_flag,\n" +
+            "                    (select rtrim(name) name\n" +
+            "                     from a_employee_mi with (nolock)\n" +
+            "                     where code = hz_doctor2)                                                           hz_doctor2_name,\n" +
+            "                    hz_doctor2,\n" +
+            "                    hz_date,\n" +
+            "                    dept_code,\n" +
+            "                    hz_level,\n" +
+            "    hz_level_name = (select rtrim(name) from ysh_zd_hz_level where code = hz_level),\n" +
+            "                    cast(act_order_no AS decimal)                                                    as act_order_no,\n" +
+            "    birth         = (select birth_date from a_patient_mi where a.inpatient_no = a_patient_mi.inpatient_no)\n" +
+            "from ysh_hz_record a with (nolock)\n" +
+            "         inner join view_zy_patient_all b with (nolock) on\n" +
+            "    (b.inpatient_no = a.inpatient_no and b.admiss_times = a.admiss_times)\n" +
+            "where a.inpatient_no = #{inpatientNo}\n" +
+            "  and a.admiss_times = #{admissTimes}\n" +
+            "  and a.req_times = #{reqTimes}")
+    JieShouHuiZhenPojo getHuanZheXinXi(@Param("inpatientNo") String inpatientNo,
+                                       @Param("admissTimes") Integer admissTimes,
+                                       @Param("reqTimes") Integer reqTimes);
+
+
+    @Select("select a.report_url,\n" +
+            "       a.patient_uid,\n" +
+            "       b.order_type,examin_eparts\n" +
+            "from t_check_data a,\n" +
+            "     ysh_yj_req b\n" +
+            "where pat_no = #{patNo}\n" +
+            "  and times = #{times}\n" +
+            "  and a.top_req_no = b.req_no\n" +
+            "  and report_url is not null")
+    List<CheckTheCallbacks> getCheckData(String patNo, Integer times);
+
+}

+ 59 - 0
thyy-archive/src/main/java/org/thyy/archive/dao/archive/ArchiveDao.java

@@ -0,0 +1,59 @@
+package org.thyy.archive.dao.archive;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Select;
+import org.thyy.archive.data.Patient;
+import org.thyy.archive.data.UserInfo;
+import org.thyy.archive.data.archive.PatientArchive;
+
+import java.util.List;
+
+@Mapper
+public interface ArchiveDao extends BaseMapper<PatientArchive> {
+
+    @Select("select code,rtrim(name) as name from a_employee_mi where code = #{code}")
+    UserInfo getUserByCode(String code);
+
+    @Select("select rtrim(a.inpatient_no) as inpatient_no,\n" +
+            "       admiss_times,admiss_date,dis_date,birth_date\n" +
+            "from view_zy_patient_all a left join a_patient_mi b on (a.inpatient_no = b.inpatient_no)\n" +
+            "where a.inpatient_no = #{patNo} and admiss_times = #{times}")
+    Patient getPatientInfo(String patNo, Integer times);
+
+    default List<PatientArchive> getPatientList(String patNo, Integer times) {
+        LambdaQueryWrapper<PatientArchive> eq = PatientArchive
+                .lambdaQueryWrapper()
+                .eq(PatientArchive::getPatNo, patNo)
+                .eq(PatientArchive::getTimes, times);
+        return this.selectList(eq);
+    }
+
+    default boolean dirNameRepeat(PatientArchive data) {
+        LambdaQueryWrapper<PatientArchive> eq = PatientArchive
+                .lambdaQueryWrapper()
+                .eq(PatientArchive::getPatNo, data.getPatNo())
+                .eq(PatientArchive::getTimes, data.getTimes())
+                .eq(PatientArchive::getName, data.getName());
+
+        if (data.getParentId() == null) {
+            eq.isNull(PatientArchive::getParentId);
+        } else {
+            eq.eq(PatientArchive::getParentId, data.getParentId());
+        }
+
+        return this.selectCount(eq) > 0;
+    }
+
+    default boolean hasDir(String patNo, Integer times, String id) {
+        LambdaQueryWrapper<PatientArchive> eq = PatientArchive
+                .lambdaQueryWrapper()
+                .eq(PatientArchive::getPatNo, patNo)
+                .eq(PatientArchive::getTimes, times)
+                .eq(PatientArchive::getParentId, id);
+
+        return this.selectCount(eq) > 0;
+    }
+
+}

+ 46 - 0
thyy-archive/src/main/java/org/thyy/archive/dao/archive/ArchiveTaskDao.java

@@ -0,0 +1,46 @@
+package org.thyy.archive.dao.archive;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import org.thyy.archive.data.archive.ArchiveTask;
+import org.thyy.archive.data.archive.TaskPatient;
+
+import java.util.Date;
+import java.util.List;
+
+@Mapper
+public interface ArchiveTaskDao extends BaseMapper<ArchiveTask> {
+
+    default ArchiveTask hasData(TaskPatient data) {
+        String id = ArchiveTask.setUid(data.getPatNo(), data.getTimes());
+        return this.selectOne(ArchiveTask.lambdaQueryWrapper().eq(ArchiveTask::getId, id));
+    }
+
+    default void setData(TaskPatient data) {
+        String id = ArchiveTask.setUid(data.getPatNo(), data.getTimes());
+        String pass = data.getPassTaskName() == null ? null :
+                String.join(",", data.getPassTaskName());
+        ArchiveTask build = ArchiveTask
+                .builder()
+                .id(id)
+                .createData(new Date())
+                .submitCode(data.getSubmitCode())
+                .passTask(pass)
+                .build();
+        this.insert(build);
+    }
+
+    default void delByTaskPatient(TaskPatient data) {
+        String id = ArchiveTask.setUid(data.getPatNo(), data.getTimes());
+        this.deleteById(id);
+    }
+
+    default List<ArchiveTask> getAllData() {
+        return this.selectList(null);
+    }
+
+    default void delAll() {
+        this.delete(null);
+    }
+
+}

+ 9 - 0
thyy-archive/src/main/java/org/thyy/archive/dao/archive/ArchiveUploadDao.java

@@ -0,0 +1,9 @@
+package org.thyy.archive.dao.archive;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import org.thyy.archive.data.archive.ArchiveUpload;
+
+@Mapper
+public interface ArchiveUploadDao extends BaseMapper<ArchiveUpload> {
+}

+ 23 - 0
thyy-archive/src/main/java/org/thyy/archive/dao/emr/EmrPatientDao.java

@@ -0,0 +1,23 @@
+package org.thyy.archive.dao.emr;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import org.thyy.archive.data.archive.TaskPatient;
+import org.thyy.archive.data.emr.EmrPatientData;
+
+import java.util.List;
+
+@Mapper
+public interface EmrPatientDao extends BaseMapper<EmrPatientData> {
+
+    default List<EmrPatientData> getEmrData(TaskPatient info) {
+        LambdaQueryWrapper<EmrPatientData> wq = EmrPatientData.lambdaQueryWrapper();
+        wq.eq(EmrPatientData::getPatNo, info.getPatNo());
+        wq.eq(EmrPatientData::getTimes, info.getTimes());
+        wq.eq(EmrPatientData::getDelFlag, 0);
+        wq.orderByAsc(EmrPatientData::getCreateDate);
+        return this.selectList(wq);
+    }
+
+}

+ 123 - 0
thyy-archive/src/main/java/org/thyy/archive/data/CheckTheCallbacks.java

@@ -0,0 +1,123 @@
+package org.thyy.archive.data;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.util.Date;
+import java.util.List;
+
+@Data
+@TableName("t_check_data")
+public class CheckTheCallbacks {
+
+    /**
+     * 影像号
+     */
+    @TableId(type = IdType.NONE, value = "patient_uid")
+    private String patientUid;
+
+    private Integer topReqNo;
+
+    /**
+     * 申请单号
+     */
+    @TableField(exist = false)
+    private List<Integer> reqNo;
+
+    /**
+     * 患者姓名
+     */
+    private String patientName;
+
+    /**
+     * 患者性别
+     */
+    @TableField(exist = false)
+    private String patientSex;
+
+    /**
+     * 患者年龄
+     */
+    @TableField(exist = false)
+    private String patientAge;
+
+
+    /**
+     * 住院号/门诊号
+     */
+    private String patNo;
+
+    /**
+     * 住院次数/门诊次数
+     */
+    private Integer times;
+
+    /**
+     * 检查部位
+     */
+    private String examinEparts;
+
+    /**
+     * 检查科室编码
+     */
+    private String roomCode;
+
+    @TableField(exist = false)
+    private String roomName;
+
+    /**
+     * 检查所见
+     */
+    private String examinationSee;
+
+    /**
+     * 检查结论
+     */
+    private String examinationreSult;
+
+    /**
+     * 报告医生编码
+     */
+    private String doctorCode;
+
+    @TableField(exist = false)
+    private String doctorName;
+
+    /**
+     * 审核医生编码
+     */
+    private String checkDoctorCode;
+
+    @TableField(exist = false)
+    private String checkDoctorName;
+
+    /**
+     * 审核时间
+     */
+    private Date checkTime;
+
+    /**
+     * 报告时间
+     */
+    private Date reportTime;
+
+    /**
+     * 病人来源 (1-住院, 2-门诊)
+     */
+    private Integer patientFrom;
+
+    @TableField(exist = false)
+    private String reqDoctor;
+
+    @TableField(exist = false)
+    private String reqName;
+    private String reportUrl;
+    private String studyuid;
+
+    @TableField(exist = false)
+    private String orderType;
+
+}

+ 228 - 0
thyy-archive/src/main/java/org/thyy/archive/data/Patient.java

@@ -0,0 +1,228 @@
+package org.thyy.archive.data;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+@Data
+public class Patient {
+    private String table;
+    private String inpatientNo;
+    private String inOutStatusFlag;
+    private Integer admissTimes;
+
+    /**
+     * 出院日期
+     */
+    private Date disDate;
+    private Integer ledgerSn;
+    private String staffId;
+    private String psnCertType;
+    private String socialNo;
+    private String mzNo;
+    private String name;
+    private Integer sex;
+    private String birthDate;
+    private String homeTel;
+    private String icdCode;
+    private String icdText;
+    private String admissPhysician;
+    private String admissPhysicianName;
+    private String referPhysician;
+    private String referPhysicianName;
+    private String acctUsedFlag;
+    private String dutyNurse;
+    private String dutyNurseName;
+    private String country;
+    private String nation;
+    private String contactName;
+    private String contactRelation;
+    private String contactAddrName;
+    private String contactPhone;
+    private String admissDiagStr;
+    private String clinicDiagStr;
+    private String admissWard;
+    private String admissWardName;
+    private String admissDept;
+    private String smallDept;
+    private String smallDeptName;
+    private String deptCode;
+    private Date admissDate;
+    private Date ybRegisterDate;
+    private String responceType;
+    private String responceName;
+    private Integer dismissOrder;
+    private String zkWard;
+    private String zkWardName;
+    private String bedNo;
+    private String statusFlag;
+    private String totalCharge;
+    private String indiId;
+    private String zySerialNo;
+    private String centerId;
+    private String ward;
+    private String sid;
+    private String disDiagStatus;
+    private String operation;
+    private String chargeYb;
+    private Boolean midSetl; // 中间断账标志
+    private Date zjdzDatetime; // 中间断账时间
+    private Date dismissOrderDate; // 口腔科生成出院医嘱的出院时间
+    private String medType;
+    private String medTypeName;
+    private String insutype;
+    private String insutypeName;
+    private Integer admdvs;
+    private Integer[] admdvsCascader;
+    private String matnType; // 生育类别
+    private String latechbFlag; // 是否晚育
+    private String pretFlag; // 是否早产
+    private String mdtrtId;
+    private Integer injuryArea; // 工伤归属地:1-长沙,2-望城,3-省直
+    private String injurySerialNo;
+    private String actIptDays; // 入院时间
+    private String revokeRemark;
+
+    /**
+     * 离院方式
+     */
+    private String zyDismissWay;
+    /**
+     * 患者居住地
+     */
+    private String homeStreet;
+    private Integer age;
+
+    /**
+     * 纬度
+     */
+    private BigDecimal latitude;
+
+    /**
+     * 经度
+     */
+    private BigDecimal longitude;
+
+    /**
+     * 转化成功标志
+     */
+    private Integer addrTransedFlag;
+
+    /**
+     * 同区域的人数
+     */
+    private Integer numberOfPeopleInTheSameArea;
+
+    /**
+     * 是否有出纳
+     */
+    private Integer timesBilled;
+
+    private String yp;
+    private String jyjc;
+    private String yb;
+    // 余额
+    private String balance;
+
+    private Date begntime;
+    private Date endtime;
+    private String diseCode;
+    private String diseName;
+    private String expContent;
+    private Boolean deathFlag;
+
+
+    /**
+     * DRG权重
+     */
+    private String groupInfoWeight;
+
+    /**
+     * 分组结果名称
+     */
+    private String groupInfoName;
+
+    /**
+     * 倍率(DRG)
+     */
+    private String groupInfoBl;
+
+    /**
+     * 盈亏额
+     */
+    private String groupInfoProfit;
+
+    /**
+     * 标杆费用
+     */
+    private String groupInfoFeeStand;
+    /**
+     * 病危状态
+     */
+    private String criticallyIllStatus;
+    /**
+     * 护理级别
+     */
+    private String nursingLevel;
+    /**
+     * 手术状态
+     */
+    private String oprtStatus;
+
+    /**
+     * 是否已经审核了
+     */
+    private Integer emrAudit;
+
+    /**
+     * 质控医生 来源 batj_ba2 zkys
+     */
+    private String zkys;
+
+    /**
+     * 主治医生
+     */
+    private String consultPhysician;
+    private String consultPhysicianName;
+
+    /**
+     * 主任医生
+     */
+    private String deptDirector;
+    private String deptDirectorName;
+
+    private Integer finalControl;
+
+    private String crmName;
+
+    private String disDeptName;
+    private String disMainDiag;
+
+    public String getSexName() {
+        if (sex == null) {
+            return "";
+        }
+        return switch (sex) {
+            case 1 -> "男";
+            case 2 -> "女";
+            default -> "未知";
+        };
+    }
+
+    public Boolean getMidSetl() {
+        return null != midSetl && midSetl;
+    }
+
+    public String getTable() {
+        return null == table ? "zy_actpatient" : (table).trim();
+    }
+
+    public String mainInfo() {
+        return "姓名:" + name + ",住院号:" + inpatientNo + ",住院次数:" + admissTimes;
+    }
+
+    public String getPatId() {
+        return inpatientNo + "_" + admissTimes;
+    }
+}

+ 9 - 0
thyy-archive/src/main/java/org/thyy/archive/data/UserInfo.java

@@ -0,0 +1,9 @@
+package org.thyy.archive.data;
+
+import lombok.Data;
+
+@Data
+public class UserInfo {
+    private String code;
+    private String name;
+}

+ 18 - 0
thyy-archive/src/main/java/org/thyy/archive/data/archive/ArchiveClass.java

@@ -0,0 +1,18 @@
+package org.thyy.archive.data.archive;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Builder
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class ArchiveClass {
+    private String name;
+    private Integer sort;
+    private Boolean test;
+    private Boolean startupParameter;
+    private Class<?> clazz;
+}

+ 79 - 0
thyy-archive/src/main/java/org/thyy/archive/data/archive/ArchiveTask.java

@@ -0,0 +1,79 @@
+package org.thyy.archive.data.archive;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+@TableName(value = "archive_task")
+public class ArchiveTask implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 974120735292108305L;
+
+    /**
+     * 用住院号和住院次数拼接的id
+     */
+    @TableId
+    private String id;
+
+    /**
+     * createData
+     */
+    @TableField(value = "create_data")
+    private Date createData;
+
+    /**
+     * 提交人
+     */
+    @TableField(value = "submit_code")
+    private String submitCode;
+
+    @TableField(value = "pass_task")
+    private String passTask;
+
+    public static LambdaQueryWrapper<ArchiveTask> lambdaQueryWrapper() {
+        return new LambdaQueryWrapper<>();
+    }
+
+    public static QueryWrapper<ArchiveTask> queryWrapper() {
+        return new QueryWrapper<>();
+    }
+
+    public static String setUid(String patNo, Integer times) {
+        return patNo + "_" + times;
+    }
+
+    public TaskPatient conversionTask() {
+        String[] split = id.split("_");
+
+        List<String> passTaskList = new ArrayList<>();
+        if (passTask != null) {
+            Collections.addAll(passTaskList, passTask.split(","));
+        }
+
+        return TaskPatient.builder()
+                .patNo(split[0])
+                .times(Integer.parseInt(split[1]))
+                .submitCode(submitCode)
+                .passTaskName(passTaskList)
+                .build();
+    }
+
+}

+ 66 - 0
thyy-archive/src/main/java/org/thyy/archive/data/archive/ArchiveUpload.java

@@ -0,0 +1,66 @@
+package org.thyy.archive.data.archive;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+@TableName(value = "archive_upload")
+public class ArchiveUpload implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 2139832499690335062L;
+
+    /**
+     * id
+     */
+    @TableId
+    private String id;
+
+    /**
+     * name
+     */
+    @TableField(value = "name")
+    private String name;
+
+    /**
+     * path
+     */
+    @TableField(value = "path")
+    private String path;
+
+    /**
+     * createId
+     */
+    @TableField(value = "create_id")
+    private String createId;
+
+    /**
+     * createDate
+     */
+    @TableField(value = "create_date")
+    private Date createDate;
+
+    public static LambdaQueryWrapper<ArchiveUpload> lambdaQueryWrapper() {
+        return new LambdaQueryWrapper<>();
+    }
+
+    public static QueryWrapper<ArchiveUpload> queryWrapper() {
+        return new QueryWrapper<>();
+    }
+
+
+}

+ 114 - 0
thyy-archive/src/main/java/org/thyy/archive/data/archive/PatientArchive.java

@@ -0,0 +1,114 @@
+package org.thyy.archive.data.archive;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.List;
+
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+@TableName(value = "patient_archive")
+public class PatientArchive implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 5110111070376944721L;
+
+    /**
+     * id
+     */
+    @TableId
+    private String id;
+
+    /**
+     * patNo
+     */
+    @TableField(value = "pat_no")
+    private String patNo;
+
+    /**
+     * times
+     */
+    @TableField(value = "times")
+    private Integer times;
+
+    @TableField(value = "create_id")
+    private String createId;
+    /**
+     * 第三方的唯一id
+     */
+    @TableField(value = "document_id")
+    private String documentId;
+
+
+    /**
+     * 文件名字
+     */
+    @TableField(value = "name")
+    private String name;
+
+    /**
+     * 是否文件夹
+     */
+    @TableField(value = "is_dir")
+    private Boolean isDir = Boolean.FALSE;
+
+    /**
+     * parentId
+     */
+    @TableField(value = "parent_id")
+    private String parentId;
+
+    /**
+     * 文件路径,如果是空的就说明生成错误
+     */
+    @TableField(value = "path")
+    private String path;
+
+    /**
+     * 1-电子病历  2-病案首页  3-检验
+     */
+    @TableField(value = "type")
+    private Integer type;
+
+    /**
+     * sort
+     */
+    @TableField(value = "sort")
+    private Integer sort;
+
+    /**
+     * 一些其他信息
+     */
+    @TableField(value = "info")
+    private String info;
+
+    @TableField(exist = false)
+    private List<PatientArchive> children;
+
+    public static LambdaQueryWrapper<PatientArchive> lambdaQueryWrapper() {
+        return new LambdaQueryWrapper<>();
+    }
+
+    public static QueryWrapper<PatientArchive> queryWrapper() {
+        return new QueryWrapper<>();
+    }
+
+    public static PatientArchiveBuilder defaultData(TaskPatient data) {
+        return PatientArchive.builder()
+                .patNo(data.getPatNo())
+                .times(data.getTimes())
+                .createId(data.getSubmitCode());
+    }
+
+}

+ 91 - 0
thyy-archive/src/main/java/org/thyy/archive/data/archive/TaskPatient.java

@@ -0,0 +1,91 @@
+package org.thyy.archive.data.archive;
+
+import cn.hutool.core.util.IdUtil;
+import cn.hutool.extra.spring.SpringUtil;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.thyy.archive.config.envionment.ArchiveConfig;
+import org.thyy.archive.data.Patient;
+import org.thyy.archive.enumtype.ArchiveType;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+public class TaskPatient {
+    private String patNo;
+    private Integer times;
+    private String submitCode;
+    private String submitName;
+    private Patient patient;
+
+    private static ArchiveConfig config = SpringUtil.getBean(ArchiveConfig.class);
+
+    private Boolean forceStart = false;
+    private List<String> passTaskName;
+
+    private List<PatientArchive> archives = new ArrayList<>();
+    private List<PatientArchive> oldArchives = new ArrayList<>();
+
+    public List<PatientArchive> getOldArchivesByType(ArchiveType type) {
+        return oldArchives
+                .stream()
+                .filter(i -> i.getType().equals(type.getValue()))
+                .collect(Collectors.toList());
+    }
+
+    public Map<String, PatientArchive> getDocumentIdMap(ArchiveType type) {
+        List<PatientArchive> data = getOldArchivesByType(type);
+        return data.stream()
+                .collect(Collectors.toMap(PatientArchive::getDocumentId, i -> i));
+    }
+
+    public Map<String, PatientArchive> getDocumentIdMap() {
+        return oldArchives.stream()
+                .collect(Collectors.toMap(PatientArchive::getDocumentId, i -> i));
+    }
+
+    /**
+     * 必填 documentId,type,name,如果是文件这个不需要填写path <br/>
+     * 选填 sort,parentId,isDir=false,id
+     *
+     * @return PatientArchiveBuilder
+     */
+    public PatientArchive.PatientArchiveBuilder defaultData() {
+        return PatientArchive.builder()
+                .patNo(patNo)
+                .times(times)
+                .isDir(false)
+                .id(IdUtil.simpleUUID())
+                .createId(submitCode);
+    }
+
+    public String getPath(ArchiveType type, String id) {
+        return config.getEmrPath() + "/"
+                + this.patNo + "/"
+                + this.times
+                + type.getDirName() + "/" + id + ".pdf";
+    }
+
+    public PatientArchive getFolderByName(String name) {
+        return getAllData().stream()
+                .filter(i -> i.getName().equals(name) && i.getIsDir())
+                .findFirst()
+                .orElse(null);
+    }
+
+    public List<PatientArchive> getAllData() {
+        List<PatientArchive> newArchives = new ArrayList<>();
+        newArchives.addAll(oldArchives);
+        newArchives.addAll(archives);
+        return newArchives;
+    }
+
+}

+ 184 - 0
thyy-archive/src/main/java/org/thyy/archive/data/emr/EmrPatientData.java

@@ -0,0 +1,184 @@
+package org.thyy.archive.data.emr;
+
+import cn.hutool.json.JSONObject;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * @author xc
+ */
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class EmrPatientData implements Serializable {
+
+    private static final long serialVersionUID = 172057163746918134L;
+
+    /**
+     * id
+     * 自增id
+     */
+    private Integer id;
+
+    /**
+     * 患者住院号
+     */
+    private String patNo;
+
+    /**
+     * 患者住院次数
+     */
+    private Integer times;
+
+    /**
+     * 电子病历中的唯一值
+     */
+    @TableId
+    private String emrDocumentId;
+
+    /**
+     * 病历模板编码。
+     */
+    private String emrCategoryCode;
+
+    /**
+     * 是否删除
+     */
+    private Integer delFlag;
+
+    /**
+     * 模板的名称
+     */
+    private String emrName;
+
+    /**
+     * 医生取得名称
+     */
+    private String name;
+
+    /**
+     * 创建人
+     */
+    private String createId;
+
+    @TableField(exist = false)
+    private String createName;
+
+    /**
+     * 创建时间
+     */
+    private Date createDate;
+
+    /**
+     * 修改人
+     */
+    private String modifyId;
+
+    /**
+     * 修改时间
+     */
+    private Date modifyDate;
+
+    /**
+     * 子节点
+     */
+    @TableField(exist = false)
+    private List<EmrPatientData> children;
+
+    /**
+     * 电子病历结构化数据
+     */
+    @TableField(exist = false)
+    private JSONObject emrDataElement;
+
+    @TableField(exist = false)
+    private String emrDataElementStr;
+
+    /**
+     * 是否提交
+     */
+    private Integer submit;
+
+    /**
+     * 提交人
+     */
+    private String submitId;
+
+    /**
+     * 提交时间
+     */
+    private Date submitTime;
+
+
+    /**
+     * 前端保存的页面数据结构
+     */
+    @TableField(exist = false)
+    private JSONObject documentData;
+
+    /**
+     * 父级节点
+     */
+    private String parent;
+
+    /**
+     * 管床医生
+     */
+    private String referPhysician;
+
+    /**
+     * 主治医生
+     */
+    private String consultPhysician;
+
+    /**
+     * 主任医生
+     */
+    private String deptDirector;
+
+    /**
+     * 审核医生
+     */
+    private String reviewDoctors;
+
+    /**
+     * 审核时间
+     */
+    private Date reviewTime;
+
+    /**
+     * { code: 1, name: "扫码" },   { code: 2, name: "移动平板" },   { code: 3, name: "有线手写板" }
+     */
+    private Integer signType;
+
+    /**
+     * 签名完成
+     */
+    private Boolean signComplete;
+
+    private String archivePath;
+
+    @TableField(exist = false)
+    private Integer sort;
+
+    public String getEmrDataElementStr() {
+        if (emrDataElement != null) {
+            return emrDataElement.toString();
+        }
+        return "";
+    }
+
+    public static LambdaQueryWrapper<EmrPatientData> lambdaQueryWrapper() {
+        return new LambdaQueryWrapper<>();
+    }
+}

+ 11 - 0
thyy-archive/src/main/java/org/thyy/archive/data/huizhen/CreateHuiZhen.java

@@ -0,0 +1,11 @@
+package org.thyy.archive.data.huizhen;
+
+import lombok.Data;
+
+@Data
+public class CreateHuiZhen {
+    private String inpatientNo;
+    private Integer admissTimes;
+    private Integer reqTimes;
+    private Boolean cover = Boolean.FALSE;
+}

+ 87 - 0
thyy-archive/src/main/java/org/thyy/archive/data/huizhen/JieShouHuiZhenPojo.java

@@ -0,0 +1,87 @@
+package org.thyy.archive.data.huizhen;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * <p>
+ * 描述: 接受会诊
+ * </p>
+ *
+ * @author xc
+ * @date 2021-07-27 10:36
+ */
+@Data
+public class JieShouHuiZhenPojo {
+    private String inpatientNo;
+    private Integer admissTimes;
+    private Integer reqTimes;
+
+    private Date admissDate;
+    private String name;
+    private Integer bedNo;
+    private Integer sex;
+    private Date reqDate;
+    private String reqDept1;
+    /**
+     * 会诊科室的名字
+     */
+    private String reqDept1Name;
+    private String reqDept2;
+    private Date birthDate;
+    private String birth;
+    private Integer statusFlag;
+    private String deptCodeName;
+    private String reqDoctor;
+    private String hzType;
+    private String hzDoctor2;
+
+    /**
+     * 会诊类别
+     */
+    private String hzLevel;
+    private String hzLevelName;
+    /**
+     * 会诊医生名字
+     */
+    private String hzDoctor2Name;
+
+    /**
+     * 会诊科室,这个数据库不存需要根据医生的科室来。
+     */
+    private String hzDeptName;
+
+    private Integer age;
+
+    private String reqComment;
+    private String hzZd;
+    private String hzMd;
+    private String hzComment;
+
+    /**
+     * 会诊时间
+     */
+    private Date hzDate;
+
+    /**
+     * 下面的用来 会诊统计查询
+     */
+    private long currentPage;
+
+    private long pageSize;
+
+    private String startTime;
+
+    private String endTime;
+
+    private String deptCode;
+
+    private String hzDoctor;
+
+    private String inputName;
+
+    private BigDecimal actOrderNo;
+
+}

+ 172 - 0
thyy-archive/src/main/java/org/thyy/archive/data/huizhen/YshHzRecord.java

@@ -0,0 +1,172 @@
+package org.thyy.archive.data.huizhen;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.Date;
+
+@Data
+public class YshHzRecord implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 9147698163126718543L;
+
+    /**
+     * 住院号
+     */
+    private String inpatientNo;
+
+    /**
+     * 住院次数
+     */
+    private Integer admissTimes;
+
+    /**
+     * 请求时间
+     */
+    private Integer reqTimes;
+
+    /**
+     * zySerialNo
+     */
+    private String zySerialNo;
+
+    /**
+     * 请求简介
+     */
+    private String reqComment;
+
+    /**
+     * 输入人的id
+     */
+    private String inputId;
+
+    /**
+     * 输入的时间
+     */
+    private Date inputDate;
+
+    /**
+     * 请求的时间 和 上面那个一样
+     */
+    private Date reqDate;
+
+    /**
+     * 科室 code
+     */
+    private String deptCode;
+
+    /**
+     * 病房 code 和上面的一样
+     */
+    private String wardCode;
+
+    /**
+     * 请求的科室
+     */
+    private String reqDept1;
+
+    /**
+     * 请求会诊科室2
+     */
+    private String reqDept2;
+
+    /**
+     * 状态标志 1 未读 2 已读
+     */
+    private String statusFlag;
+
+    /**
+     * hzDoctor1
+     */
+    private String hzDoctor1;
+
+    /**
+     * hzDoctor2
+     */
+    private String hzDoctor2;
+    /**
+     * hzDoctor2
+     */
+    private String hzDoctor2Name;
+
+    /**
+     * hzId
+     */
+    private String hzId;
+
+    /**
+     * 会诊日期
+     */
+    private Date hzDate;
+
+    /**
+     * 会诊意见
+     */
+    private String hzComment;
+
+    /**
+     * 会诊的级别
+     */
+    private String hzType;
+
+    /**
+     * 会诊的类别
+     */
+    private String hzLevel;
+
+    /**
+     * 目前的主要诊断
+     */
+    private String hzZd;
+
+    /**
+     * 会诊的目的
+     */
+    private String hzMd;
+
+    /**
+     * 新开的医嘱号
+     */
+    private BigDecimal actOrderNo;
+
+    /**
+     * 订单代码
+     */
+    private String orderCode;
+
+    /**
+     * 订单名称
+     */
+    private String orderName;
+
+    private String inputName;
+
+    private String DeptName;
+
+    private String reqDeptName;
+
+    private String hzLevelName;
+
+    private String hzDoctor1Name;
+
+    /**
+     * 生成的医嘱 自费还是紧急
+     */
+    private String emergencyFlag;
+    private String ybSelfFlag;
+
+    private String inspectStuff;
+
+    public String getHzTypeName() {
+        return switch (hzType) {
+            case "1" -> "主治医师";
+            case "2" -> "副主任医生";
+            case "3" -> "主任医生";
+            default -> "";
+        };
+    }
+
+}

+ 83 - 0
thyy-archive/src/main/java/org/thyy/archive/data/jianyan/QueryNormal.java

@@ -0,0 +1,83 @@
+package org.thyy.archive.data.jianyan;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class QueryNormal {
+    /**
+     * 报告单申请科室的范围
+     * 空字符串 = 全部申请科室
+     * 科室编码 = 单个申请科室
+     * 逗号连接的多个科室编码 = 多个申请科室
+     */
+    private String departId;
+
+    /**
+     * 患者姓名,后台查询模式是完全匹配,不支持模糊查询
+     */
+    private String patientName;
+
+    /**
+     * 卡类型,枚举值
+     * SocialSecurityCard = 国家社保卡
+     * HealthCard = 居民健康卡
+     * MedicalCard = 院内诊疗卡
+     * IDCard = 居民身份证
+     * Undefine = 未知(默认值)
+     */
+    private CardType cardType;
+
+    /**
+     * 卡号
+     */
+    private String cardNo;
+
+    /**
+     * 患者病历号类别(对应检验网的验单录入界面的病历号类型),枚举值
+     * OutPatient = 门诊号
+     * InPatient = 住院号
+     * Lis = 验单号
+     * Pis = 体检号
+     * Marry = 婚检号
+     * None = 未知(默认值)
+     */
+    private PatientNumType patientNumType;
+
+    /**
+     * 对应 patientNumType 参数的具体号码
+     */
+    private String patientNum;
+
+    /**
+     * 查询起始日期:yyyy-MM-dd
+     */
+    private String startDate;
+
+    /**
+     * 查询截止日期:yyyy-MM-dd
+     */
+    private String endDate;
+
+    public enum CardType {
+        SocialSecurityCard,
+        HealthCard,
+        MedicalCard,
+        IDCard,
+        Undefine
+    }
+
+    public enum PatientNumType {
+        OutPatient,
+        InPatient,
+        Lis,
+        Pis,
+        Marry,
+        None
+    }
+}

+ 20 - 0
thyy-archive/src/main/java/org/thyy/archive/enumtype/ArchiveType.java

@@ -0,0 +1,20 @@
+package org.thyy.archive.enumtype;
+
+import lombok.Getter;
+
+@Getter
+public enum ArchiveType {
+    EMR(1, "/emr"),
+    HUI_ZHEN(2, "/huizhen"),
+    USER_UPLOAD(3, "/upload"),
+    JIAN_YAN(4, "/jianYan"),
+    JIAN_CHA(5, "/jianCha");
+
+    final Integer value;
+    final String dirName;
+
+    ArchiveType(int value, String dirName) {
+        this.value = value;
+        this.dirName = dirName;
+    }
+}

+ 81 - 0
thyy-archive/src/main/java/org/thyy/archive/service/JianYanApi.java

@@ -0,0 +1,81 @@
+package org.thyy.archive.service;
+
+import cn.hutool.http.HttpRequest;
+import cn.hutool.json.JSONArray;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import lombok.Data;
+import org.thyy.archive.data.jianyan.QueryNormal;
+
+public class JianYanApi {
+
+    @Data
+    public static class Res {
+        private Boolean code;
+        private String message;
+        private Integer returnNumber;
+        private String data;
+    }
+
+    private static Res postHttp(String url, JSONObject data) {
+        try {
+            String API_IP = "http://172.16.32.178/apis";
+            String resStr = HttpRequest.post(API_IP + url)
+                    .header("Content-Type", "application/json")
+                    .body(data.toString())
+                    .setConnectionTimeout(3000)
+                    .execute()
+                    .body();
+
+            Res res = JSONUtil.toBean(resStr, Res.class);
+
+            if (res == null) {
+                throw new RuntimeException("接口调用失败");
+            }
+            return res;
+        } catch (Exception e) {
+            throw new RuntimeException(e.getMessage());
+        }
+    }
+
+    public static JSONArray getNormal(QueryNormal param) {
+        JSONObject js = new JSONObject();
+        js.set("departId", "");
+        js.set("patientNum", param.getPatientNum());
+        js.set("patientNumType", "InPatient");
+
+
+        js.set("startDate", param.getStartDate());
+        js.set("endDate", param.getEndDate());
+
+        Res res = postHttp("/third/report/query/normal", js);
+        if (res.getCode()) {
+            JSONObject data = JSONUtil.toBean(res.getData(), JSONObject.class);
+            return data.getJSONArray("items");
+        }
+        throw new RuntimeException(res.getMessage());
+    }
+
+    public static JSONObject getDetail(String reportId) {
+        JSONObject js = new JSONObject();
+        js.set("reportId", reportId);
+        js.set("responseMode", "Json");
+        Res res = postHttp("/third/report/query/detail", js);
+        if (res.getCode()) {
+            return JSONUtil.toBean(res.getData(), JSONObject.class);
+        }
+        throw new RuntimeException(res.getMessage());
+    }
+
+    public static JSONObject getImage(String reportId) {
+        JSONObject js = new JSONObject();
+        js.set("reportId", reportId);
+        js.set("responseMode", "Json");
+        Res res = postHttp("/third/report/query/pic", js);
+        if (res.getCode()) {
+            return JSONUtil.toBean(res.getData(), JSONObject.class);
+        }
+        return null;
+    }
+
+}

+ 91 - 0
thyy-archive/src/main/java/org/thyy/archive/service/UploadService.java

@@ -0,0 +1,91 @@
+package org.thyy.archive.service;
+
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.io.file.FileNameUtil;
+import cn.hutool.core.util.IdUtil;
+import lombok.Data;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+import org.thyy.archive.config.envionment.ArchiveConfig;
+import org.thyy.archive.dao.archive.ArchiveUploadDao;
+import org.thyy.archive.data.archive.ArchiveUpload;
+import org.thyy.archive.utils.TokenUtil;
+import org.thyy.utils.exception.ExceptionEnum;
+import org.thyy.utils.result.R;
+import org.thyy.utils.result.ResultVo;
+
+@Service
+public class UploadService {
+    private final ArchiveConfig config;
+    private final ArchiveUploadDao dao;
+
+    public UploadService(ArchiveConfig config, ArchiveUploadDao dao) {
+        this.config = config;
+        this.dao = dao;
+    }
+
+    @Data
+    public static class UploadResult {
+        private String id;
+        private String path;
+    }
+
+    public UploadResult upload(MultipartFile file, String path, Boolean isTest) {
+        String fileName = file.getOriginalFilename();
+        if (path == null) {
+            path = "/upload";
+        }
+        if (path.contains(".")) {
+            throw new RuntimeException("path字段,不能带有【.】 不能带有文件名称");
+        }
+        String id = IdUtil.simpleUUID();
+
+        if (!path.startsWith("/"))
+            path = "/" + path;
+        if (path.endsWith("/"))
+            path = path.substring(0, path.length() - 1);
+
+        if (isTest) {
+            path = config.getTestPath() + path;
+        } else {
+            path = config.getPrdPath() + path;
+        }
+
+        String suffix = FileNameUtil.getSuffix(fileName);
+        String completePath = path + "/" + id + "." + suffix;
+
+        insert(file, completePath, id);
+        try {
+            FileUtil.writeBytes(file.getBytes(), completePath);
+        } catch (Exception e) {
+            dao.deleteById(id);
+            throw new RuntimeException(e);
+        }
+        UploadResult result = new UploadResult();
+        result.setId(id);
+        result.setPath(completePath.replace(config.getPath(), ""));
+        return result;
+    }
+
+    private void insert(MultipartFile file, String path, String id) {
+        ArchiveUpload build = ArchiveUpload
+                .builder()
+                .id(id)
+                .name(file.getOriginalFilename())
+                .path(path)
+                .createId(TokenUtil.getInstance().getTokenUserId())
+                .build();
+        dao.insert(build);
+    }
+
+    public ResultVo<String> delUploadById(String id) {
+        ArchiveUpload upload = dao.selectById(id);
+        if (upload == null) {
+            return R.fail(ExceptionEnum.LOGICAL_ERROR, "文件不存在。");
+        }
+        FileUtil.del(upload.getPath());
+        dao.deleteById(id);
+        return R.ok();
+    }
+
+}

+ 18 - 0
thyy-archive/src/main/java/org/thyy/archive/service/archive/task/ArchiveComponent.java

@@ -0,0 +1,18 @@
+package org.thyy.archive.service.archive.task;
+
+import java.lang.annotation.*;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
+@Documented
+public @interface ArchiveComponent {
+
+    String name() default "";
+
+    int sort() default Integer.MAX_VALUE;
+
+    boolean test() default false;
+
+    boolean startupParameter() default true;
+
+}

+ 92 - 0
thyy-archive/src/main/java/org/thyy/archive/service/archive/task/ArchiveLog.java

@@ -0,0 +1,92 @@
+package org.thyy.archive.service.archive.task;
+
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.text.StrFormatter;
+import cn.hutool.extra.spring.SpringUtil;
+import cn.hutool.json.JSONArray;
+import cn.hutool.json.JSONObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.thyy.archive.config.envionment.ArchiveConfig;
+import org.thyy.archive.data.archive.TaskPatient;
+import org.thyy.archive.service.socket.SocketApi;
+
+public class ArchiveLog {
+    private final Logger logger;
+    private final TaskPatient info;
+    private final String serverName;
+    private static final String path = SpringUtil.getBean(ArchiveConfig.class)
+            .getEmrPath();
+
+    private final String patientLogPath;
+
+    public ArchiveLog(TaskPatient info, Class<?> clazz, String serverName) {
+        this.logger = LoggerFactory.getLogger(clazz);
+        this.info = info;
+        this.serverName = serverName;
+        this.patientLogPath = path + "/" + info.getPatNo() + "/" + info.getTimes() + "/archive.txt";
+    }
+
+    public String getKey() {
+        return info.getSubmitCode() + "-" + info.getSubmitName();
+    }
+
+    public void info(String msg, Object... args) {
+        String logText = StrFormatter.format(msg, args);
+        writeLog(logText, "INFO");
+        logger.info("【{}】 --> {}", getKey(), logText);
+    }
+
+    public void delimiter(String name) {
+        String log = "----------------------------------" + name + "----------------------------------\n\n";
+        info(log);
+    }
+
+    public void error(String msg, Object... args) {
+        String logText = StrFormatter.format(msg, args);
+        writeLog(logText, "ERROR");
+        logger.error("【{}】 --> {}", getKey(), logText);
+    }
+
+    public void warn(String msg, Object... args) {
+        String logText = StrFormatter.format(msg, args);
+        writeLog(logText, "WARN");
+        logger.warn("【{}】 --》 {}", getKey(), logText);
+    }
+
+    private void writeLog(String logText, String level) {
+        JSONArray jsonArray = new JSONArray();
+        jsonArray.add(DateUtil.now());
+        jsonArray.add(level);
+        jsonArray.add(getKey());
+        jsonArray.add(serverName);
+        jsonArray.add(logText);
+        String content = jsonArray.toJSONString(0);
+        FileUtil.appendUtf8String(content + ",\n", patientLogPath);
+        sendLog(content);
+    }
+
+    private void sendLog(String log) {
+        sendSocketMsg("log", log);
+    }
+
+    public void sendSocketMsg(String code, String message) {
+        JSONObject js = new JSONObject();
+        js.putOpt("roomCode", info.getPatNo() + "_" + info.getTimes());
+        JSONObject msg = new JSONObject();
+        msg.putOpt("code", code);
+        msg.putOpt("data", message);
+        js.putOpt("msg", msg.toJSONString(0));
+        // 发送消息
+        SocketApi.sendArchiveRoomMsg(js);
+    }
+
+    public void fileChangeSend(String code, String message) {
+        JSONObject data = new JSONObject();
+        data.set("code", code);
+        data.set("message", message);
+        sendSocketMsg("change", data.toJSONString(0));
+    }
+
+}

+ 334 - 0
thyy-archive/src/main/java/org/thyy/archive/service/archive/task/ArchiveService.java

@@ -0,0 +1,334 @@
+package org.thyy.archive.service.archive.task;
+
+import cn.hutool.core.io.FileTypeUtil;
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.io.file.FileNameUtil;
+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.extra.spring.SpringUtil;
+import cn.hutool.json.JSONArray;
+import org.reflections.Reflections;
+import org.reflections.scanners.Scanners;
+import org.reflections.util.ConfigurationBuilder;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+import org.thyy.archive.config.envionment.ArchiveConfig;
+import org.thyy.archive.dao.archive.ArchiveDao;
+import org.thyy.archive.dao.archive.ArchiveTaskDao;
+import org.thyy.archive.data.Patient;
+import org.thyy.archive.data.UserInfo;
+import org.thyy.archive.data.archive.ArchiveClass;
+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.utils.TokenUtil;
+import org.thyy.utils.exception.BizException;
+import org.thyy.utils.exception.ExceptionEnum;
+import org.thyy.utils.result.R;
+import org.thyy.utils.result.ResultVo;
+
+import java.io.IOException;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Parameter;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+@Service
+public class ArchiveService {
+    private final ArchiveDao dao;
+    private final List<ArchiveClass> archiveMethods;
+    private static boolean hasTest = false;
+    private final ThreadPoolExecutor threadPoolExecutor;
+    private final ArchiveTaskDao taskDao;
+    private final ArchiveConfig config;
+
+    public ArchiveService(ArchiveDao dao, ArchiveTaskDao taskDao, ArchiveConfig config) {
+        this.dao = dao;
+        this.taskDao = taskDao;
+        this.config = config;
+        archiveMethods = getArchiveMethods();
+
+        threadPoolExecutor = ExecutorBuilder.create()
+                .setCorePoolSize(5)
+                .setMaxPoolSize(10)
+                .setKeepAliveTime(0L, TimeUnit.MILLISECONDS)
+                .setThreadFactory(new ThreadFactoryBuilder()
+                        .setNamePrefix("Archive-Task-Thread-")
+                        .build()
+                )
+                .setHandler(new ThreadPoolExecutor.AbortPolicy())
+                .build();
+    }
+
+    public List<ArchiveClass> getArchiveMethods() {
+        String packageName = ArchiveService.class.getPackage().getName();
+        Reflections reflections = new Reflections(
+                new ConfigurationBuilder()
+                        .forPackages(packageName)
+                        .addScanners(Scanners.TypesAnnotated)
+        );
+        Class<? extends Annotation> annotationType = ArchiveComponent.class;
+        Set<Class<?>> typesAnnotatedWith = reflections.getTypesAnnotatedWith(annotationType);
+        List<ArchiveClass> classes = new ArrayList<>();
+
+        for (Class<?> aClass : typesAnnotatedWith) {
+            ArchiveComponent annotation = aClass.getAnnotation(ArchiveComponent.class);
+            ArchiveClass build = ArchiveClass.builder()
+                    .sort(annotation.sort())
+                    .clazz(aClass)
+                    .name(annotation.name())
+                    .test(annotation.test())
+                    .startupParameter(annotation.startupParameter())
+                    .build();
+            classes.add(build);
+            if (annotation.test()) {
+                hasTest = true;
+            }
+        }
+        classes.sort(Comparator.comparing((o) -> o.getSort() == null ? 0 : o.getSort()));
+        return classes;
+    }
+
+    public void submitTask(List<TaskPatient> data) {
+        for (TaskPatient datum : data) {
+            if (datum.getForceStart())
+                taskDao.delByTaskPatient(datum);
+            ArchiveTask archiveTask = taskDao.hasData(datum);
+            if (archiveTask == null)
+                submitTask(datum);
+        }
+    }
+
+    public void submitTask(TaskPatient data) {
+        threadPoolExecutor.execute(() -> {
+            ArchiveLog log = new ArchiveLog(data, ArchiveService.class, "main");
+            Patient patInfo = dao.getPatientInfo(data.getPatNo(), data.getTimes());
+            if (patInfo == null) {
+                return;
+            }
+            data.setPatient(patInfo);
+            taskDao.setData(data);
+            data.setOldArchives(dao.getPatientList(data.getPatNo(), data.getTimes()));
+            log.delimiter("生成pdf任务启动");
+            executePdfServer(data, log);
+            createSql(data, log);
+            log.delimiter("生成pdf任务结束");
+            log.sendSocketMsg("taskComplete", "");
+            taskDao.delByTaskPatient(data);
+        });
+    }
+
+    private void executePdfServer(TaskPatient data, ArchiveLog log) {
+        // 如果有注解是在测试的且不是正式环境的话就为真
+        boolean test = hasTest && !config.getProd();
+
+        for (ArchiveClass archiveMethod : archiveMethods) {
+            // 如果为真且这个方法不是测试的话就跳过这个循环,去执行测试类
+            if (test && !archiveMethod.getTest()) {
+                continue;
+            }
+            // 判断是否跳过任务
+            if (data.getPassTaskName() != null &&
+                    data.getPassTaskName().contains(archiveMethod.getName())) {
+                log.warn("跳过任务:{}", archiveMethod.getName());
+                continue;
+            }
+            String name = archiveMethod.getName();
+            ArchiveLog archiveLog = new ArchiveLog(data, archiveMethod.getClazz(), name);
+            Object[] params = getParams(archiveMethod.getClazz(), data, archiveLog);
+            try {
+                log.info("【{}】任务启动", name);
+                ReflectUtil.newInstance(archiveMethod.getClazz(), params);
+                log.info("【{}】任务结束", name);
+            } catch (Exception e) {
+                log.error("【{}】任务错误\n消息:{}", name, e.getMessage());
+            }
+        }
+    }
+
+    public void createSql(TaskPatient data, ArchiveLog log) {
+        try {
+            dao.insert(data.getArchives());
+        } catch (Exception e) {
+            log.error("生成目录失败:{}", "请重新执行任务。");
+        }
+    }
+
+    /**
+     * 获取构造函数的参数
+     *
+     * @param imp  类
+     * @param data 患者数据
+     * @param log  日志
+     * @return 参数
+     */
+    public Object[] getParams(Class<?> imp, TaskPatient data, ArchiveLog log) {
+        Constructor<?>[] constructors = imp.getConstructors();
+        // 获取第一个构造参数
+        Constructor<?> constructor = constructors[0];
+        Object[] parameters = new Object[constructor.getParameterCount()];
+        for (int i = 0; i < constructor.getParameterCount(); i++) {
+            Parameter parameter = constructor.getParameters()[i];
+            if (parameter.getType() == TaskPatient.class) {
+                parameters[i] = data;
+                continue;
+            }
+            if (ArchiveLog.class == parameter.getType()) {
+                parameters[i] = log;
+                continue;
+            }
+            Object bean = SpringUtil.getBean(parameter.getType());
+            parameters[i] = bean;
+        }
+        return parameters;
+    }
+
+    public ArchiveLog getPatLog(String patNo, Integer times, String serverName) {
+        String code = TokenUtil.getInstance().getTokenUserId();
+        UserInfo userByCode = dao.getUserByCode(code);
+        String submitName = userByCode == null ? "9999" : userByCode.getName();
+
+        TaskPatient build = TaskPatient.builder()
+                .patNo(patNo)
+                .times(times)
+                .submitCode(TokenUtil.getInstance().getTokenUserId())
+                .submitName(submitName)
+                .build();
+        return new ArchiveLog(build, this.getClass(), serverName);
+    }
+
+    public ResultVo<PatientArchive> upload(MultipartFile file, String patNo, Integer times, String parent) {
+        try {
+            String type = FileTypeUtil.getType(file.getInputStream());
+            if (type == null) {
+                return R.fail(ExceptionEnum.LOGICAL_ERROR, "文件上传类型只能是pdf");
+            }
+            if (!type.contains("pdf")) {
+                return R.fail(ExceptionEnum.LOGICAL_ERROR, "文件上传类型只能是pdf");
+            }
+        } catch (IOException e) {
+            return R.fail(ExceptionEnum.LOGICAL_ERROR, "文件上传类型只能是pdf");
+        }
+
+        ArchiveLog logger = getPatLog(patNo, times, "上传文件");
+
+        String filename = FileNameUtil.mainName(file.getOriginalFilename());
+        String id = IdUtil.simpleUUID();
+        // 上传到 upload 文件夹中
+        String path = config.getEmrPath() + patNo + "/" + times + "/upload/" + id + ".pdf";
+
+        PatientArchive build = PatientArchive.builder()
+                .id(id)
+                .isDir(false)
+                .type(ArchiveType.USER_UPLOAD.getValue())
+                .children(null)
+                .documentId(id)
+                .name(filename)
+                .parentId(parent.equals("null") ? null : parent)
+                .patNo(patNo)
+                .times(times)
+                .sort(null)
+                .path(path.replace(config.getEmrPath(), ""))
+                .createId(TokenUtil.getInstance().getTokenUserId())
+                .build();
+
+        try {
+            FileUtil.writeBytes(file.getBytes(), path);
+            logger.fileChangeSend(build.getCreateId(), "上传文件");
+            logger.info("上传文件成功:文件名【{}】", build.getName());
+        } catch (IOException e) {
+            return R.fail(ExceptionEnum.ERROR_MESSAGE_BOX, e.getMessage());
+        }
+        try {
+            dao.insert(build);
+        } catch (Exception e) {
+            FileUtil.del(path);
+            logger.error("上传文件失败:文件名【{}】,错误信息:{}", build.getName(), e.getMessage());
+            return R.fail(ExceptionEnum.LOGICAL_ERROR, e.getMessage());
+        }
+        return R.ok(build);
+    }
+
+    public List<String> getTaskNameList() {
+        List<String> result = new ArrayList<>();
+        for (ArchiveClass item : this.archiveMethods) {
+            if (!item.getStartupParameter()) {
+                continue;
+            }
+            if (!item.getTest())
+                result.add(item.getName());
+        }
+        return result;
+    }
+
+    public ResultVo<String> addDir(PatientArchive archive) {
+        archive.setId(IdUtil.simpleUUID());
+        archive.setCreateId(TokenUtil.getInstance().getTokenUserId());
+        archive.setSort(null);
+        dirNameRepeat(archive);
+        dao.insert(archive);
+        ArchiveLog logger = getPatLog(archive.getPatNo(), archive.getTimes(), "新增文件夹");
+        logger.fileChangeSend(TokenUtil.getInstance().getTokenUserId(), "新增文件夹");
+        logger.info("名称:{}", archive.getName());
+        return R.ok();
+    }
+
+    public ResultVo<String> delFile(PatientArchive archive) {
+        if (dao.hasDir(archive.getPatNo(), archive.getTimes(), archive.getId())) {
+            return R.fail(ExceptionEnum.LOGICAL_ERROR, "文件夹下面还存在文件无法删除。");
+        }
+
+        ArchiveLog logger = getPatLog(archive.getPatNo(), archive.getTimes(), "删除文件");
+
+        if (!archive.getIsDir()) {
+            FileUtil.del(config.getEmrPath() + "/" + archive.getPath());
+        }
+        PatientArchive oldData = dao.selectById(archive.getId());
+        dao.deleteById(archive.getId());
+        logger.warn("删除{}:文件名 {}", oldData.getIsDir() ? "文件夹" : "文件", oldData.getName());
+        logger.fileChangeSend(TokenUtil.getInstance().getTokenUserId(), "删除文件");
+        return R.ok(ExceptionEnum.SUCCESS_AND_EL_MESSAGE);
+    }
+
+    public ResultVo<String> rename(String id, String name) {
+        PatientArchive oldData = dao.selectById(id);
+        PatientArchive build = PatientArchive.builder()
+                .id(id)
+                .name(name)
+                .patNo(oldData.getPatNo())
+                .times(oldData.getTimes())
+                .build();
+        dirNameRepeat(build);
+        ArchiveLog logger = getPatLog(oldData.getPatNo(), oldData.getTimes(), "重命名");
+        logger.info("文件id:{},原名称:【{}】,新名称:【{}】", id, oldData.getName(), name);
+        logger.fileChangeSend(TokenUtil.getInstance().getTokenUserId(), "重命名文件");
+        dao.updateById(build);
+        return R.ok(ExceptionEnum.SUCCESS_AND_EL_MESSAGE);
+    }
+
+    public JSONArray getAllLog(String patNo, Integer times) {
+        String path = config.getEmrPath() + "/" + patNo + "/" + times + "/archive.txt";
+        if (!FileUtil.exist(path)) {
+            return new JSONArray();
+        }
+        String data = FileUtil.readString(path, StandardCharsets.UTF_8);
+        return new JSONArray("[" + data + "]");
+    }
+
+    private void dirNameRepeat(PatientArchive data) {
+        boolean repeat = dao.dirNameRepeat(data);
+        if (repeat) {
+            throw new BizException(ExceptionEnum.LOGICAL_ERROR, "文件夹名称重复请重新命名。");
+        }
+    }
+
+}

+ 178 - 0
thyy-archive/src/main/java/org/thyy/archive/service/archive/task/start/ArchiveCreateFolder.java

@@ -0,0 +1,178 @@
+package org.thyy.archive.service.archive.task.start;
+
+import org.thyy.archive.dao.archive.ArchiveDao;
+import org.thyy.archive.data.archive.PatientArchive;
+import org.thyy.archive.data.archive.TaskPatient;
+import org.thyy.archive.service.archive.task.ArchiveComponent;
+import org.thyy.archive.service.archive.task.ArchiveLog;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+@ArchiveComponent(sort = -1, name = "初始化文件夹", startupParameter = false)
+public class ArchiveCreateFolder {
+    private final TaskPatient info;
+    private final ArchiveLog logger;
+    private final ArchiveDao archiveDao;
+
+    public ArchiveCreateFolder(TaskPatient info, ArchiveLog logger, ArchiveDao archiveDao) {
+        this.info = info;
+        this.logger = logger;
+        this.archiveDao = archiveDao;
+        init();
+    }
+
+    private static final Set<String> 文件夹 = new LinkedHashSet<>();
+    public static final Map<String, String> 检查分类 = new LinkedHashMap<>();
+
+    static {
+        添加文件夹();
+        添加检查分类();
+    }
+
+    static void 添加文件夹() {
+        文件夹.add("未整理");
+        文件夹.add("住院病案首页");
+        文件夹.add("出院或死亡记录");
+        文件夹.add("死亡病例讨论纪要");
+        文件夹.add("入院记录或再入院记录");
+        文件夹.add("病程记录");
+        文件夹.add("手术相关");
+        文件夹.add("产时、产后记录"); // 不在电子病历里面,护理那边的
+        文件夹.add("疑难病例讨论纪要、全院大会诊纪要");
+        文件夹.add("会诊单");
+        文件夹.add("三大常规报告单");
+        文件夹.add("血液生化报告粘贴单");
+        文件夹.add("各种特殊检查");
+        文件夹.add("特殊治疗报告单");
+        文件夹.add("各种告知书、各类申请书、同意书等");
+        文件夹.add("其他知情文件");
+        文件夹.add("各类评估表、记录单");
+        文件夹.add("各类测定记录");
+        文件夹.add("护理记录");
+        文件夹.add("护理会诊单、会诊结果");
+        文件夹.add("长期医嘱单");
+        文件夹.add("临时医嘱单");
+        文件夹.add("三测单");
+        文件夹.add("授权委托书");
+        文件夹.add("医保相关同意书、审批表");
+        文件夹.add("临床路径记录");
+        文件夹.add("外院检查资料");
+        文件夹.add("新生儿记录");
+        文件夹.add("死亡患者门诊、急诊病历");
+        文件夹.add("居民死亡医学证明(推断)书");
+    }
+
+    static void 添加检查分类() {
+        检查分类.put("01", "B超");
+        检查分类.put("02", "彩超");
+        检查分类.put("03", "心脏彩超");
+        检查分类.put("04", "超声引导下介入操作");
+        检查分类.put("05", "脑彩超");
+        检查分类.put("06", "心电图");
+        检查分类.put("07", "DR");
+        检查分类.put("08", "CT");
+        检查分类.put("10", "造影");
+        检查分类.put("11", "透视");
+        检查分类.put("14", "电子胃镜");
+        检查分类.put("15", "电子肠镜");
+        检查分类.put("26", "MRI");
+        检查分类.put("27", "ECT(外送总院)");
+        检查分类.put("28", "脑电图(外送总院)");
+        检查分类.put("29", "胃肠");
+        检查分类.put("30", "介入放射");
+        检查分类.put("31", "床旁彩超");
+        检查分类.put("32", "外院X、CT、MRI片会诊");
+        检查分类.put("33", "脑电图");
+        检查分类.put("34", "支纤镜");
+        检查分类.put("35", "放射材料");
+        检查分类.put("36", "四维彩超");
+        检查分类.put("37", "高压氧治疗");
+        检查分类.put("38", "病理");
+        检查分类.put("39", "精准肿瘤放疗");
+        检查分类.put("40", "肌电图");
+        检查分类.put("41", "TCD");
+        检查分类.put("42", "肺功能");
+        检查分类.put("43", "睡眠监测");
+        检查分类.put("44", "X射线");
+        检查分类.put("45", "眼科检查");
+        检查分类.put("46", "产科检查");
+        检查分类.put("47", "妇科检查");
+        检查分类.put("48", "耳鼻咽喉科检查");
+        检查分类.put("49", "儿科检查");
+        检查分类.put("50", "诊查类");
+        检查分类.put("51", "US");
+        检查分类.put("52", "检查大类");
+    }
+
+    void init() {
+        Map<String, PatientArchive> 一级文件目录 = info.getOldArchives()
+                .stream()
+                .filter(i -> i.getIsDir() && i.getParentId() == null)
+                .collect(Collectors.toMap(PatientArchive::getName, a -> a, (k1, k2) -> k1));
+
+        List<PatientArchive> list = new ArrayList<>();
+
+        int index = 0;
+        for (String item : 文件夹) {
+            if (一级文件目录.get(item) == null) {
+                list.add(创建文件夹(item, index, null));
+            }
+            index++;
+        }
+        添加文件(list, "根目录");
+        创建检查分类();
+    }
+
+    void 创建检查分类() {
+        PatientArchive 父节点 = info.getOldArchives()
+                .stream()
+                .filter(i -> i.getIsDir() && i.getParentId() == null && i.getName().equals("各种特殊检查"))
+                .findFirst()
+                .orElse(null);
+        if (父节点 == null) {
+            logger.error("未找到【各种特殊检查】目录,无法创建检查分类目录");
+            return;
+        }
+
+        Map<String, PatientArchive> 子目录 = info.getOldArchives()
+                .stream()
+                .filter(i -> i.getIsDir() && 父节点.getId().equals(i.getParentId()))
+                .collect(Collectors.toMap(PatientArchive::getName, a -> a, (k1, k2) -> k1));
+
+        List<PatientArchive> list = new ArrayList<>();
+
+        int index = 0;
+        for (Map.Entry<String, String> item : 检查分类.entrySet()) {
+            String name = item.getValue();
+            if (子目录.get(name) == null) {
+                list.add(创建文件夹(name, index, 父节点.getId()));
+            }
+            index++;
+        }
+        添加文件(list, "检查分类目录");
+    }
+
+    PatientArchive 创建文件夹(String name, int index, String parent) {
+        logger.info("创建文件夹:【{}】", name);
+        return info.defaultData()
+                .name(name)
+                .documentId(name)
+                .parentId(parent)
+                .sort(index + 1)
+                .type(0)
+                .isDir(true)
+                .build();
+    }
+
+    void 添加文件(List<PatientArchive> list, String name) {
+        if (list == null || list.isEmpty()) {
+            logger.info("无需创建:【{}】", name);
+            return;
+        }
+        archiveDao.insert(list);
+        info.getOldArchives().addAll(list);
+    }
+
+
+}

+ 206 - 0
thyy-archive/src/main/java/org/thyy/archive/service/archive/task/start/ArchiveEmr.java

@@ -0,0 +1,206 @@
+package org.thyy.archive.service.archive.task.start;
+
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.lang.Opt;
+import cn.hutool.core.util.IdUtil;
+import cn.hutool.json.JSONUtil;
+import org.thyy.archive.config.envionment.ArchiveConfig;
+import org.thyy.archive.dao.emr.EmrPatientDao;
+import org.thyy.archive.data.archive.PatientArchive;
+import org.thyy.archive.data.archive.TaskPatient;
+import org.thyy.archive.data.emr.EmrPatientData;
+import org.thyy.archive.enumtype.ArchiveType;
+import org.thyy.archive.service.archive.task.ArchiveComponent;
+import org.thyy.archive.service.archive.task.ArchiveLog;
+import org.thyy.archive.service.emr.EmrService;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@ArchiveComponent(name = "电子病历", sort = 2)
+public class ArchiveEmr {
+
+    private final ArchiveConfig archiveData;
+    private final TaskPatient info;
+    private final ArchiveLog logger;
+    private final EmrPatientDao dao;
+
+    public static final String THIS_IS_DIR = "This is Dir";
+    private String 未整理文件夹id;
+    private final Map<String, PatientArchive> oldData;
+    private int currentIndex = 1;
+
+
+    public ArchiveEmr(ArchiveConfig archiveData,
+                      TaskPatient info,
+                      ArchiveLog logger,
+                      EmrPatientDao dao) {
+        this.archiveData = archiveData;
+        this.info = info;
+        this.logger = logger;
+        this.dao = dao;
+        this.oldData = info.getDocumentIdMap();
+        init();
+    }
+
+    // 如果病历的父节点字段对照到了就完整的填充过去
+    public static Map<String, String> 病历文件夹 = new HashMap<>();
+
+    static {
+        病历文件夹.put("eb75a8e054f911eda5682b9e7526e088", "出院或死亡记录");
+        病历文件夹.put("281710a054fe11edb28ac955a5f5cad1", "手术相关");
+        病历文件夹.put("2e1e133054ff11edb28ac955a5f5cad1", "各种告知书、各类申请书、同意书等");
+        病历文件夹.put("15db157054fa11eda5682b9e7526e088", "死亡病例讨论纪要");
+        病历文件夹.put("4959e2c054fd11edb28ac955a5f5cad1", "入院记录或再入院记录");
+        病历文件夹.put("58a5e6c054fd11edb28ac955a5f5cad1", "病程记录");
+        病历文件夹.put("f61dae0054fe11edb28ac955a5f5cad1", "疑难病例讨论纪要、全院大会诊纪要");
+    }
+
+    void 创建自定义文件夹(List<EmrPatientData> emrData) {
+        未整理文件夹id = info.getFolderByName("未整理")
+                .getId();
+        for (EmrPatientData item : emrData) {
+            if (item.getParent() == null) continue;
+            if (!THIS_IS_DIR.equals(item.getEmrCategoryCode())) continue;
+            String 放入文件夹 = 病历文件夹.get(item.getParent());
+            if (放入文件夹 == null) continue;
+            创建文件夹(item, 放入文件夹);
+        }
+    }
+
+    void 创建文件夹(EmrPatientData item, String 放入文件夹) {
+        PatientArchive old = oldData.get(item.getEmrDocumentId());
+        if (old != null) return;
+
+        PatientArchive folder = info.getFolderByName(放入文件夹);
+
+        PatientArchive build = info.defaultData()
+                .documentId(item.getEmrDocumentId())
+                .isDir(true)
+                .parentId(folder.getId())
+                .name(item.getName())
+                .type(ArchiveType.EMR.getValue())
+                .build();
+
+        info.getArchives().add(build);
+    }
+
+    private void init() {
+        List<EmrPatientData> data = dao.getEmrData(info);
+        if (data == null) {
+            return;
+        }
+        创建自定义文件夹(data);
+        for (EmrPatientData item : data) {
+            try {
+                implement(item);
+                currentIndex++;
+            } catch (Exception e) {
+                logger.error(e.getMessage());
+            }
+        }
+    }
+
+    private String dirPath(EmrPatientData item) {
+        return info.getPath(ArchiveType.EMR, item.getEmrDocumentId());
+    }
+
+    private void implement(EmrPatientData item) {
+        if (THIS_IS_DIR.equals(item.getEmrCategoryCode())) {
+            return;
+        }
+        String path = dirPath(item);
+        if (exist(item, path)) {
+            assemblyData(item, path);
+            return;
+        }
+        try {
+            logger.info("正在生成【{}】。", item.getName());
+            byte[] file = EmrService.getPdfByte(item.getEmrDocumentId());
+            FileUtil.writeBytes(file, path);
+            assemblyData(item, path);
+            logger.info("【{}】生成成功。", item.getName());
+        } catch (Exception e) {
+            logger.error("病历:【{}】生成错误,文档id:{} ,调用接口错误:{}", item.getName(), item.getEmrDocumentId(), e.getMessage());
+        }
+    }
+
+    private void assemblyData(EmrPatientData data, String path) {
+        PatientArchive tmpOld = oldData.get(data.getEmrDocumentId());
+        if (tmpOld == null) {
+            info.getArchives().add(archiveData(data, path));
+        }
+    }
+
+    private boolean exist(EmrPatientData data, String path) {
+        // 如果有文件,并且病历的修改时间,小于文件的创建时间的话就不动
+        if (FileUtil.exist(path)) {
+            if (data.getModifyDate() == null) {
+                logger.warn("病历【{}】存在且没有修改文件,不再生成。", data.getName());
+                return true;
+            }
+            int compare = DateUtil.compare(data.getModifyDate(), FileUtil.lastModifiedTime(path));
+
+            if (compare > 0) {
+                logger.warn("病历【{}】存在且没有修改文件,不再生成。", data.getName());
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private PatientArchive archiveData(EmrPatientData emr, String path) {
+        String info = JSONUtil.createObj()
+                .set("emrCategoryCode", emr.getEmrCategoryCode())
+                .toString();
+
+        String parent = emr.getParent();
+        PatientArchive build = PatientArchive.builder()
+                .id(IdUtil.simpleUUID())
+                .documentId(emr.getEmrDocumentId())
+                .patNo(this.info.getPatNo())
+                .times(this.info.getTimes())
+                .name(emr.getName())
+                .sort(currentIndex)
+                .parentId(parent)
+                .isDir(false)
+                .createId(this.info.getSubmitCode())
+                .path(path.replace(archiveData.getPath(), ""))
+                .type(ArchiveType.EMR.getValue())
+                .info(info)
+                .build();
+
+        String 父级文件夹名称 = 病历文件夹.get(parent);
+
+        // 如果一级文件没有命中的话就说明不知道放在什么文件夹中
+        // 长度为 32 的话就说明是病历中的一级文件
+        int 一级目录id长度 = 32;
+        if (parent.length() == 一级目录id长度) {
+            if (父级文件夹名称 == null) {
+                build.setParentId(未整理文件夹id);
+            } else {
+                build.setParentId(this.info.getFolderByName(父级文件夹名称).getId());
+            }
+        } else {
+            // 尝试转化 parent 节点,如果转化失败就放到未整理节点
+            build.setParentId(转化父节点(build.getParentId()));
+        }
+        return build;
+    }
+
+    String 转化父节点(String parent) {
+        PatientArchive data = info.getAllData()
+                .stream()
+                .filter(i -> i.getDocumentId().equals(parent))
+                .findFirst()
+                .orElse(null);
+
+        return Opt.ofNullable(data)
+                .map(PatientArchive::getId)
+                .orElse(未整理文件夹id);
+    }
+
+
+}

+ 141 - 0
thyy-archive/src/main/java/org/thyy/archive/service/archive/task/start/ArchiveJianCha.java

@@ -0,0 +1,141 @@
+package org.thyy.archive.service.archive.task.start;
+
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.io.IoUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.core.util.URLUtil;
+import org.thyy.archive.config.envionment.ArchiveConfig;
+import org.thyy.archive.dao.OtherDao;
+import org.thyy.archive.data.CheckTheCallbacks;
+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.archive.task.ArchiveComponent;
+import org.thyy.archive.service.archive.task.ArchiveLog;
+import org.thyy.archive.utils.PdfUtil;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+@ArchiveComponent(name = "检查", sort = 4)
+public class ArchiveJianCha {
+    private final TaskPatient info;
+    private final ArchiveLog logger;
+    private final OtherDao dao;
+    private final ArchiveConfig config;
+
+    private final Map<String, PatientArchive> documentIdMap;
+    private Map<String, PatientArchive> 目录对照id = null;
+
+    public ArchiveJianCha(TaskPatient info, ArchiveLog logger, OtherDao dao, ArchiveConfig config) {
+        this.info = info;
+        this.logger = logger;
+        documentIdMap = info.getDocumentIdMap(ArchiveType.JIAN_CHA);
+        this.dao = dao;
+        this.config = config;
+
+        init();
+    }
+
+    String getPath(String id) {
+        return info.getPath(ArchiveType.JIAN_CHA, id);
+    }
+
+    void init() {
+        List<CheckTheCallbacks> checkData = dao.getCheckData(info.getPatNo(), info.getTimes());
+        if (checkData == null || checkData.isEmpty()) {
+            logger.warn("未查询到检查结果回报");
+            return;
+        }
+        获取目录id();
+
+        for (CheckTheCallbacks item : checkData) {
+            try {
+                String path = getPath(item.getPatientUid());
+                foreach(item, path);
+                addData(item, path);
+            } catch (Exception e) {
+                logger.error("检查生成错误:{},名称:{},id:{}", e.getMessage(), item.getExaminEparts(), item.getReqNo());
+            }
+        }
+    }
+
+    void foreach(CheckTheCallbacks item, String path) {
+        if (StrUtil.isBlank(item.getPatientUid())) {
+            logger.warn("未查询到检查uid");
+            return;
+        }
+
+        if (StrUtil.isBlank(item.getReportUrl())) {
+            logger.warn("未查询到检查报告");
+            return;
+        }
+
+        if (FileUtil.exist(path)) {
+            logger.warn("项目【{}】文件存在,不在生成", item.getExaminEparts());
+            return;
+        }
+        URL url = URLUtil.url(item.getReportUrl());
+        byte[] imageBytes = readInput(url);
+        logger.info("检查名称:【{}】", item.getExaminEparts());
+        FileUtil.writeBytes(imageBytes, path);
+    }
+
+    byte[] readInput(URL url) {
+        InputStream inputStream = null;
+        try {
+            URLConnection connection = url.openConnection();
+            inputStream = connection.getInputStream();
+            byte[] imageBytes = PdfUtil.readInputStream(inputStream);
+            return PdfUtil.imageToPdf(imageBytes);
+        } catch (IOException e) {
+            throw new RuntimeException("加载图片地址错误");
+        } catch (Exception e) {
+            throw new RuntimeException("读取图片失败");
+        } finally {
+            IoUtil.close(inputStream);
+        }
+    }
+
+
+    void 获取目录id() {
+        String dirId = info.getFolderByName("各种特殊检查").getId();
+        目录对照id = info.getOldArchives()
+                .stream()
+                .filter(i -> i.getIsDir() && dirId.equals(i.getParentId()))
+                .collect(Collectors.toMap(PatientArchive::getName, i -> i, (k1, k2) -> k1));
+    }
+
+    void addData(CheckTheCallbacks item, String path) {
+        if (documentIdMap.get(item.getPatientUid()) != null) {
+            return;
+        }
+        PatientArchive builder = info.defaultData()
+                .documentId(item.getPatientUid())
+                .name(item.getExaminEparts())
+                .type(ArchiveType.JIAN_CHA.getValue())
+                .path(path.replace(config.getPath(), ""))
+                .parentId(parentId(item.getOrderType()))
+                .build();
+        info.getArchives().add(builder);
+    }
+
+    String parentId(String orderType) {
+        String name = ArchiveCreateFolder.检查分类.get(orderType);
+        if (null == name) {
+            return null;
+        }
+        PatientArchive patientArchive = 目录对照id.get(name);
+        if (patientArchive == null) {
+            return null;
+        }
+        return patientArchive.getId();
+    }
+
+
+}

+ 167 - 0
thyy-archive/src/main/java/org/thyy/archive/service/archive/task/start/ArchiveJianYan.java

@@ -0,0 +1,167 @@
+package org.thyy.archive.service.archive.task.start;
+
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.util.IdUtil;
+import cn.hutool.json.JSONArray;
+import cn.hutool.json.JSONObject;
+import org.thyy.archive.config.envionment.ArchiveConfig;
+import org.thyy.archive.data.archive.PatientArchive;
+import org.thyy.archive.data.archive.TaskPatient;
+import org.thyy.archive.data.jianyan.QueryNormal;
+import org.thyy.archive.enumtype.ArchiveType;
+import org.thyy.archive.service.JianYanApi;
+import org.thyy.archive.service.archive.task.ArchiveComponent;
+import org.thyy.archive.service.archive.task.ArchiveLog;
+import org.thyy.archive.utils.PdfUtil;
+
+import java.util.*;
+
+@ArchiveComponent(name = "检验", sort = 3)
+public class ArchiveJianYan {
+    private final ArchiveConfig archiveData;
+    private final TaskPatient info;
+    private final ArchiveLog logger;
+
+    public static final String DATE = "yyyy-MM-dd";
+
+    private String 常规单id;
+    private String 生化单id;
+    private final Map<String, PatientArchive> oldMapData;
+    private int currentIndex = 0;
+    private String currentKey;
+    private String currentPath;
+
+    private static final String[] 三大常规 = new String[]{"尿液分析", "血细胞分析", "大便常规"};
+
+
+    public ArchiveJianYan(ArchiveConfig archiveData, TaskPatient info, ArchiveLog logger) {
+        this.archiveData = archiveData;
+        this.info = info;
+        this.logger = logger;
+
+        oldMapData = info.getDocumentIdMap(ArchiveType.JIAN_YAN);
+        init();
+    }
+
+
+    void checkDir() {
+        常规单id = info.getFolderByName("三大常规报告单").getId();
+        生化单id = info.getFolderByName("血液生化报告粘贴单").getId();
+    }
+
+    public String getPath(String id) {
+        return info.getPath(ArchiveType.JIAN_YAN, id);
+    }
+
+    void init() {
+        checkDir();
+        String start = DateUtil.format(info.getPatient().getAdmissDate(), DATE);
+        String end = DateUtil.format(Optional.ofNullable(info.getPatient().getDisDate())
+                .orElseGet(Date::new), DATE);
+
+        QueryNormal params = QueryNormal.builder()
+                .patientNum(info.getPatient().getInpatientNo())
+                .startDate(start)
+                .endDate(end)
+                .build();
+        JSONArray normal = JianYanApi.getNormal(params);
+        if (normal == null) {
+            logger.error("没有查询到检验信息");
+            return;
+        }
+        create(normal);
+    }
+
+    void create(JSONArray data) {
+        for (int i = 0; i < data.size(); i++) {
+            JSONObject item = data.getJSONObject(i);
+            currentIndex = i;
+            currentKey = item.getStr("reportId");
+            currentPath = getPath(currentKey);
+            try {
+                foreach(item);
+                checkFile(item);
+            } catch (Exception e) {
+                logger.error("生成检验错误:{},检验id:{}", e.getMessage(), currentKey);
+            }
+        }
+    }
+
+    void foreach(JSONObject item) {
+        if (FileUtil.exist(currentPath)) {
+            logger.warn("检验:{},存在不再生成", item.getStr("examPurpose"));
+            return;
+        }
+        String key = item.getStr("reportId");
+        List<String> images = getImages(key);
+        try {
+            byte[] bytes = PdfUtil.base64ToPdf(images);
+            FileUtil.writeBytes(bytes, currentPath);
+            logger.info("生成检验成功:【{}】", item.getStr("examPurpose"));
+        } catch (Exception e) {
+            throw new RuntimeException("图片转pdf错误" + e.getMessage());
+        }
+    }
+
+
+    List<String> getImages(String key) {
+        JSONObject image = JianYanApi.getImage(key);
+        if (image == null) {
+            throw new RuntimeException("获取检查图片错误");
+        }
+        ArrayList<String> result = new ArrayList<>();
+        JSONArray jsonArray;
+        try {
+            jsonArray = image.getJSONArray("items");
+        } catch (Exception e) {
+            throw new RuntimeException("调用检验接口失败" + image.getStr("items"));
+        }
+        for (int i = 0; i < jsonArray.size(); i++) {
+            JSONObject item = jsonArray.getJSONObject(i);
+            result.add(item.getStr("imgData"));
+        }
+        return result;
+    }
+
+    void checkFile(JSONObject item) {
+        PatientArchive old = oldMapData.get(currentKey);
+        if (old != null) {
+            return;
+        }
+
+        String name = item.getStr("examPurpose");
+        String parentId = getId(name);
+
+        PatientArchive build = info.defaultData()
+                .id(IdUtil.simpleUUID())
+                .documentId(currentKey)
+                .name(truncateString(name))
+                .sort(currentIndex)
+                .type(ArchiveType.JIAN_YAN.getValue())
+                .isDir(false)
+                .path(currentPath.replace(archiveData.getPath(), ""))
+                .parentId(parentId)
+                .build();
+
+        info.getArchives().add(build);
+    }
+
+    public static String truncateString(String input) {
+        if (input == null || input.length() <= 400) {
+            return input;
+        } else {
+            return input.substring(0, 400);
+        }
+    }
+
+    public String getId(String name) {
+        for (String s : 三大常规) {
+            if (name.contains(s)) {
+                return 常规单id;
+            }
+        }
+        return 生化单id;
+    }
+
+}

+ 135 - 0
thyy-archive/src/main/java/org/thyy/archive/service/archive/task/start/Archivehuizhen.java

@@ -0,0 +1,135 @@
+package org.thyy.archive.service.archive.task.start;
+
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.util.IdUtil;
+import cn.hutool.json.JSONConfig;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import org.thyy.archive.config.envionment.ArchiveConfig;
+import org.thyy.archive.dao.OtherDao;
+import org.thyy.archive.data.archive.PatientArchive;
+import org.thyy.archive.data.archive.TaskPatient;
+import org.thyy.archive.data.huizhen.JieShouHuiZhenPojo;
+import org.thyy.archive.data.huizhen.YshHzRecord;
+import org.thyy.archive.enumtype.ArchiveType;
+import org.thyy.archive.service.archive.task.ArchiveComponent;
+import org.thyy.archive.service.archive.task.ArchiveLog;
+import org.thyy.archive.service.emr.EmrService;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+@ArchiveComponent(name = "会诊", sort = 3)
+public class Archivehuizhen {
+    private final TaskPatient info;
+    private final ArchiveLog logger;
+    private final ArchiveConfig config;
+    private final OtherDao dao;
+
+    private final Map<String, PatientArchive> documentIdMap;
+    private String patientId;
+    public static JSONConfig jsonConfig = JSONConfig.create().setDateFormat("yyyy-MM-dd HH:mm");
+
+    public Archivehuizhen(TaskPatient info, ArchiveLog logger, ArchiveConfig config, OtherDao dao) {
+        this.info = info;
+        this.logger = logger;
+        this.config = config;
+        this.dao = dao;
+        documentIdMap = info.getDocumentIdMap();
+        init();
+    }
+
+    void init() {
+        List<YshHzRecord> list = dao.chaKanHuiZhenShenQing(info.getPatNo(), info.getTimes());
+        if (list == null || list.isEmpty()) {
+            logger.warn("患者没有会诊申请");
+            return;
+        }
+        patientId = info.getFolderByName("会诊单").getId();
+
+        for (YshHzRecord item : list) {
+            try {
+                createData(item);
+            } catch (Exception e) {
+                logger.error("第【{}】次会诊生成错误,{}", item.getReqTimes(), e.getMessage());
+            }
+        }
+    }
+
+    public String dirPath(Integer times) {
+        String id = info.getPatNo() + "_" + info.getTimes() + "_" + times;
+        return info.getPath(ArchiveType.HUI_ZHEN, id);
+    }
+
+    void createData(YshHzRecord item) {
+        if (!"2".equals(item.getStatusFlag())) {
+            logger.warn("申请时间:{},第【{}】次会诊未完成,无法生成。",
+                    DateUtil.format(item.getReqDate(), "yyyy-MM-dd HH:mm"), item.getReqTimes());
+            return;
+        }
+        JieShouHuiZhenPojo data = dao.getHuanZheXinXi(info.getPatNo(), info.getTimes(), item.getReqTimes());
+        if (data == null)
+            return;
+        String path = dirPath(item.getReqTimes());
+
+        if (FileUtil.exist(path)) {
+            logger.warn("第【{}】次会诊存在,不再生成。", item.getReqTimes());
+            return;
+        }
+
+        createPdf(data, path);
+        logger.info("正在生成会诊:{}", item.getReqTimes());
+
+        assemblyData(data, path);
+    }
+
+    public static void createPdf(JieShouHuiZhenPojo data, String path) {
+        if (data == null)
+            return;
+        JSONObject json = JSONUtil.parseObj(data);
+        json.set("reqDateName", DateUtil.format(data.getReqDate(), "yyyy-MM-dd HH:mm"));
+        json.set("hzDateName", DateUtil.format(data.getHzDate(), "yyyy-MM-dd HH:mm"));
+        json.set("sexName", data.getSex() == 1 ? "男" : "女");
+        String birth = data.getBirth();
+        Date birthDate = DateUtil.parse(birth, "yyyy-MM-dd");
+        json.set("age", DateUtil.age(birthDate, data.getReqDate()));
+        byte[] file = EmrService.getPdfByTemplate("27a3c3a0806911efbee4c9cb16f3aab5", "huizhenjiludan", json);
+        if (file == null) {
+            throw new RuntimeException("接口调用失败。");
+        }
+
+        FileUtil.writeBytes(file, path);
+    }
+
+    /**
+     * 添加json文件
+     *
+     * @param item 文件
+     * @param path 文件存放路径
+     */
+    void assemblyData(JieShouHuiZhenPojo item, String path) {
+        String tmp = path.replace(config.getPath(), "");
+        PatientArchive build = PatientArchive.builder()
+                .id(IdUtil.simpleUUID())
+                .documentId(Convert.toStr(item.getReqTimes()))
+                .patNo(info.getPatNo())
+                .times(info.getTimes())
+                .name("请" + item.getReqDept1Name() + "会诊" + " / " + DateUtil.formatDate(item.getReqDate()))
+                .sort(item.getReqTimes())
+                .createId(info.getSubmitCode())
+                .path(tmp)
+                .isDir(false)
+                .parentId(patientId)
+                .type(ArchiveType.HUI_ZHEN.getValue())
+                .build();
+
+        if (documentIdMap.get(item.getReqTimes().toString()) == null) {
+            info.getArchives().add(build);
+        }
+    }
+
+
+}

+ 79 - 0
thyy-archive/src/main/java/org/thyy/archive/service/emr/EmrService.java

@@ -0,0 +1,79 @@
+package org.thyy.archive.service.emr;
+
+import cn.hutool.core.codec.Base64;
+import cn.hutool.core.convert.Convert;
+import cn.hutool.extra.spring.SpringUtil;
+import cn.hutool.http.HttpRequest;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import jakarta.annotation.PostConstruct;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Service;
+import org.thyy.archive.config.envionment.ArchiveConfig;
+
+@Service
+public class EmrService {
+    private static String emrToken = null;
+    private static final ArchiveConfig config = SpringUtil.getBean(ArchiveConfig.class);
+
+    @PostConstruct
+    @Scheduled(cron = "* * */12 * * ?")
+    public void token() {
+        String result = HttpRequest.post(config.getEmrUrl() + "/emr/runtime/api/v1/oauth/token")
+                .header("Authorization", "Basic " + Base64.encode("user:dc71ccfec05b799ad52360c48d504019"))
+                .form("grant_type", "client_credentials")
+                .execute()
+                .body();
+        JSONObject data = JSONUtil.toBean(result, JSONObject.class);
+        emrToken = data.getStr("access_token");
+    }
+
+    private static byte[] createEmr(JSONObject param) {
+        String url = config.getEmrUrl() + "/emr/archive/pdf";
+        String text = HttpRequest.post(url)
+                .body(param.toJSONString(0))
+                .header("emr-token", emrToken)
+                .execute()
+                .body();
+        int index = text.lastIndexOf("<br/>buffer:,");
+        if (index != -1) {
+            String[] split = text.substring(text.lastIndexOf("<br/>buffer:,") + 13)
+                    .split(",");
+            return Convert.toPrimitiveByteArray(split);
+        }
+        throw new RuntimeException(text);
+    }
+
+    public static byte[] getPdfByte(String documentId) {
+        JSONObject param = new JSONObject();
+        param.set("emrToken", true);
+        param.set("stream", true);
+        param.set("arcive", false);
+        param.set("patientId", false);
+        param.set("params", new JSONObject() {{
+            set("documentId", documentId);
+        }});
+        param.set("fileName", "病历pdf");
+        param.set("type", "document");
+        return createEmr(param);
+    }
+
+    public static byte[] getPdfByTemplate(String id, String categoryCode, Object data) {
+        JSONObject param = new JSONObject();
+        param.set("emrToken", true);
+        param.set("stream", true);
+        param.set("arcive", false);
+        param.set("patientId", false);
+        param.set("params", new JSONObject() {{
+            set("categoryId", id);
+            set("categoryCode", categoryCode);
+            set("appContext", new JSONObject() {{
+                set("data", data);
+            }});
+        }});
+        param.set("fileName", "病历pdf");
+        param.set("type", "template");
+        return createEmr(param);
+    }
+
+}

+ 25 - 0
thyy-archive/src/main/java/org/thyy/archive/service/socket/SocketApi.java

@@ -0,0 +1,25 @@
+package org.thyy.archive.service.socket;
+
+import cn.hutool.extra.spring.SpringUtil;
+import cn.hutool.http.HttpRequest;
+import cn.hutool.json.JSONObject;
+import org.springframework.stereotype.Service;
+import org.thyy.archive.config.envionment.ArchiveConfig;
+
+@Service
+public class SocketApi {
+
+    public static String URL = SpringUtil.getBean(ArchiveConfig.class)
+            .getSocketUrl();
+
+    public static void sendArchiveRoomMsg(JSONObject js) {
+        try {
+            HttpRequest.post(URL + "/sendArchiveRoomMsg")
+                    .header("Content-Type", "application/json")
+                    .body(js.toJSONString(0))
+                    .setConnectionTimeout(3000)
+                    .execute();
+        } catch (Exception ignore) {
+        }
+    }
+}

+ 74 - 0
thyy-archive/src/main/java/org/thyy/archive/utils/PdfUtil.java

@@ -0,0 +1,74 @@
+package org.thyy.archive.utils;
+
+import cn.hutool.core.codec.Base64;
+import com.itextpdf.text.Document;
+import com.itextpdf.text.DocumentException;
+import com.itextpdf.text.Image;
+import com.itextpdf.text.pdf.PdfWriter;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+public class PdfUtil {
+
+    public static byte[] readInputStream(InputStream inputStream) throws Exception {
+        ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream();
+        int bufferSize = 1024;
+        byte[] buffer = new byte[bufferSize];
+
+        int len;
+        while ((len = inputStream.read(buffer)) != -1) {
+            byteBuffer.write(buffer, 0, len);
+        }
+        return byteBuffer.toByteArray();
+    }
+
+    public static byte[] imageToPdf(List<byte[]> images) throws DocumentException, IOException {
+        // 创建 A4 尺寸 PDF 文档
+        Document document = new Document(com.itextpdf.text.PageSize.A4, 0, 0, 0, 0);
+        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+        PdfWriter.getInstance(document, byteArrayOutputStream);
+        document.open();
+        float a4Width = document.getPageSize().getWidth();
+        float a4Height = document.getPageSize().getHeight();
+        // 将图像添加到 PDF
+        for (byte[] item : images) {
+            // 创建图像对象
+            Image img = Image.getInstance(item);
+            // 计算图像的宽高比例
+            float imgWidth = img.getScaledWidth();
+            float imgHeight = img.getScaledHeight();
+            // 设置缩放系数以控制宽度
+            float scaleFactor = Math.min(a4Width / imgWidth, a4Height / imgHeight);
+            img.scaleAbsolute(imgWidth * scaleFactor, imgHeight * scaleFactor);
+            document.add(img);
+            document.newPage();
+        }
+        // 关闭文档
+        document.close();
+        // 返回 PDF 的字节数组
+        return byteArrayOutputStream.toByteArray();
+    }
+
+    public static byte[] imageToPdf(byte[] images) throws DocumentException, IOException {
+        List<byte[]> imagesList = new ArrayList<>();
+        imagesList.add(images);
+        return imageToPdf(imagesList);
+    }
+
+
+    public static byte[] base64ToPdf(List<String> images) throws DocumentException, IOException {
+        List<byte[]> imagesList = new ArrayList<>(images.size());
+
+        for (String item : images) {
+            imagesList.add(Base64.decode(item));
+        }
+
+        return imageToPdf(imagesList);
+    }
+
+
+}

+ 45 - 0
thyy-archive/src/main/java/org/thyy/archive/utils/TokenUtil.java

@@ -0,0 +1,45 @@
+package org.thyy.archive.utils;
+
+import jakarta.servlet.http.HttpServletRequest;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+import org.thyy.archive.config.InterceptorConfig;
+
+import java.util.Objects;
+
+public class TokenUtil {
+    private static final String PRIVATE_KEY = "w2XS014bk6Ma7tYh";
+
+    private TokenUtil() {
+    }
+
+    private static volatile TokenUtil INSTANCE = null;
+
+    public static TokenUtil getInstance() {
+        if (null == INSTANCE) {
+            synchronized (TokenUtil.class) {
+                if (null == INSTANCE) {
+                    INSTANCE = new TokenUtil();
+                }
+            }
+        }
+        return INSTANCE;
+    }
+
+    private HttpServletRequest getRequest() {
+        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder
+                .getRequestAttributes();
+        return requestAttributes == null ? null : requestAttributes.getRequest();
+    }
+
+    public String getTokenUserId() {
+        String token = Objects.requireNonNull(getRequest()).getHeader("Authorization");
+        try {
+            String check = InterceptorConfig.check(token);
+            return check.split("_")[0];
+        } catch (Exception ignore) {
+            return "99999";
+        }
+
+    }
+}

+ 25 - 0
thyy-archive/src/main/resources/application-prod.yml

@@ -0,0 +1,25 @@
+spring:
+  application:
+    name: archive-server
+  datasource:
+    url: "jdbc:sqlserver://172.16.32.168:1433;databaseName=thxyhisdb"
+    username: "sa"
+    password:
+    driver-class-name: "com.microsoft.sqlserver.jdbc.SQLServerDriver"
+  jackson:
+    time-zone: Asia/Shanghai
+    date-format: yyyy-MM-dd HH:mm:ss
+  mvc:
+    format:
+      date: yyyy-MM-dd
+      date-time: yyyy-MM-dd HH:mm:ss
+server:
+  port: 20921
+  servlet:
+    context-path: "/thyy/api/archive"
+
+thyy:
+  archive:
+    path: "/mnt/archive"
+    prod: true
+    socket-url: "http://172.16.32.160:20922"

+ 25 - 0
thyy-archive/src/main/resources/application.yml

@@ -0,0 +1,25 @@
+spring:
+  application:
+    name: archive-server
+  datasource:
+    url: "jdbc:sqlserver://172.16.32.179:1433;databaseName=thxyhisdb"
+    username: "sa"
+    password:
+    driver-class-name: "com.microsoft.sqlserver.jdbc.SQLServerDriver"
+  jackson:
+    time-zone: Asia/Shanghai
+    date-format: yyyy-MM-dd HH:mm:ss
+  mvc:
+    format:
+      date: yyyy-MM-dd
+      date-time: yyyy-MM-dd HH:mm:ss
+server:
+  port: 20921
+  servlet:
+    context-path: /thyy/api/archive
+
+thyy:
+  archive:
+    path: "Y:\\"
+    prod: false
+    socket-url: "http://172.16.30.66:20922"

+ 123 - 0
thyy-archive/src/main/resources/logback-spring.xml

@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration>
+
+    <springProperty scope="context" name="LOG_HOME" source="logging.path"
+                    defaultValue="serverlog"/>
+
+    <!-- 日志级别 -->
+    <springProperty scope="context" name="LOG_ROOT_LEVEL" source="logging.level.root" defaultValue="INFO"/>
+
+    <!--  标识这个"STDOUT" 将会添加到这个logger -->
+    <springProperty scope="context" name="STDOUT" source="log.stdout" defaultValue="STDOUT"/>
+
+    <!-- 日志文件名称-->
+    <property name="LOG_PREFIX" value="spring-boot-logback"/>
+
+    <!-- 日志文件编码-->
+    <property name="LOG_CHARSET" value="UTF-8"/>
+
+    <!-- 日志文件路径+日期-->
+    <property name="LOG_DIR" value="${LOG_HOME}/%d{yyyy-MM-dd}"/>
+
+    <!--对日志进行格式化-->
+    <property name="LOG_MSG" value="- [%d{yyyy-MM-dd HH:mm:ss.SSS}] [%level] [%thread] [%logger{16}] --> %msg%n "/>
+
+    <!--文件大小,默认10MB-->
+    <property name="MAX_FILE_SIZE" value="30MB"/>
+
+    <!-- 配置日志的滚动时间 ,value表示保留日志的天数,0表示永久保存 -->
+    <property name="MAX_HISTORY" value="0"/>
+
+
+    <!-- 彩色日志 -->
+    <!-- 彩色日志依赖的渲染类 -->
+    <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
+    <conversionRule conversionWord="wex"
+                    converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
+    <conversionRule conversionWord="wEx"
+                    converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
+    <!-- 彩色日志格式 -->
+    <property name="CONSOLE_LOG_PATTERN"
+              value="-[%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){blue}] [%clr(%level)] [%clr(%thread)] [%clr(%logger{16}){cyan}] %clr(-->){red} %clr(%msg%n){yellow}"/>
+
+    <!--输出到控制台-->
+    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+        <!-- 输出的日志内容格式化-->
+        <layout class="ch.qos.logback.classic.PatternLayout">
+            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
+        </layout>
+    </appender>
+
+    <!--输出到文件-->
+    <appender name="0" class="ch.qos.logback.core.rolling.RollingFileAppender">
+    </appender>
+
+    <!-- 定义 ALL 日志的输出方式:-->
+    <appender name="FILE_ALL" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <!--日志文件路径,日志文件名称-->
+        <File>${LOG_HOME}/all_${LOG_PREFIX}.log</File>
+
+        <!-- 设置滚动策略,当天的日志大小超过 ${MAX_FILE_SIZE} 文件大小时候,新的内容写入新的文件, 默认10MB -->
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+
+            <!--日志文件路径,新的 ALL 日志文件名称,“ i ” 是个变量 -->
+            <FileNamePattern>${LOG_DIR}/all_${LOG_PREFIX}.log</FileNamePattern>
+
+            <!-- 配置日志的滚动时间 ,表示只保留最近 10 天的日志-->
+            <MaxHistory>${MAX_HISTORY}</MaxHistory>
+            <maxFileSize>${MAX_FILE_SIZE}</maxFileSize>
+        </rollingPolicy>
+
+        <!-- 输出的日志内容格式化-->
+        <layout class="ch.qos.logback.classic.PatternLayout">
+            <pattern>${LOG_MSG}</pattern>
+        </layout>
+    </appender>
+
+    <!-- 定义 ERROR 日志的输出方式:-->
+    <appender name="FILE_ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <!-- 下面为配置只输出error级别的日志 -->
+        <filter class="ch.qos.logback.classic.filter.LevelFilter">
+            <level>ERROR</level>
+            <OnMismatch>DENY</OnMismatch>
+            <OnMatch>ACCEPT</OnMatch>
+        </filter>
+        <!--日志文件路径,日志文件名称-->
+        <File>${LOG_HOME}/err_${LOG_PREFIX}.log</File>
+
+        <!-- 设置滚动策略,当天的日志大小超过 ${MAX_FILE_SIZE} 文件大小时候,新的内容写入新的文件, 默认10MB -->
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+
+            <!--日志文件路径,新的 ERR 日志文件名称,“ i ” 是个变量 -->
+            <FileNamePattern>${LOG_DIR}/err_${LOG_PREFIX}.log</FileNamePattern>
+
+            <!-- 配置日志的滚动时间 ,表示只保留最近 10 天的日志-->
+            <MaxHistory>${MAX_HISTORY}</MaxHistory>
+            <maxFileSize>${MAX_FILE_SIZE}</maxFileSize>
+        </rollingPolicy>
+
+        <!-- 输出的日志内容格式化-->
+        <layout class="ch.qos.logback.classic.PatternLayout">
+            <Pattern>${LOG_MSG}</Pattern>
+        </layout>
+    </appender>
+
+    <!-- additivity 设为false,则logger内容不附加至root ,配置以配置包下的所有类的日志的打印,级别是 ERROR-->
+
+    <logger name="org.springframework" level="ERROR"/>
+    <logger name="org.apache.commons" level="ERROR"/>
+    <!--    <logger name="th.itcenter.apps.controller.logger.LoggingController" level="WARN"/>-->
+    <!-- ${LOG_ROOT_LEVEL} 日志级别 -->
+    <root level="${LOG_ROOT_LEVEL}">
+
+        <!-- 标识这个"${STDOUT}"将会添加到这个logger -->
+        <appender-ref ref="${STDOUT}"/>
+
+        <!-- FILE_ALL 日志输出添加到 logger -->
+        <appender-ref ref="FILE_ALL"/>
+
+        <!-- FILE_ERROR 日志输出添加到 logger -->
+        <appender-ref ref="FILE_ERROR"/>
+    </root>
+
+</configuration>

+ 33 - 0
thyy-socket/.gitignore

@@ -0,0 +1,33 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/

+ 39 - 0
thyy-socket/pom.xml

@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.thyy.business</groupId>
+        <artifactId>thyy-business</artifactId>
+        <version>0.0.1</version>
+    </parent>
+    <artifactId>thyy-socket</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+    <name>thyy-socket</name>
+    <description>thyy-socket</description>
+
+    <properties>
+        <java.version>21</java.version>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-websocket</artifactId>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

+ 13 - 0
thyy-socket/src/main/java/org/thyy/socket/ThyySocketApplication.java

@@ -0,0 +1,13 @@
+package org.thyy.socket;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class ThyySocketApplication {
+
+    public static void main(String[] args) {
+        SpringApplication.run(ThyySocketApplication.class, args);
+    }
+
+}

+ 26 - 0
thyy-socket/src/main/java/org/thyy/socket/config/CloseCodes.java

@@ -0,0 +1,26 @@
+package org.thyy.socket.config;
+
+import lombok.Getter;
+import org.springframework.web.socket.CloseStatus;
+
+/**
+ * 3000 ~ 4000 之间
+ */
+@Getter
+public enum CloseCodes {
+
+    EDITING_IN_PROGRESS(3001, "还有人在编辑无法连接"),
+
+    KICKING_PEOPLE(3002, "已有医生在踢人,无法操作。"),
+
+    NO_BUSINESS(3003, "无效的连接"),
+
+    FORCED_KICK_OUT(3004, "其他用户强制踢出");
+
+    private final CloseStatus status;
+
+    CloseCodes(int code, String reason) {
+        this.status = new CloseStatus(code, reason);
+    }
+
+}

+ 30 - 0
thyy-socket/src/main/java/org/thyy/socket/config/CorsConfig.java

@@ -0,0 +1,30 @@
+package org.thyy.socket.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
+import org.springframework.web.filter.CorsFilter;
+
+/**
+ * @author dj
+ */
+@Configuration
+public class CorsConfig {
+    private CorsConfiguration buildConfig() {
+        CorsConfiguration corsConfiguration = new CorsConfiguration();
+        corsConfiguration.addAllowedOriginPattern("*");
+        corsConfiguration.addAllowedHeader("*");
+        corsConfiguration.addAllowedMethod("*");
+        corsConfiguration.setAllowCredentials(true);
+        return corsConfiguration;
+    }
+
+    @Bean
+    public CorsFilter corsFilter() {
+        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
+        source.registerCorsConfiguration("/**", buildConfig());
+        return new CorsFilter(source);
+    }
+
+}

+ 67 - 0
thyy-socket/src/main/java/org/thyy/socket/config/SocketService.java

@@ -0,0 +1,67 @@
+package org.thyy.socket.config;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import org.springframework.web.socket.CloseStatus;
+import org.springframework.web.socket.WebSocketHandler;
+import org.springframework.web.socket.WebSocketMessage;
+import org.springframework.web.socket.WebSocketSession;
+import org.thyy.socket.service.Business;
+
+import java.util.Map;
+
+@Slf4j
+@Component
+public class SocketService implements WebSocketHandler {
+
+    private final Map<String, Business> businessMap;
+
+    public SocketService(Map<String, Business> businessMap) {
+        this.businessMap = businessMap;
+    }
+
+    @Override
+    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
+        String businessStr = (String) session.getAttributes().get("business");
+        String sid = (String) session.getAttributes().get("sid");
+        Business business = businessMap.get(businessStr);
+        if (business == null) {
+            Business.closeReason(session, CloseCodes.NO_BUSINESS);
+            return;
+        }
+        business.onOpen(session, sid);
+    }
+
+
+    @Override
+    public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
+        String businessStr = (String) session.getAttributes().get("business");
+        String sid = (String) session.getAttributes().get("sid");
+        Business business = businessMap.get(businessStr);
+        if (business == null) {
+            return;
+        }
+        business.onClose(session, sid);
+    }
+
+    @Override
+    public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
+        String businessStr = (String) session.getAttributes().get("business");
+        String sid = (String) session.getAttributes().get("sid");
+        Business business = businessMap.get(businessStr);
+        if (business == null) {
+            return;
+        }
+        business.onMessage(session, sid, (String) message.getPayload());
+    }
+
+    @Override
+    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
+
+    }
+
+    @Override
+    public boolean supportsPartialMessages() {
+        return true;
+    }
+}

+ 65 - 0
thyy-socket/src/main/java/org/thyy/socket/config/WebSocketConfig.java

@@ -0,0 +1,65 @@
+package org.thyy.socket.config;
+
+import cn.hutool.core.util.StrUtil;
+import io.micrometer.common.util.StringUtils;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.server.ServerHttpRequest;
+import org.springframework.http.server.ServerHttpResponse;
+import org.springframework.http.server.ServletServerHttpRequest;
+import org.springframework.http.server.ServletServerHttpResponse;
+import org.springframework.web.socket.WebSocketHandler;
+import org.springframework.web.socket.config.annotation.EnableWebSocket;
+import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
+import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
+import org.springframework.web.socket.server.HandshakeInterceptor;
+
+import java.util.List;
+import java.util.Map;
+
+@Slf4j
+@Configuration
+@EnableWebSocket
+public class WebSocketConfig implements WebSocketConfigurer {
+    private final SocketService socketService;
+
+    public WebSocketConfig(SocketService socketService) {
+        this.socketService = socketService;
+    }
+
+
+    @Override
+    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
+        registry.addHandler(socketService, "/websocket/{business}/{sid}")
+                .setAllowedOrigins("*")
+                .addInterceptors(new MyWebSocketInterceptor());
+    }
+
+    @Slf4j
+    static class MyWebSocketInterceptor implements HandshakeInterceptor {
+
+        @Override
+        public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
+            if (!(request instanceof ServletServerHttpRequest req)) return true;
+            HttpServletRequest httpRequest = req.getServletRequest();
+            String servletPath = httpRequest.getServletPath();
+            List<String> split = StrUtil.split(servletPath, "/", true, true);
+            attributes.put("business", split.get(1));
+            attributes.put("sid", split.get(2));
+            return true;
+        }
+
+
+        @Override
+        public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
+            HttpServletRequest httpRequest = ((ServletServerHttpRequest) request).getServletRequest();
+            HttpServletResponse httpResponse = ((ServletServerHttpResponse) response).getServletResponse();
+            String header = httpRequest.getHeader("Sec-WebSocket-Protocol");
+            if (StringUtils.isNotEmpty(header)) {
+                httpResponse.addHeader("Sec-WebSocket-Protocol", header);
+            }
+        }
+    }
+}

+ 45 - 0
thyy-socket/src/main/java/org/thyy/socket/controller/EmrController.java

@@ -0,0 +1,45 @@
+package org.thyy.socket.controller;
+
+import com.alibaba.fastjson2.JSONObject;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.socket.WebSocketSession;
+import org.thyy.socket.service.Business;
+import org.thyy.socket.service.emr.EmrMapCenter;
+import org.thyy.socket.config.CloseCodes;
+import org.thyy.utils.result.R;
+import org.thyy.utils.result.ResultVo;
+
+
+@RestController
+@RequestMapping("/emr")
+public class EmrController {
+
+    private final EmrMapCenter mapCenter;
+
+    public EmrController(EmrMapCenter mapCenter) {
+        this.mapCenter = mapCenter;
+    }
+
+
+    @GetMapping("/document/{id}")
+    public ResultVo<String> getCurrentDocument(@PathVariable("id") String id) {
+        WebSocketSession session = mapCenter.getDocument().get(id);
+        if (session == null) {
+            return R.ok();
+        }
+        JSONObject userInfo = (JSONObject) session.getAttributes().get("userInfo");
+        return R.ok(userInfo.getString("code"));
+    }
+
+
+    @PostMapping("/forcedKickOut/{id}")
+    public ResultVo<String> forcedKickOut(@PathVariable("id") String id) {
+        WebSocketSession session = mapCenter.getDocument().get(id);
+        if (session == null) {
+            return R.ok();
+        }
+        Business.closeReason(session, CloseCodes.FORCED_KICK_OUT);
+        return R.ok();
+    }
+
+}

+ 43 - 0
thyy-socket/src/main/java/org/thyy/socket/controller/PublicController.java

@@ -0,0 +1,43 @@
+package org.thyy.socket.controller;
+
+import cn.hutool.crypto.asymmetric.KeyType;
+import cn.hutool.crypto.asymmetric.RSA;
+import com.alibaba.fastjson2.JSONObject;
+import lombok.extern.slf4j.Slf4j;
+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 org.thyy.socket.service.Archive;
+import org.thyy.utils.result.R;
+import org.thyy.utils.result.ResultVo;
+
+@RestController
+@RequestMapping("/")
+@Slf4j
+public class PublicController {
+    private static final String publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCdaN/7SqU/2UcEkZcCbjbTSOa+zhfCYY3nAvGqNL8bpUWHS3dzvEKiTU1WgHnfcPLvfa5a5yGgWisswARbcOZIzJu10tq64GQ3LPlxgY+is62ddQOgUr6sInCvsI1IO7F0DGjy7ocQoxBd/0uXNV0bvhZwST6Vw1qwQc76ptFgkQIDAQAB";
+    private static final String privateKey = "MIICdAIBADANBgkqhkiG9w0BAQEFAASCAl4wggJaAgEAAoGBAJ1o3/tKpT/ZRwSRlwJuNtNI5r7OF8JhjecC8ao0vxulRYdLd3O8QqJNTVaAed9w8u99rlrnIaBaKyzABFtw5kjMm7XS2rrgZDcs+XGBj6KzrZ11A6BSvqwicK+wjUg7sXQMaPLuhxCjEF3/S5c1XRu+FnBJPpXDWrBBzvqm0WCRAgMBAAECf1oUKhkkHmzrajizGDfL2pQOo/2M8wvJx+rrRGCizrqDWcaS6UjR80/t7NQTsaqseFmVErHyksuwzgRN3jB161HXp1zbfWyItcjrJpr4HjxhmcZbwT0YPh2605Hj7UTVZ2ikSJDQCadQwGv1FBRenmr9oxWH6GF+Ntpw2wB4pFkCQQC9xizXCKwZ7G3l7UTrNi1OCN4T1iBfcRp34vJmeScD0HB/SYWPt6RPqLpqcD3Pb9AHQoEhR68beqqDGBs66KQVAkEA1FdcZ4urIRm9scD+Egb4dMhMHrkgGqcxmu1TML8Zxf5L/X8sjBAjMJ8khuCG7CIXGXZQuCR9G+nm5MsRhr49jQJBAKd0SDrSvzH8yxiZHjhCzQVPk86DInu1TLq++k/r1b5q3emjkYQ4cneMFjo3ooL5UeImD9Wy8FPaEL2uttBrWeUCQFDQBrzprPFmCInT914JBRn8+OtgbVV3eCuMUvLkZ/ywcV9CvUN5CMuCZbZnUTIhL0VjgAj06iBrfpJornznfikCQBKovOysSYEgWog7s+aiI0GEGDb/yljl7lBj6KiwH3GTSHXCjBfJiPc53QrYYWyOoNBCKCCII4ku5QKIkYNDD4E=";
+
+    public static RSA rsa = new RSA(privateKey, publicKey);
+
+    private final Archive archive;
+
+    public PublicController(Archive archive) {
+        this.archive = archive;
+    }
+
+    @PostMapping("/authentication")
+    public ResultVo<String> authentication(@RequestBody JSONObject params) {
+        params.put("creationTime", System.currentTimeMillis());
+        String key = rsa.encryptBase64(params.toJSONString(), KeyType.PublicKey);
+        return R.ok(key);
+    }
+
+    @PostMapping("/sendArchiveRoomMsg")
+    public ResultVo<String> archiveSendRoom(@RequestBody Archive.SendMsg data) {
+        archive.sendRoom(data);
+        return R.ok();
+    }
+
+}

+ 15 - 0
thyy-socket/src/main/java/org/thyy/socket/data/CurrentDocument.java

@@ -0,0 +1,15 @@
+package org.thyy.socket.data;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class CurrentDocument {
+    private String id;
+    private String name;
+}

+ 14 - 0
thyy-socket/src/main/java/org/thyy/socket/data/OutDocumentParams.java

@@ -0,0 +1,14 @@
+package org.thyy.socket.data;
+
+import lombok.Data;
+
+import java.util.Date;
+
+
+@Data
+public class OutDocumentParams {
+    private String id;
+    private UserInfo userInfo;
+    private String roomId;
+    private Date createTime;
+}

+ 11 - 0
thyy-socket/src/main/java/org/thyy/socket/data/UserInfo.java

@@ -0,0 +1,11 @@
+package org.thyy.socket.data;
+
+import lombok.Data;
+
+@Data
+public class UserInfo {
+    private String name;
+    private String code;
+    private String codeRs;
+    private String deptName;
+}

+ 75 - 0
thyy-socket/src/main/java/org/thyy/socket/service/Archive.java

@@ -0,0 +1,75 @@
+package org.thyy.socket.service;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.springframework.stereotype.Component;
+import org.springframework.web.socket.WebSocketSession;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+
+@Component
+public class Archive implements Business {
+    private static final ConcurrentHashMap<String, List<ArchiveSession>> room = new ConcurrentHashMap<>();
+
+    @Data
+    @Builder
+    @AllArgsConstructor
+    @NoArgsConstructor
+    static class ArchiveSession {
+        private WebSocketSession session;
+        private String code;
+
+        public static ArchiveSession analysisSid(WebSocketSession session, String sid) {
+            String[] params = sid.split("-");
+            return ArchiveSession.builder()
+                    .session(session)
+                    .code(params[2])
+                    .build();
+        }
+    }
+
+    @Data
+    public static class SendMsg {
+        private String roomCode;
+        private String msg;
+    }
+
+    @Override
+    public void onOpen(WebSocketSession session, String sid) {
+        String[] params = sid.split("-");
+        String roomCode = params[0] + "_" + params[1];
+        room.computeIfAbsent(roomCode, k -> new ArrayList<>()).add(ArchiveSession.analysisSid(session, sid));
+    }
+
+    @Override
+    public void onClose(WebSocketSession session, String sid) {
+        String[] params = sid.split("-");
+        String roomCode = params[0] + "_" + params[1];
+        if (!room.containsKey(roomCode)) {
+            return;
+        }
+        List<ArchiveSession> archiveSessions = room.get(roomCode);
+        for (int i = 0; i < archiveSessions.size(); i++) {
+            ArchiveSession item = archiveSessions.get(i);
+            if (session.getId().equals(item.getSession().getId())) {
+                archiveSessions.remove(i);
+                return;
+            }
+        }
+    }
+
+    public void sendRoom(SendMsg data) {
+        List<ArchiveSession> list = room.get(data.getRoomCode());
+        if (list == null) {
+            return;
+        }
+        for (ArchiveSession item : list) {
+            Business.send(item.getSession(), data.getMsg());
+        }
+    }
+
+}

+ 34 - 0
thyy-socket/src/main/java/org/thyy/socket/service/Business.java

@@ -0,0 +1,34 @@
+package org.thyy.socket.service;
+
+import org.springframework.web.socket.TextMessage;
+import org.springframework.web.socket.WebSocketSession;
+import org.thyy.socket.config.CloseCodes;
+
+import java.io.IOException;
+
+public interface Business {
+
+    void onOpen(WebSocketSession session, String sid);
+
+    void onClose(WebSocketSession session, String sid);
+
+    default void onMessage(WebSocketSession session, String sid, String data) {
+    }
+
+    static void send(WebSocketSession session, String data) {
+        if (session == null) return;
+        try {
+            session.sendMessage(new TextMessage(data));
+        } catch (Exception ignore) {
+        }
+    }
+
+    static void closeReason(WebSocketSession session, CloseCodes codes) {
+        if (session == null) return;
+        try {
+            session.close(codes.getStatus());
+        } catch (IOException ignored) {
+        }
+    }
+
+}

+ 51 - 0
thyy-socket/src/main/java/org/thyy/socket/service/emr/EmrDocument.java

@@ -0,0 +1,51 @@
+package org.thyy.socket.service.emr;
+
+import com.alibaba.fastjson2.JSONObject;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import org.springframework.web.socket.WebSocketSession;
+import org.thyy.socket.service.Business;
+import org.thyy.socket.config.CloseCodes;
+
+
+@Getter
+@Component
+@Slf4j
+public class EmrDocument implements Business {
+
+    private final EmrMapCenter mapCenter;
+
+    public EmrDocument(EmrMapCenter mapCenter) {
+        this.mapCenter = mapCenter;
+    }
+
+    @Override
+    public void onOpen(WebSocketSession session, String sid) {
+        WebSocketSession session1 = mapCenter.getDocument().get(sid);
+        if (session1 == null) {
+            mapCenter.getDocument().put(sid, session);
+        } else {
+            Business.closeReason(session, CloseCodes.EDITING_IN_PROGRESS);
+        }
+    }
+
+    @Override
+    public void onClose(WebSocketSession session, String sid) {
+        mapCenter.getDocument().remove(sid);
+    }
+
+    @Override
+    public void onMessage(WebSocketSession session, String sid, String data) {
+        JSONObject json = JSONObject.parseObject(data);
+        String string = json.getString("code");
+        if ("USER_INFO".equals(string)) {
+            session.getAttributes().put("userInfo", json.getObject("data", JSONObject.class));
+            return;
+        }
+        if (json.getBooleanValue("kicked", false)) {
+            WebSocketSession kicked = mapCenter.getKicked().get(sid);
+            Business.send(kicked, data);
+        }
+    }
+}

+ 146 - 0
thyy-socket/src/main/java/org/thyy/socket/service/emr/EmrEditor.java

@@ -0,0 +1,146 @@
+package org.thyy.socket.service.emr;
+
+import com.alibaba.fastjson2.JSONObject;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import org.springframework.web.socket.WebSocketSession;
+import org.thyy.socket.service.Business;
+import org.thyy.socket.data.CurrentDocument;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Consumer;
+
+@Slf4j
+@Component
+public class EmrEditor implements Business {
+
+    @Data
+    @Builder
+    @AllArgsConstructor
+    @NoArgsConstructor
+    static class FunctionParams {
+        private String sid;
+        private JSONObject message;
+        private WebSocketSession session;
+    }
+
+    Map<String, List<WebSocketSession>> ROOM = new ConcurrentHashMap<>();
+    Map<String, Map<String, CurrentDocument>> DOCUMENT = new ConcurrentHashMap<>();
+    Map<String, Consumer<FunctionParams>> function = new HashMap<>();
+
+    public EmrEditor() {
+        function.put("USER_INFO", (params) -> {
+            params.getSession().getAttributes().put("userInfo", params.getMessage());
+        });
+
+        function.put("VIEW_DOCUMENT", (params) -> {
+            String[] split = params.getSid().split("_");
+            String roomId = split[0] + "_" + split[1];
+            Map<String, CurrentDocument> roomMap = DOCUMENT.computeIfAbsent(roomId, k -> new HashMap<>());
+            roomMap.put(params.getSession().getId(), CurrentDocument.builder()
+                    .id(params.getMessage().getString("id"))
+                    .name(params.getMessage().getString("name"))
+                    .build());
+            sendCurrentDocument(roomId);
+        });
+
+    }
+
+    public void sendCurrentDocument(String roomId) {
+        Map<String, CurrentDocument> data = DOCUMENT.get(roomId);
+        if (data == null) {
+            return;
+        }
+        List<WebSocketSession> sessions = ROOM.get(roomId);
+        if (sessions == null) {
+            return;
+        }
+
+        String message = JSONObject.toJSONString(data.values());
+        JSONObject js = new JSONObject() {{
+            put("code", "CURRENT_DOCUMENT");
+            put("data", message);
+        }};
+        sessions.forEach(session -> {
+            Business.send(session, js.toJSONString());
+        });
+    }
+
+    @Override
+    public void onOpen(WebSocketSession session, String sid) {
+        String[] split = sid.split("_");
+        String roomId = split[0] + "_" + split[1];
+        List<WebSocketSession> sessions = ROOM.computeIfAbsent(roomId, k -> new ArrayList<>());
+        sessions.add(session);
+        Business.send(session, """
+                {
+                    "code" : "ping",
+                    "data":  null
+                }
+                """);
+    }
+
+    @Override
+    public void onClose(WebSocketSession session, String sid) {
+        String[] split = sid.split("_");
+        String roomId = split[0] + "_" + split[1];
+        ROOM.get(roomId).remove(session);
+        JSONObject json = new JSONObject() {{
+            put("code", "exitEmrEditor");
+            put("data", session.getAttributes().get("userInfo"));
+        }};
+        onMessage(session, sid, json.toJSONString());
+        removeDocument(roomId, session);
+    }
+
+    public void removeDocument(String roomId, WebSocketSession session) {
+        Map<String, CurrentDocument> data = DOCUMENT.get(roomId);
+        if (data == null) {
+            return;
+        }
+        data.remove(session.getId());
+    }
+
+    @Override
+    public void onMessage(WebSocketSession session, String sid, String data) {
+        try {
+            JSONObject json = JSONObject.parseObject(data);
+            String string = json.getString("code");
+            Consumer<FunctionParams> functionParamsConsumer = function.get(string);
+            if (functionParamsConsumer != null) {
+                functionParamsConsumer.accept(FunctionParams.builder()
+                        .sid(sid)
+                        .session(session)
+                        .message(json.getJSONObject("data"))
+                        .build());
+                return;
+            }
+            if (!json.getBooleanValue("notified", false)) {
+                return;
+            }
+            String[] split = sid.split("_");
+            String roomId = split[0] + "_" + split[1];
+            List<WebSocketSession> sessions = ROOM.get(roomId);
+            for (WebSocketSession item : sessions) {
+                if (item == null) {
+                    continue;
+                }
+                if (item.getId().equals(session.getId())) {
+                    continue;
+                }
+                Business.send(item, data);
+            }
+        } catch (Exception e) {
+            log.error(e.getMessage());
+        }
+    }
+}
+

+ 15 - 0
thyy-socket/src/main/java/org/thyy/socket/service/emr/EmrMapCenter.java

@@ -0,0 +1,15 @@
+package org.thyy.socket.service.emr;
+
+import lombok.Getter;
+import org.springframework.stereotype.Component;
+import org.springframework.web.socket.WebSocketSession;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+@Getter
+@Component
+public class EmrMapCenter {
+    Map<String, WebSocketSession> document = new ConcurrentHashMap<>();
+    Map<String, WebSocketSession> kicked = new ConcurrentHashMap<>();
+}

+ 94 - 0
thyy-socket/src/main/java/org/thyy/socket/service/emr/EmrRefresh.java

@@ -0,0 +1,94 @@
+package org.thyy.socket.service.emr;
+
+import com.alibaba.fastjson2.JSONObject;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import org.springframework.web.socket.WebSocketSession;
+import org.thyy.socket.service.Business;
+import org.thyy.socket.config.CloseCodes;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Consumer;
+
+@Component
+@Slf4j
+public class EmrRefresh implements Business {
+
+    private final EmrMapCenter mapCenter;
+
+    Map<String, Consumer<FunctionParams>> function = new HashMap<>();
+
+    @Data
+    @Builder
+    @AllArgsConstructor
+    @NoArgsConstructor
+    static class FunctionParams {
+        private JSONObject message;
+        private WebSocketSession session;
+        private String sid;
+    }
+
+    public EmrRefresh(EmrMapCenter mapCenter) {
+        this.mapCenter = mapCenter;
+        function.put("kickItOut", (value) -> {
+            WebSocketSession session = mapCenter.getDocument().get(value.getSid());
+            if (session == null) {
+                nonExistent(value.getSession());
+            } else {
+                existent(session, value.getMessage());
+            }
+        });
+    }
+
+    void existent(WebSocketSession session, JSONObject message) {
+        Business.send(session, message.toJSONString());
+    }
+
+    void nonExistent(WebSocketSession session) {
+        Business.send(session, """
+                {
+                    "code": "nonExistent",
+                    "data": null
+                }
+                """);
+    }
+
+    @Override
+    public void onOpen(WebSocketSession session, String sid) {
+        WebSocketSession data = mapCenter.getKicked().get(sid);
+        if (data == null) {
+            mapCenter.getKicked().put(sid, session);
+        } else {
+            Business.closeReason(session, CloseCodes.KICKING_PEOPLE);
+        }
+    }
+
+    @Override
+    public void onClose(WebSocketSession session, String sid) {
+        WebSocketSession data = mapCenter.getKicked().get(sid);
+        if (data != null) {
+            if (session.getId().equals(data.getId())) {
+                mapCenter.getKicked().remove(sid);
+            }
+        }
+    }
+
+    @Override
+    public void onMessage(WebSocketSession session, String sid, String data) {
+        try {
+            JSONObject jsonObject = JSONObject.parseObject(data);
+            String code = jsonObject.getString("code");
+            function.get(code).accept(FunctionParams.builder()
+                    .message(jsonObject)
+                    .session(session)
+                    .sid(sid)
+                    .build());
+        } catch (Exception ignored) {
+        }
+    }
+}

+ 2 - 0
thyy-socket/src/main/resources/application.yml

@@ -0,0 +1,2 @@
+server:
+  port: 20922

+ 33 - 0
thyy-utils/.gitignore

@@ -0,0 +1,33 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/

+ 14 - 0
thyy-utils/pom.xml

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.thyy.business</groupId>
+        <artifactId>thyy-business</artifactId>
+        <version>0.0.1</version>
+    </parent>
+    <artifactId>thyy-utils</artifactId>
+    <version>0.0.1</version>
+    <name>thyy-utils</name>
+    <description>thyy-utils</description>
+</project>

+ 33 - 0
thyy-utils/src/main/java/org/thyy/utils/exception/BizException.java

@@ -0,0 +1,33 @@
+package org.thyy.utils.exception;
+
+import lombok.Getter;
+
+import java.io.Serial;
+
+@Getter
+public class BizException extends RuntimeException {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    private Integer code;
+    private String msg;
+
+    public BizException() {
+        super();
+    }
+
+    public BizException(ExceptionEnum exceptionEnum) {
+        super(exceptionEnum.getMessage());
+        this.code = exceptionEnum.getCode();
+        this.msg = exceptionEnum.getMessage();
+    }
+
+    public BizException(ExceptionEnum exceptionEnum, String msg) {
+        super(msg);
+        this.code = exceptionEnum.getCode();
+        this.msg = msg;
+    }
+
+
+}

+ 69 - 0
thyy-utils/src/main/java/org/thyy/utils/exception/ExceptionEnum.java

@@ -0,0 +1,69 @@
+package org.thyy.utils.exception;
+
+import lombok.Getter;
+
+/**
+ * @author dj
+ */
+
+@Getter
+public enum ExceptionEnum {
+    /**
+     * 成功
+     */
+    SUCCESS(200, "成功。"),
+    /**
+     * 提示成功
+     */
+    SUCCESS_AND_EL_MESSAGE(201, "操作成功"),
+    SUCCESS_AND_ALERT(202, "操作成功"),
+    SUCCESS_AND_EL_NOTIFICATION(203, "操作成功"),
+
+    // 以下是需要消息提示的错误
+    INTERNAL_SERVER_ERROR(1001, "服务器内部错误!"),
+    NULL_POINTER(1002, "有不合法的空值存在!"),
+    NETWORK_ERROR(1003, "网络异常!"),
+    EXIST_NEGATIVE_FEES(1004, "此患者费用清单存在负数。"),
+    EXIST_UNHANDLED_DRUG_ORDER(1104, "此患者有未处理的药单,请联系药房处理。"),
+    EXIST_UNHANDLED_REFUND_DRUG(1105, "此患者有未处理的护士退药单。"),
+    NO_DATA_EXIST(1005, "没有查询到符合条件的数据。"),
+    ABNORMAL_YZ_ACT_ORDER(1006, "此患者存在异常状态医嘱。"),
+    INVALID_PARAM(1007, "参数异常,请检查。"),
+    NO_PERMISSION_TO_VIEW(1008, "无权查看。"),
+    UNAUTHORIZED_OPERATION(1009, "无权操作。"),
+
+    // 以下是需要弹窗提示的错误
+    LOGICAL_ERROR(2001, "错误。"),
+    NEED_PROOFREAD(2002, "错误。"),
+    LOGICAL_HTML_ERROR(2003, "错误。"),
+
+    // 以下是需要弹窗提示并且重定向到登录页面
+    TOKEN_NOT_EXIST(3001, "没有找到令牌,请重新登录!"),
+    TOKEN_ERROR(3001, "令牌错误,请重新登录!"),
+    USER_NOT_EXIST(3002, "用户不存在,请重新登录!"),
+    INVALID_PASSWORD(3003, "密码错误,请重新登录!"),
+    LOCK_OUT_THE_USER(3003, "用户锁定!"),
+    BAD_REQUEST(3004, "无法转换请求,请用正确的方式访问!"),
+    TOKEN_EXPIRED(3005, "令牌已过期,请重新登录!"),
+    TEST_ENVIRONMENT(3006, "测试环境已关闭请使用正式环境接口"),
+
+    // 以下是不需要提示的错误
+    SLIGHTLY_ERROR(4001, "无需提示的返回。"),
+    PRE_DISCHARGE_ERROR(4002, "出院预审有可疑数据。"),
+    SERVER_IS_UPDATE(5001, "服务器正在升级,请稍后在试一下。"),
+    ERROR_MESSAGE(6001, "前端是返回成功的"),
+    ERROR_MESSAGE_BOX(6002, "数据错误"),
+    // 电子病历保存错误信息
+    EMR_SAVE(7001, "保存失败"),
+    EMR_EXTRACT_OBJECTS(7002, "提取数据元失败,但病历已保存。"),
+    NOT_EL_MESSAGE(8001, "前端没提示的错误信息");
+
+    private final int code;
+    private final String message;
+
+    ExceptionEnum(int code, String message) {
+        this.code = code;
+        this.message = message;
+    }
+
+}

+ 58 - 0
thyy-utils/src/main/java/org/thyy/utils/exception/GlobalExceptionHandler.java

@@ -0,0 +1,58 @@
+package org.thyy.utils.exception;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.validation.BindingResult;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+import org.thyy.utils.result.R;
+import org.thyy.utils.result.ResultVo;
+
+import java.util.Objects;
+
+@Slf4j
+@RestControllerAdvice
+public class GlobalExceptionHandler {
+    private static final String DATABASE = "database";
+    private static final String JSON_PARSE = "JSON parse error";
+
+    @ExceptionHandler(value = RuntimeException.class)
+    public ResultVo<String> runtimeException(RuntimeException e) {
+        return R.fail(ExceptionEnum.INTERNAL_SERVER_ERROR, e.getMessage());
+    }
+
+
+    @ExceptionHandler(value = MethodArgumentNotValidException.class)
+    public ResultVo<String> bindException(MethodArgumentNotValidException e) {
+        BindingResult bindingResult = e.getBindingResult();
+        return R.fail(ExceptionEnum.NULL_POINTER, Objects.requireNonNull(bindingResult.getFieldError()).getDefaultMessage());
+    }
+
+    @ExceptionHandler(value = BizException.class)
+    public ResultVo<String> businessException(BizException e) {
+        log.error("【BizException】发生业务异常", e);
+        return R.fail(e.getCode(), e.getMessage());
+    }
+
+    @ExceptionHandler(value = Exception.class)
+    @ResponseBody
+    public ResultVo<String> exceptionHandler(Exception e) {
+        String message = e.getCause().getMessage();
+        log.error("【Exception】发生异常", e);
+        if (message.contains(DATABASE)) {
+            message = "数据库执行出错,请联系信息中心处理。错误原因:" + e.getCause().getMessage();
+        } else if (message.contains(JSON_PARSE)) {
+            message = "JSON转换发生异常,请检查参数。错误原因:" + e.getCause().getMessage();
+        }
+        return R.fail(ExceptionEnum.INTERNAL_SERVER_ERROR, message);
+    }
+
+    @ExceptionHandler(value = NullPointerException.class)
+    @ResponseBody
+    public ResultVo<String> exceptionHandler(NullPointerException e) {
+        log.error("【NullPointerException】发生空指针异常", e);
+        return R.fail(ExceptionEnum.NULL_POINTER, "错误原因:" + e.getCause().getMessage());
+    }
+
+}

+ 32 - 0
thyy-utils/src/main/java/org/thyy/utils/result/R.java

@@ -0,0 +1,32 @@
+package org.thyy.utils.result;
+
+
+import org.thyy.utils.exception.ExceptionEnum;
+
+public class R {
+
+    public static <T> ResultVo<T> ok() {
+        return new ResultVo<>();
+    }
+
+    public static <T> ResultVo<T> ok(T data) {
+        return new ResultVo<>(data);
+    }
+
+    public static <T> ResultVo<T> ok(ExceptionEnum data) {
+        return new ResultVo<>(data.getCode(), data.getMessage());
+    }
+
+    public static <T> ResultVo<T> fail(ExceptionEnum exceptionEnum) {
+        return new ResultVo<>(exceptionEnum.getCode(), exceptionEnum.getMessage());
+    }
+
+    public static <T> ResultVo<T> fail(ExceptionEnum exceptionEnum, String message) {
+        return new ResultVo<>(exceptionEnum.getCode(), message);
+    }
+
+    public static <T> ResultVo<T> fail(Integer code, String message) {
+        return new ResultVo<>(code, message);
+    }
+
+}

+ 35 - 0
thyy-utils/src/main/java/org/thyy/utils/result/ResultVo.java

@@ -0,0 +1,35 @@
+package org.thyy.utils.result;
+
+import lombok.Data;
+import org.thyy.utils.exception.ExceptionEnum;
+
+@Data
+public class ResultVo<D> {
+    private Integer code;
+    private String message;
+    private D data;
+
+    public ResultVo() {
+        this.code = 200;
+        this.message = "success";
+    }
+
+    public ResultVo(Integer code, String message, D data) {
+        this.code = code;
+        this.message = message;
+        this.data = data;
+    }
+
+    public ResultVo(Integer code, String message) {
+        this.code = code;
+        this.message = message;
+        this.data = null;
+    }
+
+    public ResultVo(D data) {
+        this.code = ExceptionEnum.SUCCESS.getCode();
+        this.message = ExceptionEnum.SUCCESS.getMessage();
+        this.data = data;
+    }
+
+}

+ 1 - 0
thyy-utils/src/main/resources/application.properties

@@ -0,0 +1 @@
+spring.application.name=thyy-utils