Selaa lähdekoodia

feat:20260121 然健康氚云对接卫瓴接口代码推送

zw 2 viikkoa sitten
commit
6fdf483bb4
100 muutettua tiedostoa jossa 10373 lisäystä ja 0 poistoa
  1. 42 0
      mjava-RJK/.gitignore
  2. 0 0
      mjava-RJK/README.md
  3. BIN
      mjava-RJK/mjava-rjk/log/2024-10-22/info-0.log.gz
  4. BIN
      mjava-RJK/mjava-rjk/log/2024-10-22/point-0.log.gz
  5. BIN
      mjava-RJK/mjava-rjk/log/2024-10-22/warn-0.log.gz
  6. BIN
      mjava-RJK/mjava-rjk/log/2024-10-28/info-0.log.gz
  7. BIN
      mjava-RJK/mjava-rjk/log/2024-10-28/point-0.log.gz
  8. BIN
      mjava-RJK/mjava-rjk/log/2024-11-12/info-0.log.gz
  9. BIN
      mjava-RJK/mjava-rjk/log/2024-11-12/point-0.log.gz
  10. BIN
      mjava-RJK/mjava-rjk/log/2024-11-12/warn-0.log.gz
  11. BIN
      mjava-RJK/mjava-rjk/log/2025-02-20/info-0.log.gz
  12. BIN
      mjava-RJK/mjava-rjk/log/2025-02-20/point-0.log.gz
  13. BIN
      mjava-RJK/mjava-rjk/log/2025-02-20/warn-0.log.gz
  14. BIN
      mjava-RJK/mjava-rjk/log/2025-02-26/info-0.log.gz
  15. BIN
      mjava-RJK/mjava-rjk/log/2025-02-26/point-0.log.gz
  16. 100 0
      mjava-RJK/mjava-rjk/pom.xml
  17. 29 0
      mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/Boot.java
  18. 5 0
      mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/common/bizObjectCommon.java
  19. 35 0
      mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/config/YouZanConfig.java
  20. 59 0
      mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/controller/HXController.java
  21. 120 0
      mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/controller/RJKController.java
  22. 88 0
      mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/controller/TBController.java
  23. 27 0
      mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/controller/YouZanController.java
  24. 13 0
      mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/dto/CallbackBody.java
  25. 151 0
      mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/dto/WeilingCallbackDTO.java
  26. 36 0
      mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/dto/YouZanTokenResponse.java
  27. 218 0
      mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/mapping/DuildBusinessCustom.java
  28. 377 0
      mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/mapping/DuildContactCustom.java
  29. 449 0
      mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/mapping/LabelToValue.java
  30. 18 0
      mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/server/HXClient.java
  31. 80 0
      mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/server/KingDee.java
  32. 24 0
      mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/server/RjkServer.java
  33. 35 0
      mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/server/TBService.java
  34. 9 0
      mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/server/YouZanServer.java
  35. 501 0
      mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/server/impl/HXImplClient.java
  36. 1242 0
      mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/server/impl/KingDeeImpl.java
  37. 1737 0
      mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/server/impl/RjkServerImpl.java
  38. 474 0
      mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/server/impl/TbServiceImpl.java
  39. 251 0
      mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/server/impl/YouZanServerImpl.java
  40. 90 0
      mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/timers/timerOne.java
  41. 68 0
      mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/util/CryptUtil.java
  42. 141 0
      mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/util/DownHelp.java
  43. 37 0
      mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/util/HttpRequestUtil.java
  44. 22 0
      mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/util/SHA1.java
  45. 164 0
      mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/util/YouZanTokenUtil.java
  46. 24 0
      mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/util/convertTimestampToDateTimeUtil.java
  47. 42 0
      mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/util/convertToTimestampUtil.java
  48. 125 0
      mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/util/javaHelp.java
  49. 74 0
      mjava-RJK/mjava-rjk/src/main/resources/application-dev.yml
  50. 61 0
      mjava-RJK/mjava-rjk/src/main/resources/application-prod.yml
  51. 414 0
      mjava-RJK/mjava-rjk/src/test/java/test.java
  52. 51 0
      mjava-RJK/mjava/pom.xml
  53. 29 0
      mjava-RJK/mjava/src/main/java/com/malk/Boot.java
  54. 29 0
      mjava-RJK/mjava/src/main/java/com/malk/base/BaseDao.java
  55. 112 0
      mjava-RJK/mjava/src/main/java/com/malk/base/BaseDto.java
  56. 64 0
      mjava-RJK/mjava/src/main/java/com/malk/base/BasePo.java
  57. 9 0
      mjava-RJK/mjava/src/main/java/com/malk/base/BaseRepository.java
  58. 78 0
      mjava-RJK/mjava/src/main/java/com/malk/base/JpaMap.java
  59. 35 0
      mjava-RJK/mjava/src/main/java/com/malk/config/JpaConfiguration.java
  60. 58 0
      mjava-RJK/mjava/src/main/java/com/malk/config/WebConfiguration.java
  61. 41 0
      mjava-RJK/mjava/src/main/java/com/malk/config/mutilSource/DataSourceConfig.java
  62. 80 0
      mjava-RJK/mjava/src/main/java/com/malk/config/mutilSource/PrimaryConfig.java
  63. 76 0
      mjava-RJK/mjava/src/main/java/com/malk/config/mutilSource/SlaveConfig.java
  64. 73 0
      mjava-RJK/mjava/src/main/java/com/malk/controller/DDCallbackController.java
  65. 61 0
      mjava-RJK/mjava/src/main/java/com/malk/controller/TBCallBackController.java
  66. 101 0
      mjava-RJK/mjava/src/main/java/com/malk/core/AsyncConfig.java
  67. 36 0
      mjava-RJK/mjava/src/main/java/com/malk/delegate/DDEvent.java
  68. 17 0
      mjava-RJK/mjava/src/main/java/com/malk/delegate/McDelegate.java
  69. 25 0
      mjava-RJK/mjava/src/main/java/com/malk/delegate/TBEvent.java
  70. 49 0
      mjava-RJK/mjava/src/main/java/com/malk/delegate/impl/DDImplEvent.java
  71. 21 0
      mjava-RJK/mjava/src/main/java/com/malk/delegate/impl/McImplDelegate.java
  72. 29 0
      mjava-RJK/mjava/src/main/java/com/malk/delegate/impl/TBImplEvent.java
  73. 215 0
      mjava-RJK/mjava/src/main/java/com/malk/filter/CatchException.java
  74. 53 0
      mjava-RJK/mjava/src/main/java/com/malk/filter/ExceptionNotice.java
  75. 32 0
      mjava-RJK/mjava/src/main/java/com/malk/filter/RequestFilter.java
  76. 42 0
      mjava-RJK/mjava/src/main/java/com/malk/filter/RequestInterceptor.java
  77. 13 0
      mjava-RJK/mjava/src/main/java/com/malk/repository/dao/primary/McAuthorizationDao.java
  78. 10 0
      mjava-RJK/mjava/src/main/java/com/malk/repository/dao/primary/McTableDao.java
  79. 11 0
      mjava-RJK/mjava/src/main/java/com/malk/repository/dao/slave/McTableDao.java
  80. 51 0
      mjava-RJK/mjava/src/main/java/com/malk/repository/entity/primary/McAuthorizationPo.java
  81. 34 0
      mjava-RJK/mjava/src/main/java/com/malk/repository/entity/primary/McTablePo.java
  82. 34 0
      mjava-RJK/mjava/src/main/java/com/malk/repository/entity/slave/McTablePo.java
  83. 58 0
      mjava-RJK/mjava/src/main/java/com/malk/schedule/McScheduleTask.java
  84. 187 0
      mjava-RJK/mjava/src/main/java/com/malk/server/aliwork/YDConf.java
  85. 225 0
      mjava-RJK/mjava/src/main/java/com/malk/server/aliwork/YDParam.java
  86. 32 0
      mjava-RJK/mjava/src/main/java/com/malk/server/aliwork/YDR.java
  87. 33 0
      mjava-RJK/mjava/src/main/java/com/malk/server/aliyun/ALYR.java
  88. 42 0
      mjava-RJK/mjava/src/main/java/com/malk/server/common/FilePath.java
  89. 87 0
      mjava-RJK/mjava/src/main/java/com/malk/server/common/McConf.java
  90. 167 0
      mjava-RJK/mjava/src/main/java/com/malk/server/common/McException.java
  91. 40 0
      mjava-RJK/mjava/src/main/java/com/malk/server/common/McPage.java
  92. 105 0
      mjava-RJK/mjava/src/main/java/com/malk/server/common/McR.java
  93. 33 0
      mjava-RJK/mjava/src/main/java/com/malk/server/common/McREnum.java
  94. 55 0
      mjava-RJK/mjava/src/main/java/com/malk/server/common/VenR.java
  95. 76 0
      mjava-RJK/mjava/src/main/java/com/malk/server/dingtalk/DDConf.java
  96. 78 0
      mjava-RJK/mjava/src/main/java/com/malk/server/dingtalk/DDConfigSign.java
  97. 79 0
      mjava-RJK/mjava/src/main/java/com/malk/server/dingtalk/DDFormComponentDto.java
  98. 24 0
      mjava-RJK/mjava/src/main/java/com/malk/server/dingtalk/DDInterActiveCard.java
  99. 101 0
      mjava-RJK/mjava/src/main/java/com/malk/server/dingtalk/DDR.java
  100. 0 0
      mjava-RJK/mjava/src/main/java/com/malk/server/dingtalk/DDR_New.java

+ 42 - 0
mjava-RJK/.gitignore

@@ -0,0 +1,42 @@
+# IntelliJ project files
+.idea
+*.iml
+out
+gen
+### Java template
+# Compiled class file
+*.class
+
+# Log file
+*.log
+/log/
+
+# BlueJ files
+*.ctxt
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.jar
+*.war
+*.nar
+*.ear
+*.zip
+*.tar.gz
+*.rar
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+
+mvnw
+mvnw.cmd
+
+# tmp file
+.tmp
+*/tmp
+*/target
+
+# web2 file
+*/src/main/resources/static/web
+*/src/main/resources/static/mjs

+ 0 - 0
mjava-RJK/README.md


BIN
mjava-RJK/mjava-rjk/log/2024-10-22/info-0.log.gz


BIN
mjava-RJK/mjava-rjk/log/2024-10-22/point-0.log.gz


BIN
mjava-RJK/mjava-rjk/log/2024-10-22/warn-0.log.gz


BIN
mjava-RJK/mjava-rjk/log/2024-10-28/info-0.log.gz


BIN
mjava-RJK/mjava-rjk/log/2024-10-28/point-0.log.gz


BIN
mjava-RJK/mjava-rjk/log/2024-11-12/info-0.log.gz


BIN
mjava-RJK/mjava-rjk/log/2024-11-12/point-0.log.gz


BIN
mjava-RJK/mjava-rjk/log/2024-11-12/warn-0.log.gz


BIN
mjava-RJK/mjava-rjk/log/2025-02-20/info-0.log.gz


BIN
mjava-RJK/mjava-rjk/log/2025-02-20/point-0.log.gz


BIN
mjava-RJK/mjava-rjk/log/2025-02-20/warn-0.log.gz


BIN
mjava-RJK/mjava-rjk/log/2025-02-26/info-0.log.gz


BIN
mjava-RJK/mjava-rjk/log/2025-02-26/point-0.log.gz


+ 100 - 0
mjava-RJK/mjava-rjk/pom.xml

@@ -0,0 +1,100 @@
+<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>java-mcli</artifactId>
+        <groupId>com.malk</groupId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>mjava-rjk</artifactId>
+    <description>然健康项目</description>
+
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+        <sqlserver-jdbc.version>6.4.0.jre8</sqlserver-jdbc.version>
+    </properties>
+
+    <dependencies>
+        <!-- 核心模块-->
+        <dependency>
+            <groupId>com.malk</groupId>
+            <artifactId>mjava</artifactId>
+            <version>${mjava.version}</version>
+        </dependency>
+
+        <!-- sqlserver依赖 -->
+        <dependency>
+            <groupId>com.microsoft.sqlserver</groupId>
+            <artifactId>mssql-jdbc</artifactId>
+            <scope>runtime</scope>
+            <version>${sqlserver-jdbc.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.jetbrains</groupId>
+            <artifactId>annotations</artifactId>
+            <version>RELEASE</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.googlecode.json-simple</groupId>
+            <artifactId>json-simple</artifactId>
+            <version>1.1</version>
+        </dependency>
+        <dependency>
+            <groupId>com.squareup.okhttp3</groupId>
+            <artifactId>okhttp</artifactId>
+            <version>4.4.1</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.example</groupId>
+            <artifactId>apigwclient</artifactId>
+            <version>0.1.5</version>
+            <scope>system</scope>
+            <systemPath> ${project.basedir}/lib/apigwclient-0.1.5.jar</systemPath>
+        </dependency>
+        <dependency>
+            <groupId>com.google.code.gson</groupId>
+            <artifactId>gson</artifactId>
+            <version>2.8.9</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.json</groupId>
+            <artifactId>json</artifactId>
+            <version>20240303</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+<!--                <version>2.1.1.RELEASE</version>-->
+                <version>2.7.0</version>
+                <configuration>
+                    <includeSystemScope>true</includeSystemScope>
+                    <!-- 如果没有该配置,devtools不会生效: 打包时关闭 -->
+                    <fork>false</fork>
+                    <!-- 避免中文乱码 -->
+                    <jvmArguments>-Dfile.encoding=UTF-8</jvmArguments>
+                </configuration>
+                <!-- 允许生成可运行jar -->
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+        <finalName>${project.artifactId}</finalName>
+    </build>
+
+</project>

+ 29 - 0
mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/Boot.java

@@ -0,0 +1,29 @@
+package com.malk.rjk;
+
+import com.querydsl.jpa.impl.JPAQueryFactory;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Bean;
+import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
+
+import javax.persistence.EntityManager;
+
+
+//@EnableAsync
+ @EnableJpaAuditing
+@SpringBootApplication(scanBasePackages = {"com.malk"})
+public class Boot {
+
+    public static void main(String... args) {
+        SpringApplication.run(Boot.class, args);
+    }
+
+    /**
+     * 让Spring管理JPAQueryFactory [不使用Qualifier详见mjava-Boot]
+     */
+    @Bean
+    public JPAQueryFactory jpaQueryFactory(EntityManager entityManager) {
+        
+        return new JPAQueryFactory(entityManager);
+    }
+}

+ 5 - 0
mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/common/bizObjectCommon.java

@@ -0,0 +1,5 @@
+package com.malk.rjk.common;
+
+public class bizObjectCommon {
+
+}

+ 35 - 0
mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/config/YouZanConfig.java

@@ -0,0 +1,35 @@
+package com.malk.rjk.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.stereotype.Component;
+
+/**
+ * 有赞云配置类
+ * 
+ * @author: shunxi
+ * @createTime: 2026-01-14
+ */
+@Data
+@Configuration
+@Component
+@ConfigurationProperties(prefix = "youzan")
+public class YouZanConfig {
+    
+    /**
+     * 应用的 client_id
+     */
+    private String clientId;
+    
+    /**
+     * 应用的 client_secret
+     */
+    private String clientSecret;
+    
+    /**
+     * 店铺的 kdt_id
+     */
+    private String kdtId;
+
+}

+ 59 - 0
mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/controller/HXController.java

@@ -0,0 +1,59 @@
+package com.malk.rjk.controller;
+
+import com.alibaba.fastjson.JSON;
+import com.malk.rjk.server.HXClient;
+import com.malk.server.common.McR;
+import com.malk.utils.UtilMap;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.io.File;
+import java.util.Map;
+
+@Slf4j
+@RestController
+@RequestMapping()
+public class HXController {
+    @Autowired
+    private HXClient hxClient;
+
+
+    @PostMapping("h3yun-http")
+    McR http(@RequestBody Map<String, String> data, @RequestParam String code) {
+        //deleteFile("/home/files");
+        log.info("氚云http请求, code = {}, body = {}", code, JSON.toJSONString(data));
+       System.out.println("氚云http请求, code = "+code+", body = "+JSON.toJSONString(data));
+      //  deleteFile("D:\\本地下载aaa\\");
+        String type = String.valueOf(data.get("HIH3type"));
+        if (type.equals("CStype01") || type.equals("CStype02")) {//船次附件数据上传
+            String sss= hxClient.CStoDP( data);
+        }
+        if (type.equals("CW01")|| type.equals("CW02")) {//财务附件数据上传 CW01:付款    CW02:收款
+            String sss= hxClient.CWtoDP( data);
+        }
+        //  String sss= hxClient.syncDP(String.valueOf(data.get("attachments")), String.valueOf(data.get("projectName")), String.valueOf(data.get("typeName")));
+        return McR.success(UtilMap.map("status", "是"));
+    }
+public  void  deleteFile(String directoryPath){
+    // 创建一个表示目录的File对象
+    File directory = new File(directoryPath);
+
+    // 获取目录下的所有文件和目录
+    File[] files = directory.listFiles();
+
+    // 确保目录存在且不是文件本身
+    if (files != null && directory.isDirectory()) {
+        for (File file : files) {
+            // 删除文件
+            if (file.isFile()) {
+                System.out.println("Deleting file: " + file.getName());
+                file.delete();
+            }
+        }
+    }
+}
+
+
+
+}

+ 120 - 0
mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/controller/RJKController.java

@@ -0,0 +1,120 @@
+package com.malk.rjk.controller;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.malk.rjk.dto.CallbackBody;
+import com.malk.rjk.server.RjkServer;
+import com.malk.rjk.util.CryptUtil;
+import com.malk.rjk.util.SHA1;
+import com.malk.server.common.McR;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+
+
+@Slf4j
+@RestController
+@RequestMapping("/weiling")
+public class RJKController {
+
+    private final static String TOKEN = "Re7szzfwkolU2TPZs59nkGEcGqehjF0F";
+    private final static String ENCODING_AES_KEY ="Re7szzfwkolU2TPZs59nkGEcGqehjF0FuSCvxldv6dr";
+
+    @Autowired
+    private RjkServer rjkServer;
+
+
+    /*如果该项目存在子项目继续查*/
+    @SneakyThrows
+    @PostMapping("/CustomerProfileSubmission")
+    McR CustomerProfileSubmission(@RequestBody Map<String,String> data){
+        // 先返回成功响应
+        CompletableFuture.runAsync(() -> {
+            try {
+                Thread.sleep(5000);
+                log.info("响应客户资料等待完成");
+                rjkServer.H3UserToweiling(data);
+            } catch (Exception e) {
+                log.error("客户资料同步失败", e);
+            }
+        });
+        return McR.success();
+    }
+
+    @SneakyThrows
+    @PostMapping("/StoreOrderSubmission")
+    McR StoreOrderSubmission(@RequestBody Map<String,String> data){
+        // 先返回成功响应
+        CompletableFuture.runAsync(() -> {
+            try {
+                Thread.sleep(5000);
+                log.info("响应商机资料等待完成");
+                rjkServer.H3BusinessToWeiling(data);
+            } catch (Exception e) {
+                log.error("商机资料同步失败", e);
+            }
+        });
+        return McR.success();
+    }
+
+
+    /**
+     * 回调服务 测试请求地址的合法性
+     *
+     * @param msgSignature 消息签名,msg_signature计算结合了企业填写的token、请求中的timestamp、nonce、echostr
+     * @param timeStamp    时间戳
+     * @param nonce        随机数
+     * @param echoStr      加密字符串
+     * @return
+     */
+    @GetMapping(value = "/callback")
+    public String listen(@RequestParam("msg_signature") String msgSignature,
+                         @RequestParam("timestamp") String timeStamp,
+                         @RequestParam("nonce") String nonce,
+                         @RequestParam("echostr") String echoStr) {
+        //通过token, timestamp. nonce, echostr四个参数生成签名
+        String signature = SHA1.gen(TOKEN, timeStamp, nonce, echoStr);
+        //如果签名相同,则解密echostr并返回
+        if (signature.equals(msgSignature)) {
+            CryptUtil util = new CryptUtil(ENCODING_AES_KEY);
+//            System.out.println("解密成功"+util.decrypt(echoStr));
+            return util.decrypt(echoStr);
+        }
+        return null;
+    }
+
+    /**
+     * 回调服务示例 解密后包含回调信息
+     *
+     * @param msgSignature 消息签名,msg_signature计算结合了企业填写的token、
+     *                     请求中的timestamp、nonce、请求体中的encrypt_msg
+     * @param timeStamp    时间戳
+     * @param nonce        随机数
+     * @param body         请求体(回调应用的appid、 加密信息encrypt_msg)
+     * @return
+     */
+    @PostMapping(value = "/callback")
+    public String listen(@RequestParam("msg_signature") String msgSignature,
+                         @RequestParam("timestamp") String timeStamp,
+                         @RequestParam("nonce") String nonce,
+                         @RequestBody CallbackBody body) {
+        String encrypted = body.getEncrypt_msg();
+        String signature = SHA1.gen(TOKEN, timeStamp, nonce, encrypted);
+        if (signature.equals(msgSignature)) {
+            CryptUtil util = new CryptUtil(ENCODING_AES_KEY);
+            // msg中包含回调信息
+            String msg = util.decrypt(encrypted);
+            log.info("回调信息:msg={}", msg);
+            // 解析JSON字符串为JSONObject
+            JSONObject callbackJson = JSON.parseObject(msg);
+            rjkServer.syncWeilingToH3yun(callbackJson);
+        }
+        return "success";
+    }
+
+
+
+}

+ 88 - 0
mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/controller/TBController.java

@@ -0,0 +1,88 @@
+package com.malk.rjk.controller;
+
+import com.malk.rjk.server.TBService;
+import com.malk.server.common.McR;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Map;
+
+
+@Slf4j
+@RestController
+@RequestMapping("/TBConstructionLog")
+public class TBController {
+    @Autowired
+    private TBService tbService;
+
+    /*TODO:获取应用授权token*/
+    @SneakyThrows
+    @GetMapping("/GetAppToken")
+    McR AppAccessToken(){
+        log.info("获取到appToken---------");
+        return tbService.AppAccessToken();
+    }
+
+    /*TODO:根据钉钉id获取TBid(data里面传dingUserId)*/
+    @SneakyThrows
+    @PostMapping("/getTbId")
+    McR GetTbId(@RequestBody Map<String,String> data){
+        log.info("开始查询TBid----------");
+        return McR.success(tbService.GetTbId(data));
+    }
+
+
+    /*TODO:查询项目*/
+    @SneakyThrows
+    @PostMapping("/GetProject")
+    McR SearchProject(@RequestBody Map<String,String> data){
+        log.info("开始查询项目信息---------");
+        return McR.success(tbService.SearchProject(data));
+    }
+
+    /*TODO:查询项目对应下的任务*/
+    /*TODO:data传参:{"name":"科力纳米xx海外"(项目名称),"renwu":"施工装配阶段(任务列表名)"}*/
+    @SneakyThrows
+    @PostMapping("/RenWuDetail")
+    McR SearchRenWu(@RequestBody Map<String,String> data){
+        log.info("开始查询任务列表----------");
+        /*TODO:1、搜索任务列表id*/
+        McR renWuList = tbService.RenWuList(data);
+        System.out.println("项目id(=stageId):"+renWuList.getData());
+
+        /*TODO:2、查询项目任务*/
+        return (tbService.ProjectRenWu(data));
+    }
+
+
+    /*TODO:更改项目验收任务状态*/
+    /*TODO:data传参:
+       {"name":"科力纳米xx海外"(项目名称),
+       "renwuDetail":"项目客户已验收"(任务名称),
+       "dingUserIds":"manager4496"(钉钉id)}*/
+    @SneakyThrows
+    @PostMapping("/UpdateTask")
+    McR UpdateTask(@RequestBody Map<String,String> data){
+        log.info("开始更改任务状态----------");
+        return tbService.UpdateTask(data);
+    }
+
+    /*TODO:更改项目施工日志*/
+    /*TODO:data传参:
+       {"name":"科力纳米xx海外"(项目名称),
+       "renwu":"项目内部验收、交付客户"(任务列表名称),
+       "renwuDetail":"项目质保金"(任务名称),
+       "dingUserIds":"manager4496"(钉钉id),
+       subtask(子任务名称,若有):"1"}*/
+    /*如果该项目存在子项目继续查*/
+    @SneakyThrows
+    @PostMapping("/UpdateConstructionLog")
+    McR UpdateConstructionLog(@RequestBody Map<String,String> data){
+        log.info("开始更改任务状态----------");
+        return McR.success(tbService.UpdateConstructionLog(data));
+    }
+
+
+}

+ 27 - 0
mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/controller/YouZanController.java

@@ -0,0 +1,27 @@
+package com.malk.rjk.controller;
+
+import com.alibaba.fastjson.JSONObject;
+import com.malk.rjk.server.YouZanServer;
+import com.malk.server.common.McR;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+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;
+
+
+@Slf4j
+@RestController
+@RequestMapping("/youzan")
+public class YouZanController {
+
+    @Autowired
+    private YouZanServer youZanServer;
+
+    @PostMapping("/OrderSuccessCallback")
+    McR OrderSuccessCallback(@RequestBody JSONObject callbackData){
+        youZanServer.OrderSuccessCallback(callbackData);
+        return McR.success();
+    }
+}

+ 13 - 0
mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/dto/CallbackBody.java

@@ -0,0 +1,13 @@
+package com.malk.rjk.dto;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+public class CallbackBody implements Serializable {
+
+    private String to_app;
+
+    private String encrypt_msg;
+}

+ 151 - 0
mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/dto/WeilingCallbackDTO.java

@@ -0,0 +1,151 @@
+package com.malk.rjk.dto;
+
+import lombok.Data;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 卫瓴事件回调DTO
+ */
+@Data
+public class WeilingCallbackDTO {
+    
+    /**
+     * 回调事件具体内容
+     */
+    private EventData event;
+    
+    /**
+     * 回调参数头
+     */
+    private HeaderData header;
+    
+    /**
+     * 事件数据
+     */
+    @Data
+    public static class EventData {
+        /**
+         * 联系人id
+         */
+        private String contact_id;
+        
+        /**
+         * 联系人名称
+         */
+        private String user_name;
+        
+        /**
+         * 状态
+         */
+        private Integer status;
+        
+        /**
+         * 归属人
+         */
+        private String owner_id;
+        
+        /**
+         * 创建人
+         */
+        private String create_user_id;
+        
+        /**
+         * 创建时间
+         */
+        private Long create_time;
+        
+        /**
+         * 联络方式列表
+         */
+        private List<ContactWay> contact_ways;
+        
+        /**
+         * 变更类型(更新事件才有)
+         */
+        private String change_type;
+        
+        /**
+         * 性别
+         */
+        private Integer gender;
+        
+        /**
+         * 是否重复
+         */
+        private Boolean is_repeat;
+        
+        /**
+         * 是否废弃
+         */
+        private Boolean is_useless;
+        
+        /**
+         * 线索领取时间
+         */
+        private Long claim_time;
+        
+        /**
+         * 来源渠道名称
+         */
+        private String from_channel_name;
+    }
+    
+    /**
+     * 联络方式
+     */
+    @Data
+    public static class ContactWay {
+        /**
+         * 联络方式类型(1-手机号 2-座机 3-邮箱 4-注册加微)
+         */
+        private Integer type;
+        
+        /**
+         * 联络方式具体信息
+         */
+        private String contact_way;
+    }
+    
+    /**
+     * 回调参数头
+     */
+    @Data
+    public static class HeaderData {
+        /**
+         * 回调应用的appid
+         */
+        private String to_app;
+        
+        /**
+         * 事件类型(contact_add:联系人新增, contact_update:联系人更新)
+         */
+        private String event_type;
+        
+        /**
+         * 回调时间,13位时间戳,精确到毫秒
+         */
+        private String time_stamp;
+    }
+    
+    /**
+     * 提取所有联络方式中的 contact_way 值
+     * @return 联络方式字符串列表
+     */
+    public List<String> extractContactWays() {
+        List<String> result = new ArrayList<>();
+        if (event == null || event.getContact_ways() == null) {
+            return result;
+        }
+        for (ContactWay contactWay : event.getContact_ways()) {
+            if (contactWay != null && contactWay.getContact_way() != null) {
+                String way = contactWay.getContact_way().trim();
+                if (!way.isEmpty()) {
+                    result.add(way);
+                }
+            }
+        }
+        return result;
+    }
+}
+

+ 36 - 0
mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/dto/YouZanTokenResponse.java

@@ -0,0 +1,36 @@
+package com.malk.rjk.dto;
+
+import lombok.Data;
+
+/**
+ * 有赞云 Token 响应实体
+ * @author: shunxi
+ * @createTime: 2026-01-14
+ */
+@Data
+public class YouZanTokenResponse {
+    /**
+     * 访问令牌
+     */
+    private String access_token;
+    
+    /**
+     * 刷新令牌
+     */
+    private String refresh_token;
+    
+    /**
+     * 令牌类型,通常为 "Bearer"
+     */
+    private String token_type;
+    
+    /**
+     * 过期时间(秒)
+     */
+    private Long expires_in;
+    
+    /**
+     * 作用域
+     */
+    private String scope;
+}

+ 218 - 0
mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/mapping/DuildBusinessCustom.java

@@ -0,0 +1,218 @@
+package com.malk.rjk.mapping;
+
+import com.alibaba.fastjson.JSONObject;
+import com.fasterxml.jackson.databind.JsonNode;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import static com.malk.rjk.mapping.LabelToValue.*;
+import static com.malk.rjk.util.convertToTimestampUtil.convertToTimestamp;
+
+@Component
+@Slf4j
+public class DuildBusinessCustom {
+
+    public JSONObject BusinessCustom(JsonNode bizObjectNode) {
+         // 构建business_custom对象
+        JSONObject businessCustom = new JSONObject();
+
+        //订单状态
+        String ddztStr= bizObjectNode.path("F0000082").asText();
+        if (ddztStr != null && !ddztStr.isEmpty() && !"null".equals(ddztStr)) {
+            String ddztSValue = getOrderStatusValueByLabel(ddztStr);
+            if (ddztSValue != null) {
+                try {
+                    int ddzt = Integer.parseInt(ddztSValue);
+                    businessCustom.put("ddzt", ddzt);
+                } catch (NumberFormatException e) {
+                    log.warn("ddzt 字段转换失败: " + ddztStr + ", 值: " + ddztSValue);
+                }
+            }
+        }
+
+        // 获取氚云中的自定义字段值
+        //录入表单
+        String dataSoureStr = bizObjectNode.path("dataSoure").asText();
+        if (dataSoureStr != null && !dataSoureStr.isEmpty() && !"null".equals(dataSoureStr)) {
+            String dataSoureValue = getOrderTypeValueByLabel(dataSoureStr);
+            if (dataSoureValue != null) {
+                try {
+                    int dataSoure = Integer.parseInt(dataSoureValue);
+                    businessCustom.put("lrbd", dataSoure);
+                } catch (NumberFormatException e) {
+                    log.warn("dataSoure 字段转换失败: " + dataSoureStr + ", 值: " + dataSoureValue);
+                }
+            }
+        }
+
+        String orderID = bizObjectNode.path("orderID").asText(); // 订单编号
+        if (orderID != null && !orderID.isEmpty() && !"null".equals(orderID)) {
+            businessCustom.put("ddbh", orderID);
+        }
+
+        String paymentDateStr = bizObjectNode.path("paymentDate").asText(); // 订单时间
+        if (paymentDateStr != null && !paymentDateStr.isEmpty() && !"null".equals(paymentDateStr)) {
+            long paymentDate = convertToTimestamp(paymentDateStr);
+            if (paymentDate > 0) {
+                businessCustom.put("ddsj", paymentDate);
+            }
+        }
+
+        String cashCollection = bizObjectNode.path("F0000009").asText(); // 现金收款
+        if (cashCollection != null && !cashCollection.isEmpty() && !"null".equals(cashCollection)) {
+            try {
+                double cashCollectionValue = Double.parseDouble(cashCollection);
+                businessCustom.put("zjsk", String.valueOf(cashCollectionValue));
+            } catch (NumberFormatException e) {
+                log.warn("cashCollection 字段转换失败: " + cashCollection);
+            }
+        }
+
+//        String customerName = bizObjectNode.path("customerName").asText(""); // 客户档案-卫领接口没有客户档案
+//        businessCustom.put("khbm", customerName);
+        String customerID = bizObjectNode.path("customerID").asText(""); // 客户编号
+        if (customerID != null && !customerID.isEmpty() && !"null".equals(customerID)) {
+            businessCustom.put("khbh", customerID);
+        }
+
+//        String employee = bizObjectNode.path("employee").asText(""); // 客户经理-氚云表中没有客户经理字段
+//        businessCustom.put("ddbh", employee);
+        String shopStr = bizObjectNode.path("shop").asText(); // 门店
+        if (shopStr != null && !shopStr.isEmpty() && !"null".equals(shopStr)) {
+            String shopValue = getStoreValueByLabel(shopStr);
+            if (shopValue != null) {
+                try {
+                    int shop = Integer.parseInt(shopValue);
+                    businessCustom.put("mendian", shop);
+                } catch (NumberFormatException e) {
+                    log.warn("shop 字段转换失败: " + shopStr + ", 值: " + shopValue);
+                }
+            }
+        }
+
+        String changeAmountStr = bizObjectNode.path("F0000067").asText(); // 零钱袋变动金额
+        if (changeAmountStr != null && !changeAmountStr.isEmpty() && !"null".equals(changeAmountStr)) {
+            try {
+                double changeAmountValue = Double.parseDouble(changeAmountStr);
+                businessCustom.put("lqdbdje", String.valueOf(changeAmountValue));
+            } catch (NumberFormatException e) {
+                log.warn("changeAmount 字段转换失败: " + changeAmountStr);
+            }
+        }
+
+        String orderNumberStr = bizObjectNode.path("F0000012").asText(); // 零钱袋余额
+        if (orderNumberStr != null && !orderNumberStr.isEmpty() && !"null".equals(orderNumberStr)) {
+            try {
+                double orderNumber = Double.parseDouble(orderNumberStr);
+                businessCustom.put("lqdye", String.valueOf(orderNumber));
+            } catch (NumberFormatException e) {
+                log.warn("orderNumber 字段转换失败: " + orderNumberStr);
+            }
+        }
+
+        String changeBalance = bizObjectNode.path("F0000018").asText(); // 选择会员卡
+        if (changeBalance != null && !changeBalance.isEmpty() && !"null".equals(changeBalance)) {
+            businessCustom.put("xzhyk", changeBalance);
+        }
+
+        String membershipChangeAmount = bizObjectNode.path("F0000068").asText(); // 会员卡变动金额
+        if (membershipChangeAmount != null && !membershipChangeAmount.isEmpty() && !"null".equals(membershipChangeAmount)) {
+            try {
+                double membershipChangeAmountValue = Double.parseDouble(membershipChangeAmount);
+                businessCustom.put("hykjebd", String.valueOf(membershipChangeAmountValue));
+            } catch (NumberFormatException e) {
+                log.warn("membershipChangeAmount 字段转换失败: " + membershipChangeAmount);
+            }
+        }
+
+        String memberBalance = bizObjectNode.path("F0000019").asText(); // 会员卡余额
+        if (memberBalance != null && !memberBalance.isEmpty() && !"null".equals(memberBalance)) {
+            try {
+                double memberBalanceValue = Double.parseDouble(memberBalance);
+                businessCustom.put("hykye", String.valueOf(memberBalanceValue));
+            } catch (NumberFormatException e) {
+                log.warn("memberBalance 字段转换失败: " + memberBalance);
+            }
+        }
+
+
+        String cashReceivingUnitStr = bizObjectNode.path("F0000010").asText(); // 现金入账单位
+        if (cashReceivingUnitStr != null && !cashReceivingUnitStr.isEmpty() && !"null".equals(cashReceivingUnitStr)) {
+            try {
+                int cashReceivingUnit = Integer.parseInt(getCashInUnitValueByLabel(cashReceivingUnitStr));
+                businessCustom.put("zjrzdw", cashReceivingUnit);
+            } catch (NumberFormatException e) {
+                log.warn("cashReceivingUnit 字段转换失败: " + cashReceivingUnitStr);
+            }
+        }
+
+        String serviceFee = bizObjectNode.path("F0000011").asText(); // 手续费
+        if (serviceFee != null && !serviceFee.isEmpty() && !"null".equals(serviceFee)) {
+            try {
+                double serviceFeeValue = Double.parseDouble(serviceFee);
+                businessCustom.put("sxf", String.valueOf(serviceFeeValue));
+            } catch (NumberFormatException e) {
+                log.warn("serviceFee 字段转换失败: " + serviceFee);
+            }
+        }
+
+        String orderNotes = bizObjectNode.path("F0000033").asText(); // 订单备注
+        if (orderNotes != null && !orderNotes.isEmpty() && !"null".equals(orderNotes)) {
+            businessCustom.put("ddbz", orderNotes);
+        }
+
+        String reasonOrderCancellation = bizObjectNode.path("F0000034").asText(); // 退费原因
+        if (reasonOrderCancellation != null && !reasonOrderCancellation.isEmpty() && !"null".equals(reasonOrderCancellation)) {
+            businessCustom.put("tfyy", reasonOrderCancellation);
+        }
+
+        String refundRecipient = bizObjectNode.path("F0000043").asText(); // 退款支付对象
+        if (refundRecipient != null && !refundRecipient.isEmpty() && !"null".equals(refundRecipient)) {
+            businessCustom.put("tkzfdx", refundRecipient);
+        }
+
+        String customerBank = bizObjectNode.path("F0000045").asText(); // 客户开户行
+        if (customerBank != null && !customerBank.isEmpty() && !"null".equals(customerBank)) {
+            businessCustom.put("khkhh", customerBank);
+        }
+
+        String bankcardNumber = bizObjectNode.path("F0000070").asText(); // 客户银行卡号
+        if (bankcardNumber != null && !bankcardNumber.isEmpty() && !"null".equals(bankcardNumber)) {
+            businessCustom.put("khyhkh", bankcardNumber);
+        }
+
+        JsonNode subTableDetails = bizObjectNode.path("D150508sylqgyf7kyrma7ikoad");// 购买产品明细-子表单
+        StringBuilder subTableStringBuilder = new StringBuilder();
+        if (subTableDetails.isArray()) {
+            for (int i = 0; i < subTableDetails.size(); i++) {
+                JsonNode row = subTableDetails.get(i);
+                String minProduct = row.path("minProduct").asText();// 购买产品明细.产品名称 mxcpmc
+//                String minSort = row.path("minSort").asText();// 购买产品明细.所属子类 mxsszl
+//                String Healer = row.path("F0000050").asText(); // 购买产品明细.疗愈师/协同人 mxlysxtr
+                String priceType = row.path("priceType").asText(); // 购买产品明细.购买价格类型 mxgmjglx
+//                String quantity = String.valueOf(row.path("quantity").asInt()); // 购买产品明细.购买数量 mxgmsl
+                String actualTotalPrice = String.valueOf(row.path("actualTotalPrice").asDouble()); // 购买产品明细.实收总价 mxsszj
+//                String amountWrittenFrequency = String.valueOf(row.path("F0000046").asDouble()); // 购买产品明细.次数核销金额 mxcshxje
+//                String workinghours = String.valueOf(row.path("F0000052").asInt()); // 购买产品明细.工时数 mxgss
+
+                // 构建格式
+                if (i > 0) {
+                    subTableStringBuilder.append(",");
+                }
+                subTableStringBuilder.append("{")
+                        .append(minProduct).append(",")
+//                        .append(minSort).append(",")
+//                        .append(Healer).append(",")
+                        .append(priceType).append(",")
+//                        .append(quantity).append(",")
+                        .append(actualTotalPrice)
+//                        .append(amountWrittenFrequency).append(",")
+//                        .append(workinghours)
+                        .append("}");
+            }
+            String subTableArrayString = subTableStringBuilder.toString();
+            businessCustom.put("gmcp", subTableArrayString);
+//            System.out.println("购买产品明细字段: "+subTableArrayString);
+        }
+        return businessCustom;
+    }
+}

+ 377 - 0
mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/mapping/DuildContactCustom.java

@@ -0,0 +1,377 @@
+package com.malk.rjk.mapping;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.fasterxml.jackson.databind.JsonNode;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import java.util.ArrayList;
+import java.util.List;
+import static com.malk.rjk.mapping.LabelToValue.*;
+import static com.malk.rjk.util.convertToTimestampUtil.convertToTimestamp;
+
+
+@Component
+@Slf4j
+public class DuildContactCustom {
+    public JSONObject ContactCustom(JsonNode bizObjectNode) {
+        JSONObject contactCustom = new JSONObject();
+
+        // 渠道
+        String fromChannel = bizObjectNode.path("F0000210").asText();
+        if (fromChannel != null && !fromChannel.isEmpty() && !"null".equals(fromChannel)) {
+            contactCustom.put("cyqd", fromChannel);
+        }
+        // 客户创建时间
+        String khcjsjStr = bizObjectNode.path("F0000002").asText();
+        if (khcjsjStr != null && !khcjsjStr.isEmpty() && !"null".equals(khcjsjStr)) {
+            long khcjsj = convertToTimestamp(khcjsjStr);
+            contactCustom.put("khcjsj", khcjsj);
+        }
+        // 氚云客户编号
+        String cykhbh = bizObjectNode.path("SeqNo").asText();
+        if (cykhbh != null && !cykhbh.isEmpty() && !"null".equals(cykhbh)) {
+            contactCustom.put("cykhbh", cykhbh);
+        }
+//        String wxId = bizObjectNode.path("F0000192").asText("");//微信  "property_source": 1
+//        String email = bizObjectNode.path("F0000195").asText("");//邮箱 "property_source": 1
+        String cardId = bizObjectNode.path("F0000196").asText("");//身份证号
+        if (cardId != null && !cardId.isEmpty() && !"null".equals(cardId)) {
+            contactCustom.put("sfzh", cardId);
+        }
+        // 客户类型
+        String khlxStr = bizObjectNode.path("F0000005").asText();
+        if (khlxStr != null && !khlxStr.isEmpty() && !"null".equals(khlxStr)) {
+            String khlxValue = getCustomerTypeValueByLabel(khlxStr);
+            if (khlxValue != null) {
+                try {
+                    int khlx = Integer.parseInt(khlxValue);
+                    contactCustom.put("khlx", khlx);
+                } catch (NumberFormatException e) {
+                    log.warn("khlx 字段转换失败: {}, 值: {}", khlxStr, khlxValue);
+                }
+            }
+        }
+//        String userName = bizObjectNode.path("F0000001").asText("");//联系人 "property_source": 1
+//        String remark = bizObjectNode.path("F0000164").asText("");//备注 "property_source": 1
+//        JsonNode contactWays = bizObjectNode.path("D150508sbr6fqych1u2hldkud3x");//联系方式 "property_source": 1
+//        String owner = bizObjectNode.path("OwnerId").asText("");//归属人 "property_source": 1
+//        String ownerId = getUserIdByName(owner);
+//        String relatedUsers = bizObjectNode.path("F0000188").asText();//协作人 "property_source": 1
+//        int addUsers = bizObjectNode.path("F0000197").asInt();//添加员工 "property_source": 1
+        // 客户状态
+        String khztStr = bizObjectNode.path("F0000003").asText();
+        if (khztStr != null && !khztStr.isEmpty() && !"null".equals(khztStr)) {
+            String khztValue = getCustomerStatusValueByLabel(khztStr);
+            if (khztValue != null) {
+                try {
+                    int khzt = Integer.parseInt(khztValue);
+                    contactCustom.put("khzt", khzt);
+                } catch (NumberFormatException e) {
+                    log.warn("khzt 字段转换失败: {}, 值: {}", khztStr, khztValue);
+                }
+            }
+        }
+        // 客户活跃度
+        String khhydStr = bizObjectNode.path("F0000116").asText();
+        if (khhydStr != null && !khhydStr.isEmpty() && !"null".equals(khhydStr)) {
+            String khhydValue = getCustomerEngagementValueByLabel(khhydStr);
+            if (khhydValue != null) {
+                try {
+                    int khhyd = Integer.parseInt(khhydValue);
+                    contactCustom.put("khhyd", khhyd);
+                } catch (NumberFormatException e) {
+                    log.warn("khhyd 字段转换失败: {}, 值: {}", khhydStr, khhydValue);
+                }
+            }
+        }
+        // 客户重要性
+        String khzyxStr = bizObjectNode.path("F0000108").asText();
+        if (khzyxStr != null && !khzyxStr.isEmpty() && !"null".equals(khzyxStr)) {
+            String khzyxValue = getCustomerImportanceValueByLabel(khzyxStr);
+            if (khzyxValue != null) {
+                try {
+                    int khzyx = Integer.parseInt(khzyxValue);
+                    contactCustom.put("khzyx", khzyx);
+                } catch (NumberFormatException e) {
+                    log.warn("khzyx 字段转换失败: {}, 值: {}", khzyxStr, khzyxValue);
+                }
+            }
+        }
+        // 客户来源
+        String khlyStr = bizObjectNode.path("F0000055").asText();
+        if (khlyStr != null && !khlyStr.isEmpty() && !"null".equals(khlyStr)) {
+            String khlyValue = getSourceSourceValueByLabel(khlyStr);
+            if (khlyValue != null) {
+                try {
+                    int khly = Integer.parseInt(khlyValue);
+                    contactCustom.put("khly", khly);
+                } catch (NumberFormatException e) {
+                    log.warn("khly 字段转换失败: {}, 值: {}", khlyStr, khlyValue);
+                }
+            }
+        }
+        // 推荐人
+        String tjr = bizObjectNode.path("F0000218").asText();
+        contactCustom.put("tjr", tjr);
+
+        // 一级渠道
+        String yjqd = bizObjectNode.path("F0000219").asText();
+        contactCustom.put("yjqd", yjqd);
+        // 二级渠道
+        String ejqd = bizObjectNode.path("F0000220").asText();
+        contactCustom.put("ejqd", ejqd);
+        // 是否肖老师引流客户
+        String sfxlsylkhStr = bizObjectNode.path("F0000170").asText();
+        if (sfxlsylkhStr != null && !sfxlsylkhStr.isEmpty() && !"null".equals(sfxlsylkhStr)) {
+            String sfxlsylkhValue = getIsTeacherValueByLabel(sfxlsylkhStr);
+            if (sfxlsylkhValue != null) {
+                try {
+                    int sfxlsylkh = Integer.parseInt(sfxlsylkhValue);
+                    contactCustom.put("sfxlsylkh", sfxlsylkh);
+                } catch (NumberFormatException e) {
+                    log.warn("sfxlsylkh 字段转换失败: {}, 值: {}", sfxlsylkhStr, sfxlsylkhValue);
+                }
+            }
+        }
+//        String sourceStr = bizObjectNode.path("F0000055").asText("");//来源-数组 "property_source": 1
+//        int source = Integer.parseInt(getSourceValueByLabel(sourceStr));
+//        String gender = bizObjectNode.path("F0000107").asText("");//性别-数值
+        // 生日日期
+        String srrqStr = bizObjectNode.path("F0000091").asText();
+        if (srrqStr != null && !srrqStr.isEmpty() && !"null".equals(srrqStr)) {
+            long srrq = convertToTimestamp(srrqStr);
+            if (srrq > 0) {
+                contactCustom.put("srrq", srrq);
+            }
+        }
+        // 生日月份
+        String sryf = bizObjectNode.path("F0000102").asText();
+        if (sryf != null && !sryf.isEmpty() && !"null".equals(sryf)) {
+            contactCustom.put("sryf", sryf);
+        }
+//        String regionCode = bizObjectNode.path("F0000114").asText("");//地域 对应氚云的客户地址(省)+客户地址(市)+客户地址(详细地址)  "property_source": 1
+        // 客户需求 - 转换为数字数组类型
+        String khxqStr = bizObjectNode.path("F0000119").asText();
+//        System.out.println("khxqStr: " + khxqStr);
+        if (khxqStr != null && !khxqStr.isEmpty() && !"null".equals(khxqStr)) {
+            String[] values = khxqStr.split(";"); // 按分号分割
+            JSONArray khxqArray = new JSONArray();
+
+            for (String value : values) {
+                value = value.trim(); // 去除空格
+                if (!value.isEmpty()) {
+                    String khxqValue = getCustomerNeedsValueByLabel(value);
+                    if (khxqValue != null) {
+                        try {
+                            int khxq = Integer.parseInt(khxqValue);
+                            khxqArray.add(khxq); // 添加到数组中
+                        } catch (NumberFormatException e) {
+                            log.warn("khxq 字段转换失败: {}, 值: {}", value, khxqValue);
+                        }
+                    }
+                }
+            }
+
+            if (!khxqArray.isEmpty()) {
+                contactCustom.put("khxq", khxqArray);
+//                System.out.println("khxqArray: " + khxqArray);
+            }
+        }
+//        String customerCompanyList = bizObjectNode.path("F0000202").asText("");//企业 "property_source": 1
+//        String corpAddress = bizObjectNode.path("F0000204").asText("");//企业地址 "property_source": 1
+//        String tagGroups = bizObjectNode.path("F0000201").asText("");//标签 "property_source": 1
+        // 学历
+        String xlStr = bizObjectNode.path("F0000122").asText();
+        if (xlStr != null && !xlStr.isEmpty() && !"null".equals(xlStr)) {
+            String xlValue = getEducationValueByLabel(xlStr);
+            if (xlValue != null) {
+                try {
+                    int xl = Integer.parseInt(xlValue);
+                    contactCustom.put("xl", xl);
+                } catch (NumberFormatException e) {
+                    log.warn("xl 字段转换失败: {}, 值: {}", xlStr, xlValue);
+                }
+            }
+        }
+        // 职业
+        String zhiyeStr = bizObjectNode.path("F0000123").asText();
+        if (zhiyeStr != null && !zhiyeStr.isEmpty() && !"null".equals(zhiyeStr)) {
+            String zhiyeValue = getOccupationValueByLabel(zhiyeStr);
+            if (zhiyeValue != null) {
+                try {
+                    int zhiye = Integer.parseInt(zhiyeValue);
+                    contactCustom.put("zhiye", zhiye);
+                } catch (NumberFormatException e) {
+                    log.warn("zhiye 字段转换失败: {}, 值: {}", zhiyeStr, zhiyeValue);
+                }
+            }
+        }
+        // 客户年收入
+        String khnsrStr = bizObjectNode.path("F0000132").asText();
+        if (khnsrStr != null && !khnsrStr.isEmpty() && !"null".equals(khnsrStr)) {
+            try {
+                double khnsrValue = Double.parseDouble(khnsrStr);
+                contactCustom.put("khnsr", String.valueOf(khnsrValue));
+            } catch (NumberFormatException e) {
+                log.warn("khnsr 字段转换失败: {}", khnsrStr);
+            }
+        }
+        // 说明(备注)
+        String shuoming = bizObjectNode.path("F0000164").asText();
+        if (shuoming != null && !shuoming.isEmpty() && !"null".equals(shuoming)) {
+            contactCustom.put("shuoming", shuoming);
+        }
+//        String newestProgressTimeStr = bizObjectNode.path("F0000023").asText("");//上次写跟进记录时间 "property_source": 1
+//        long newestProgressTime = convertToTimestamp(newestProgressTimeStr);
+//        String lastActiveTalkTimeStr = bizObjectNode.path("F0000198").asText("");//上次客户发消息时间 "property_source": 1
+//        long lastActiveTalkTime = convertToTimestamp(lastActiveTalkTimeStr);
+//        String lastPassitiveTalkTimeStr = bizObjectNode.path("F0000199").asText("");//上次员工发消息时间 "property_source": 1
+//        long lastPassitiveTalkTime = convertToTimestamp(lastPassitiveTalkTimeStr);
+//        String lastTalkTimeStr = bizObjectNode.path("F0000200").asText("");//上次聊天时间 "property_source": 1
+//        long lastTalkTime = convertToTimestamp(lastTalkTimeStr);
+        //下次跟进时间
+        String xcgjsjStr = bizObjectNode.path("F0000100").asText();
+        if (xcgjsjStr != null && !xcgjsjStr.isEmpty() && !"null".equals(xcgjsjStr)) {
+            long xcgjsj = convertToTimestamp(xcgjsjStr);
+            if (xcgjsj > 0) {
+                contactCustom.put("xcgjsj", xcgjsj);
+            }
+        }
+//        String claimTimeStr = bizObjectNode.path("F0000169").asText("");//领取时间 "property_source": 1
+//        long claimTime = convertToTimestamp(claimTimeStr);
+        //首次成交日期
+        String sccjrqStr = bizObjectNode.path("F0000056").asText();
+        if (sccjrqStr != null && !sccjrqStr.isEmpty() && !"null".equals(sccjrqStr)) {
+            long sccjrq = convertToTimestamp(sccjrqStr);
+            if (sccjrq > 0) {
+                contactCustom.put("sccjrq", sccjrq);
+            }
+        }
+        //渠道结算截止
+        String qdjsjzStr = bizObjectNode.path("F0000057").asText();
+        if (qdjsjzStr != null && !qdjsjzStr.isEmpty() && !"null".equals(qdjsjzStr)) {
+            long qdjsjz = convertToTimestamp(qdjsjzStr);
+            if (qdjsjz > 0) {
+                contactCustom.put("qudaojiesuanjiezhi", qdjsjz);
+            }
+        }
+        //累计已充值
+        String ljyczStr = bizObjectNode.path("F0000043").asText();
+        if (ljyczStr != null && !ljyczStr.isEmpty() && !"null".equals(ljyczStr)) {
+            try {
+                double ljyczValue = Double.parseDouble(ljyczStr);
+                contactCustom.put("ljycz", String.valueOf(ljyczValue));
+            } catch (NumberFormatException e) {
+                log.warn("ljycz 字段转换失败: {}", ljyczStr);
+            }
+        }
+        //累计燃点
+        String ljrdStr = bizObjectNode.path("F0000045").asText();
+        if (ljrdStr != null && !ljrdStr.isEmpty() && !"null".equals(ljrdStr)) {
+            try {
+                double ljrdValue = Double.parseDouble(ljrdStr);
+                contactCustom.put("ljrd", String.valueOf(ljrdValue));
+            } catch (NumberFormatException e) {
+                log.warn("ljrd 字段转换失败: {}", ljrdStr);
+            }
+        }
+
+        //剩余燃点
+        String syrdStr = bizObjectNode.path("F0000046").asText();
+        if (syrdStr != null && !syrdStr.isEmpty() && !"null".equals(syrdStr)) {
+            try {
+                double syrdValue = Double.parseDouble(syrdStr);
+                contactCustom.put("syrd", String.valueOf(syrdValue));
+            } catch (NumberFormatException e) {
+                log.warn("syrd 字段转换失败: {}", syrdStr);
+            }
+        }
+        //零钱剩余
+        String lqsyStr = bizObjectNode.path("F0000049").asText();
+        if (lqsyStr != null && !lqsyStr.isEmpty() && !"null".equals(lqsyStr)) {
+            try {
+                double lqsyValue = Double.parseDouble(lqsyStr);
+                contactCustom.put("lqsy", String.valueOf(lqsyValue));
+            } catch (NumberFormatException e) {
+                log.warn("lqsy 字段转换失败: {}", lqsyStr);
+            }
+        }
+        //零钱袋入账单位
+        String lqdrzdw = bizObjectNode.path("F0000121").asText();
+        if (lqdrzdw != null && !lqdrzdw.isEmpty() && !"null".equals(lqdrzdw)) {
+            contactCustom.put("lqdrzdw", lqdrzdw);
+        }
+
+        // 合计已开票金额
+        String hjykpjeStr = bizObjectNode.path("F0000133").asText();
+        if (hjykpjeStr != null && !hjykpjeStr.isEmpty() && !"null".equals(hjykpjeStr)) {
+            try {
+                double hjykpjeValue = Double.parseDouble(hjykpjeStr);
+                contactCustom.put("hjykpje", String.valueOf(hjykpjeValue));
+            } catch (NumberFormatException e) {
+                log.warn("hjykpje 字段转换失败: {}", hjykpjeStr);
+            }
+        }
+        JsonNode membershipCardDetails = bizObjectNode.path("D150508sgg7hj6i7ae2zk0ow6sbd");//会员卡明细-子表单
+        StringBuilder membershipCardStringBuilder = new StringBuilder();
+        if (membershipCardDetails.isArray()) {
+            for (int i = 0; i < membershipCardDetails.size(); i++) {
+                JsonNode row = membershipCardDetails.get(i);
+                String hyklx = row.path("F0000068").asText();//会员卡.会员卡类型
+                String hyksyye = String.valueOf(row.path("F0000071").asDouble());//会员卡.剩余余额
+                String zhuangtai = row.path("F0000079").asText();//会员卡.状态
+
+                // 构建格式: {hyklx,hyksyye,zhuangtai}
+                if (i > 0) {
+                    membershipCardStringBuilder.append(",");
+                }
+                membershipCardStringBuilder.append("{")
+                        .append(hyklx)
+                        .append(",")
+                        .append(hyksyye)
+                        .append(",")
+                        .append(zhuangtai)
+                        .append("}");
+            }
+            String membershipCardArrayString = membershipCardStringBuilder.toString();
+            contactCustom.put("hyk2", membershipCardArrayString);
+//            System.out.println("会员卡明细字段: " + membershipCardArrayString);
+        }
+
+        JsonNode customerTagDetails = bizObjectNode.path("D150508st8zyrxlei0u6cfrgrut9");//客户标签-子表单
+        // 使用列表来收集标签名称
+        List<String> curedSymptomsList = new ArrayList<>();    // 已治愈症状标签列表
+        List<String> uncuredSymptomsList = new ArrayList<>();  // 未治愈症状标签列表
+        if (customerTagDetails.isArray()) {
+            for (int i = 0; i < customerTagDetails.size(); i++) {
+                JsonNode row = customerTagDetails.get(i);
+
+//                String bqfl = row.path("F0000109").asText();//客户标签.标签分类
+                String khbqbq = row.path("F0000112").asText();//客户标签.标签
+                String khbqsfzy = row.path("F0000113").asText();//客户标签.是否治愈
+//                String khbqsm = row.path("F0000111").asText();//客户标签.说明
+
+                // 根据是否治愈进行分类
+                if ("是".equals(khbqsfzy)) {
+                    curedSymptomsList.add(khbqbq);
+                } else if ("否".equals(khbqsfzy)) {
+                    uncuredSymptomsList.add(khbqbq);
+                }
+            }
+
+            // 将列表用逗号拼接成字符串
+            String curedSymptomsString = String.join(",", curedSymptomsList);
+            String uncuredSymptomsString = String.join(",", uncuredSymptomsList);
+
+//            System.out.println("已治愈症状字段: " + curedSymptomsString);
+//            System.out.println("未治愈症状字段: " + uncuredSymptomsString);
+//        String yzyzz = bizObjectNode.path("F0000193").asText();//氚云已治愈症状
+//        String wzyzz = bizObjectNode.path("F0000194").asText();//氚云未治愈症状
+            contactCustom.put("yzyzz", curedSymptomsString);
+            contactCustom.put("wzyzz", uncuredSymptomsString);
+        }
+
+        return contactCustom;
+    }
+}

+ 449 - 0
mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/mapping/LabelToValue.java

@@ -0,0 +1,449 @@
+package com.malk.rjk.mapping;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class LabelToValue {
+
+    private static final Map<String, String> SOURCE_CHANNEL_MAPPING = new HashMap<>();
+    // 客户LabelToValue
+    static {
+        // 一级分类
+        SOURCE_CHANNEL_MAPPING.put("官网", "1");
+        SOURCE_CHANNEL_MAPPING.put("活动", "200001");
+        SOURCE_CHANNEL_MAPPING.put("自然流量", "400001");
+        SOURCE_CHANNEL_MAPPING.put("广告平台", "10001");
+        SOURCE_CHANNEL_MAPPING.put("线下", "20001");
+        SOURCE_CHANNEL_MAPPING.put("其他", "30001");
+        SOURCE_CHANNEL_MAPPING.put("直播", "31901");
+
+        // 二级分类 - 官网下
+        SOURCE_CHANNEL_MAPPING.put("注册/登录", "1001");
+        SOURCE_CHANNEL_MAPPING.put("表单留资", "1002");
+        SOURCE_CHANNEL_MAPPING.put("拨打电话", "1003");
+        SOURCE_CHANNEL_MAPPING.put("在线客服", "1004");
+        SOURCE_CHANNEL_MAPPING.put("电子邮件", "1005");
+        SOURCE_CHANNEL_MAPPING.put("添加微信", "1006");
+        SOURCE_CHANNEL_MAPPING.put("官网-其他", "1007");  // 加前缀避免重复
+
+        // 二级分类 - 活动下
+        SOURCE_CHANNEL_MAPPING.put("朋友圈", "201001");
+        SOURCE_CHANNEL_MAPPING.put("公众号", "201002");
+        SOURCE_CHANNEL_MAPPING.put("活动-官网", "201003");  // 加前缀避免重复
+        SOURCE_CHANNEL_MAPPING.put("现场报名", "201004");
+        SOURCE_CHANNEL_MAPPING.put("活动-其他", "201105");  // 加前缀避免重复
+
+        // 二级分类 - 自然流量下
+        SOURCE_CHANNEL_MAPPING.put("百度", "401001");
+        SOURCE_CHANNEL_MAPPING.put("360", "401002");
+        SOURCE_CHANNEL_MAPPING.put("搜狗", "401003");
+        SOURCE_CHANNEL_MAPPING.put("神马", "401004");
+        SOURCE_CHANNEL_MAPPING.put("UC", "401005");
+        SOURCE_CHANNEL_MAPPING.put("必应", "401006");
+        SOURCE_CHANNEL_MAPPING.put("谷歌", "401007");
+        SOURCE_CHANNEL_MAPPING.put("小红书", "401008");
+        SOURCE_CHANNEL_MAPPING.put("自然流量-其他", "401009");  // 加前缀避免重复
+
+        // 二级分类 - 广告平台下
+        SOURCE_CHANNEL_MAPPING.put("百度广告", "11001");
+        SOURCE_CHANNEL_MAPPING.put("腾讯广告", "11002");
+        SOURCE_CHANNEL_MAPPING.put("巨量引擎", "11004");
+        SOURCE_CHANNEL_MAPPING.put("360广告", "11005");
+        SOURCE_CHANNEL_MAPPING.put("搜狗广告", "11006");
+        SOURCE_CHANNEL_MAPPING.put("神马广告", "11007");
+        SOURCE_CHANNEL_MAPPING.put("快手", "11008");
+        SOURCE_CHANNEL_MAPPING.put("UC广告", "11009");
+        SOURCE_CHANNEL_MAPPING.put("谷歌广告", "11010");
+        SOURCE_CHANNEL_MAPPING.put("必应广告", "11011");
+        SOURCE_CHANNEL_MAPPING.put("小红书广告", "11013");
+        SOURCE_CHANNEL_MAPPING.put("知乎", "11114");
+        SOURCE_CHANNEL_MAPPING.put("广告平台-其他", "11099");  // 加前缀避免重复
+        SOURCE_CHANNEL_MAPPING.put("美团", "11115");
+
+        // 二级分类 - 线下
+        SOURCE_CHANNEL_MAPPING.put("会销", "21001");
+        SOURCE_CHANNEL_MAPPING.put("线下门店", "21002");
+        SOURCE_CHANNEL_MAPPING.put("现场拜访", "21003");
+        SOURCE_CHANNEL_MAPPING.put("线下-其他", "21099");  // 加前缀避免重复
+
+        // 二级分类 - 其他
+        SOURCE_CHANNEL_MAPPING.put("销售导入", "31001");
+        SOURCE_CHANNEL_MAPPING.put("欢迎语", "31002");
+        SOURCE_CHANNEL_MAPPING.put("其他-朋友圈", "31003");  // 加前缀避免重复
+        SOURCE_CHANNEL_MAPPING.put("群聊", "31004");
+        SOURCE_CHANNEL_MAPPING.put("短信", "31005");
+        SOURCE_CHANNEL_MAPPING.put("邮件", "31006");
+        SOURCE_CHANNEL_MAPPING.put("其他-公众号", "31007");  // 加前缀避免重复
+        SOURCE_CHANNEL_MAPPING.put("拓客", "31008");
+        SOURCE_CHANNEL_MAPPING.put("APP", "31009");
+        SOURCE_CHANNEL_MAPPING.put("海报", "31010");
+        SOURCE_CHANNEL_MAPPING.put("其他-其他", "31099");  // 加前缀避免重复
+    }
+
+    private static final Map<String, String> GENDER_MAPPING = new HashMap<>();
+    static {
+        GENDER_MAPPING.put("男", "1");
+        GENDER_MAPPING.put("女", "2");
+        GENDER_MAPPING.put("未知", "0");
+    }
+    private static final Map<String, String> STAGE_MAPPING = new HashMap<>();
+    static {
+        STAGE_MAPPING.put("需求沟通", "1");
+        STAGE_MAPPING.put("方案评估", "2");
+        STAGE_MAPPING.put("赢单", "100");
+        STAGE_MAPPING.put("丢单", "99");
+    }
+
+    private static final Map<String, String> FAIL_REASON_MAPPING = new HashMap<>();
+    static {
+        FAIL_REASON_MAPPING.put("无法满足客户需求", "1");
+        FAIL_REASON_MAPPING.put("价格超出预期", "2");
+        FAIL_REASON_MAPPING.put("同行调研", "3");
+        FAIL_REASON_MAPPING.put("其它", "99");
+    }
+
+    private static final Map<String, String> CUSTOMER_TYPE_MAPPING = new HashMap<>();
+    private static final Map<String, String> REVERSE_CUSTOMER_TYPE_MAPPING  = new HashMap<>();
+    static {
+        CUSTOMER_TYPE_MAPPING.put("正常", "1");
+        CUSTOMER_TYPE_MAPPING.put("灰名单", "2");
+        CUSTOMER_TYPE_MAPPING.put("黑名单", "3");
+        // 创建反向映射
+        for (Map.Entry<String, String> entry : CUSTOMER_TYPE_MAPPING.entrySet()) {
+            REVERSE_CUSTOMER_TYPE_MAPPING.put(entry.getValue(), entry.getKey());
+        }
+    }
+
+    private static final Map<String, String> CUSTOMER_STATUS_MAPPING = new HashMap<>();
+    private static final Map<String, String> REVERSE_CUSTOMER_STATUS_MAPPING = new HashMap<>();
+    static {
+        CUSTOMER_STATUS_MAPPING.put("无效客户", "1");
+        CUSTOMER_STATUS_MAPPING.put("忠诚客户", "2");
+        CUSTOMER_STATUS_MAPPING.put("成功客户", "3");
+        CUSTOMER_STATUS_MAPPING.put("意向客户", "4");
+        CUSTOMER_STATUS_MAPPING.put("潜在客户", "5");
+        // 创建反向映射
+        for (Map.Entry<String, String> entry : CUSTOMER_STATUS_MAPPING.entrySet()) {
+            REVERSE_CUSTOMER_STATUS_MAPPING.put(entry.getValue(), entry.getKey());
+        }
+    }
+
+    private static final Map<String, String> CUSTOMER_ENGAGEMENT_MAPPING = new HashMap<>();
+    private static final Map<String, String> REVERSE_CUSTOMER_ENGAGEMENT_MAPPING = new HashMap<>();
+    static {
+        CUSTOMER_ENGAGEMENT_MAPPING.put("被动客户", "1");
+        CUSTOMER_ENGAGEMENT_MAPPING.put("积极客户", "2");
+        CUSTOMER_ENGAGEMENT_MAPPING.put("沉睡客户", "3");
+        CUSTOMER_ENGAGEMENT_MAPPING.put("已放弃客户", "4");
+        CUSTOMER_ENGAGEMENT_MAPPING.put("内部人员", "5");
+        CUSTOMER_ENGAGEMENT_MAPPING.put("内部人员-离职", "6");
+        // 创建反向映射
+        for (Map.Entry<String, String> entry : CUSTOMER_ENGAGEMENT_MAPPING.entrySet()) {
+            REVERSE_CUSTOMER_ENGAGEMENT_MAPPING.put(entry.getValue(), entry.getKey());
+        }
+    }
+
+    private static final Map<String, String> CUSTOMER_IMPORTANCE_MAPPING = new HashMap<>();
+    private static final Map<String, String> REVERSE_CUSTOMER_IMPORTANCE_MAPPING = new HashMap<>();
+    static {
+        CUSTOMER_IMPORTANCE_MAPPING.put("1星", "1");
+        CUSTOMER_IMPORTANCE_MAPPING.put("2星", "2");
+        CUSTOMER_IMPORTANCE_MAPPING.put("3星", "3");
+        CUSTOMER_IMPORTANCE_MAPPING.put("4星", "4");
+        CUSTOMER_IMPORTANCE_MAPPING.put("5星", "5");
+        // 创建反向映射
+        for (Map.Entry<String, String> entry : CUSTOMER_IMPORTANCE_MAPPING.entrySet()) {
+            REVERSE_CUSTOMER_IMPORTANCE_MAPPING.put(entry.getValue(), entry.getKey());
+        }
+    }
+
+    private static final Map<String, String> CUSTOMER_SOURCE_MAPPING = new HashMap<>();
+    private static final Map<String, String> REVERSE_CUSTOMER_SOURCE_MAPPING = new HashMap<>();
+    static {
+        // 新增的客户来源渠道映射
+        CUSTOMER_SOURCE_MAPPING.put("小鹅通-老客户介绍", "1");
+        CUSTOMER_SOURCE_MAPPING.put("市场活动", "2");
+        CUSTOMER_SOURCE_MAPPING.put("抖音号", "3");
+        CUSTOMER_SOURCE_MAPPING.put("视频号", "4");
+        CUSTOMER_SOURCE_MAPPING.put("小红书", "5");
+        CUSTOMER_SOURCE_MAPPING.put("门店路过", "6");
+        CUSTOMER_SOURCE_MAPPING.put("TED演讲", "7");
+        CUSTOMER_SOURCE_MAPPING.put("书籍", "8");
+        CUSTOMER_SOURCE_MAPPING.put("公域投流", "9");
+        CUSTOMER_SOURCE_MAPPING.put("大众点评", "10");
+        CUSTOMER_SOURCE_MAPPING.put("市场部", "11");
+        CUSTOMER_SOURCE_MAPPING.put("信息采集", "12");
+        CUSTOMER_SOURCE_MAPPING.put("公众号", "13");
+        CUSTOMER_SOURCE_MAPPING.put("老客户介绍 (非渠道)", "14");
+        CUSTOMER_SOURCE_MAPPING.put("渠道推荐", "15");
+        CUSTOMER_SOURCE_MAPPING.put("公益行", "16");
+        CUSTOMER_SOURCE_MAPPING.put("互动吧", "17");
+        CUSTOMER_SOURCE_MAPPING.put("活动行", "18");
+        CUSTOMER_SOURCE_MAPPING.put("小鹅通-账号绑定", "19");
+        CUSTOMER_SOURCE_MAPPING.put("新媒体-B类推广", "20");
+        CUSTOMER_SOURCE_MAPPING.put("留言", "21");
+        CUSTOMER_SOURCE_MAPPING.put("小鹅通-信息采集", "22");
+        CUSTOMER_SOURCE_MAPPING.put("其它", "23");
+
+        // 创建反向映射
+        for (Map.Entry<String, String> entry : CUSTOMER_SOURCE_MAPPING.entrySet()) {
+            REVERSE_CUSTOMER_SOURCE_MAPPING.put(entry.getValue(), entry.getKey());
+        }
+    }
+
+    private static final Map<String, String> SOURCE_MAPPING = new HashMap<>();
+    static {
+        SOURCE_MAPPING.put("单条创建", "0");
+        SOURCE_MAPPING.put("批量导入", "1");
+        SOURCE_MAPPING.put("移动表单加微", "2");
+        SOURCE_MAPPING.put("线索转发", "3");
+        SOURCE_MAPPING.put("注册试用", "4");
+        SOURCE_MAPPING.put("系统导入", "5");
+        SOURCE_MAPPING.put("智能收集表", "6");
+        SOURCE_MAPPING.put("活动营销", "7");
+        SOURCE_MAPPING.put("链接加微", "8");
+        SOURCE_MAPPING.put("普通智能码", "100");
+        SOURCE_MAPPING.put("资料库加微", "101");
+        SOURCE_MAPPING.put("高级智能码(扫码加微)", "102");
+        SOURCE_MAPPING.put("表单加微", "103");
+        SOURCE_MAPPING.put("手机号搜好友加微", "104");
+        SOURCE_MAPPING.put("一键加微", "105");
+        SOURCE_MAPPING.put("客服加微", "106");
+        SOURCE_MAPPING.put("注册加微", "107");
+        SOURCE_MAPPING.put("短信加微", "108");
+        SOURCE_MAPPING.put("短链加微", "109");
+        SOURCE_MAPPING.put("活动营销加微", "110");
+        SOURCE_MAPPING.put("智能收集表加微", "111");
+        SOURCE_MAPPING.put("集客通小程序版一键加微", "112");
+        SOURCE_MAPPING.put("海报库", "113");
+        SOURCE_MAPPING.put("一键加微获客链接版", "114");
+        SOURCE_MAPPING.put("表单加微获客链接版", "115");
+        SOURCE_MAPPING.put("获客助手", "116");
+        SOURCE_MAPPING.put("集客通获客助手表单加微", "120");
+        SOURCE_MAPPING.put("未知来源", "1000");
+        SOURCE_MAPPING.put("扫描二维码", "1001");
+        SOURCE_MAPPING.put("搜索手机号", "1002");
+        SOURCE_MAPPING.put("名片分享", "1003");
+        SOURCE_MAPPING.put("群聊", "1004");
+        SOURCE_MAPPING.put("手机通讯录", "1005");
+        SOURCE_MAPPING.put("微信联系人", "1006");
+        SOURCE_MAPPING.put("来自微信的添加好友申请", "1007");
+        SOURCE_MAPPING.put("安装第三方应用时自动添加的客服人员", "1008");
+        SOURCE_MAPPING.put("搜索邮箱", "1009");
+        SOURCE_MAPPING.put("视频号主页添加", "1010");
+        SOURCE_MAPPING.put("通过日程参与人添加", "1011");
+        SOURCE_MAPPING.put("通过会议参与人添加", "1012");
+        SOURCE_MAPPING.put("添加微信好友对应的企业微信", "1013");
+        SOURCE_MAPPING.put("通过智慧硬件专属客服添加", "1014");
+        SOURCE_MAPPING.put("通过上门服务客服添加", "1015");
+        SOURCE_MAPPING.put("通过获客链接添加", "1016");
+        SOURCE_MAPPING.put("通过定制开发添加", "1017");
+        SOURCE_MAPPING.put("通过需求回复添加", "1018");
+        SOURCE_MAPPING.put("通过第三方售前客服添加", "1021");
+        SOURCE_MAPPING.put("通过可能的商务伙伴添加", "1022");
+        SOURCE_MAPPING.put("通过接受微信账号收到的好友申请添加", "1024");
+        SOURCE_MAPPING.put("内部成员共享", "1201");
+        SOURCE_MAPPING.put("管理员/负责人分配", "1202");
+    }
+
+    private static final Map<String, String> IS_TEACHER_MAPPING = new HashMap<>();
+    static {
+        IS_TEACHER_MAPPING.put("是", "1");
+        IS_TEACHER_MAPPING.put("否", "2");
+    }
+
+    private static final Map<String, String> CUSTOMER_NEEDS_MAPPING = new HashMap<>();
+    private static final Map<String, String> REVERSE_CUSTOMER_NEEDS_MAPPING = new HashMap<>();
+    static {
+        CUSTOMER_NEEDS_MAPPING.put("排盘课", "1");
+        CUSTOMER_NEEDS_MAPPING.put("线上课程", "2");
+        CUSTOMER_NEEDS_MAPPING.put("专业班", "3");
+        CUSTOMER_NEEDS_MAPPING.put("游学营", "5");
+        CUSTOMER_NEEDS_MAPPING.put("肖老师个案", "6");
+        CUSTOMER_NEEDS_MAPPING.put("肖老师身心评估", "7");
+        CUSTOMER_NEEDS_MAPPING.put("直播", "8");
+        CUSTOMER_NEEDS_MAPPING.put("学习爱好者", "9");
+        CUSTOMER_NEEDS_MAPPING.put("中医", "10");
+        CUSTOMER_NEEDS_MAPPING.put("五行系统关系教练", "11");
+        CUSTOMER_NEEDS_MAPPING.put("调理", "12");
+        CUSTOMER_NEEDS_MAPPING.put("沙龙", "13");
+        CUSTOMER_NEEDS_MAPPING.put("工作坊", "14");
+        CUSTOMER_NEEDS_MAPPING.put("身心学基础班", "15");
+        CUSTOMER_NEEDS_MAPPING.put("中医心理学一家企结构分析师专业班", "16");
+        CUSTOMER_NEEDS_MAPPING.put("身心全息沟通师专业班", "17");
+        // 创建反向映射
+        for (Map.Entry<String, String> entry : CUSTOMER_NEEDS_MAPPING.entrySet()) {
+            REVERSE_CUSTOMER_NEEDS_MAPPING.put(entry.getValue(), entry.getKey());
+        }
+    }
+
+    private static final Map<String, String> EDUCATION_MAPPING = new HashMap<>();
+    private static final Map<String, String> REVERSE_EDUCATION_MAPPING = new HashMap<>();
+    static {
+        EDUCATION_MAPPING.put("博士", "1");
+        EDUCATION_MAPPING.put("硕士", "2");
+        EDUCATION_MAPPING.put("未知", "3");
+        EDUCATION_MAPPING.put("高中", "4");
+        EDUCATION_MAPPING.put("中专", "5");
+        EDUCATION_MAPPING.put("大专", "6");
+        EDUCATION_MAPPING.put("本科", "7");
+        // 创建反向映射
+        for (Map.Entry<String, String> entry : EDUCATION_MAPPING.entrySet()) {
+            REVERSE_EDUCATION_MAPPING.put(entry.getValue(), entry.getKey());
+        }
+    }
+
+    private static final Map<String, String> OCCUPATION_MAPPING = new HashMap<>();
+    private static final Map<String, String> REVERSE_OCCUPATION_MAPPING = new HashMap<>();
+    static {
+        OCCUPATION_MAPPING.put("心理咨询师", "1");
+        OCCUPATION_MAPPING.put("医生", "2");
+        OCCUPATION_MAPPING.put("教师", "3");
+        OCCUPATION_MAPPING.put("公司负责人", "4");
+        OCCUPATION_MAPPING.put("技术人员", "5");
+        OCCUPATION_MAPPING.put("高管", "6");
+        OCCUPATION_MAPPING.put("自由职业", "7");
+        OCCUPATION_MAPPING.put("全职太太", "8");
+        OCCUPATION_MAPPING.put("普通员工", "9");
+
+        // 创建反向映射
+        for (Map.Entry<String, String> entry : OCCUPATION_MAPPING.entrySet()) {
+            REVERSE_OCCUPATION_MAPPING.put(entry.getValue(), entry.getKey());
+        }
+    }
+    //商机LabelToValue
+    private static final Map<String, String> ORDER_TYPE_MAPPING = new HashMap<>();
+    static {
+        ORDER_TYPE_MAPPING.put("调理核销录入", "1");
+        ORDER_TYPE_MAPPING.put("产品购买", "2");
+        ORDER_TYPE_MAPPING.put("实物购买", "3");
+        ORDER_TYPE_MAPPING.put("零钱袋预存提交", "4");
+        ORDER_TYPE_MAPPING.put("合作方预存", "5");
+        ORDER_TYPE_MAPPING.put("换课", "6");
+        ORDER_TYPE_MAPPING.put("课程退款", "7");
+        ORDER_TYPE_MAPPING.put("会员卡退费", "8");
+        ORDER_TYPE_MAPPING.put("调理次数退费", "9");
+        ORDER_TYPE_MAPPING.put("零钱袋退费", "10");
+        ORDER_TYPE_MAPPING.put("渠道费用提现", "11");
+        ORDER_TYPE_MAPPING.put("课程转让", "12");
+        ORDER_TYPE_MAPPING.put("会员卡转费", "13");
+        ORDER_TYPE_MAPPING.put("零钱袋转费", "14");
+        ORDER_TYPE_MAPPING.put("会员卡充值", "15");
+        ORDER_TYPE_MAPPING.put("课程购买", "16");
+        ORDER_TYPE_MAPPING.put("调理项目购买", "17");
+    }
+
+    private static final Map<String, String> STORE_MAPPING = new HashMap<>();
+    static {
+        STORE_MAPPING.put("然健康(陆家嘴店)", "1");
+        STORE_MAPPING.put("然健康(合生汇店)", "2");
+        STORE_MAPPING.put("市场部", "3");
+        STORE_MAPPING.put("总部", "4");
+        STORE_MAPPING.put("然健康(武夷山店)", "5");
+    }
+
+    private static final Map<String, String> CASH_IN_UNIT_MAPPING = new HashMap<>();
+    static {
+        CASH_IN_UNIT_MAPPING.put("上海凡墨", "1");
+        CASH_IN_UNIT_MAPPING.put("上海智慧然宇", "2");
+        CASH_IN_UNIT_MAPPING.put("上海肖然健康", "3");
+        CASH_IN_UNIT_MAPPING.put("互动吧", "4");
+        CASH_IN_UNIT_MAPPING.put("北京智慧然宇", "5");
+        CASH_IN_UNIT_MAPPING.put("北京肖然健康", "6");
+        CASH_IN_UNIT_MAPPING.put("北京肖然文化", "7");
+        CASH_IN_UNIT_MAPPING.put("大众点评", "8");
+        CASH_IN_UNIT_MAPPING.put("小鹅通", "9");
+        CASH_IN_UNIT_MAPPING.put("武夷山智慧然宇", "10");
+        CASH_IN_UNIT_MAPPING.put("河北然元一", "11");
+        CASH_IN_UNIT_MAPPING.put("河北然启", "12");
+        CASH_IN_UNIT_MAPPING.put("活动行", "13");
+    }
+
+    private static final Map<String, String> ORDER_STATUS_MAPPING = new HashMap<>();
+    static {
+        ORDER_STATUS_MAPPING.put("已结束", "1");
+        ORDER_STATUS_MAPPING.put("已删除", "2");
+    }
+
+    public static String getSourceChannelValueByLabel(String label) {
+        return SOURCE_CHANNEL_MAPPING.get(label);
+    }
+
+    public static String getGenderValueByLabel(String label) {
+        return GENDER_MAPPING.get(label);
+    }
+    public static String getStageValueByLabel(String label) {
+        return STAGE_MAPPING.get(label);
+    }
+
+    public static String getFailReasonValueByLabel(String label) {
+        return FAIL_REASON_MAPPING.get(label);
+    }
+    public static String getCustomerTypeValueByLabel(String label) { //客户类型
+        return CUSTOMER_TYPE_MAPPING.get(label);
+    }
+    public static String getCustomerTypeLabelByValue(String value) {
+        return REVERSE_CUSTOMER_TYPE_MAPPING.get(value);
+    }
+    public static String getCustomerStatusValueByLabel(String label) { //客户状态
+        return CUSTOMER_STATUS_MAPPING.get(label);
+    }
+    public static String getCustomerStatusLabelByValue(String label) { //客户状态
+        return REVERSE_CUSTOMER_STATUS_MAPPING.get(label);
+    }
+    public static String getCustomerEngagementValueByLabel(String label) { //客户活跃度
+        return CUSTOMER_ENGAGEMENT_MAPPING.get(label);
+    }
+    public static String getCustomerEngagementLabelByValue(String value) {
+        return REVERSE_CUSTOMER_ENGAGEMENT_MAPPING.get(value);
+    }
+
+    public static String getCustomerImportanceValueByLabel(String label) { //客户重要性
+        return CUSTOMER_IMPORTANCE_MAPPING.get(label);
+    }
+    public static String getCustomerImportanceLabelByValue(String value) {
+        return REVERSE_CUSTOMER_IMPORTANCE_MAPPING.get(value);
+    }
+    public static String getSourceSourceValueByLabel(String label) { //客户来源
+        return CUSTOMER_SOURCE_MAPPING.get(label);
+    }
+    public static String getCustomerSourceLabelByValue(String value) { //客户来源
+        return REVERSE_CUSTOMER_SOURCE_MAPPING.get(value);
+    }
+    public static String getIsTeacherValueByLabel(String label) {
+        return IS_TEACHER_MAPPING.get(label);
+    }
+    public static String getSourceValueByLabel(String label) {
+        return SOURCE_MAPPING.get(label);
+    }
+    public static String getCustomerNeedsValueByLabel(String label) { //客户需求
+        return CUSTOMER_NEEDS_MAPPING.get(label);
+    }
+    public static String getCustomerNeedsLabelByValue(String value) { //学历
+        return REVERSE_CUSTOMER_NEEDS_MAPPING.get(value);
+    }
+    public static String getEducationValueByLabel(String label) { //学历
+        return EDUCATION_MAPPING.get(label);
+    }
+    public static String getEducationLabelByValue(String label) { //职业
+        return REVERSE_EDUCATION_MAPPING.get(label);
+    }
+    public static String getOccupationValueByLabel(String label) { //职业
+        return OCCUPATION_MAPPING.get(label);
+    }
+    public static String getOccupationLabelByValue(String value) {
+        return REVERSE_OCCUPATION_MAPPING.get(value);
+    }
+    public static String getOrderTypeValueByLabel(String label) {
+        return ORDER_TYPE_MAPPING.get(label);
+    }
+    public static String getStoreValueByLabel(String label) {
+        return STORE_MAPPING.get(label);
+    }
+    public static String getCashInUnitValueByLabel(String label) {
+        return CASH_IN_UNIT_MAPPING.get(label);
+    }
+    public static String getOrderStatusValueByLabel(String label) {
+        return ORDER_STATUS_MAPPING.get(label);
+    }
+}

+ 18 - 0
mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/server/HXClient.java

@@ -0,0 +1,18 @@
+package com.malk.rjk.server;
+
+import org.springframework.scheduling.annotation.Async;
+
+import java.util.Map;
+
+public interface HXClient {
+    /**
+     * 同步氚云附件到钉盘 [异步]
+     */
+    @Async
+    String syncDP(String sAttachments, String sProjectName, String typeName);
+
+    String CStoDP( Map<String, String> data );
+
+    String CWtoDP( Map<String, String> data );
+
+}

+ 80 - 0
mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/server/KingDee.java

@@ -0,0 +1,80 @@
+package com.malk.rjk.server;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+
+public interface KingDee {
+/**
+ * 测试用
+ */
+String get_WL_AA() throws IOException, NoSuchAlgorithmException, InvalidKeyException;
+
+
+    /**
+     * 获得所有商品写入氚云
+     */
+    String get_WL() throws IOException, NoSuchAlgorithmException, InvalidKeyException;
+
+
+    /**
+     * 获取商品列表
+     *
+     * @return
+     */
+    String get_WLlist(String domain, String appSecret, int pagenum,String tokne) throws IOException, NoSuchAlgorithmException, InvalidKeyException;
+
+    /**
+     * 根据商品number,获取商品详情并写入氚云
+     *
+     * @param shop_number
+     * @param appSecret
+     * @param domain
+     * @return
+     * @throws IOException
+     * @throws NoSuchAlgorithmException
+     * @throws InvalidKeyException
+     */
+    String get_WLByNumBher(String shop_number, String appSecret, String domain,String tokne) throws IOException, NoSuchAlgorithmException, InvalidKeyException;
+
+
+    /**
+     * 获取采购进度
+     */
+    void get_request() throws IOException, NoSuchAlgorithmException, InvalidKeyException;
+
+    /**
+     * 获取采购进度列表
+     *
+     * @return
+     */
+    String get_requestlist(String domain, String appSecret, String XMMC) throws IOException, NoSuchAlgorithmException, InvalidKeyException;
+
+    /**
+     *
+     */
+    /**
+     * 自定义字段查询(支持多表体)
+     * @return
+     */
+
+    String custom_mult_field(String entity_number,String name ,String domain,String appSecret) throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException;
+
+
+    //获取轻智造登录token
+    String getQZZtoken() throws IOException, NoSuchAlgorithmException, InvalidKeyException;
+
+    /**
+     * 获取生产进度
+     * @return
+     */
+    String get_SCJD()throws IOException, NoSuchAlgorithmException, InvalidKeyException ;
+    /**
+     * 获取生产进度列表
+     *
+     * @return
+     */
+    String get_SCJDlist(String access_token, String XMMC,String groupName,String  accountid,String domain) throws IOException, NoSuchAlgorithmException, InvalidKeyException;
+
+}

+ 24 - 0
mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/server/RjkServer.java

@@ -0,0 +1,24 @@
+package com.malk.rjk.server;
+
+import java.io.IOException;
+import java.util.Map;
+
+public interface RjkServer {
+
+    void getToken()throws IOException;
+
+    /**
+     * 氚云数据写入卫瓴
+     * @param data
+     */
+    void  H3UserToweiling(Map<String,String> data)  ;
+
+    void H3BusinessToWeiling(Map<String, String> data);
+
+    /**
+     * 根据卫瓴回调事件同步到氚云客户表
+     * @param callbackData 卫瓴回调数据
+     */
+    void syncWeilingToH3yun(Object callbackData);
+
+}

+ 35 - 0
mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/server/TBService.java

@@ -0,0 +1,35 @@
+package com.malk.rjk.server;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.malk.server.common.McR;
+import lombok.SneakyThrows;
+
+import java.util.Map;
+
+public interface TBService {
+    /*TODO:获取apptoken*/
+    McR AppAccessToken() throws JsonProcessingException;
+
+    /*TODO:钉钉userid获取到TBid*/
+    McR GetTbId(Map<String,String> data) throws JsonProcessingException;
+
+    /*TODO:查询项目(模糊查询名称)*/
+    @SneakyThrows
+    McR SearchProject(Map<String,String> data) throws JsonProcessingException;
+
+    /*TODO:搜索任务列表*/
+    @SneakyThrows
+    McR RenWuList(Map<String,String> data) throws JsonProcessingException;
+
+    /*TODO:查询项目任务*/
+    @SneakyThrows
+    McR ProjectRenWu(Map<String,String> data) throws JsonProcessingException;
+
+    /*TODO:更新任务状态*/
+    @SneakyThrows
+    McR UpdateTask(Map<String,String> data) throws JsonProcessingException;
+
+    /*TODO;更改项目施工日志*/
+    @SneakyThrows
+    McR UpdateConstructionLog(Map<String,String> data) throws JsonProcessingException;
+}

+ 9 - 0
mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/server/YouZanServer.java

@@ -0,0 +1,9 @@
+package com.malk.rjk.server;
+
+import com.alibaba.fastjson.JSONObject;
+
+public interface YouZanServer {
+
+    void OrderSuccessCallback(JSONObject callbackData);
+
+}

+ 501 - 0
mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/server/impl/HXImplClient.java

@@ -0,0 +1,501 @@
+package com.malk.rjk.server.impl;
+
+import com.alibaba.fastjson.JSON;
+import com.malk.rjk.server.HXClient;
+import com.malk.server.dingtalk.DDConf;
+import com.malk.service.dingtalk.DDClient;
+import com.malk.service.dingtalk.DDClient_Contacts;
+import com.malk.service.dingtalk.DDClient_Storage;
+import com.malk.service.dingtalk.DDService;
+import com.malk.service.h3yun.CYClient;
+import com.malk.utils.UtilMap;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Service
+@Slf4j
+public class HXImplClient implements HXClient {
+
+    @Autowired
+    private DDClient_Contacts ddClient_contacts;
+    @Autowired
+    private DDClient ddClient;
+    @Autowired
+    private DDConf ddConf;
+    @Autowired
+    private DDClient_Storage ddClient_storage;
+    @Autowired
+    private DDService ddService;
+    @Autowired
+    private CYClient cyClient;
+    @Override
+    public String syncDP(String sAttachments, String sProjectName, String typeName) {
+//        if (UtilString.isBlankCompatNull(sAttachments) || StringUtils.isBlank(sProjectName) || StringUtils.isBlank(typeName)) {
+//            return "";
+//        }
+//        // prd: 文件目录:钉盘空间 / 氚云项目附件 + 项目名称/ 日期(年月日)/ 附件名称
+//        String spaceId = "2928487153"; // 钉盘空间
+//        String parentId = "120089274640"; // 氚云项目附件
+//        // 获取用户unionId
+//        String unionId = String.valueOf(ddClient_contacts.getUserInfoById(ddClient.getAccessToken(), ddConf.getOperator()).get("unionid"));
+//        // 获取项目文件夹 [RETURN_DENTRY_IF_EXISTS: 文件夹名称冲突策略, 返回已存在文件夹, 避免查询文件夹列表]UtilMap.map("conflictStrategy", "RETURN_DENTRY_IF_EXISTS")
+//        Map dentry = ddClient_storage.addFolders(ddClient.getAccessToken(), unionId, spaceId, parentId, sProjectName, UtilMap.map("conflictStrategy", "RETURN_DENTRY_IF_EXISTS"));
+//        dentry = ddClient_storage.addFolders(ddClient.getAccessToken(), unionId, spaceId, String.valueOf(dentry.get("id")), typeName, UtilMap.map("conflictStrategy", "RETURN_DENTRY_IF_EXISTS"));
+//        // 获取氚云附件ID, 项目名称
+//        List<Map> attachments = (List<Map>) JSON.parse(sAttachments);
+//        for (Map attachment : attachments) {
+//            H3file.getFile(String.valueOf(attachment.get("ObjectId")));
+//            String urlFile = cyClient.getTemporaryUrls(ddClient.getAccessToken(), String.valueOf(attachment.get("ObjectId")));
+//            // 上传文件
+//            ddService.uploadFileFormUrlForDingDrive(ddClient.getAccessToken(), unionId, spaceId, String.valueOf(dentry.get("id")), urlFile, String.valueOf(attachment.get("FileName")));
+//        }
+        return "";
+
+    }
+
+    /**
+     * 上传FN合同(合同附件)、Packing List清单;上传提单样本及盖章保函 到航次文件夹
+     *
+     * @param data
+     * @return
+     */
+    @Override
+    public String CStoDP(Map<String, String> data) {
+        log.info("data{0}", data);
+        String spaceId = "1818087334";//总文件夹空间ID
+        String parentId = "159568092237";//航次文件夹id
+        String CStype = String.valueOf(data.get("HIH3type"));//CStype01:上传FN合同(合同附件)、Packing List清单;CStype02: 上传提单样本及盖章保函
+        String YiJML = String.valueOf(data.get("YiJML"));
+        String ErJML = String.valueOf(data.get("ErJML"));
+        String SanJML = "提单保函";
+        String SiJML = String.valueOf(data.get("SiJML"));
+        String sAttachments = String.valueOf(data.get("sAttachments"));//附件列表
+        String CreatedById = String.valueOf(data.get("CreatedById"));//创建人 暂时没用到
+        // 获取用户unionId / token
+        String unionId = String.valueOf(ddClient_contacts.getUserInfoById(ddClient.getAccessToken(), ddConf.getOperator()).get("unionid"));
+        String token = ddClient.getAccessToken();
+        //获取全部文件或文件夹
+        Map map_MR = new HashMap();
+        List<Map> map_ALL = ddClient_storage.getDentries(token, unionId, "1818087334", parentId, map_MR);
+        String PAR_ID1 = GetParentId(map_ALL, YiJML);//判断一级名称有无存在
+        if (PAR_ID1.equals("")) { //航次文件夹下无所属部门名称 文件夹
+            //创建一级文件夹 所属部门名称
+            Map dentry = ddClient_storage.addFolders(ddClient.getAccessToken(), unionId, spaceId, parentId, YiJML, UtilMap.map("conflictStrategy", "RETURN_DENTRY_IF_EXISTS"));
+            parentId = String.valueOf(dentry.get("id"));
+            //新建第二层 船名+航次编号
+            dentry = ddClient_storage.addFolders(ddClient.getAccessToken(), unionId, spaceId, parentId, ErJML, UtilMap.map("conflictStrategy", "RETURN_DENTRY_IF_EXISTS"));
+            parentId = String.valueOf(dentry.get("id"));
+            if (CStype.equals("CStype01")) {
+                //新建第三层 FN
+                dentry = ddClient_storage.addFolders(ddClient.getAccessToken(), unionId, spaceId, parentId, "FN", UtilMap.map("conflictStrategy", "RETURN_DENTRY_IF_EXISTS"));
+                parentId = String.valueOf(dentry.get("id"));
+
+                // 获取氚云附件ID, 项目名称
+                List<Map> attachments = (List<Map>) JSON.parse(sAttachments);
+                for (Map attachment : attachments) {
+                    //上传文件
+                    String FileMsg = ddService.getFile_new(ddClient.getAccessToken(), unionId, spaceId, parentId, String.valueOf(attachment.get("FileId")), String.valueOf(attachment.get("FileName")));
+                }
+
+            } else if (CStype.equals("CStype02")) {
+                if (!SiJML.equals("")) {//新建第三层
+                    //新建第三层 放单保函
+                    dentry = ddClient_storage.addFolders(ddClient.getAccessToken(), unionId, spaceId, parentId, SanJML, UtilMap.map("conflictStrategy", "RETURN_DENTRY_IF_EXISTS"));
+                    parentId = String.valueOf(dentry.get("id"));
+                    //  新建第四层 租家+合同号
+                    if (!SiJML.equals("")) {
+                        dentry = ddClient_storage.addFolders(ddClient.getAccessToken(), unionId, spaceId, parentId, SiJML, UtilMap.map("conflictStrategy", "RETURN_DENTRY_IF_EXISTS"));
+                        parentId = String.valueOf(dentry.get("id"));
+                        List<Map> attachments = (List<Map>) JSON.parse(sAttachments);
+                        for (Map attachment : attachments) {
+                            //上传文件
+                            String FileMsg = ddService.getFile_new(ddClient.getAccessToken(), unionId, spaceId, parentId, String.valueOf(attachment.get("FileId")), String.valueOf(attachment.get("FileName")));
+                        }
+                    }
+                }
+            }
+        } else {//航次文件夹下有 所属部门名称 文件夹
+            parentId = PAR_ID1;
+            // 判断第二层 船名+航次编号 有无
+            map_ALL = ddClient_storage.getDentries(token, unionId, "1818087334", parentId, map_MR);//获取第二层全部文件或文件夹
+            String PAR_ID2 = GetParentId(map_ALL, ErJML);
+            if (PAR_ID2.equals("")) {  //// 所属部门名称下无 船名+航次编号文件夹
+                //新建第二层 船名+航次编号
+
+                Map dentry = ddClient_storage.addFolders(ddClient.getAccessToken(), unionId, spaceId, parentId, ErJML, UtilMap.map("conflictStrategy", "RETURN_DENTRY_IF_EXISTS"));
+                parentId = String.valueOf(dentry.get("id"));
+                if (CStype.equals("CStype01")) {
+                    //新建第三层 FN
+                    dentry = ddClient_storage.addFolders(ddClient.getAccessToken(), unionId, spaceId, parentId, "FN", UtilMap.map("conflictStrategy", "RETURN_DENTRY_IF_EXISTS"));
+                    parentId = String.valueOf(dentry.get("id"));
+                    // 获取氚云附件ID, 项目名称
+                    List<Map> attachments = (List<Map>) JSON.parse(sAttachments);
+                    for (Map attachment : attachments) {
+                        //上传文件
+                        String FileMsg = ddService.getFile_new(ddClient.getAccessToken(), unionId, spaceId, parentId, String.valueOf(attachment.get("FileId")), String.valueOf(attachment.get("FileName")));
+                    }
+                } else if (CStype.equals("CStype02")) {
+                    if (!SiJML.equals("")) {//新建第三层
+                        //新建第三层 放单保函
+                        dentry = ddClient_storage.addFolders(ddClient.getAccessToken(), unionId, spaceId, parentId, SanJML, UtilMap.map("conflictStrategy", "RETURN_DENTRY_IF_EXISTS"));
+                        parentId = String.valueOf(dentry.get("id"));
+                        //  新建第四层 租家+合同号
+                        if (!SiJML.equals("")) {
+                            dentry = ddClient_storage.addFolders(ddClient.getAccessToken(), unionId, spaceId, parentId, SiJML, UtilMap.map("conflictStrategy", "RETURN_DENTRY_IF_EXISTS"));
+                            parentId = String.valueOf(dentry.get("id"));
+                            List<Map> attachments = (List<Map>) JSON.parse(sAttachments);
+                            for (Map attachment : attachments) {
+                                //上传文件
+                                String FileMsg = ddService.getFile_new(ddClient.getAccessToken(), unionId, spaceId, parentId, String.valueOf(attachment.get("FileId")), String.valueOf(attachment.get("FileName")));
+                            }
+                        }
+                    }
+                }
+            } else { //// 所属部门名称下有 船名+航次编号文件夹
+                parentId = PAR_ID2;
+                if (CStype.equals("CStype01")) {
+                    //判断有无放单保函
+                    map_ALL = ddClient_storage.getDentries(token, unionId, "1818087334", parentId, map_MR);//获取第二层全部文件或文件夹
+                    String PAR_ID3 = GetParentId(map_ALL, "FN");
+                    if (PAR_ID3.equals("")) { //如果FN
+                        //新建第三层 FN
+                        Map dentry = ddClient_storage.addFolders(ddClient.getAccessToken(), unionId, spaceId, parentId, "FN", UtilMap.map("conflictStrategy", "RETURN_DENTRY_IF_EXISTS"));
+                        parentId = String.valueOf(dentry.get("id"));
+                        // 获取氚云附件ID, 项目名称
+                        List<Map> attachments = (List<Map>) JSON.parse(sAttachments);
+                        for (Map attachment : attachments) {
+                            //上传文件
+                            String FileMsg = ddService.getFile_new(ddClient.getAccessToken(), unionId, spaceId, parentId, String.valueOf(attachment.get("FileId")), String.valueOf(attachment.get("FileName")));
+                        }
+                    } else {
+                        parentId = PAR_ID3;
+                        // 获取氚云附件ID, 项目名称
+                        List<Map> attachments = (List<Map>) JSON.parse(sAttachments);
+                        for (Map attachment : attachments) {
+                            //上传文件
+                            String FileMsg = ddService.getFile_new(ddClient.getAccessToken(), unionId, spaceId, parentId, String.valueOf(attachment.get("FileId")), String.valueOf(attachment.get("FileName")));
+                        }
+
+                    }
+
+                } else if (CStype.equals("CStype02")) {
+                    if (!SiJML.equals("")) {//租家+合同号文件数据
+                        //判断有无放单保函
+                        map_ALL = ddClient_storage.getDentries(token, unionId, "1818087334", parentId, map_MR);//获取第二层全部文件或文件夹
+                        String PAR_ID3 = GetParentId(map_ALL, SanJML);
+                        if (PAR_ID3.equals("")) { //如果无放单保函
+                            if (!SiJML.equals("")) {//新建第三层
+                                //新建第三层 放单保函
+                                Map dentry = ddClient_storage.addFolders(ddClient.getAccessToken(), unionId, spaceId, parentId, SanJML, UtilMap.map("conflictStrategy", "RETURN_DENTRY_IF_EXISTS"));
+                                parentId = String.valueOf(dentry.get("id"));
+                                //  新建第四层 租家+合同号
+                                if (!SiJML.equals("")) {
+                                    dentry = ddClient_storage.addFolders(ddClient.getAccessToken(), unionId, spaceId, parentId, SiJML, UtilMap.map("conflictStrategy", "RETURN_DENTRY_IF_EXISTS"));
+                                    parentId = String.valueOf(dentry.get("id"));
+                                    List<Map> attachments = (List<Map>) JSON.parse(sAttachments);
+                                    for (Map attachment : attachments) {
+                                        //上传文件
+                                        String FileMsg = ddService.getFile_new(ddClient.getAccessToken(), unionId, spaceId, parentId, String.valueOf(attachment.get("FileId")), String.valueOf(attachment.get("FileName")));
+                                    }
+                                }
+                            }
+                        } else { //有放单保函
+                            parentId = PAR_ID3;
+                            //  新建第四层 租家+合同号
+                            if (!SiJML.equals("")) {
+                                //判断有无放单保函
+                                map_ALL = ddClient_storage.getDentries(token, unionId, "1818087334", parentId, map_MR);//获取第放单保函下层全部文件或文件夹
+                                String PAR_ID4 = GetParentId(map_ALL, SiJML);
+                                if (PAR_ID4.equals("")) { //判断无 租家+合同号文件
+                                    Map dentry = ddClient_storage.addFolders(ddClient.getAccessToken(), unionId, spaceId, parentId, SiJML, UtilMap.map("conflictStrategy", "RETURN_DENTRY_IF_EXISTS"));
+                                    parentId = String.valueOf(dentry.get("id"));
+                                    List<Map> attachments = (List<Map>) JSON.parse(sAttachments);
+                                    for (Map attachment : attachments) {
+                                        //上传文件
+                                        String FileMsg = ddService.getFile_new(ddClient.getAccessToken(), unionId, spaceId, parentId, String.valueOf(attachment.get("FileId")), String.valueOf(attachment.get("FileName")));
+                                    }
+                                } else {////判断有 租家+合同号文件
+                                    parentId = PAR_ID4;
+                                    List<Map> attachments = (List<Map>) JSON.parse(sAttachments);
+                                    for (Map attachment : attachments) {
+                                        //上传文件
+                                        String FileMsg = ddService.getFile_new(ddClient.getAccessToken(), unionId, spaceId, parentId, String.valueOf(attachment.get("FileId")), String.valueOf(attachment.get("FileName")));
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        log.info("上传完成!");
+        return null;
+
+    }
+
+
+    @Override
+    public String CWtoDP(Map<String, String> data) {
+        String parentId = "159568150707";//财务文件夹id
+        String spaceId = "1818087334";//总文件夹空间ID
+        String CStype = String.valueOf(data.get("HIH3type"));
+        String YiJML = String.valueOf(data.get("YiJML"));
+        String ErJML = String.valueOf(data.get("ErJML"));
+        String SanJML = String.valueOf(data.get("SanJML"));
+        String SiJML = String.valueOf(data.get("SiJML"));
+        String WuJML = String.valueOf(data.get("WuJML"));
+        String LiuJML = String.valueOf(data.get("LiuJML"));
+        String sAttachments = String.valueOf(data.get("sAttachments"));//附件列表
+        String CreatedById = String.valueOf(data.get("CreatedById"));//创建人 暂时没用到
+        String unionId = String.valueOf(ddClient_contacts.getUserInfoById(ddClient.getAccessToken(), ddConf.getOperator()).get("unionid"));
+        String token = ddClient.getAccessToken();
+        //获取全部文件或文件夹
+        Map map_MR = new HashMap();
+        List<Map> map_ALL = ddClient_storage.getDentries(token, unionId, "1818087334", parentId, map_MR);
+        String PAR_ID1 = GetParentId(map_ALL, YiJML);//判断一级名称有无存在
+        if (PAR_ID1.equals("")) {//如果没有一级文件夹
+            //创建一级文件夹  年
+            Map dentry = ddClient_storage.addFolders(ddClient.getAccessToken(), unionId, spaceId, parentId, YiJML, UtilMap.map("conflictStrategy", "RETURN_DENTRY_IF_EXISTS"));
+            parentId = String.valueOf(dentry.get("id"));
+            //新建第二层 账户+币种
+            dentry = ddClient_storage.addFolders(ddClient.getAccessToken(), unionId, spaceId, parentId, ErJML, UtilMap.map("conflictStrategy", "RETURN_DENTRY_IF_EXISTS"));
+            parentId = String.valueOf(dentry.get("id"));
+            //新建第三层  付款/收款
+            dentry = ddClient_storage.addFolders(ddClient.getAccessToken(), unionId, spaceId, parentId, SanJML, UtilMap.map("conflictStrategy", "RETURN_DENTRY_IF_EXISTS"));
+            parentId = String.valueOf(dentry.get("id"));
+            //新建第四层  月
+            dentry = ddClient_storage.addFolders(ddClient.getAccessToken(), unionId, spaceId, parentId, SiJML, UtilMap.map("conflictStrategy", "RETURN_DENTRY_IF_EXISTS"));
+            parentId = String.valueOf(dentry.get("id"));
+            if (CStype.equals("CW01")) {   //如果是付款
+                //新建第五层  天
+                dentry = ddClient_storage.addFolders(ddClient.getAccessToken(), unionId, spaceId, parentId, WuJML, UtilMap.map("conflictStrategy", "RETURN_DENTRY_IF_EXISTS"));
+                parentId = String.valueOf(dentry.get("id"));
+                //新建第六层  币种 + 金额
+                dentry = ddClient_storage.addFolders(ddClient.getAccessToken(), unionId, spaceId, parentId, LiuJML, UtilMap.map("conflictStrategy", "RETURN_DENTRY_IF_EXISTS"));
+                parentId = String.valueOf(dentry.get("id"));
+                //循环上传文件
+                List<Map> attachments = (List<Map>) JSON.parse(sAttachments);
+                for (Map attachment : attachments) {
+
+                    String FileMsg = ddService.getFile_new(ddClient.getAccessToken(), unionId, spaceId, parentId, String.valueOf(attachment.get("FileId")), String.valueOf(attachment.get("FileName")));
+                }
+            } else if (CStype.equals("CW02")) {
+                //新建第五层  币种+金额+船名+开票抬头
+                dentry = ddClient_storage.addFolders(ddClient.getAccessToken(), unionId, spaceId, parentId, LiuJML, UtilMap.map("conflictStrategy", "RETURN_DENTRY_IF_EXISTS"));
+                parentId = String.valueOf(dentry.get("id"));
+                //循环上传文件
+                List<Map> attachments = (List<Map>) JSON.parse(sAttachments);
+                for (Map attachment : attachments) {
+                    String FileMsg = ddService.getFile_new(ddClient.getAccessToken(), unionId, spaceId, parentId, String.valueOf(attachment.get("FileId")), String.valueOf(attachment.get("FileName")));
+                }
+            }
+
+
+        } else {
+            parentId = PAR_ID1;
+            // 判断第二层  账户+币种  有无
+            map_ALL = ddClient_storage.getDentries(token, unionId, "1818087334", parentId, map_MR);//获取第二层全部文件或文件夹
+            String PAR_ID2 = GetParentId(map_ALL, ErJML);
+            if (PAR_ID2.equals("")) {    ////  如果无 账户+币种 文件夹
+                //新建第二层 账户+币种
+                Map dentry = ddClient_storage.addFolders(ddClient.getAccessToken(), unionId, spaceId, parentId, ErJML, UtilMap.map("conflictStrategy", "RETURN_DENTRY_IF_EXISTS"));
+                parentId = String.valueOf(dentry.get("id"));
+                //新建第三层  付款/收款
+                dentry = ddClient_storage.addFolders(ddClient.getAccessToken(), unionId, spaceId, parentId, SanJML, UtilMap.map("conflictStrategy", "RETURN_DENTRY_IF_EXISTS"));
+                parentId = String.valueOf(dentry.get("id"));
+                //新建第四层  月
+                dentry = ddClient_storage.addFolders(ddClient.getAccessToken(), unionId, spaceId, parentId, SiJML, UtilMap.map("conflictStrategy", "RETURN_DENTRY_IF_EXISTS"));
+                parentId = String.valueOf(dentry.get("id"));
+                if (CStype.equals("CW01")) {   //如果是付款
+                    //新建第五层  天
+                    dentry = ddClient_storage.addFolders(ddClient.getAccessToken(), unionId, spaceId, parentId, WuJML, UtilMap.map("conflictStrategy", "RETURN_DENTRY_IF_EXISTS"));
+                    parentId = String.valueOf(dentry.get("id"));
+                    //新建第六层  币种 + 金额
+                    dentry = ddClient_storage.addFolders(ddClient.getAccessToken(), unionId, spaceId, parentId, LiuJML, UtilMap.map("conflictStrategy", "RETURN_DENTRY_IF_EXISTS"));
+                    parentId = String.valueOf(dentry.get("id"));
+                    //循环上传文件
+                    List<Map> attachments = (List<Map>) JSON.parse(sAttachments);
+                    for (Map attachment : attachments) {
+
+                        String FileMsg = ddService.getFile_new(ddClient.getAccessToken(), unionId, spaceId, parentId, String.valueOf(attachment.get("FileId")), String.valueOf(attachment.get("FileName")));
+                    }
+                } else if (CStype.equals("CW02")) {
+                    //新建第五层  币种+金额+船名+开票抬头
+                    dentry = ddClient_storage.addFolders(ddClient.getAccessToken(), unionId, spaceId, parentId, LiuJML, UtilMap.map("conflictStrategy", "RETURN_DENTRY_IF_EXISTS"));
+                    parentId = String.valueOf(dentry.get("id"));
+                    //循环上传文件
+                    List<Map> attachments = (List<Map>) JSON.parse(sAttachments);
+                    for (Map attachment : attachments) {
+                        String FileMsg = ddService.getFile_new(ddClient.getAccessToken(), unionId, spaceId, parentId, String.valueOf(attachment.get("FileId")), String.valueOf(attachment.get("FileName")));
+                    }
+                }
+
+            } else {
+                parentId = PAR_ID2;
+                map_ALL = ddClient_storage.getDentries(token, unionId, "1818087334", parentId, map_MR);//获取第二层全部文件或文件夹
+                String PAR_ID3 = GetParentId(map_ALL, SanJML);
+                if (PAR_ID3.equals("")) {////  如果无 付款/收款 文件夹
+                    //新建第三层  付款/收款
+                    Map dentry = ddClient_storage.addFolders(ddClient.getAccessToken(), unionId, spaceId, parentId, SanJML, UtilMap.map("conflictStrategy", "RETURN_DENTRY_IF_EXISTS"));
+                    parentId = String.valueOf(dentry.get("id"));
+                    //新建第四层  月
+                    dentry = ddClient_storage.addFolders(ddClient.getAccessToken(), unionId, spaceId, parentId, SiJML, UtilMap.map("conflictStrategy", "RETURN_DENTRY_IF_EXISTS"));
+                    parentId = String.valueOf(dentry.get("id"));
+                    if (CStype.equals("CW01")) {   //如果是付款
+                        //新建第五层  天
+                        dentry = ddClient_storage.addFolders(ddClient.getAccessToken(), unionId, spaceId, parentId, WuJML, UtilMap.map("conflictStrategy", "RETURN_DENTRY_IF_EXISTS"));
+                        parentId = String.valueOf(dentry.get("id"));
+                        //新建第六层  币种 + 金额
+                        dentry = ddClient_storage.addFolders(ddClient.getAccessToken(), unionId, spaceId, parentId, LiuJML, UtilMap.map("conflictStrategy", "RETURN_DENTRY_IF_EXISTS"));
+                        parentId = String.valueOf(dentry.get("id"));
+                        //循环上传文件
+                        List<Map> attachments = (List<Map>) JSON.parse(sAttachments);
+                        for (Map attachment : attachments) {
+
+                            String FileMsg = ddService.getFile_new(ddClient.getAccessToken(), unionId, spaceId, parentId, String.valueOf(attachment.get("FileId")), String.valueOf(attachment.get("FileName")));
+                        }
+                    } else if (CStype.equals("CW02")) {
+                        //新建第五层  币种+金额+船名+开票抬头
+                        dentry = ddClient_storage.addFolders(ddClient.getAccessToken(), unionId, spaceId, parentId, LiuJML, UtilMap.map("conflictStrategy", "RETURN_DENTRY_IF_EXISTS"));
+                        parentId = String.valueOf(dentry.get("id"));
+                        //循环上传文件
+                        List<Map> attachments = (List<Map>) JSON.parse(sAttachments);
+                        for (Map attachment : attachments) {
+                            String FileMsg = ddService.getFile_new(ddClient.getAccessToken(), unionId, spaceId, parentId, String.valueOf(attachment.get("FileId")), String.valueOf(attachment.get("FileName")));
+                        }
+                    }
+
+                } else { //如果有 付款/收款 文件夹
+                    parentId = PAR_ID3;
+                    map_ALL = ddClient_storage.getDentries(token, unionId, "1818087334", parentId, map_MR);//获取第三层全部文件或文件夹
+                    String PAR_ID4 = GetParentId(map_ALL, SiJML);
+                    if (PAR_ID4.equals("")) {////  如果无 月 文件夹
+                        //新建第四层  月
+                        Map dentry = ddClient_storage.addFolders(ddClient.getAccessToken(), unionId, spaceId, parentId, SiJML, UtilMap.map("conflictStrategy", "RETURN_DENTRY_IF_EXISTS"));
+                        parentId = String.valueOf(dentry.get("id"));
+                        if (CStype.equals("CW01")) {   //如果是付款
+                            //新建第五层  天
+                            dentry = ddClient_storage.addFolders(ddClient.getAccessToken(), unionId, spaceId, parentId, WuJML, UtilMap.map("conflictStrategy", "RETURN_DENTRY_IF_EXISTS"));
+                            parentId = String.valueOf(dentry.get("id"));
+                            //新建第六层  币种 + 金额
+                            dentry = ddClient_storage.addFolders(ddClient.getAccessToken(), unionId, spaceId, parentId, LiuJML, UtilMap.map("conflictStrategy", "RETURN_DENTRY_IF_EXISTS"));
+                            parentId = String.valueOf(dentry.get("id"));
+                            //循环上传文件
+                            List<Map> attachments = (List<Map>) JSON.parse(sAttachments);
+                            for (Map attachment : attachments) {
+
+                                String FileMsg = ddService.getFile_new(ddClient.getAccessToken(), unionId, spaceId, parentId, String.valueOf(attachment.get("FileId")), String.valueOf(attachment.get("FileName")));
+                            }
+                        } else if (CStype.equals("CW02")) {
+                            //新建第五层  币种+金额+船名+开票抬头
+                            dentry = ddClient_storage.addFolders(ddClient.getAccessToken(), unionId, spaceId, parentId, LiuJML, UtilMap.map("conflictStrategy", "RETURN_DENTRY_IF_EXISTS"));
+                            parentId = String.valueOf(dentry.get("id"));
+                            //循环上传文件
+                            List<Map> attachments = (List<Map>) JSON.parse(sAttachments);
+                            for (Map attachment : attachments) {
+                                String FileMsg = ddService.getFile_new(ddClient.getAccessToken(), unionId, spaceId, parentId, String.valueOf(attachment.get("FileId")), String.valueOf(attachment.get("FileName")));
+                            }
+                        }
+
+                    } else { ////  如果有  月  文件夹
+                        parentId = PAR_ID4;
+                        if (CStype.equals("CW01")) {   //如果是付款
+                            map_ALL = ddClient_storage.getDentries(token, unionId, "1818087334", parentId, map_MR);//获取第四层全部文件或文件夹
+                            String PAR_ID5 = GetParentId(map_ALL, WuJML);
+                            if (PAR_ID5.equals("")) {//如果无 天 文件夹
+                                //新建第五层  天
+                                Map dentry = ddClient_storage.addFolders(ddClient.getAccessToken(), unionId, spaceId, parentId, WuJML, UtilMap.map("conflictStrategy", "RETURN_DENTRY_IF_EXISTS"));
+                                parentId = String.valueOf(dentry.get("id"));
+                                //新建第六层  币种 + 金额
+                                dentry = ddClient_storage.addFolders(ddClient.getAccessToken(), unionId, spaceId, parentId, LiuJML, UtilMap.map("conflictStrategy", "RETURN_DENTRY_IF_EXISTS"));
+                                parentId = String.valueOf(dentry.get("id"));
+                                //循环上传文件
+                                List<Map> attachments = (List<Map>) JSON.parse(sAttachments);
+                                for (Map attachment : attachments) {
+
+                                    String FileMsg = ddService.getFile_new(ddClient.getAccessToken(), unionId, spaceId, parentId, String.valueOf(attachment.get("FileId")), String.valueOf(attachment.get("FileName")));
+                                }
+
+                            } else {
+                                parentId = PAR_ID5;
+
+                                //新建第六层  币种 + 金额
+                                Map dentry = ddClient_storage.addFolders(ddClient.getAccessToken(), unionId, spaceId, parentId, LiuJML, UtilMap.map("conflictStrategy", "RETURN_DENTRY_IF_EXISTS"));
+                                parentId = String.valueOf(dentry.get("id"));
+                                //循环上传文件
+                                List<Map> attachments = (List<Map>) JSON.parse(sAttachments);
+                                for (Map attachment : attachments) {
+
+                                    String FileMsg = ddService.getFile_new(ddClient.getAccessToken(), unionId, spaceId, parentId, String.valueOf(attachment.get("FileId")), String.valueOf(attachment.get("FileName")));
+                                }
+
+                            }
+                        } else if (CStype.equals("CW02")) {
+                            //新建第五层  币种+金额+船名+开票抬头
+                            Map dentry = ddClient_storage.addFolders(ddClient.getAccessToken(), unionId, spaceId, parentId, LiuJML, UtilMap.map("conflictStrategy", "RETURN_DENTRY_IF_EXISTS"));
+                            parentId = String.valueOf(dentry.get("id"));
+                            //循环上传文件
+                            List<Map> attachments = (List<Map>) JSON.parse(sAttachments);
+                            for (Map attachment : attachments) {
+                                String FileMsg = ddService.getFile_new(ddClient.getAccessToken(), unionId, spaceId, parentId, String.valueOf(attachment.get("FileId")), String.valueOf(attachment.get("FileName")));
+                            }
+                        }
+
+                    }
+
+                }
+
+
+            }
+
+        }
+
+
+        return "";
+    }
+
+    /*
+    根据名称,判断对应的parentId
+     */
+    public String GetParentId(List<Map> map_ALL, String name) {
+        String parentId = "";
+        if (map_ALL != null) {
+            for (Map<String, String> map : map_ALL) {
+                if (map.get("name").equals(name)) {//
+                    parentId = map.get("id");
+                }
+            }
+        }
+        return parentId;
+    }
+
+
+    //删除
+//    String directoryPath = "D:\\本地下载aaa\\";
+//
+//    // 创建File对象表示目录
+//    File directory = new File(directoryPath);
+//    // 确保目录存在并且是一个目录
+//                if (directory.exists() && directory.isDirectory()) {
+//        // 获取目录下所有文件和文件夹
+//        File[] files = directory.listFiles();
+//
+//        if (files != null) { // 确保文件数组不为空
+//            for (File file : files) {
+//                // 如果是文件,则可以直接删除
+//                if (file.isFile()) {
+//                    file.delete();
+//                }
+//            }
+//        }
+//        System.out.println("删除成功!");
+//    } else {
+//        System.out.println("Directory does not exist or is not a directory.");
+//    }
+}

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 1242 - 0
mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/server/impl/KingDeeImpl.java


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 1737 - 0
mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/server/impl/RjkServerImpl.java


+ 474 - 0
mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/server/impl/TbServiceImpl.java

@@ -0,0 +1,474 @@
+package com.malk.rjk.server.impl;
+
+import com.alibaba.fastjson.JSONObject;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.gson.JsonParser;
+import com.malk.rjk.server.TBService;
+import com.malk.server.common.McR;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.methods.HttpPut;
+import org.apache.http.client.utils.URIBuilder;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.util.EntityUtils;
+import org.json.JSONArray;
+import org.springframework.stereotype.Service;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.URI;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Map;
+
+@Slf4j
+@Service
+public  class TbServiceImpl implements TBService {
+
+    /*TODO:获取apptoken*/
+    @Override
+    public McR AppAccessToken() throws JsonProcessingException {
+        String accessToken = "https://open.teambition.com/api/appToken";
+        String result = "";
+        String appToken = "";
+        //创建HttpClient
+        try(CloseableHttpClient httpClient = HttpClients.createDefault()){
+            //创建HttpPost实例
+            HttpPost postRequest = new HttpPost(accessToken);
+            //准备请求体数据
+            JSONObject json = new JSONObject();
+            json.put("appId","67bed45cb4d0d3f8945d90d1");//TB应用ID
+            json.put("appSecret","VnazNCCMNic5foLjgZwO2cMb6cdnxAvL");//TB密钥
+
+            //设置请求体
+            StringEntity entity = new StringEntity(json.toString(), "UTF-8");
+            postRequest.setEntity(entity);
+            postRequest.setHeader("Accept","application/json");
+            postRequest.setHeader("Content-type","application/json");
+
+            //执行请求并获取响应
+            CloseableHttpResponse response = httpClient.execute(postRequest);
+            //输出响应结果
+            result = EntityUtils.toString(response.getEntity(), "UTF-8");
+            log.info("appToken【30分钟会过期】:--------------->",result);
+            appToken = new JsonParser().parse(result).getAsJsonObject().get("appToken").getAsString();
+            System.out.println("获取到appToken:"+appToken);
+
+        }catch (Exception e){
+            e.printStackTrace();
+        }
+        return McR.success(appToken);
+    }
+
+    /*TODO:钉钉userid获取TBid*/
+    @SneakyThrows
+    @Override
+    public McR GetTbId(Map<String,String> data) throws JsonProcessingException {
+        String tbid = "";
+        String tbUserId = "";
+        String url = "https://open.teambition.com/api/idmap/dingtalk/getTbUserId";
+        String xTenantType = "organization";
+        String xTenantId = "66dba69964dd1c73a2a7926d";
+        Object appToken = AppAccessToken().getData();
+        String fullurl = url + "?dingUserIds=" +data.get("dingUserIds");
+        URL url1 = new URL(fullurl);
+        HttpURLConnection connection = (HttpURLConnection)url1.openConnection();
+        connection.setRequestMethod("GET");
+        connection.setRequestProperty("Authorization", "Bearer "+ appToken);
+        connection.setRequestProperty("X-Tenant-type",xTenantType);
+        connection.setRequestProperty("X-Tenant-Id",xTenantId);
+        BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
+        StringBuilder response = new StringBuilder();
+        String line;
+        while ((line = reader.readLine())!= null){
+            tbid = response.append(line).toString();
+            ObjectMapper mapper = new ObjectMapper();
+            JsonNode jsonNode = mapper.readTree(tbid);
+            JsonNode resultNode = jsonNode.path("result").get(0);
+            tbUserId = resultNode.path("tbUserId").asText();
+            System.out.println("拿到dingUserId:"+data.get("dingUserIds")+"          TbitionId账号:"+tbUserId);
+        }
+        return McR.success(tbUserId);
+    }
+
+    /*TODO:查询项目*/
+    @SneakyThrows
+    @Override
+    public McR SearchProject(Map<String,String> data) throws JsonProcessingException {
+        String responseBody = "";
+        //设置请求信息
+        McR Authorization = AppAccessToken();
+        Object appToken = Authorization.getData();
+        System.out.println("appToken:"+appToken);
+
+        String Tenant_Id = "66dba69964dd1c73a2a7926d";//企业ID
+        String Tenant_Type = "organization";//租户类型,固定值
+        Integer pageSize = 10; //分页大小
+
+        //构造URL
+        URIBuilder uriBuilder = new URIBuilder();
+        URI uri = uriBuilder.setScheme("https")
+                .setHost("open.teambition.com")
+                .setPath("/api/v3/project/query")
+                .addParameter("name", data.get("name"))
+                .addParameter("pageSize", String.valueOf(pageSize)).build();
+
+        System.out.println("Request URL: " + uri.toString());
+
+        //创建HttpClient实例
+        CloseableHttpClient client = HttpClients.createDefault();
+        //创建HttpCet实例
+        HttpGet request = new HttpGet(uri);
+        //添加请求头
+        request.setHeader("Authorization","Bearer " + appToken);
+        request.setHeader("X-Tenant-Id", Tenant_Id);
+        request.setHeader("X-Tenant-Type", Tenant_Type);
+        //执行请求并获取相应
+        HttpResponse response = client.execute(request);
+        //检查响应状态码
+        if(response.getStatusLine().getStatusCode() == 200){
+            responseBody = EntityUtils.toString(response.getEntity(), "UTF-8");
+            System.out.println(responseBody);//输出接口调用返回情况(成功)
+            log.info(responseBody);
+        }else {
+            System.out.println(response.getStatusLine().getReasonPhrase());//(失败)
+        }
+        return McR.success(responseBody);
+    }
+
+
+    /*TODO:搜索任务列表*/
+    @Override
+    public McR RenWuList(Map<String, String> data) throws JsonProcessingException {
+        McR project = SearchProject(data);//先查询项目(根据名称)
+        String projectData = (String) project.getData();
+        System.out.println("项目:"+project.getData());
+
+        String Tenant_Id = "66dba69964dd1c73a2a7926d";//企业ID
+        String Tenant_Type = "organization";//租户类型,固定值
+        McR Authorization = AppAccessToken();
+        Object appToken = Authorization.getData();
+
+        String list = "";
+
+        String TBid = "";//任务id
+
+        //解析获取到的项目信息(json)
+        org.json.JSONObject jsonObject = new org.json.JSONObject(projectData);
+        JSONArray resultArray = jsonObject.getJSONArray("result");
+        if(resultArray.length() > 0){
+            org.json.JSONObject jsonObject1 = resultArray.getJSONObject(0);
+            String idValue = jsonObject1.getString("id");//获取到项目id
+            System.out.println("查询到的项目id"+idValue);
+            String url = String.format("https://open.teambition.com/api/v3/project/%s/stage/search",idValue);
+            //创建HttpClient实例
+            try(CloseableHttpClient httpClient = HttpClients.createDefault()){
+                //创建HttpGet请求
+                HttpGet request = new HttpGet(url);
+                //设置请求头
+                request.setHeader("Authorization","Bearer "+appToken);
+                request.setHeader("X-Tenant-Id", Tenant_Id);
+                request.setHeader("X-Tenant-Type", Tenant_Type);
+
+                CloseableHttpResponse response = httpClient.execute(request);
+                list = EntityUtils.toString(response.getEntity());
+                System.out.println("任务列表:"+list);
+                String s = list.toString();
+                org.json.JSONObject jsonObject2 = new org.json.JSONObject(s);
+                JSONArray array = jsonObject2.getJSONArray("result");
+                for(int i=0;i<array.length();i++){
+                    org.json.JSONObject jsonObject3 = array.getJSONObject(i);
+                    String name = jsonObject3.getString("name");
+                    if(data.get("renwu").equals(name)){
+                        TBid = jsonObject3.getString("id");
+                        break;
+                    }
+                }
+
+            }catch (Exception e){
+                e.printStackTrace();
+            }
+        }else {
+            System.out.println("未查询到任务列表");
+        }
+
+        return McR.success(TBid);
+    }
+
+    @Override
+    public McR ProjectRenWu(Map<String, String> data) throws JsonProcessingException {
+        Object data1 = RenWuList(data).getData();
+        System.out.println("data1:"+data1);//获取到任务列表的id
+        McR project = SearchProject(data);//获取项目信息
+        String projectId = "";//项目id
+        System.out.println("项目信息;"+project.getData());
+        String pdata = (String) project.getData();
+        org.json.JSONObject jsonObject = new org.json.JSONObject(pdata);
+        JSONArray array = jsonObject.getJSONArray("result");
+        if(array.length() > 0){
+            org.json.JSONObject jsonObject1 = array.getJSONObject(0);
+            projectId = jsonObject1.getString("id");
+            log.info("项目id:"+projectId);
+        }
+
+        String responseString = "";//项目任务
+        String stageId = (String) data1;
+        String operatorId = "622ee3450cf3bb5e1a486f1f";//查询人ID
+        Object appToken = AppAccessToken().getData();
+        String TenantId = "66dba69964dd1c73a2a7926d";//企业id
+        String TenantType = "organization";
+        URIBuilder uriBuilder = new URIBuilder();
+        try{
+            URI uri = uriBuilder.setScheme("https")
+                    .setHost("open.teambition.com")
+                    .setPath("/api/v3/project/" + projectId + "/task/query")
+                    .addParameter("q", "stageId=" + stageId + " AND taskLayer = onlyTopLevel")
+                    .build();
+            HttpGet request = new HttpGet(uri);
+            request.setHeader("Authorization","Bearer "+appToken);
+            request.setHeader("X-Tenant-Id",TenantId);
+            request.setHeader("X-Tenant-Type",TenantType);
+            request.setHeader("x-operator-id",operatorId);
+            CloseableHttpClient client = HttpClients.createDefault();
+            CloseableHttpResponse response = client.execute(request);
+            if(response.getEntity() != null){
+                responseString = EntityUtils.toString(response.getEntity(), "UTF-8");
+                System.out.println("查询项目任务:"+responseString);
+            }
+        }catch (Exception e){
+            e.printStackTrace();
+        }
+        return McR.success(responseString);
+    }
+
+    /*TODO:[公共方法]更新任务状态调用方法(封装好的,直接传入任务id即可)*/
+    @SneakyThrows
+    public McR ChangeTaskStatus(Map<String,String> data,String id){
+        String taskId = id;//任务id
+        String tfsName = "已完成";//任务状态名称
+        String tbid = GetTbId(data).getData().toString();//tbid
+        System.out.println("TBtionId:"+tbid);
+        Object appToken = AppAccessToken().getData();//token
+        String TenantId = "66dba69964dd1c73a2a7926d";//企业id
+        String TenantType = "organization";
+        String url = "https://open.teambition.com/api/v3/task/"+ taskId + "/taskflowstatus";
+        HttpPut request = new HttpPut(url);
+        request.setHeader("Authorization","Bearer "+ appToken);
+        request.setHeader("X-Tenant-Id",TenantId);
+        request.setHeader("X-Tenant-Type",TenantType);
+        request.setHeader("x-operator-id",tbid);
+
+        String responseString = "";
+
+        try{
+            String json = "{\"tfsName\": \"" + tfsName + "\"}";
+            StringEntity entity = new StringEntity(json, "UTF-8");
+            entity.setContentType("application/json");
+            request.setEntity(entity);
+            try(CloseableHttpClient client = HttpClients.createDefault()){
+                CloseableHttpResponse response = client.execute(request);
+                System.out.println("接口返回Code:"+ response.getStatusLine().getStatusCode());
+                if(response.getEntity() != null){
+                    responseString = EntityUtils.toString(response.getEntity(), "UTF-8");
+                    System.out.println("Response:"+responseString);
+                }
+            }
+        }catch (Exception e){
+            e.printStackTrace();
+        }
+        return McR.success(responseString);
+    }
+
+    /*TODO:[公共方法]查询任务是否含有子任务*/
+    @SneakyThrows
+    public McR ContainsSubtasks(Map<String,String> data,String taskid){
+
+        String Authorization  = AppAccessToken().getData().toString();
+        String TenantId = "66dba69964dd1c73a2a7926d";
+        String TenantType = "organization";
+        String responseString = "";//返回的子任务列表
+        URIBuilder uriBuilder = new URIBuilder();
+        URI uri = uriBuilder.setScheme("https")
+                .setHost("open.teambition.com")
+                .setPath("/api/v3/task/query")
+                .addParameter("parentTaskId", taskid)
+                .build();
+        HttpGet httpGet = new HttpGet(uri);
+        httpGet.setHeader("Authorization","Bearer " + Authorization);
+        httpGet.setHeader("X-Tenant-Id",TenantId);
+        httpGet.setHeader("X-Tenant-Type",TenantType);
+
+        try(CloseableHttpClient client = HttpClients.createDefault()) {
+            CloseableHttpResponse response = client.execute(httpGet);
+            if (response.getEntity() != null) {
+                responseString = EntityUtils.toString(response.getEntity(), "UTF-8");
+                ObjectMapper mapper = new ObjectMapper();
+                JsonNode rootNode = mapper.readTree(responseString);
+                JsonNode array = rootNode.path("result");
+                if (array.isArray() && array.size() == 0) {
+                    log.info("该任务下不包含子任务!");
+                } else {
+                    log.info("查询到子任务");
+                    System.out.println("任务详情:" + responseString);
+                }
+            }
+        }
+        return McR.success(responseString);
+    }
+
+    /*TODO:更改任务状态*/
+    @SneakyThrows
+    @Override
+    public McR UpdateTask(Map<String, String> data) throws JsonProcessingException {
+        String RenWuList = ProjectRenWu(data).getData().toString();
+        ObjectMapper mapper = new ObjectMapper();
+        JsonNode rootNode = mapper.readTree(RenWuList);
+        JsonNode resultArray = rootNode.path("result");
+        String id = "";
+        for(JsonNode node: resultArray){
+            String content =node.path("content").asText();
+            if(data.get("renwuDetail").equals(content)){
+                id = node.path("id").asText();
+                System.out.println("匹配到的任务:"+content);
+                System.out.println("任务id:"+ id);
+                break;//找出后推出循环
+            }
+        }
+        return McR.success(ChangeTaskStatus(data,id));
+        /*String tfsName = "已完成";//任务状态名称
+        String tbid = GetTbId(data).getData().toString();//tbid
+        System.out.println("TBtionId:"+tbid);
+        Object appToken = AppAccessToken().getData();//token
+        String TenantId = "66dba69964dd1c73a2a7926d";//企业id
+        String TenantType = "organization";
+        String url = "https://open.teambition.com/api/v3/task/"+ taskId + "/taskflowstatus";
+        HttpPut request = new HttpPut(url);
+        request.setHeader("Authorization","Bearer "+ appToken);
+        request.setHeader("X-Tenant-Id",TenantId);
+        request.setHeader("X-Tenant-Type",TenantType);
+        request.setHeader("x-operator-id",tbid);
+
+        String responseString = "";
+
+        try{
+            String json = "{\"tfsName\": \"" + tfsName + "\"}";
+            StringEntity entity = new StringEntity(json, "UTF-8");
+            entity.setContentType("application/json");
+            request.setEntity(entity);
+            try(CloseableHttpClient client = HttpClients.createDefault()){
+                CloseableHttpResponse response = client.execute(request);
+                System.out.println("接口返回Code:"+ response.getStatusLine().getStatusCode());
+                if(response.getEntity() != null){
+                    responseString = EntityUtils.toString(response.getEntity(), "UTF-8");
+                    System.out.println("Response:"+responseString);
+                }
+            }
+        }catch (Exception e){
+            e.printStackTrace();
+        }
+
+        return McR.success(responseString);*/
+    }
+
+    @SneakyThrows
+    @Override
+    public McR UpdateConstructionLog(Map<String, String> data) throws JsonProcessingException {
+        String s = ProjectRenWu(data).getData().toString();//查询任务列表
+        System.out.println("任务列表:"+s);
+        ObjectMapper objectMapper = new ObjectMapper();
+        JsonNode jsonNode = objectMapper.readTree(s);
+        JsonNode resultArray = jsonNode.path("result");
+        String mcR = "";
+        for(JsonNode task: resultArray){
+            if(data.get("renwuDetail").equals(task.path("content").asText())){
+                log.info("已匹配到任务");
+                String taskId = task.path("id").asText().toString();//任务id,下面需要判断是否存在子任务(很重要! )
+
+                /*下面的代码是查询任务是否含有子任务*/
+                String Authorization  = AppAccessToken().getData().toString();
+                String TenantId = "66dba69964dd1c73a2a7926d";
+                String TenantType = "organization";
+                URIBuilder uriBuilder = new URIBuilder();
+                URI uri = uriBuilder.setScheme("https")
+                        .setHost("open.teambition.com")
+                        .setPath("/api/v3/task/query")
+                        .addParameter("parentTaskId", taskId)
+                        .build();
+                HttpGet httpGet = new HttpGet(uri);
+                httpGet.setHeader("Authorization","Bearer " + Authorization);
+                httpGet.setHeader("X-Tenant-Id",TenantId);
+                httpGet.setHeader("X-Tenant-Type",TenantType);
+
+                ArrayList<Object> taskIds = new ArrayList<>();//子任务id存起来
+                ArrayList<Object> taskNames = new ArrayList<>();//子任务名称存起来
+
+                try(CloseableHttpClient client = HttpClients.createDefault()){
+                    CloseableHttpResponse response = client.execute(httpGet);
+                    if(response.getEntity() != null){
+                        String responseString = EntityUtils.toString(response.getEntity(), "UTF-8");
+                        ObjectMapper mapper = new ObjectMapper();
+                        JsonNode rootNode = mapper.readTree(responseString);
+                        JsonNode array = rootNode.path("result");
+                        if(array.isArray() && array.size() == 0){
+                            log.info("该任务下不包含子任务!");
+                            ChangeTaskStatus(data,taskId);
+                        }else {
+                            log.info("查询到子任务");
+                            //开始解析对应的子任务id(可能含有多个)
+                            ObjectMapper mapper1 = new ObjectMapper();
+                            JsonNode jsonNode1 = mapper1.readTree(responseString);
+                            JsonNode resultarray = jsonNode1.path("result");
+                            if(resultarray.isArray()){
+                                for (JsonNode task1 : resultarray){
+                                    String taskId1 = task1.path("taskId").asText();
+                                    String taskName = task1.path("content").asText();
+                                    System.out.println("TaskID="+taskId1+",TaskName="+taskName);
+                                    if(!taskId1.isEmpty()){
+                                        taskIds.add(taskId1);
+                                        taskNames.add(taskName);
+                                    }
+                                }
+                            }
+                        }
+                        //若含有子任务,则会将子任务id存在taskIds数组里面
+                        /*TODO:更新子任务的任务状态*/
+                        for(Object taskname: taskNames){
+                            if(taskname.equals(data.get("subtask"))){
+                                for(Object arr : taskIds){
+                                    ChangeTaskStatus(data, (String) arr);
+                                }
+                            }
+                        }
+                        /*TODO:判断父任务下的子任务是否全部完成,若已完成,则更新父任务状态,否则状态不更新*/
+                        String parentTaskId = ContainsSubtasks(data, taskId).getData().toString();//父任务id
+                        ObjectMapper mapper1 = new ObjectMapper();
+                        JsonNode node = mapper1.readTree(parentTaskId);
+                        JsonNode resultnode = node.path("result");
+                        for(JsonNode no:resultnode){
+                            if(no.path("isDone").asText().equals("false")){//如果找到一个未完成的任务
+                                break;
+                            }else {//否则,任务全部完成,修改父任务
+                                parentTaskId = no.path("parentTaskId").asText();
+                                mcR = String.valueOf(ChangeTaskStatus(data, parentTaskId));
+                            }
+                        }
+                    }
+                }
+                break;
+            }
+        }
+
+        return McR.success(mcR);
+    }
+
+
+}

+ 251 - 0
mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/server/impl/YouZanServerImpl.java

@@ -0,0 +1,251 @@
+package com.malk.rjk.server.impl;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.malk.rjk.config.YouZanConfig;
+import com.malk.rjk.dto.YouZanTokenResponse;
+import com.malk.rjk.server.YouZanServer;
+import com.malk.rjk.util.YouZanTokenUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+
+@Service
+@Slf4j
+public class YouZanServerImpl implements YouZanServer {
+    @Autowired
+    private  YouZanConfig youZanConfig;
+
+    @Autowired
+    private YouZanTokenUtil youZanTokenUtil;
+
+    public static String H3yunSchemaCode_KH = "D150508ssdcuajhqk0evtzkhgm4h";
+    public static String H3yunSchemaCode_YH = "D150508sx2lo3bvu40ws86omkyci";
+
+
+    public static volatile String access_token = null;
+    public void GetToken() throws Exception {
+        // 从配置中获取有赞云凭证
+        String clientId = youZanConfig.getClientId();
+        String clientSecret = youZanConfig.getClientSecret();
+        String kdtId = youZanConfig.getKdtId();
+
+        System.out.println("Client ID: " + clientId);
+        System.out.println("Client Secret: " + clientSecret);
+        System.out.println("KDT ID: " + kdtId);
+
+        // 调用工具类获取 token
+        YouZanTokenResponse response = YouZanTokenUtil.getAccessToken(clientId, clientSecret, kdtId);
+        // 断言 token 不为空
+        assert response.getAccess_token() != null && !response.getAccess_token().isEmpty() : "access_token 不能为空";
+
+        access_token = response.getAccess_token();
+        System.out.println("获取 token 成功!: "+ access_token);
+    }
+
+
+    @Override
+    public void OrderSuccessCallback(JSONObject callbackData) {
+        try {
+            JSONObject event = callbackData.getJSONObject("event");
+            // 提取手机号列表
+            List<String> contactWays = new ArrayList<>();
+            JSONArray contactWaysArray = event.getJSONArray("contact_ways");
+
+            // 第一个对象
+            if (contactWaysArray != null) {
+                for (int i = 0; i < contactWaysArray.size(); i++) {
+                    JSONObject way = contactWaysArray.getJSONObject(i);
+                    if (way != null && way.getInteger("type") != null && way.getInteger("type") == 1) {
+                        // type=1 表示手机号
+                        String phone = way.getString("contact_way");
+                        if (phone != null && !phone.trim().isEmpty()) {
+                            contactWays.add(phone.trim());
+                        }
+                    }
+                }
+            }
+
+            if (contactWays.isEmpty()) {
+            log.warn("回调数据中未找到手机号,跳过同步");
+            }
+
+            // 根据手机号模糊查询氚云客户表
+            List<JsonNode> h3yunContacts = queryH3yunContactsByPhones(contactWays);
+
+            if (h3yunContacts.isEmpty()) {
+                // 无数据,新增
+                log.info("氚云中未找到对应客户,执行新增操作");
+                createH3yunContact(event);
+            } else {
+                // 有数据,更新(取第一条)
+                JsonNode existingContact = h3yunContacts.get(0);
+                String bizObjectId = existingContact.path("ObjectId").asText();
+                log.info("氚云中找到对应客户,执行更新操作, BizObjectId={}", bizObjectId);
+                updateH3yunContactWithStatus(bizObjectId,event);
+            }
+
+        } catch (Exception e) {
+            log.error("同步卫瓴数据到氚云异常", e);
+        }
+    }
+
+
+    /**
+     * 在氚云中创建客户数据
+     * 使用 CreateBizObjects 批量创建接口
+     */
+    private void createH3yunContact(JSONObject event) throws IOException {
+
+        // 构建BizObject数据
+       JSONObject bizObject = new JSONObject();
+
+        //有赞数据ID contact_id
+        String contactId = event.getString("contact_id");
+        if (contactId != null){
+            bizObject.put("F0000211", contactId);
+        }
+
+        // 新增基本信息
+        String userName = event.getString("user_name"); //客户名称
+        if (userName != null) {
+            bizObject.put("F0000001", userName);//传给氚云客户昵称
+        }
+
+        //营销顾问
+        if (event.containsKey("marketingConsultant")){
+            String marketingConsultant = event.getString("marketingConsultant");
+            if (marketingConsultant != null && !marketingConsultant.isEmpty()){
+                bizObject.put("employee", marketingConsultant);//传给氚云客户经理
+            }
+        }
+
+        Map<String, Object> paramMap = new HashMap<>();
+        paramMap.put("ActionName", "CreateBizObject");
+        paramMap.put("SchemaCode", H3yunSchemaCode_KH);
+        paramMap.put("BizObject", JSON.toJSONString(bizObject));
+        paramMap.put("IsSubmit", "true");
+
+        String result = youZanTokenUtil.callH3yunApi(paramMap);
+        log.info("氚云批量创建客户响应: {}", result);
+    }
+
+    /**
+     * 更新氚云客户数据
+     */
+    private void updateH3yunContactWithStatus(String bizObjectId, JSONObject event) {
+
+        Map<String, Object> bizObject = new HashMap<>();
+
+        // 更新基本信息
+        String userName = event.getString("user_name"); //客户名称
+        if (userName != null) {
+            bizObject.put("F0000001", userName);//传给氚云客户昵称
+        }
+
+        //营销顾问
+        if (event.containsKey("marketingConsultant")){
+            String marketingConsultant = event.getString("marketingConsultant");
+            if (marketingConsultant != null && !marketingConsultant.isEmpty()){
+                bizObject.put("employee", marketingConsultant);//传给氚云客户经理
+            }
+        }
+
+
+        // 调用更新接口
+        try {
+            Map<String, Object> paramMap = new HashMap<>();
+            paramMap.put("ActionName", "UpdateBizObject");
+            paramMap.put("SchemaCode", H3yunSchemaCode_KH);
+            paramMap.put("BizObjectId", bizObjectId);
+            paramMap.put("BizObject", JSON.toJSONString(bizObject));
+
+            String result = youZanTokenUtil.callH3yunApi(paramMap);
+            log.info("氚云更新客户响应, BizObjectId={}, result={}", bizObjectId, result);
+        } catch (Exception e) {
+            log.error("调用氚云更新接口异常, BizObjectId={}", bizObjectId, e);
+        }
+    }
+
+    /**
+     * 根据手机号列表模糊查询氚云客户表
+     * 使用 LoadBizObjects 批量查询接口
+     */
+    private List<JsonNode> queryH3yunContactsByPhones(List<String> phones) throws IOException {
+        if (phones == null || phones.isEmpty()) {
+            return new ArrayList<>();
+        }
+
+        // 构建Filter,使用手机号字段(F0000212)进行模糊查询
+        // 由于是模糊查询,使用 Like 操作符(Operator=8)
+        JSONArray matchers = new JSONArray();
+        for (String phone : phones) {
+            if (phone != null && !phone.trim().isEmpty()) {
+                // 构建单个手机号的查询条件(模糊匹配)
+                JSONObject phoneMatcher = new JSONObject();
+                phoneMatcher.put("Type", "Item");
+                phoneMatcher.put("Name", "F0000212"); // 手机号数组字段
+                phoneMatcher.put("Operator", 8); // 8=Like(包含)
+                phoneMatcher.put("Value", phone.trim());
+                matchers.add(phoneMatcher);
+            }
+        }
+
+        // 如果有多个手机号,使用 Or 连接
+        JSONObject orMatcher = new JSONObject();
+        orMatcher.put("Type", "Or");
+        orMatcher.put("Matchers", matchers);
+
+        // 外层使用 And
+        JSONObject andMatcher = new JSONObject();
+        andMatcher.put("Type", "And");
+        JSONArray andMatchers = new JSONArray();
+        andMatchers.add(orMatcher);
+        andMatcher.put("Matchers", andMatchers);
+
+        // 构建完整的Filter
+        JSONObject filter = new JSONObject();
+        filter.put("FromRowNum", 0);
+        filter.put("ToRowNum", 500);
+        filter.put("RequireCount", false);
+        filter.put("ReturnItems", new JSONArray());
+        filter.put("SortByCollection", new JSONArray());
+        filter.put("Matcher", andMatcher);
+
+        Map<String, Object> paramMap = new HashMap<>();
+        paramMap.put("ActionName", "LoadBizObjects");
+        paramMap.put("SchemaCode", H3yunSchemaCode_KH);
+        paramMap.put("Filter", filter.toJSONString());
+
+        String result = youZanTokenUtil.callH3yunApi(paramMap);
+        log.info("氚云批量查询响应: {}", result);
+
+        List<JsonNode> contacts = new ArrayList<>();
+        try {
+            ObjectMapper objectMapper = new ObjectMapper();
+            JsonNode rootNode = objectMapper.readTree(result);
+            JsonNode returnData = rootNode.path("ReturnData");
+            JsonNode bizObjectArray = returnData.path("BizObjectArray");
+
+            if (bizObjectArray.isArray()) {
+                for (JsonNode item : bizObjectArray) {
+                    contacts.add(item);
+                }
+            }
+        } catch (Exception e) {
+            log.error("解析氚云批量查询响应异常", e);
+        }
+
+        return contacts;
+    }
+
+}

+ 90 - 0
mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/timers/timerOne.java

@@ -0,0 +1,90 @@
+package com.malk.rjk.timers;
+
+
+import com.malk.rjk.server.RjkServer;
+import com.malk.rjk.server.impl.KingDeeImpl;
+import com.malk.server.common.McException;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.scheduling.annotation.Scheduled;
+
+@Slf4j
+@Configuration
+@EnableScheduling
+public class timerOne {
+
+    @Autowired
+    private RjkServer rjkServer; // 物料服务
+//
+//    // 采购进度计算任务
+//    @Scheduled(fixedRateString = "${timer.purchase.interval:7200000}")
+//    public void calculatePurchaseProgress() {
+//        log.info("开始执行采购进度计算任务");
+//        long startTime = System.currentTimeMillis();
+//
+//        try {
+//          kingDeeImpl.get_request();
+//            log.info("采购进度计算任务完成,耗时: {}ms", System.currentTimeMillis() - startTime);
+//        } catch (McException e) {
+//            log.error("采购进度计算任务失败: {}", e.getMessage(), e);
+//            // 可以根据需要添加告警逻辑
+//        } catch (Exception e) {
+//            log.error("采购进度计算任务发生未预期错误", e);
+//        }
+//    }
+//
+//    // 生产进度同步任务
+//
+//    @Scheduled(fixedRateString = "${timer.production.interval:7200000}")
+//    public void syncProductionProgress() {
+//        log.info("开始执行生产进度同步任务");
+//        long startTime = System.currentTimeMillis();
+//
+//        try {
+//          kingDeeImpl.get_SCJD();
+//            log.info("生产进度同步任务完成,耗时: {}ms", System.currentTimeMillis() - startTime);
+//        } catch (McException e) {
+//            log.error("生产进度同步任务失败: {}", e.getMessage(), e);
+//        } catch (Exception e) {
+//            log.error("生产进度同步任务发生未预期错误", e);
+//        }
+//    }
+//
+//    // 物料同步任务
+//    @Scheduled(fixedRateString = "${timer.material.interval:3600000}")
+//    public void syncMaterials() {
+//        log.info("开始执行物料同步任务");
+//        long startTime = System.currentTimeMillis();
+//
+//        try {
+//            kingDeeImpl.get_WL();
+//            log.info("物料同步任务完成,耗时: {}ms", System.currentTimeMillis() - startTime);
+//        } catch (McException e) {
+//            log.error("物料同步任务失败: {}", e.getMessage(), e);
+//        } catch (Exception e) {
+//            log.error("物料同步任务发生未预期错误", e);
+//        }
+//    }
+
+
+
+    //定时获取token并缓存
+    @Scheduled(fixedRateString = "${timer.material.interval:1800000}")
+    public void syncMaterials() {
+
+
+        try {
+
+            rjkServer.getToken();
+
+        } catch (McException e) {
+
+        } catch (Exception e) {
+
+        }
+    }
+
+}

+ 68 - 0
mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/util/CryptUtil.java

@@ -0,0 +1,68 @@
+package com.malk.rjk.util;
+
+import com.google.common.base.CharMatcher;
+import com.google.common.io.BaseEncoding;
+import org.apache.commons.codec.binary.Base64;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+
+public class CryptUtil {
+    protected byte[] aesKey;
+
+    public CryptUtil(String encodingAesKey) {
+        this.aesKey = BaseEncoding.base64().decode(CharMatcher.whitespace().removeFrom(encodingAesKey));
+    }
+
+    public String decrypt(String encryptedText) {
+        byte[] original;
+        try {
+            // 设置解密模式为AES的CBC模式
+            Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
+            SecretKeySpec keySpec = new SecretKeySpec(this.aesKey, "AES");
+            IvParameterSpec iv = new IvParameterSpec(Arrays.copyOfRange(this.aesKey, 0, 16));
+            cipher.init(Cipher.DECRYPT_MODE, keySpec, iv);
+            // 使用BASE64对密文进行解码
+            byte[] encrypted = Base64.decodeBase64(encryptedText);
+            // 解密
+            original = cipher.doFinal(encrypted);
+        } catch (Exception e) {
+            return null;
+        }
+        String trueContent;
+        try {
+            // 去除补位字符
+            byte[] bytes = decode(original);
+            // 分离16位随机字符串,网络字节序
+            byte[] networkOrder = Arrays.copyOfRange(bytes, 16, 20);
+            int contentLength = bytesNetworkOrder2Number(networkOrder);
+            trueContent = new String(Arrays.copyOfRange(bytes, 20, 20 + contentLength), StandardCharsets.UTF_8);
+        } catch (Exception e) {
+            return null;
+        }
+        return trueContent;
+    }
+
+    public static byte[] decode(byte[] decrypted) {
+        int pad = decrypted[decrypted.length - 1];
+        if (pad < 1 || pad > 32) {
+            pad = 0;
+        }
+        return Arrays.copyOfRange(decrypted, 0, decrypted.length - pad);
+    }
+
+    /**
+     * 4个字节的网络字节序bytes数组还原成一个数字.
+     */
+    private static int bytesNetworkOrder2Number(byte[] bytesInNetworkOrder) {
+        int sourceNumber = 0;
+        for (int i = 0; i < 4; i++) {
+            sourceNumber <<= 8;
+            sourceNumber |= bytesInNetworkOrder[i] & 0xff;
+        }
+        return sourceNumber;
+    }
+}

+ 141 - 0
mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/util/DownHelp.java

@@ -0,0 +1,141 @@
+package com.malk.rjk.util;
+
+import java.io.*;
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+public class DownHelp {
+    public static void getInternetResdoPost(  String fileId) {
+        // 定义输出流 字节输出流,可以用来写转换之后的字节到文件中
+        OutputStreamWriter out = null;
+        // 定义输入流
+        BufferedReader br = null;
+        String result = "";
+        FileOutputStream outfile = null;
+        try {
+            // 统一资源
+            URL url = new URL("https://www.h3yun.com/Api/DownloadBizObjectFile");
+
+            // 打开和url之间的连接
+            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+
+            // 设定请求的方法为"POST",默认是GET
+            // post与get的不同之处在于post的参数不是放在URL字串里面,而是放在http请求的正文内。
+            conn.setRequestMethod("POST");
+
+            // 设置30秒连接超时
+            conn.setConnectTimeout(30000);
+            // 设置30秒读取超时
+            conn.setReadTimeout(30000);
+
+            // 设置是否向httpUrlConnection输出,因为这个是post请求,参数要放在http正文内,因此需要设为true, 默认情况下是false;
+            conn.setDoOutput(true);
+            // 设置是否从httpUrlConnection读入,默认情况下是true;
+            conn.setDoInput(true);
+
+            // Post请求不能使用缓存
+            conn.setUseCaches(false);
+
+            // 设置通用的请求属性
+            conn.setRequestProperty("accept", "*/*");
+            conn.setRequestProperty("connection", "Keep-Alive"); // 维持长链接
+            conn.setRequestProperty("Content-Type", "application/json;charset=utf-8");
+            // 必须要有的身份认证参数 在组织 界面 钉钉右上角 我的头像点击 后 系统管理》系统集成中有这两个字段
+            conn.setRequestProperty("EngineCode", "jd4fneb66qz2gfvau6svhkxk1");
+            conn.setRequestProperty("EngineSecret", "F8K2W/09HZpQGiN0IgRNNi+EJKCFz9Zu6xTNsz6yjCpsAS0+tbSEeQ==");
+            // 连接,从上述url.openConnection()至此的配置必须要在connect之前完成,
+            conn.connect();
+            String jsonInputString = "{\"EngineCode\":\"jd4fneb66qz2gfvau6svhkxk1\", \"attachmentId\":\""+fileId+"\"}";
+
+            /**
+             * 下面的三句代码,就是调用第三方http接口
+             */
+            // 获取URLConnection对象对应的输出流
+            // 此处getOutputStream会隐含的进行connect(即:如同调用上面的connect()方法,所以在开发中不调用上述的connect()也可以)。
+            out = new OutputStreamWriter(conn.getOutputStream(), "UTF-8");
+            // 发送请求参数即数据
+            out.write(jsonInputString);
+            // flush输出流的缓冲
+            out.flush();
+
+            /**
+             * 下面的代码相当于,获取调用第三方http接口后返回的结果
+             */
+            // 获取URLConnection对象对应的输入流
+            InputStream is = conn.getInputStream();
+
+            // 获取文件类型 例如:返回的格式是:attachment;filename=1.png
+            String headerField = conn.getHeaderField("Content-Disposition");
+            headerField = new String(headerField.getBytes("GB2312"));
+            headerField = new String(headerField.getBytes("utf-8"));
+            // 截取得到正确的文件名
+            String filename = substringGetType(headerField);
+            byte[] NEWdata = getByteData(is);
+
+            // 建立存储的目录、保存的文件名
+            File file = new File("D:\\本地下载aaa\\" + "文件下载");
+            if (!file.exists()) {
+                file.mkdirs();
+            }
+            // 修改文件名
+            File res = new File(file + File.separator + filename);
+            // 写入输出流
+            outfile = new FileOutputStream(res);
+            outfile.write(NEWdata);
+            System.out.println("下载成功!");
+            // 关闭流
+            is.close();
+            // 断开连接,disconnect是在底层tcp socket链接空闲时才切断,如果正在被其他线程使用就不切断。
+            conn.disconnect();
+
+        } catch (Exception e) {
+            e.printStackTrace();
+            System.out.println("下载失败!");
+        } finally {
+            try {
+                if (out != null) {
+                    out.close();
+                    outfile.close();
+                }
+                if (br != null) {
+                    br.close();
+                }
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    /**
+     *
+     * @Title: getByteData
+     * @Description: 从输入流中获取字节数组
+     */
+    private static byte[] getByteData(InputStream in) throws IOException {
+        byte[] b = new byte[1024];
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        int len = 0;
+        while ((len = in.read(b)) != -1) {
+            bos.write(b, 0, len);
+        }
+        if (null != bos) {
+            bos.close();
+        }
+        return bos.toByteArray();
+    }
+
+    /**
+     *
+     * @Title: substringGetPng
+     * @Description: 截取字符串得到正确的文件类型
+     *
+     */
+    public static String substringGetType(String str) {
+        // 获得第=号的位置
+        int index = str.indexOf("=");
+        String result = str.substring(index + 1);
+        // 输出结果
+        System.out.println("下载文件名:" + result);
+        return result;
+    }
+}

+ 37 - 0
mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/util/HttpRequestUtil.java

@@ -0,0 +1,37 @@
+package com.malk.rjk.util;
+
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.util.EntityUtils;
+
+import java.io.IOException;
+
+public class HttpRequestUtil {
+    public static String sendPost(String url, String jsonPayload, org.apache.http.Header... headers) throws IOException {
+        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
+            HttpPost postRequest = new HttpPost(url);
+
+            // 设置请求头
+            if (headers != null) {
+                for (org.apache.http.Header header : headers) {
+                    postRequest.addHeader(header);
+                }
+            }
+
+            // 设置请求体
+            StringEntity entity = new StringEntity(jsonPayload);
+            postRequest.setEntity(entity);
+
+            postRequest.setHeader("accept", "*/*");
+            postRequest.setHeader("connection", "Keep-Alive");
+            postRequest.setHeader("Content-type", "application/json;charset=utf-8");
+
+            try (CloseableHttpResponse response = httpClient.execute(postRequest)) {
+                return EntityUtils.toString(response.getEntity());
+            }
+        }
+    }
+}

+ 22 - 0
mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/util/SHA1.java

@@ -0,0 +1,22 @@
+package com.malk.rjk.util;
+
+import cn.hutool.core.util.StrUtil;
+import org.apache.commons.codec.digest.DigestUtils;
+
+import java.util.Arrays;
+
+
+public class SHA1 {
+
+    public static String gen(String... arr) {
+        if (StrUtil.hasBlank(arr)) {
+            throw new IllegalArgumentException("非法请求参数,有部分参数为空 : " +  Arrays.toString(arr));
+        }
+        Arrays.sort(arr);
+        StringBuilder sb = new StringBuilder();
+        for (String a : arr) {
+            sb.append(a);
+        }
+        return DigestUtils.sha1Hex(sb.toString());
+    }
+}

+ 164 - 0
mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/util/YouZanTokenUtil.java

@@ -0,0 +1,164 @@
+package com.malk.rjk.util;
+
+import cn.hutool.http.HttpRequest;
+import cn.hutool.http.HttpResponse;
+import cn.hutool.json.JSONUtil;
+import com.alibaba.fastjson.JSON;
+import com.malk.rjk.dto.YouZanTokenResponse;
+import com.malk.server.h3yun.CYConf;
+import lombok.extern.slf4j.Slf4j;
+import okhttp3.*;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 有赞云 Token 工具类
+ * 用于获取和刷新 access_token
+ * 
+ * @author: shunxi
+ * @createTime: 2026-01-14
+ */
+@Slf4j
+@Component
+public class YouZanTokenUtil {
+
+    @Autowired
+    private  CYConf cyConf;
+
+    /**
+     * 获取  Token 的有赞接口地址
+     */
+    private static final String TOKEN_URL = "https://open.youzan.com/oauth/token";
+
+    /**
+     * 获取氚云接口地址
+     */
+    public static String H3yunUrl = "https://www.h3yun.com/OpenApi/Invoke";
+
+
+    /**
+     * 获取 access_token
+     * 
+     * @param clientId 应用的 client_id
+     * @param clientSecret 应用的 client_secret
+     * @param kdtId 店铺的 kdt_id
+     * @return YouZanTokenResponse Token 响应对象
+     * @throws Exception 获取失败时抛出异常
+     */
+    public static YouZanTokenResponse getAccessToken(String clientId, String clientSecret, String kdtId) throws Exception {
+        try {
+            HttpResponse response = HttpRequest.post(TOKEN_URL)
+                    .form("client_id", clientId)
+                    .form("client_secret", clientSecret)
+                    .form("grant_type", "silent")
+                    .form("kdt_id", kdtId)
+                    .execute();
+            
+            if (response.getStatus() != 200) {
+                log.error("获取 access_token 失败,状态码:{},响应内容:{}", response.getStatus(), response.body());
+                throw new Exception("获取 access_token 失败,状态码:" + response.getStatus() + ",响应内容:" + response.body());
+            }
+            
+            String body = response.body();
+            log.info("获取 access_token 成功,响应内容:{}", body);
+            
+            YouZanTokenResponse tokenResponse = JSONUtil.toBean(body, YouZanTokenResponse.class);
+            
+            if (tokenResponse.getAccess_token() == null || tokenResponse.getAccess_token().isEmpty()) {
+                log.error("获取 access_token 失败,响应中未包含 access_token,响应内容:{}", body);
+                throw new Exception("获取 access_token 失败,响应中未包含 access_token");
+            }
+            
+            return tokenResponse;
+        } catch (Exception e) {
+            log.error("获取 access_token 异常:", e);
+            throw e;
+        }
+    }
+    
+    /**
+     * 刷新 access_token
+     * 
+     * @param clientId 应用的 client_id
+     * @param clientSecret 应用的 client_secret
+     * @param refreshToken 刷新令牌
+     * @return YouZanTokenResponse Token 响应对象
+     * @throws Exception 刷新失败时抛出异常
+     */
+    public static YouZanTokenResponse refreshAccessToken(String clientId, String clientSecret, String refreshToken) throws Exception {
+        try {
+            HttpResponse response = HttpRequest.post(TOKEN_URL)
+                    .form("client_id", clientId)
+                    .form("client_secret", clientSecret)
+                    .form("grant_type", "refresh_token")
+                    .form("refresh_token", refreshToken)
+                    .execute();
+            
+            if (response.getStatus() != 200) {
+                log.error("刷新 access_token 失败,状态码:{},响应内容:{}", response.getStatus(), response.body());
+                throw new Exception("刷新 access_token 失败,状态码:" + response.getStatus() + ",响应内容:" + response.body());
+            }
+            
+            String body = response.body();
+            log.info("刷新 access_token 成功,响应内容:{}", body);
+            
+            YouZanTokenResponse tokenResponse = JSONUtil.toBean(body, YouZanTokenResponse.class);
+            
+            if (tokenResponse.getAccess_token() == null || tokenResponse.getAccess_token().isEmpty()) {
+                log.error("刷新 access_token 失败,响应中未包含 access_token,响应内容:{}", body);
+                throw new Exception("刷新 access_token 失败,响应中未包含 access_token");
+            }
+            
+            return tokenResponse;
+        } catch (Exception e) {
+            log.error("刷新 access_token 异常:", e);
+            throw e;
+        }
+    }
+
+
+
+    /**
+     * 调用氚云OpenAPI接口
+     *
+     * @param paramMap 请求参数Map
+     * @return API返回的响应字符串
+     * @throws IOException 网络连接异常
+     */
+    public String callH3yunApi(Map<String, Object> paramMap) throws IOException {
+        String requestBody = JSON.toJSONString(paramMap);
+
+        OkHttpClient client = new OkHttpClient.Builder()
+                .connectTimeout(60, TimeUnit.SECONDS)
+                .readTimeout(120, TimeUnit.SECONDS)
+                .writeTimeout(60, TimeUnit.SECONDS)
+                .retryOnConnectionFailure(true) // 自动重试
+                .build();
+
+        RequestBody body = RequestBody.create(
+                requestBody,
+                MediaType.parse("application/json; charset=utf-8")
+        );
+
+        Request request = new Request.Builder()
+                .url(H3yunUrl)
+                .post(body)
+                .addHeader("EngineCode", cyConf.getEngineCode())
+                .addHeader("EngineSecret",cyConf.getSecret() )
+                .addHeader("Content-Type", "application/json;charset=utf-8")
+                .build();
+
+        try (Response response = client.newCall(request).execute()) {
+            if (!response.isSuccessful()) {
+                String errorBody = response.body() != null ? response.body().string() : "No error body";
+                throw new IOException("Unexpected code " + response + ", error body: " + errorBody);
+            }
+            return response.body().string();
+        }
+    }
+
+}

+ 24 - 0
mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/util/convertTimestampToDateTimeUtil.java

@@ -0,0 +1,24 @@
+package com.malk.rjk.util;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+public class convertTimestampToDateTimeUtil {
+    /**
+     * 将13位时间戳转换为标准时间格式
+     */
+    public static String convertTimestampToDateTime(long timestamp) {
+        if (timestamp <= 0) {
+            return null;
+        }
+
+        // 创建日期格式化器,格式为 "yyyy-MM-dd"
+        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); //yyyy-MM-dd HH:mm:ss
+
+        // 将时间戳转换为Date对象
+        Date date = new Date(timestamp);
+
+        // 格式化为字符串
+        return dateFormat.format(date);
+    }
+}

+ 42 - 0
mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/util/convertToTimestampUtil.java

@@ -0,0 +1,42 @@
+package com.malk.rjk.util;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+public class convertToTimestampUtil {
+    public static long convertToTimestamp(String time) {
+        if (time == null || time.isEmpty()) {
+            return 0;
+        }
+
+        // 定义多种可能的日期格式
+        String[] formats = {
+                "yyyy/MM/dd HH:mm:ss",
+                "yyyy-MM-dd HH:mm:ss",
+                "yyyy.MM.dd HH:mm:ss",
+                "yyyy/MM/dd HH:mm",
+                "yyyy-MM-dd HH:mm",
+                "yyyy.MM.dd HH:mm",
+                "yyyy/MM/dd",
+                "yyyy-MM-dd",
+                "yyyy.MM.dd"
+        };
+
+        for (String format : formats) {
+            try {
+                SimpleDateFormat sdf = new SimpleDateFormat(format);
+                Date date = sdf.parse(time);
+                return date.getTime();
+            } catch (ParseException e) {
+                // 继续尝试下一种格式
+                continue;
+            }
+        }
+
+        // 如果所有格式都失败,抛出异常
+        throw new RuntimeException("无法解析日期格式: " + time);
+    }
+
+}
+

+ 125 - 0
mjava-RJK/mjava-rjk/src/main/java/com/malk/rjk/util/javaHelp.java

@@ -0,0 +1,125 @@
+package com.malk.rjk.util;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.smecloud.apigw.client.ApigwClient;
+import com.smecloud.apigw.model.ApiRequest;
+import com.smecloud.apigw.model.ApiResult;
+import com.smecloud.apigw.model.ApigwConfig;
+import lombok.extern.slf4j.Slf4j;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Random;
+
+import static com.smecloud.apigw.constant.HttpMethod.GET;
+@Slf4j
+public class javaHelp {
+
+    /**
+     * 获取随机正整数
+     *
+     * @return
+     */
+    public static String get_randomNumber() {
+        Random random = new Random();
+
+        // 指定随机数的范围(例如,1到100之间的随机正整数)
+        int min = 100000000;
+        int max = 999999999;
+
+        // 生成随机数
+        int randomNumber = random.nextInt((max - min) + 1) + min;
+        return String.valueOf(randomNumber);
+
+    }
+
+    /**
+     * 获取金蝶app_signature
+     */
+    public static String get_app_signature(String app_key, String app_Secret) {
+        try {
+            // 创建一个Mac实例,并指定使用HmacSHA256算法
+            Mac mac = Mac.getInstance("HmacSHA256");
+
+            // 使用appSecret作为密钥
+            SecretKeySpec secretKeySpec = new SecretKeySpec(app_Secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
+            mac.init(secretKeySpec);
+
+            // 对appKey进行加密
+            byte[] hmacBytes = mac.doFinal(app_key.getBytes(StandardCharsets.UTF_8));
+
+            // 将加密结果转换为16进制字符串
+            StringBuilder hexString = new StringBuilder(2 * hmacBytes.length);
+            for (byte b : hmacBytes) {
+                String hex = Integer.toHexString(0xff & b);
+                if (hex.length() == 1) hexString.append('0');
+                hexString.append(hex);
+            }
+
+            // 将16进制字符串进行Base64编码
+            byte[] hexBytes = hexString.toString().getBytes(StandardCharsets.UTF_8);
+            String base64Encoded = Base64.getEncoder().encodeToString(hexBytes);
+
+            return base64Encoded;
+        } catch (NoSuchAlgorithmException | InvalidKeyException e) {
+            throw new RuntimeException("Error while generating HMAC-SHA256 hash", e);
+        }
+    }
+
+    /**
+     * 获取金蝶get_appToken
+     */
+
+    public static String get_appToken() throws IOException {
+        String setClientID = "300657";
+        String setClientSecret = "9163551ef2e2a2cd2398dc05ec527132";
+        String app_key = "tcyhmh71";
+        String app_Secret = "994a439a551324bc207cf8771eb645ffc8044e1d";
+        ApigwConfig config = new ApigwConfig();
+        //设置client_id
+        config.setClientID(setClientID);
+        //设置client_secret
+        config.setClientSecret(setClientSecret);
+        ApigwClient apigwClient = ApigwClient.getInstance();
+        //初始化API网关客户端
+        apigwClient.init(config);
+        ApiRequest request = new ApiRequest(GET, "api.kingdee.com", "/jdyconnector/app_management/kingdee_auth_token");
+        Map<String, String> map = new HashMap<>();
+        map.put("app_key", app_key);
+        String app_signature = get_app_signature(app_key, app_Secret);
+        map.put("app_signature", app_signature);
+        request.setQuerys(map);
+        request.setBodyJson(JSONObject.toJSONString("").getBytes());
+        ApiResult result = ApigwClient.getInstance().send(request);
+        // 解析JSON字符串
+        String jsonBody = result.getBody();
+// 解析JSON字符串
+        JSONObject jsonObject = JSON.parseObject(jsonBody);
+        JSONObject dataObject = jsonObject.getJSONObject("data");
+        if (jsonObject.get("description").equals("成功")){
+            String appToken = dataObject.getString("app-token");
+            System.out.println("获取token成功:"+appToken);
+            log.info("获取token成功:"+appToken);
+            return appToken;
+        }else {
+            System.out.println("获取token失败:"+result.getBody() );
+            log.info("获取token失败:"+result.getBody());
+            return "";
+        }
+// 获取app-token,注意fastjson对key中包含特殊字符(如-)的处理是自动的
+
+    }
+    ////
+
+
+}
+
+

+ 74 - 0
mjava-RJK/mjava-rjk/src/main/resources/application-dev.yml

@@ -0,0 +1,74 @@
+# 环境配置
+server:
+  port: 8091
+  servlet:
+    context-path: /api/rjk
+
+# condition
+spel:
+  scheduling: true        # 定时任务是否执行
+  multiSource: false       # 是否多数据源配置
+
+nc:
+  scheduling: true        # 定时任务是否执行
+  multiSource: false       # 是否多数据源配置
+
+spring:
+  # database
+  datasource:
+    hikari:
+      connection-init-sql: SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci           # SqlServer, Oracle 无需设置类型
+    driver-class-name: com.mysql.cj.jdbc.Driver
+    username: root
+    password: 123456
+    url: jdbc:mysql://10.100.0.10:10021/admin_dev?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&useSSL=false
+  jpa:
+    hibernate:
+      ddl-auto: none      # JPA对表没有任何操作
+    show-sql: true
+    database: MYSQL
+    database-platform: org.hibernate.dialect.MySQL57Dialect
+
+#filepath
+file:
+  path:
+    file: /Users/malk/server/_Tool/var/mjava/tmp/file
+    image: /Users/malk/server/_Tool/var/mjava/tmp/image
+    tmp: /Users/malk/server/_Tool/var/mjava/tmp
+  source:
+    fonts: /Users/malk/server/_Tool/fonts/simsun.ttc
+logging:
+  file:
+    path: /Users/malk/server/_Tool/var/mjava/log
+
+# dingtalk-汉信
+dingtalk:
+  agentId: 3297559800
+  appKey: dingssx5a9pev3qcbvq9
+  appSecret: LJgj3NFht0ffbfFc7RYdHiL53blYh_QlUZk1zec2pH_tsxmhe6hYgiBlvdKY3u32
+  corpId: ding50becc1a2807185135c2f4657eb6378f
+  aesKey:
+  token:
+  operator: "683135622237866009"
+  #operator: "manager1982"   #  - OA管理员账号 [首字符若为0需要转一下字符串]
+
+
+
+#SqlServer
+sqlserver:
+  url: jdbc:sqlserver://58.246.128.122:2433;databaseName=lanyun
+  username: sa
+  password: " #"
+
+
+# h3yun- 然健康
+h3yun:
+  engineCode: oqvim4svsekedlty2e22mt630
+  secret: gyN60WLb4bwttdUZLS4EAN2bIw4VMiPN8guOAsafphyEBzOixpxhqg==
+
+# 有赞云配置
+youzan:
+  client-id: 123213214
+  client-secret: 21421443
+  kdt-id: 2434344
+

+ 61 - 0
mjava-RJK/mjava-rjk/src/main/resources/application-prod.yml

@@ -0,0 +1,61 @@
+server:
+  port: 8091
+  servlet:
+    context-path: /api/rjk
+
+# condition
+spel:
+  scheduling: true        # 定时任务是否执行
+  multiSource: false      # 是否多数据源配置
+
+nc:
+  scheduling: true        # 定时任务是否执行
+  multiSource: false      # 是否多数据源配置
+
+spring:
+  # database
+  datasource:
+    hikari:
+      connection-init-sql: SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci           # SqlServer, Oracle 无需设置类型
+    driver-class-name: com.mysql.cj.jdbc.Driver
+    username: root
+    password: 123456
+    url: jdbc:mysql://10.100.0.10:10021/admin_dev?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&useSSL=false
+  jpa:
+    database: MYSQL
+    database-platform: org.hibernate.dialect.MySQL57Dialect
+
+#filepath
+file:
+  path:
+    file: /Users/malk/server/_Tool/var/mjava/tmp/file
+    image: /Users/malk/server/_Tool/var/mjava/tmp/image
+    tmp: /Users/malk/server/_Tool/var/mjava/tmp
+  source:
+    fonts: /Users/malk/server/_Tool/fonts/simsun.ttc
+logging:
+  file:
+    path: /Users/malk/server/_Tool/var/mjava/log
+# dingtalk-汉信
+dingtalk:
+  agentId: 3297559800
+  appKey: dingssx5a9pev3qcbvq9
+  appSecret: LJgj3NFht0ffbfFc7RYdHiL53blYh_QlUZk1zec2pH_tsxmhe6hYgiBlvdKY3u32
+  corpId: ding50becc1a2807185135c2f4657eb6378f
+  aesKey:
+  token:
+ # operator: "683135622237866009"
+  operator: "manager1982"   # 李若冰- OA管理员账号 [首字符若为0需要转一下字符串]
+
+
+sqlserver:
+  url: jdbc:sqlserver://192.168.0.237:1433;databaseName=lanyun
+  userName: sa
+  password: " !2024#"
+
+
+
+# h3yun- 然健康
+h3yun:
+  engineCode: oqvim4svsekedlty2e22mt630
+  secret: gyN60WLb4bwttdUZLS4EAN2bIw4VMiPN8guOAsafphyEBzOixpxhqg==

+ 414 - 0
mjava-RJK/mjava-rjk/src/test/java/test.java

@@ -0,0 +1,414 @@
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.malk.rjk.Boot;
+import com.malk.rjk.server.RjkServer;
+import com.malk.server.common.McException;
+import com.smecloud.apigw.client.ApigwClient;
+import com.smecloud.apigw.model.ApiRequest;
+import com.smecloud.apigw.model.ApiResult;
+import com.smecloud.apigw.model.ApigwConfig;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.util.EntityUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.*;
+import java.nio.charset.StandardCharsets;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.util.*;
+
+import static com.smecloud.apigw.constant.HttpMethod.GET;
+
+
+@Slf4j
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes = Boot.class)
+public class test {
+
+    @Test
+    public void testService() throws IOException, NoSuchAlgorithmException, InvalidKeyException {
+
+        Map<String, String> mes_params = push_app_authorize("300657", "9163551ef2e2a2cd2398dc05ec527132", "356919829745242112");//根据应用ID(Client ID),第三方实例ID调取实时
+        if (mes_params.size() > 0) {
+            String appSecret = mes_params.get("appSecret").toString();
+            String domain = mes_params.get("domain").toString();
+            if (domain != "" && appSecret != "" && domain != null && appSecret != null) {
+                get_material("300657", domain, appSecret, "9163551ef2e2a2cd2398dc05ec527132");
+            }
+        }
+//
+//        CookieStore cookieStore = new BasicCookieStore();
+//        // 创建一个 Httpclient 实例
+//        CloseableHttpClient httpclient = HttpClients.custom().setDefaultCookieStore(cookieStore).build();
+//        HttpGet otherRequest = new HttpGet("https://api.kingdee.com/jdy/v2/bd/material");
+//        otherRequest.setHeader("Content-Type", "300657");
+//        otherRequest.setHeader("X-Api-ClientID", "application/json");
+//        otherRequest.setHeader("X-Api-Auth-Version", "2.0");
+//        otherRequest.setHeader("X-Api-TimeStamp", String.valueOf(System.currentTimeMillis()));
+//        otherRequest.setHeader("X-Api-SignHeaders", "X-Api-TimeStamp,X-Api-Nonce");
+//        otherRequest.setHeader("X-Api-Nonce", this.get_randomNumber());
+//        otherRequest.setHeader("X-Api-Signature", get_app_signature("tcyhmh71","206bfff1ea61c4699f55ce895848c3e52a8dff02"));
+//        otherRequest.setHeader("app-token", this.get_appToken());
+//        otherRequest.setHeader("X-GW-Router-Addr", this.get_appToken());
+//        CloseableHttpResponse otherResponse = httpclient.execute(otherRequest);
+
+    }
+
+    /**
+     * 主动获取授权
+     *
+     * @return
+     */
+    public static Map<String, String> push_app_authorize(String clientId, String Secret, String outerInstanceId) throws IOException, NoSuchAlgorithmException, InvalidKeyException {
+        Map<String, String> mes_params = new TreeMap<>();
+        String url = "https://api.kingdee.com/jdyconnector/app_management/push_app_authorize?outerInstanceId=" + outerInstanceId;
+        HttpPost httpPost = new HttpPost(url);
+        String TimeStamp = String.valueOf(System.currentTimeMillis());
+        String randomNumber = get_randomNumber();
+        /**
+         * Signature加密
+         */
+        String QQFS = "POST";
+        String path = "/jdyconnector/app_management/push_app_authorize";
+        Map<String, String> params = new TreeMap<>(); // TreeMap 会自动按键的 ASCII 码排序
+        params.put("outerInstanceId", outerInstanceId);
+        String Signature = get_app_signature(Secret, QQFS, path, params, TimeStamp, randomNumber);//X-Api-Signature加密获取
+        httpPost.setHeader("X-Api-ClientID", clientId);
+        httpPost.setHeader("X-Api-Auth-Version", "2.0");
+        httpPost.setHeader("X-Api-TimeStamp", TimeStamp);
+        httpPost.setHeader("X-Api-Nonce", randomNumber);
+        httpPost.setHeader("X-Api-SignHeaders", "X-Api-TimeStamp,X-Api-Nonce");
+        httpPost.setHeader("X-Api-Signature", Signature);
+        // 设置请求体(如果有)
+        httpPost.setEntity(new StringEntity("", "UTF-8"));
+        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
+            // 发送请求并处理响应
+            HttpResponse response = httpClient.execute(httpPost);
+            String responseBody = EntityUtils.toString(response.getEntity());
+            JSONObject jsonObject = JSON.parseObject(responseBody);
+            if (jsonObject.getString("code").equals("200")) {
+                JSONArray dataArray = jsonObject.getJSONArray("data");
+                // 假设JSON数组中只有一个对象,或者我们只关心第一个对象
+                JSONObject dataObject = dataArray.getJSONObject(0);
+                String appSecret = dataObject.getString("appSecret");
+                String domain = dataObject.getString("domain");
+                mes_params.put("appSecret", appSecret);
+                mes_params.put("domain", domain);
+
+            } else {
+
+            }
+
+
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        return mes_params;
+    }
+
+    /**
+     * 获取商品列表
+     *
+     * @return
+     */
+    public static String get_material(String clientId, String domain, String appSecret, String Secret) throws IOException, NoSuchAlgorithmException, InvalidKeyException {
+
+        String url = "https://api.kingdee.com/jdy/v2/bd/material";
+        HttpGet httpGet = new HttpGet(url);
+        String TimeStamp = String.valueOf(System.currentTimeMillis());
+        String randomNumber = get_randomNumber();
+        /**
+         * Signature加密
+         */
+        String QQFS = "GET";
+        String path = "/jdy/v2/bd/material";
+        Map<String, String> params = new TreeMap<>(); // TreeMap 会自动按键的 ASCII 码排序
+
+        String Signature = get_app_signature(Secret, QQFS, path, params, TimeStamp, randomNumber);//X-Api-Signature加密获取
+        httpGet.setHeader("X-Api-ClientID", clientId);
+        httpGet.setHeader("X-Api-Auth-Version", "2.0");
+        httpGet.setHeader("X-Api-TimeStamp", TimeStamp);
+        httpGet.setHeader("X-Api-Nonce", randomNumber);
+        httpGet.setHeader("X-Api-SignHeaders", "X-Api-TimeStamp,X-Api-Nonce");
+        httpGet.setHeader("X-Api-Signature", Signature);
+        httpGet.setHeader("Content-Type", "application/json");
+        httpGet.setHeader("app-token", get_appToken(appSecret));
+        httpGet.setHeader("X-GW-Router-Addr", domain);
+        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
+            // 发送请求并处理响应
+            HttpResponse response = httpClient.execute(httpGet);
+            String responseBody = EntityUtils.toString(response.getEntity());
+            JSONObject jsonObject = JSON.parseObject(responseBody);
+            System.out.println("jsonObject:" + jsonObject);
+            String errcode = jsonObject.getString("errcode");
+            if (errcode.equals("0")) {//判断有返回值
+                JSONObject dataObject = jsonObject.getJSONObject("data");
+                // 从data对象中获取rows数组
+                JSONArray rowsArray = dataObject.getJSONArray("rows");
+                // 循环遍历rows数组中的每个对象
+                for (int i = 0; i < rowsArray.size(); i++) {
+                    JSONObject rowObject = rowsArray.getJSONObject(i);
+                    // 获取rowObject中的任意字段,例如name
+                    String shop_name = rowObject.getString("name");//商品名称
+                    String shop_number = rowObject.getString("number");//商品编码
+
+                    if (!shop_number.equals("")){
+
+
+
+
+                    }
+
+                    // 打印或其他处理
+                    System.out.println("商品名称: " + shop_name);
+
+//根据编号查询详情
+
+                    // 你可以继续获取其他字段并进行处理
+                }
+
+            } else {
+
+            }
+
+
+        } catch (McException e) {
+             System.out.println("e:"+e.getMessage());
+        }
+        return "";
+
+    }
+
+
+    //正整数
+    public static String get_randomNumber() {
+        Random random = new Random();
+
+        // 指定随机数的范围(例如,1到100之间的随机正整数)
+        int min = 100000000;
+        int max = 999999999;
+
+        // 生成随机数
+        int randomNumber = random.nextInt((max - min) + 1) + min;
+        return String.valueOf(randomNumber);
+
+    }
+
+    public static String get_appToken(String appSecret) throws IOException {
+        ApigwConfig config = new ApigwConfig();
+        //设置client_id
+        config.setClientID("300657");
+        //设置client_secret
+        config.setClientSecret("9163551ef2e2a2cd2398dc05ec527132");
+        ApigwClient apigwClient = ApigwClient.getInstance();
+        //初始化API网关客户端
+        apigwClient.init(config);
+        ApiRequest request = new ApiRequest(GET, "api.kingdee.com", "/jdyconnector/app_management/kingdee_auth_token");
+        Map<String, String> map = new HashMap<>();
+        map.put("app_key", "tcyhmh71");
+        String app_signature = get_app_signature("tcyhmh71", appSecret);
+        map.put("app_signature", app_signature);
+        request.setQuerys(map);
+        request.setBodyJson(JSONObject.toJSONString("").getBytes());
+        ApiResult result = ApigwClient.getInstance().send(request);
+// 解析JSON字符串
+        String jsonBody = result.getBody();
+// 解析JSON字符串
+        JSONObject jsonObject = JSON.parseObject(jsonBody);
+        JSONObject dataObject = jsonObject.getJSONObject("data");
+
+// 获取app-token,注意fastjson对key中包含特殊字符(如-)的处理是自动的
+        String appToken = dataObject.getString("app-token");
+
+        System.out.println(appToken);
+        return appToken;
+    }
+
+
+    //加密X-Api-Signature加密规则
+    public static String get_app_signature(String app_key, String appSecret) {
+        try {
+            // 创建一个Mac实例,并指定使用HmacSHA256算法
+            Mac mac = Mac.getInstance("HmacSHA256");
+
+            // 使用appSecret作为密钥
+            SecretKeySpec secretKeySpec = new SecretKeySpec(appSecret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
+            mac.init(secretKeySpec);
+
+            // 对appKey进行加密
+            byte[] hmacBytes = mac.doFinal(app_key.getBytes(StandardCharsets.UTF_8));
+
+            // 将加密结果转换为16进制字符串
+            StringBuilder hexString = new StringBuilder(2 * hmacBytes.length);
+            for (byte b : hmacBytes) {
+                String hex = Integer.toHexString(0xff & b);
+                if (hex.length() == 1) hexString.append('0');
+                hexString.append(hex);
+            }
+
+            // 将16进制字符串进行Base64编码
+            byte[] hexBytes = hexString.toString().getBytes(StandardCharsets.UTF_8);
+            String base64Encoded = Base64.getEncoder().encodeToString(hexBytes);
+
+            return base64Encoded;
+        } catch (NoSuchAlgorithmException | InvalidKeyException e) {
+            throw new RuntimeException("Error while generating HMAC-SHA256 hash", e);
+        }
+    }
+
+
+    //加密X-Api-Signature生成规则
+    public static String get_app_signature(String clientSecret, String QQFS, String path, Map<String, String> params, String TimeStamp, String randomNumber) throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException {
+        String method = QQFS;
+        String originalString = path;
+        String target = "/"; // 要替换的字符,转换为字符串
+        String replacement = "%2F"; // 替换后的字符串
+        // 使用String的replace方法替换所有出现的target为replacement
+        String encodedString = originalString.replace(target, replacement);
+        StringBuilder signatureText = new StringBuilder();
+        signatureText.append(method).append("\n");
+        signatureText.append(encodedString).append("\n");
+        // 拼接 params 参数(进行两次 URL 编码并转换为大写,但 TreeMap 已排序且只需编码一次)
+
+        if (params != null) {
+            Map<String, String> encodedParams = doubleUrlEncodeParams(params);
+            // 按参数名的ASCII码升序排序
+            List<Map.Entry<String, String>> sortedParams = new ArrayList<>(encodedParams.entrySet());
+            sortedParams.sort(Comparator.comparing(Map.Entry::getKey));
+            String signString = generateSignString(sortedParams);
+            signatureText.append(signString).append("\n");
+            System.out.println("签名字符串: " + signString);
+        } else {
+            signatureText.append("\n");
+        }
+        // 添加 headers 参数(x-api-nonce 和 x-api-timestamp,小写编码但拼接时保持原样)
+        // 注意:这里实际上不需要再次编码,因为 nonce 和 timestamp 通常是数字或基64字符串,且规则要求小写(但编码后是大写字母的情况在这里不适用)
+        signatureText.append("x-api-nonce:").append(randomNumber).append("\n");
+        signatureText.append("x-api-timestamp:").append(TimeStamp).append("\n");
+
+        // 输出签名原文(实际签名算法应在此原文基础上进行)
+        System.out.println("Signature Text:\n" + signatureText.toString());
+
+        String message = signatureText.toString();
+
+
+        String hmacHex = get_app_signature(message, clientSecret);
+
+
+        return hmacHex;
+    }
+
+    /**
+     * 对参数值进行两次URL编码,并将字母转为大写
+     */
+    private static Map<String, String> doubleUrlEncodeParams(Map<String, String> params) {
+        Map<String, String> encodedParams = new HashMap<>();
+        for (Map.Entry<String, String> entry : params.entrySet()) {
+            String key = entry.getKey();
+            String value = entry.getValue();
+            try {
+                // 第一次URL编码
+                String firstEncode = URLEncoder.encode(value, StandardCharsets.UTF_8.toString());
+                // 第二次URL编码
+                String secondEncode = URLEncoder.encode(firstEncode, StandardCharsets.UTF_8.toString());
+                // 转为大写
+                encodedParams.put(key, secondEncode.toUpperCase());
+            } catch (UnsupportedEncodingException e) {
+                throw new RuntimeException("URL编码失败", e);
+            }
+        }
+        return encodedParams;
+    }
+
+    /**
+     * 生成签名字符串(key1=value1&key2=value2)
+     */
+    private static String generateSignString(List<Map.Entry<String, String>> sortedParams) {
+        StringBuilder signString = new StringBuilder();
+        for (Map.Entry<String, String> entry : sortedParams) {
+            if (signString.length() > 0) {
+                signString.append("&");
+            }
+            signString.append(entry.getKey()).append("=").append(entry.getValue());
+        }
+        return signString.toString();
+    }
+
+
+    public static String signMessage(String message, String clientSecret) throws NoSuchAlgorithmException, InvalidKeyException {
+        // 创建一个HMAC-SHA256 Mac实例
+        Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
+
+        // 将clientSecret转换为密钥规范
+        SecretKeySpec secretKeySpec = new SecretKeySpec(clientSecret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
+
+        // 初始化Mac实例
+        sha256_HMAC.init(secretKeySpec);
+
+        // 计算HMAC
+        byte[] hmacBytes = sha256_HMAC.doFinal(message.getBytes(StandardCharsets.UTF_8));
+
+        // 将字节数组转换为十六进制字符串
+        return bytesToHex(hmacBytes);
+    }
+
+    /**
+     * 将字节数组转换为十六进制字符串
+     *
+     * @param bytes 要转换的字节数组
+     * @return 转换后的十六进制字符串
+     */
+    public static String bytesToHex(byte[] bytes) {
+        Formatter formatter = new Formatter();
+        for (byte b : bytes) {
+            formatter.format("%02x", b);
+        }
+        String result = formatter.toString();
+        formatter.close();
+        return result;
+    }
+    @Autowired
+    private RjkServer rjkServer;
+    @Test
+    public void testH3UserToweiling() {
+        // 准备测试数据
+        Map<String, String> testData = new HashMap<>();
+        //  替换为实际的氚云业务对象ID
+//        testData.put("BizObjectId", "0233a0df-24a1-4ee2-9de8-b4f3029b5594");
+
+//        testData.put("BizObjectId", "d47b5b5b-0a4b-4120-8d08-6c5d79f8378f");
+//        testData.put("BizObjectId", "151452f7-33f9-407e-a95e-5e040168ba3d");
+        testData.put("BizObjectId", "967a68c1-5197-47cd-86e1-0a1cd42afb1f");
+        // 调用H3UserToweiling方法
+        rjkServer.H3UserToweiling(testData);
+    }
+
+
+    @Test
+    public void testH3BusinessToWeiling() {
+        // 准备测试数据
+        Map<String, String> testData = new HashMap<>();
+        //  替换为实际的氚云业务对象ID
+//        testData.put("BizObjectId", "b7932c25-b081-498a-9c7e-da67c14208c2");
+        testData.put("BizObjectId", "1f610bab-ada8-4247-a55c-d4d389450ce9");
+        // 调用H3UserToweiling方法
+        rjkServer.H3BusinessToWeiling(testData);
+    }
+}
+

+ 51 - 0
mjava-RJK/mjava/pom.xml

@@ -0,0 +1,51 @@
+<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>java-mcli</artifactId>
+        <groupId>com.malk</groupId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <!-- mjava版本, 不同java-cli项目区分底层依赖, 使用变量有警告 -->
+    <version>0.0.3</version>
+
+    <artifactId>mjava</artifactId>
+    <description>mjava framework</description>
+
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+    </properties>
+
+    <dependencies>
+
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <version>2.1.1.RELEASE</version>
+                <configuration>
+                    <includeSystemScope>true</includeSystemScope>
+                    <!-- 如果没有该配置,devtools不会生效: 打包时关闭 -->
+                    <fork>false</fork>
+                    <!-- 避免中文乱码 -->
+                    <jvmArguments>-Dfile.encoding=UTF-8</jvmArguments>
+                </configuration>
+                <!-- 允许生成可运行jar: 发布作为基础包提供, 注释 executions 再执行 install 到本地 maven. 若开启即可作为独立 jar 运行 -->
+                <executions>
+                    <execution>
+                        <goals>
+                            <!--                            <goal>repackage</goal>-->
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+        <finalName>${project.artifactId}</finalName>
+    </build>
+</project>

+ 29 - 0
mjava-RJK/mjava/src/main/java/com/malk/Boot.java

@@ -0,0 +1,29 @@
+package com.malk;
+
+import com.querydsl.jpa.impl.JPAQueryFactory;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Bean;
+import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
+
+import javax.persistence.EntityManager;
+
+@EnableJpaAuditing
+@SpringBootApplication
+public class Boot {
+
+    public static void main(String... args) {
+        SpringApplication.run(Boot.class, args);
+    }
+
+    /**
+     * 让Spring管理JPAQueryFactory [多数据源配置详见DataSourceConfig]
+     *
+     * @Qualifier("entityManagerFactory") 单数据源指向
+     * @Qualifier("entityManagerFactoryPrimary") 多数据源指向
+     */
+    @Bean
+    public JPAQueryFactory jpaQueryFactory(EntityManager entityManager) {
+        return new JPAQueryFactory(entityManager);
+    }
+}

+ 29 - 0
mjava-RJK/mjava/src/main/java/com/malk/base/BaseDao.java

@@ -0,0 +1,29 @@
+package com.malk.base;
+
+
+import org.springframework.data.jpa.repository.JpaRepository;
+
+/**
+ * @EntityListeners 且需要在启动类添加 @EnableJpaAuditing 注解, 即可在字段上添加 @CreatedDate & @LastModifiedDate 实现 [Date 类型数据 - 自动插入创建 & 更新时间]
+ * -
+ * 项目绑定数据库后,进行分配。即可消除@Table、 在 idea 下不能匹配到信息的报错提示
+ * @Table指定表【下划线和小驼峰都可】, 若使用驼峰则依然会有报错提示. 若类名从驼峰转为下划线和表名相同则可不需要注解
+ * -
+ * 自定义查询
+ * @Query 当设置nativeQuery=true即可以使用原生SQL进行查询, 默认为false, 使用JPQL语法 [JPQL不支持insert语法, 注意: nativeQuery查询使用表名, 不能使用对象名]
+ * - 索引参数: 索引值从1开始,查询中"?X"个数需要与方法定义的参数个数相一致,并且顺序也要一致
+ * - 命名参数: 可以定义好参数名,赋值时使用@Param("参数名"), 而不用管顺序. 对入参使用 @Param("email") 修饰, @Query内使用 :email 进行取值
+ * - 关于返回值: update 语法, 返回值为 void. select 语法, 返回单个可用集合接收; 若返回多个用对象接收就会异常
+ * @Modifying注解 编写JPQL实现DELETE和UPDATE操作的时候必须加上@modifying注解,以通知Spring Data 这是一个DELETE或UPDATE操作
+ * -
+ * 事务管理 @Transactional注解,可以修饰类或方法
+ * - 配置 jpa.database-platform: org.hibernate.dialect.MySQL57Dialect, JPA建表的默认引擎修改为:InNoDB
+ * - 在启动类添加 @EnableTransactionManagement 注解, 开启事务支持后,然后在访问数据库的Service方法上添加注解 @Transactional 即可 [Service指dao, 或调用dao的地方]
+ * -
+ * 动态匹配, 注解于类
+ * @DynamicInsert : 默认true,指定用于INSERT的 SQL 将会在运行时动态生成,并且只包含那些非空值字段。(如果是)
+ * @DynamicUpdate : 默认true, 指定用于UPDATE 的SQL将会在运行时动态生成,并且只更新那些改变过的字段
+ */
+public interface BaseDao extends JpaRepository<BasePo, Long> {
+
+}

+ 112 - 0
mjava-RJK/mjava/src/main/java/com/malk/base/BaseDto.java

@@ -0,0 +1,112 @@
+package com.malk.base;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.serializer.SerializerFeature;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.SneakyThrows;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.BeanWrapper;
+import org.springframework.beans.BeanWrapperImpl;
+
+import java.beans.PropertyDescriptor;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * 基础对象
+ *
+ * @JsonInclude(JsonInclude.Include.NON_NULL):类注解过滤null字段,包含入参和返回值【参数类搭配 @Data,才可以实例化返回值以及ToString输出】
+ * -
+ * lombok
+ * @Data : 注在类上,提供类的get、set、equals、hashCode、canEqual、toString方法
+ * @AllArgsConstructor : 注在类上,提供类的全参构造
+ * @NoArgsConstructor : 注在类上,提供类的无参构造
+ * @Setter : 注在属性上,提供 set 方法
+ * @Getter : 注在属性上,提供 getDefault 方法
+ * @EqualsAndHashCode : 注在类上,提供对应的 equals 和 hashCode 方法
+ * @Log4j/@Slf4j : 注在类上,提供对应的 Logger 对象,变量名为 log
+ * @Builder:为类生成相对略微复杂的构建器API。来初始化实例对象::类名.属性(值).属性(值).build()
+ * @Singular:在使用@Singular注释注释一个集合字段(使用@Builder注释类),lombok会将该构建器节点视为一个集合,并生成两个adder方法而不是setter方法::点一次集合增加一个元素
+ * @Builder.Default:在类中id和insertTime上都添加注解@Builder.Default,当在使用这个实体对象时,就不需要在为这两个字段进行初始化值
+ */
+@Data
+@NoArgsConstructor
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public abstract class BaseDto {
+
+    /**
+     * 对象拷贝: 若是复制一个对象, 建议使用 cloneParam 避免性能问题
+     */
+    @Deprecated
+    @SneakyThrows
+    public BaseDto copyParam() {
+        BaseDto dto = this.getClass().newInstance();
+        BeanUtils.copyProperties(this, dto);
+        return dto;
+    }
+
+    /**
+     * * todo: 4.11 继承Serializable后执行深拷贝, 不能类型转换, 不继承Serializable则返回
+     * 对象拷贝: 复制一个新的对象, 避免条件被修改, 尤其并发下分页混乱情况
+     */
+    public BaseDto cloneParam() {
+        return ObjectUtil.clone(this);
+    }
+
+    /**
+     * 对象属性合并: jda之save接口会以传入数据为准,若传入为空或不传入,更新会置空。目前解决办法两种,通过注解实现JPQL/SQL,或者查询出数据,将未传入字段属性拷贝后再更新【性能消耗】
+     */
+    public void mergeParam(BaseDto modifyDo) {
+        BeanUtils.copyProperties(this, modifyDo, getNotNullPropertyNames(modifyDo));
+    }
+
+    // 忽略有值的字段
+    private static String[] getNotNullPropertyNames(Object target) {
+        final BeanWrapper src = new BeanWrapperImpl(target);
+        PropertyDescriptor[] pds = src.getPropertyDescriptors();
+        Set<String> emptyNames = new HashSet();
+        for (PropertyDescriptor pd : pds) {
+            Object srcValue = src.getPropertyValue(pd.getName());
+            if (srcValue != null) {
+                // 此处判断可根据需求修改, 目前过滤不为null
+                emptyNames.add(pd.getName());
+            }
+        }
+        String[] result = new String[emptyNames.size()];
+        return emptyNames.toArray(result);
+    }
+
+    /**
+     * 传入映射的Map, 将实体属性和Map的key转换, 返回Map
+     */
+    public Map convertEntity(Map<String, String> reflect) {
+        Map map = JSON.parseObject(JSON.toJSONString(this, SerializerFeature.WriteNullStringAsEmpty), Map.class);
+        Map<String, String> formData = new HashMap();
+        for (String key : reflect.keySet()) {
+            String content = String.valueOf(map.get(key));
+            // json序列化已经将空字符串过滤, 若转换还有null字符串, 可能是key为null或SerializerFeature未指定到类型, 如Date
+            if (StringUtils.isNotBlank(content) && !content.equals("null")) formData.put(reflect.get(key), content);
+        }
+        return formData;
+    }
+
+
+    /**
+     * Map时间格式化, 直接从数据库取值后Map会有市区差, 方法1见BasePo, @Temporal & @JsonFormat 注解
+     * -
+     * [单独时间格式化 [废弃]]
+     * * JSON.parseArray(JSON.toJSONString(data), Map.class).stream().map(item -> {
+     * *     item.put("tStoreInTime", UtilDateTime.formatDateTime(new Date(UtilMap.getLong(item, "tStoreInTime"))));
+     * *     return item;
+     * * });
+     */
+    public static final Object jsonFormatDateTime(Object data) {
+        return JSON.parse(JSON.toJSONString(data, SerializerFeature.WriteNullStringAsEmpty, SerializerFeature.DisableCircularReferenceDetect, SerializerFeature.WriteDateUseDateFormat));
+    }
+}

+ 64 - 0
mjava-RJK/mjava/src/main/java/com/malk/base/BasePo.java

@@ -0,0 +1,64 @@
+package com.malk.base;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.alibaba.excel.annotation.ExcelIgnore;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.springframework.data.annotation.CreatedDate;
+import org.springframework.data.annotation.LastModifiedDate;
+import org.springframework.data.jpa.domain.support.AuditingEntityListener;
+
+import javax.persistence.*;
+import java.util.Date;
+
+/**
+ * 实体基类
+ *
+ * @MappedSuperclass 作为公共属性场景. 类注解, 标注后该类将不是一个完整的实体类,也不会映射到数据库表,但其的属性都将映射到其子类的数据库字段中
+ * @Cloumn java为小驼峰命名,数据库为下划线命名,若有差异,通过name指定表字段. 若相同则可不用指定name [直接设置字段值即可,无效额外注解]
+ * -
+ * 功能使用自带
+ * -
+ * @JsonFormat(pattern = "yyyy-MM-dd HH:smm:ss", timezone = "GMT+8"),数据库 Date 类型序列化后转到前台的指定格式【jackjson】
+ * @DateTimeFormat @DateTimeFormat(pattern = "yyyy-MM-dd"),使用和@jsonFormat差不多,前台传入的按照指定格式自动转为Date储存
+ * @JsonIgnoreProperties 类注解,作用是json序列化时将bean中的一些属性忽略掉,序列化和反序列化都受影响。支持多个属性 [也用于双向绑定解决循环序列化]
+ * @JsonIgnore 此注解用于属性或者方法上(最好是属性上),作用和上面的@JsonIgnoreProperties一样,屏蔽该字段在序列化和数据发挥会自动忽略
+ * @JsonGetter 用于序列化, 还可指定返回属性名,@JsonSetter 用于反序列化。注意@JsonGetter比@JsonProperty的优先级高,同时存在属性忽略会失效
+ * @Transient ORM框架将忽略该属性,不入库。如果一个属性并非数据库表的字段映射,就务必将其标示为@Transient,否则ORM框架默认其注解为@Basic
+ * @Temporal & @JsonFormat: fixme: 指定时区, new Date 会默认当前系统时区, 不添加 json 时区注解, 会出现序列化后的对象时间不是 GMT 时区 [方法2见BaseDto, jsonFormatDateTime]
+ */
+@MappedSuperclass
+@Data
+@NoArgsConstructor
+@EntityListeners(AuditingEntityListener.class)
+public abstract class BasePo extends BaseDto {
+
+    // 若是实体若不直接在 com.malk 下, 可声明继承id, 避免编辑器提示 [不加也不影响编译以及运行] [ppExt: 现有表如u8, 不继承 BaseDto, 避免默认id与时间字段匹配异常]
+    @ExcelIgnore
+    @Id
+    @JsonIgnore
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    public Long id;
+
+    @ExcelIgnore
+    @CreatedDate
+    @Temporal(TemporalType.TIMESTAMP)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createTime;
+
+    @ExcelIgnore
+    @LastModifiedDate
+    @Temporal(TemporalType.TIMESTAMP)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date updateTime;
+
+    public void upsert(BasePo po_old) {
+
+        if (ObjectUtil.isNotNull(po_old)) {
+            this.id = po_old.id;
+            this.setCreateTime(po_old.getCreateTime());
+        }
+    }
+}

+ 9 - 0
mjava-RJK/mjava/src/main/java/com/malk/base/BaseRepository.java

@@ -0,0 +1,9 @@
+package com.malk.base;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.querydsl.QuerydslPredicateExecutor;
+
+public interface BaseRepository extends JpaRepository<BasePo, Long>, QuerydslPredicateExecutor<BasePo> {
+
+}
+

+ 78 - 0
mjava-RJK/mjava/src/main/java/com/malk/base/JpaMap.java

@@ -0,0 +1,78 @@
+package com.malk.base;
+
+
+import cn.hutool.core.util.ObjectUtil;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.serializer.SerializerFeature;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.data.jpa.domain.Specification;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 查询map [不指定表名]
+ * 通过Map实现, 无需定义实体, 主键设置Long即可: public interface HSViewDao extends CrudRepository<JpaMap, Long>
+ * -
+ * 使用native查询,返回Map. 直接查询表名 + 列名 + 条件, 可不做实体映射场景
+ * 时间的时区处理: 方法1见BasePo, @Temporal & @JsonFormat 注解; 方法2见BaseDto, jsonFormatDateTime
+ * 空条件jpa实现, 查询全部数据, 通过 like LTRIM('%' + ?x + '%') 进行实现 [dsl适用于复杂场景, Specification谓词需要依赖于实体]
+ * -
+ * 多条件查询
+ * 1. sql + if [SqlServer不支持if条件]
+ * * @Query(value = "select * from vwpbCommonDataOrderPlan where if (?1 is not null, dPlanDate >= ?1, 1=1) and if (?2 is not null, dPlanDate <= ?2, 1=1) and sOrderNo like LTRIM('%' + ?3 + '%')", nativeQuery = true)
+ * * Page<Map> queryOrderPlan(Date start, Date end, String sOrderNo, Pageable pageable);
+ * 2. Specification, 谓词需要依赖于实体 [参考示例, 如下 test 方法实现]
+ * - 说明
+ * 1. @Query: nativeQuery查询使用表名, 不能使用对象名
+ * 2. ppExt: 现有表如u8, 不继承 BaseDto, 避免默认id与时间字段匹配异常
+ */
+
+@Entity
+public class JpaMap {
+
+    @Id
+    private String id;
+
+    /**
+     * 时间的时区处理
+     */
+    public static final List<Map> jsonFormatDateTime(List<Map> data) {
+        return JSON.parseArray(JSON.toJSONString(data, SerializerFeature.WriteNullStringAsEmpty, SerializerFeature.DisableCircularReferenceDetect, SerializerFeature.WriteDateUseDateFormat), Map.class);
+    }
+
+    // 示例: Specification, 谓词需要依赖于实体
+    private void test() {
+        //Page<Map> findAll (Specification < T > spec, Pageable pageable);
+
+        String sOrderNo = "";
+        Date sTime = null;
+        Date eTime = null;
+
+        Specification spec = (root, criteriaQuery, criteriaBuilder) -> {
+            List<javax.persistence.criteria.Predicate> predicates = new ArrayList<>();
+            if (StringUtils.isNotBlank(sOrderNo)) {
+                javax.persistence.criteria.Predicate predicate = criteriaBuilder.like(root.get("sOrderNo"), "%" + sOrderNo + "%");
+                predicates.add(predicate);
+            }
+
+            if (ObjectUtil.isNotNull(sTime)) {
+//            javax.persistence.criteria.Predicate predicate = criteriaBuilder.greaterThanOrEqualTo(root.getDefault("dPlanDate").as(String.class), "2023-06-03 00:00:00");
+                javax.persistence.criteria.Predicate predicate = criteriaBuilder.greaterThanOrEqualTo(root.get("dPlanDate"), sTime);
+                predicates.add(predicate);
+            }
+
+            if (ObjectUtil.isNotNull(eTime)) {
+                javax.persistence.criteria.Predicate predicate = criteriaBuilder.lessThanOrEqualTo(root.get("dPlanDate"), eTime);
+                predicates.add(predicate);
+            }
+            return criteriaBuilder.and(predicates.toArray(new javax.persistence.criteria.Predicate[predicates.size()]));
+            /// 或
+            ///return criteriaQuery.where(predicates.toArray(new javax.persistence.criteria.Predicate[predicates.size()])).getRestriction();
+        };
+    }
+}

+ 35 - 0
mjava-RJK/mjava/src/main/java/com/malk/config/JpaConfiguration.java

@@ -0,0 +1,35 @@
+package com.malk.config;
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.autoconfigure.domain.EntityScan;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
+import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
+
+/**
+ * JPA [错误抛出与拦截详见CatchException, 多数据源配置参考DataSourceConfig]
+ * -
+ * 1. 关键字语法: save find, exists, count [单表操作 - 若数据库列和对象属性字段一致, 直接使用 jpa 自带查询匹配不到启动报错]
+ * 2. 自定义查询: @Query, 通过 nativeQuery 区分 jpql 和 sql 语法 [简单查询]
+ * 3. 表关联关系: @OneToMany, @ManyToOne, @ManyToMany, @OneToOne [耦合关联]
+ * 4. Specification: Predicate 与 CriteriaBuilder 组合 [谓词, 需要依赖于实体]
+ * 5. QueryDSL: 基于ORM框架以及SQL之上的一个通用的查询框架, 分页, 关联查询原生支持 [查询框架] (通过查询关联, 而不是如 @OneToMany 等建立表关联)
+ * 6. DSL: 项目在 compile 会执行 apt-maven-plugin 插件, 将 @Entity 注解类, 添加 Q 前缀, 存放到 target 下 generated-source 目录
+ * 7. 配置: 扫描基础路径, 涉及子项目也能注册到, 避免启动报错. 子项目 Boot 配置 @SpringBootApplication(scanBasePackages = {"com.mcli"})
+ * 8. 查询: 使用native,查询列不匹配实体属性,会报错The column name xxx is not valid,返回Map可解决 (Map是Jpa的TupleBackedMap, 通过try取值)
+ * 9. 单数据源切换: 在dao与entity均添加mutual作为公共模块, 单数据源下服务于JpaConfiguration, 若是多数据源与PrimaryConfig一起作为主数据源配置
+ * 10 主子项目, 在单数据源情况下, 扫描全部. [需要注意的是, 在多数据源下同名dao是可以通过指定数据源使用, 此时若开启单数据源启动报错, 会扫描全部]
+ */
+@ConditionalOnProperty(name = "spel.multiSource", havingValue = "false")
+@Configuration
+// 单数据源, 扫描子项目与主项目primary [子项目可以访问到主项目primary]
+@EnableJpaRepositories(basePackages = {"com.malk.*.repository.dao", "com.malk.repository.dao.primary"})
+@EntityScan(basePackages = {"com.malk.*.repository.entity", "com.malk.repository.entity.primary"})
+public class JpaConfiguration {
+
+    @Bean
+    PersistenceExceptionTranslationPostProcessor persistenceExceptionTranslationPostProcessor() {
+        return new PersistenceExceptionTranslationPostProcessor();
+    }
+}

+ 58 - 0
mjava-RJK/mjava/src/main/java/com/malk/config/WebConfiguration.java

@@ -0,0 +1,58 @@
+package com.malk.config;
+
+import com.malk.filter.RequestInterceptor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.CorsRegistry;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+@Configuration
+public class WebConfiguration implements WebMvcConfigurer {
+
+    // 指定类输出日志到指定文件夹
+    private static final Logger logger = LoggerFactory.getLogger("point");
+
+    // 请求拦截
+    @Override
+    public void addInterceptors(InterceptorRegistry registry) {
+        logger.info("拦截器 ▷ 初始化");
+        registry.addInterceptor(new RequestInterceptor())
+                .addPathPatterns("/**")
+                // ppExt: 若无需对外访问, 不用添加路径. static/public 为默认
+                .excludePathPatterns("/assets/**", "/templates/**");
+    }
+
+    // 跨域支持: 端口不匹配也会报跨域, 若是单个控制器开放, 可使用 @CrossOrigin 注解
+    @Override
+    public void addCorsMappings(CorsRegistry registry) {
+        logger.info("拦截器 ▷ 开启CORS");
+        registry.addMapping("/**")  // 添加映射路径
+                .allowedOrigins("*")  // 放行哪些原始
+                .allowCredentials(true)  // 是否发送Cookie信息
+                .allowedMethods("GET", "POST", "PUT", "DELETE", "PATCH")  // 放行哪些原始域(请求方式)
+                .allowedHeaders("*")  // 放行哪些原始域(头部信息)
+                .allowCredentials(true);  // 放行证书
+    }
+
+    /**
+     * 静态资源映射
+     * -
+     * 默认的静态资源路径为: classpath:/META-INF/resources/, classpath:/resources/,classpath:/static/, classpath:/public [默认路径不会进拦截器]
+     * 读取的是target内容, 访问路径 assets: http://localhost:9001/api/assets/logo/logo-text.png [自定义拦截器添加路径排除: excludePathPatterns]
+     * web网页路径: http://localhost:9011/api/项目路径/web2/index.html#/模块名称/home [前后端模块化, 不配置Nginx从Tomcat透出vue页面]
+     * 当在SpringBoot项目内添加网页资源时,在windows服务器,需要C:\Windows\System32下添加tomcat-native-1.2.14-win32-bin.zip内x64下两个文件, 重启项目
+     * -
+     * ppExt: ClassPathResource, 需要打包/编译后才能访问到. 识别不是架包内内容 [static 可自动过滤, 自动识别子文件夹作为 path]
+     * [两个示例]: mjs http://localhost:9001/api/项目路径/mjs/mjs.min.js / http://localhost:9001/api/项目路径/json/personnel.json
+     */
+    @Override
+    public void addResourceHandlers(ResourceHandlerRegistry registry) {
+        registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
+        registry.addResourceHandler("/web2/**").addResourceLocations("classpath:/static/web2/");
+        registry.addResourceHandler("/assets/**").addResourceLocations("classpath:/assets/");
+        registry.addResourceHandler("/templates/**").addResourceLocations("classpath:/templates/");
+    }
+}

+ 41 - 0
mjava-RJK/mjava/src/main/java/com/malk/config/mutilSource/DataSourceConfig.java

@@ -0,0 +1,41 @@
+package com.malk.config.mutilSource;
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.jdbc.DataSourceBuilder;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
+
+import javax.sql.DataSource;
+
+/**
+ * 数据源配置
+ * -
+ * AOP多数据源切换:
+ * - 1. pom文件添加spring-boot-starter-jdbc依赖
+ * - 2. yml配置多主从数据库, 数据库地址从url修改为jdbc-url
+ * - 3. 通过spel.multiSource控制是否多数据源, 屏蔽JpaConfiguration, 加载DataSourceConfig
+ * 不同数据源存在同名表:
+ * - 1. 实体只需要在dao层区分即可, 作用域下实体包路径是不同的
+ * - 2. 同名的dao通过@Repository("slaveMcTableDao")定义区分; 在Autowired引用时候属性上添加@Qualifier("slaveMcTableDao")注解
+ * -
+ * 单数据源切换: 在dao与entity均添加mutual作为公共模块, 单数据源下服务于JpaConfiguration, 若是多数据源与PrimaryConfig一起作为主数据源配置
+ */
+@ConditionalOnProperty(name = "spel.multiSource", havingValue = "true")
+@Configuration
+public class DataSourceConfig {
+
+    @Bean(name = "primaryDataSource")
+    @ConfigurationProperties(prefix = "spring.datasource.primary")
+    @Primary
+    public DataSource primaryDataSource() {
+        return DataSourceBuilder.create().build();
+    }
+
+    @Bean(name = "slaveDataSource")
+    @ConfigurationProperties(prefix = "spring.datasource.slave")
+    public DataSource slaveDataSource() {
+        return DataSourceBuilder.create().build();
+    }
+}

+ 80 - 0
mjava-RJK/mjava/src/main/java/com/malk/config/mutilSource/PrimaryConfig.java

@@ -0,0 +1,80 @@
+package com.malk.config.mutilSource;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.autoconfigure.orm.jpa.HibernateProperties;
+import org.springframework.boot.autoconfigure.orm.jpa.HibernateSettings;
+import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
+import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
+import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
+import org.springframework.orm.jpa.JpaTransactionManager;
+import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+
+import javax.persistence.EntityManager;
+import javax.sql.DataSource;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * 主数据源配置,多数据源必须设置一个主数据源, 通过 @Primary 注解 [包含公共部分]
+ * -
+ * 主子项目, 在多数据源情况下, 子项目需要匹配主项目目录结构, 且符合命名规范. 配置对应扫描 EnableJpaRepositories / EntityScan 添加子项目路径
+ */
+@ConditionalOnProperty(name = "spel.multiSource", havingValue = "true")
+@Configuration
+@EnableTransactionManagement
+@EnableJpaRepositories(
+        entityManagerFactoryRef = "entityManagerFactoryPrimary", // 配置连接工厂 entityManagerFactory
+        transactionManagerRef = "transactionManagerPrimary", // 配置事物管理器  transactionManager
+        basePackages = {"com.malk.repository.dao.primary", "com.malk.*.repository.dao.primary", "com.malk.base"}  // dao层配置主数据 & 公共所在目录 [子项目可以访问到主项目primary]
+)
+public class PrimaryConfig {
+
+    @Autowired
+    @Qualifier("primaryDataSource")  // 指定这是主数据源,为了和从(其他)数据源区别开,因为@Autowired不能导入名称相同的是bean
+    private DataSource dataSourcePrimary;
+
+    @Autowired
+    private JpaProperties jpaProperties;
+
+    @Autowired
+    private HibernateProperties hibernateProperties;
+
+    @Primary
+    @Bean("entityManagerPrimary")
+    public EntityManager entityManager(EntityManagerFactoryBuilder builder) {
+        return Objects.requireNonNull(entityManagerFactoryBean(builder).getObject()).createEntityManager();
+    }
+
+    @Primary
+    @Bean("entityManagerFactoryPrimary")
+    public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean(EntityManagerFactoryBuilder builder) {
+        return builder.dataSource(dataSourcePrimary)
+                .properties(getVendorProperties())
+                // 设置实体类所在目录: 包含主数据源与公共 [子项目可以访问到主项目primary]
+                .packages("com.malk.repository.entity.primary", "com.malk.*.repository.entity.primary", "com.zhuogao.base")
+                // 持久化单元名称,当存在多个EntityManagerFactory时,需要制定此名称
+                .persistenceUnit("primaryPersistenceUnit")
+                .build();
+    }
+
+    private Map<String, Object> getVendorProperties() {
+        return hibernateProperties.determineHibernateProperties(
+                jpaProperties.getProperties(),
+                new HibernateSettings()
+        );
+    }
+
+    @Primary
+    @Bean("transactionManagerPrimary")
+    public PlatformTransactionManager transactionManager(EntityManagerFactoryBuilder builder) {
+        return new JpaTransactionManager(Objects.requireNonNull(entityManagerFactoryBean(builder).getObject()));
+    }
+}
+

+ 76 - 0
mjava-RJK/mjava/src/main/java/com/malk/config/mutilSource/SlaveConfig.java

@@ -0,0 +1,76 @@
+package com.malk.config.mutilSource;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.autoconfigure.orm.jpa.HibernateProperties;
+import org.springframework.boot.autoconfigure.orm.jpa.HibernateSettings;
+import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
+import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
+import org.springframework.orm.jpa.JpaTransactionManager;
+import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+
+import javax.persistence.EntityManager;
+import javax.sql.DataSource;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * 从数据源配置
+ * -
+ * 主子项目, 在多数据源情况下, 子项目需要匹配主项目目录结构, 且符合命名规范. 配置对应扫描 EnableJpaRepositories / EntityScan 添加子项目路径
+ */
+@ConditionalOnProperty(name = "spel.multiSource", havingValue = "true")
+@Configuration
+@EnableTransactionManagement
+@EnableJpaRepositories(
+        entityManagerFactoryRef = "entityManagerFactorySlave",
+        transactionManagerRef = "transactionManagerSlave",
+        basePackages = {"com.malk.repository.dao.slave", "com.malk.*.repository.dao.slave"} // slave无公共类, 需单独添加 [子项目可以访问到主项目primary]
+)
+public class SlaveConfig {
+
+    @Autowired
+    @Qualifier("slaveDataSource")
+    private DataSource dataSourceSlave;
+
+    @Autowired
+    private JpaProperties jpaProperties;
+
+    @Autowired
+    private HibernateProperties hibernateProperties;
+
+    @Bean("entityManagerSlave")
+    public EntityManager entityManager(EntityManagerFactoryBuilder builder) {
+        return Objects.requireNonNull(localContainerEntityManagerFactoryBean(builder).getObject()).createEntityManager();
+    }
+
+    @Bean("entityManagerFactorySlave")
+    public LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBean(EntityManagerFactoryBuilder builder) {
+        return builder.dataSource(dataSourceSlave)
+                .properties(getVendorProperties())
+                // 设置实体类所在目录 [slave无公共类, 需单独添加, 如 JpaMapSlave]
+                .packages("com.malk.repository.entity.slave", "com.malk.*.repository.entity.slave")
+                // 持久化单元名称,当存在多个EntityManagerFactory时,需要制定此名称
+                .persistenceUnit("slavePersistenceUnit")
+                .build();
+    }
+
+    private Map<String, Object> getVendorProperties() {
+        return hibernateProperties.determineHibernateProperties(
+                jpaProperties.getProperties(),
+                new HibernateSettings()
+        );
+    }
+
+    @Bean("transactionManagerSlave")
+    public PlatformTransactionManager transactionManager(EntityManagerFactoryBuilder builder) {
+        return new JpaTransactionManager(Objects.requireNonNull(localContainerEntityManagerFactoryBean(builder).getObject()));
+    }
+}
+

+ 73 - 0
mjava-RJK/mjava/src/main/java/com/malk/controller/DDCallbackController.java

@@ -0,0 +1,73 @@
+package com.malk.controller;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.malk.server.common.McR;
+import com.malk.server.dingtalk.DDConf;
+import com.malk.server.dingtalk.crypto.DingCallbackCrypto;
+import com.malk.service.dingtalk.DDClient_Event;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Arrays;
+import java.util.Map;
+
+/**
+ * 钉钉事件回调 3_1
+ * -
+ * [子项目直接继承即可有调用, 无需实现]
+ * -
+ * 注解 @RequestMapping 路径不能重复 [主子项目属同一个项目];
+ * 获取项目回调请求地址, https://mc.cloudpure.cn/frp/mc/dd/callback [调试代理: frp + nginx]
+ */
+@Slf4j
+@RestController
+@RequestMapping("/mc/dd")
+public class DDCallbackController {
+
+    @Autowired
+    private DDConf ddConf;
+
+    @Autowired
+    private DDClient_Event ddClient_event;
+
+    /**
+     * 钉钉审批回调: [依赖包方案已弃用]
+     * -
+     * DingCallbackCrypto 方案: 官网案例 DingCallbackCrypto 不在钉钉架包, 需要单独引用
+     * 在钉钉开放平台重新保存回调地址后, 所有的注册事件会被关闭:: 通过代码注册不成功; 官方回复, 要么使用调用注册的方式 要么是后台的方式, 二选一
+     */
+    @SneakyThrows
+    @RequestMapping(value = "/callback", method = RequestMethod.POST)
+    public Map<String, String> invokeCallback(@RequestParam(value = "signature", required = false) String signature,
+                                              @RequestParam(value = "timestamp", required = false) String timestamp,
+                                              @RequestParam(value = "nonce", required = false) String nonce,
+                                              @RequestBody(required = false) JSONObject json) {
+
+        DingCallbackCrypto callbackCrypto = new DingCallbackCrypto(ddConf.getToken(), ddConf.getAesKey(), ddConf.getAppKey());
+        final String decryptMsg = callbackCrypto.getDecryptMsg(signature, timestamp, nonce, json.getString("encrypt"));
+        JSONObject eventJson = JSON.parseObject(decryptMsg);
+        Map success = callbackCrypto.getEncryptedMap(DDConf.CALLBACK_RESPONSE, System.currentTimeMillis(), DingCallbackCrypto.Utils.getRandomStr(8));
+        String eventType = eventJson.getString("EventType");
+        if (DDConf.CALLBACK_CHECK.equals(eventType)) {
+            log.info("----- [DD]验证注册 -----");
+            return success;
+        }
+        // [回调任务执行逻辑: 异步] 钉钉超时3s未返回会被记录为失败, 可通过失败接口获取记录
+        if (Arrays.asList(DDConf.BPMS_INSTANCE_CHANGE, DDConf.BPMS_TASK_CHANGE).contains(eventType)) {
+            log.info("[DD]审批回调, {}", eventJson);
+            ddClient_event.callBackEvent_Workflow(eventJson);
+            return success;
+        }
+        log.info("----- [DD]已注册, 未处理的其它回调 -----, {}", eventJson);
+        return success;
+    }
+
+    @PostMapping("robot")
+    McR robot(@RequestBody Map data) {
+        log.info("xxx, {}", data);
+        return McR.success();
+    }
+}

+ 61 - 0
mjava-RJK/mjava/src/main/java/com/malk/controller/TBCallBackController.java

@@ -0,0 +1,61 @@
+package com.malk.controller;
+
+import com.alibaba.fastjson.JSONObject;
+import com.malk.delegate.TBEvent;
+import com.malk.server.teambition.TBConf;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+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;
+
+/**
+ * TB事件回调 3_1
+ * 1. 回调可选择加密与不加密方式, tb发送成功为上游, 注册服务为下游
+ * 2. 绑定回调需要安装后才会生效, 且回调范围更新后需要重新安装才会生效 [另外接口调用修改配置需要重新发布后生效]
+ */
+@Slf4j
+@RestController
+@RequestMapping("/mc/tb")
+public class TBCallBackController {
+
+    @Autowired
+    private TBEvent tbEvent;
+
+    /**
+     * * 回调说明 [ppExt: 字段更新回调, 判定字段ID, 避免循坏触发问题, 接口修改也会触发webhook]
+     * * 1. 通过接口更操作的数据,也会与手动创建一样触发相同的回调, 除了项目更新接口调用实测不会触发回调, 手动修改正常回调
+     * * 2. 项目创建会推送两次
+     * * - 1. 在第二次推送多 { data: { project: { operatorId, url }} } 这两个字段内容
+     * * - 2. 若是通过模板创建的项目,在两次项目更新回调中会回调一次 project.enable 回调, 其中任务只会回调创建, 不会回调更新
+     * * 3. 任务创建, 会先回调创建事件, 接着立即回调任务更新事件 [若是通过模板创建, 任务只会回调创建, 不会回调更新]
+     * * 4. 项目移入回收站,不会触发回调,删除后会触发项目与任务的 remove 事件; 若是将任务移入回收站, 会触发任务更新回调
+     */
+    @PostMapping("callback")
+    public String callback(@RequestBody JSONObject eventJson) {
+
+        String success = "success";
+        String eventName = eventJson.getString("event");
+
+        if (TBConf.EVENT_VERIFY_HOOK.equals(eventName)) {
+            log.info("----- [TB]验证注册 -----");
+            return success;
+        }
+
+        if (eventName.contains("task")) {
+            log.info("[TB]任务回调, {}, {}", eventName, eventJson);
+            tbEvent.callBackTask(eventJson);
+            return success;
+        }
+
+        if (eventName.contains("project")) {
+            log.info("[TB]项目回调, {}, {}", eventName, eventJson);
+            tbEvent.callBackProject(eventJson);
+            return success;
+        }
+
+        log.info("----- [TB]已注册, 未处理的其它回调 -----, {}, {}", eventName, eventJson);
+        return success;
+    }
+}

+ 101 - 0
mjava-RJK/mjava/src/main/java/com/malk/core/AsyncConfig.java

@@ -0,0 +1,101 @@
+package com.malk.core;
+
+//import com.mcli.filter.ExceptionNotice;
+
+import lombok.extern.slf4j.Slf4j;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.AsyncConfigurer;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.ThreadPoolExecutor;
+
+/**
+ * 线程池配置 [函数式编程:详见YDClient/YDService使用说明]
+ * -
+ * 前提: 1. 必须仅适用于 public 实例方法; 2.在同一个类中调用异步方法将无法正常工作(self-invocation); 3. 访问对象必须通过 @Autowired 进行注入, 否则无效
+ * 返回: 1. 一般为 void, 若需要返回值, 使用Future. 2. 不建议使用回调函数: 回调函数没有状态, 若执行过程中出现数据增删, 简单的通过数量判断不可取 [YDService]
+ * 备注: 开启异步在配置类添加 @EnableAsync 类注解, 无需在启动类添加 [非必要]
+ * 扩展:
+ * - 1. 可以搭配 lombok 的 @Synchronized, 实现异步情况下的线程安全. 会等到前一次执行成功后执行后一次线程调用 [若是不使用 @Async, 可能会超时失败, 宜搭不稳定]
+ * - 2. 指定线程 @Async("aliworkConcurrence") 配置, 统一配置调用上限避免超并发 [宜搭, YDClient内并发公共一个@Async配置]
+ */
+@Slf4j
+@Configuration
+@EnableAsync
+public class AsyncConfig implements AsyncConfigurer {
+
+    // 指定类输出日志到指定文件夹
+    private static final Logger logger = LoggerFactory.getLogger("point");
+
+//    @Autowired
+//    private ExceptionNotice notice;
+
+    /**
+     * 异步执行配置
+     *
+     * @async 更建议是为了实现异步, 若有并发等待也可满足
+     * Delayed 函数回调存在线程死锁, 导致内存问题, 已弃用
+     */
+    @Override
+    public Executor getAsyncExecutor() {
+        logger.info("asyncExecutor ▷ 多线程执行");
+        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();  // 定义线程池
+        taskExecutor.setCorePoolSize(3);  // 设置核心线程
+        taskExecutor.setMaxPoolSize(10);  // 设置最大线程
+        taskExecutor.setQueueCapacity(200);  // 设置线程队列最大线程数
+        taskExecutor.setKeepAliveSeconds(10);  // 允许线程的空闲时间 [勿动]
+        taskExecutor.setThreadNamePrefix("async-executor-");  // 线程池名的前缀
+        taskExecutor.initialize();  // 初始化
+        return taskExecutor;
+    }
+
+    /**
+     * 并发线程配置: 宜搭 [统一配置调用上限避免超过并发, YDClient内并发公共一个@Async配置]
+     *
+     * @Async("aliworkConcurrence") 指定线程
+     */
+    @Bean("aliworkConcurrence")
+    public ThreadPoolTaskExecutor aliworkTaskExecutor() {
+        logger.info("aliworkConcurrence ▷ 多线程初始化");
+        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
+        // 核心线程数:线程池创建时候初始化的线程数 ==> 宜搭并发200次/s, 限流配置 [4核偶尔出现, 3核极其偶尔]
+        executor.setCorePoolSize(2);
+        // 最大线程数:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
+        executor.setMaxPoolSize(2);
+        // 缓冲队列:用来缓冲执行任务的队列
+        executor.setQueueCapacity(200);
+        // 允许线程的空闲时间60秒:当超过了核心线程之外的线程在空闲时间到达之后会被销毁
+        executor.setKeepAliveSeconds(60);
+        // 线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池
+        executor.setThreadNamePrefix("aliwork-concurrence-");
+        // 缓冲队列满了之后的拒绝策略:由调用线程处理(一般是主线程)
+        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());
+        executor.initialize();
+        return executor;
+    }
+
+    /**
+     * 处理异步方法中未捕获的异常
+     * -
+     * ppExt 重要声明
+     * 1. @Async 内若调用了不能并发的方法, 如查询限流, 需要在调用的方法上添加 @Synchronized, 避免异步情况下执行触发限流导致抛出错误到多线程
+     * 2. @Async 方法上建议要添加 @Synchronized, 否则就在多次连续调用的时候, 入参需要做拷贝, 若同一个入参对象连续调用 @Async 会读取最后一次赋值
+     */
+    @Override
+    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
+        return (ex, method, params) -> {
+            log.error("Async反馈异常 {}. {}. {}", ex, method, params);  // 记录错误日志
+            try {
+                throw ex;
+            } catch (Throwable throwable) {
+                throwable.printStackTrace();
+            }
+        };
+    }
+}

+ 36 - 0
mjava-RJK/mjava/src/main/java/com/malk/delegate/DDEvent.java

@@ -0,0 +1,36 @@
+package com.malk.delegate;
+
+import org.springframework.scheduling.annotation.Async;
+
+/**
+ * 钉钉事件回调 3_2
+ * -
+ * [主项目若无实现, 项目启动异常; 若子项目有订阅需添加 @Primary 以实现优先注入]
+ * -
+ * 子项目实现接口 [静态代理], 添加对应 processCode 单据业务逻辑
+ * OA审批, 撤销和拒绝流程不继续执行连接器, 通过事件订阅实现实时同步
+ */
+public interface DDEvent {
+
+
+    // todo, 回调做try, 失败记录做存储, 提供查询接口
+
+    // todo, 回调参数统一, 宜搭查询接口统一
+
+    // 审批任务回调执行业务逻辑
+    @Async
+    void executeEvent_Task_Finish(String processInstanceId, String processCode, boolean isAgree, String remark);
+
+    @Async
+    void executeEvent_Task_Start(String processInstanceId, String processCode);
+
+    @Async
+    void executeEvent_Task_Redirect(String processInstanceId, String processCode);
+
+    // 审批实例回调执行业务逻辑
+    @Async
+    void executeEvent_Instance_Finish(String processInstanceId, String processCode, boolean isAgree, boolean isTerminate, String staffId);
+
+    @Async
+    void executeEvent_Instance_Start(String processInstanceId, String processCode);
+}

+ 17 - 0
mjava-RJK/mjava/src/main/java/com/malk/delegate/McDelegate.java

@@ -0,0 +1,17 @@
+package com.malk.delegate;
+
+import org.springframework.scheduling.annotation.Async;
+
+public interface McDelegate {
+
+    /**
+     * 异步线程回调
+     */
+    @Async
+    void setTimeout(Invoke fn, long millis);
+
+    @FunctionalInterface
+    interface Invoke {
+        void execute();
+    }
+}

+ 25 - 0
mjava-RJK/mjava/src/main/java/com/malk/delegate/TBEvent.java

@@ -0,0 +1,25 @@
+package com.malk.delegate;
+
+import com.alibaba.fastjson.JSONObject;
+
+/**
+ * TB事件回调 3_2
+ * -
+ * [主项目若无实现, 项目启动异常; 若子项目有订阅需添加 @Primary 以实现优先注入]
+ * -
+ * 子项目实现接口 [静态代理], 添加对应 processCode 单据业务逻辑
+ */
+public interface TBEvent {
+
+    /**
+     * 任务回调事件  [异步]
+     * ppExt
+     * 1. 若存在前后置, 未完成子任务, 点击完成TB会自动切换为未完成, 此种状态下不会触发任务回调
+     */
+    void callBackTask(JSONObject eventJson);
+
+    /**
+     * 项目回调事件  [异步]
+     */
+    void callBackProject(JSONObject eventJson);
+}

+ 49 - 0
mjava-RJK/mjava/src/main/java/com/malk/delegate/impl/DDImplEvent.java

@@ -0,0 +1,49 @@
+package com.malk.delegate.impl;
+
+import com.malk.delegate.DDEvent;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+
+/**
+ * OA审批事件 [主项目若无实现, 项目启动异常; 若子项目有订阅需添加 @Primary 以实现优先注入]
+ * -
+ * 取消方案: 撤销和拒绝流程不继续执行连接器, 因此不使用连接器与轮询审批记录方案 [低效而且占用较高钉钉api调次数];
+ * 优化方案: 通过事件订阅实现实时同步所有审批状态, 定时查询钉钉回调失败记录 [配置钉钉事件Delegate, 添加定时任务]
+ */
+@Slf4j
+@Service
+public class DDImplEvent implements DDEvent {
+
+    // 审批任务回调执行业务逻辑
+    @Async
+    @Override
+    public void executeEvent_Task_Finish(String processInstanceId, String processCode, boolean isAgree, String remark) {
+        log.info("executeEvent_Task_Finish: 未被代理");
+    }
+
+    @Async
+    @Override
+    public void executeEvent_Task_Start(String processInstanceId, String processCode) {
+        log.info("executeEvent_Task_Start: 未被代理");
+    }
+
+    @Async
+    @Override
+    public void executeEvent_Task_Redirect(String processInstanceId, String processCode) {
+        log.info("executeEvent_Task_Redirect: 未被代理");
+    }
+
+    @Async
+    @Override
+    public void executeEvent_Instance_Start(String processInstanceId, String processCode) {
+        log.info("executeEvent_Instance_Start: 未被代理");
+    }
+
+    // 审批实例回调执行业务逻辑
+    @Async
+    @Override
+    public void executeEvent_Instance_Finish(String processInstanceId, String processCode, boolean isAgree, boolean isTerminate, String staffId) {
+        log.info("executeEvent_Instance_Finish: 未被代理");
+    }
+}

+ 21 - 0
mjava-RJK/mjava/src/main/java/com/malk/delegate/impl/McImplDelegate.java

@@ -0,0 +1,21 @@
+package com.malk.delegate.impl;
+
+import com.malk.delegate.McDelegate;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+
+@Service
+@Slf4j
+public class McImplDelegate implements McDelegate {
+
+    @SneakyThrows
+    @Async
+    @Override
+    public void setTimeout(Invoke fn, long millis) {
+
+        Thread.sleep(millis);
+        fn.execute();
+    }
+}

+ 29 - 0
mjava-RJK/mjava/src/main/java/com/malk/delegate/impl/TBImplEvent.java

@@ -0,0 +1,29 @@
+package com.malk.delegate.impl;
+
+import com.alibaba.fastjson.JSONObject;
+import com.malk.delegate.TBEvent;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+
+@Slf4j
+@Service
+public class TBImplEvent implements TBEvent {
+
+    @Async
+    @Override
+    @SneakyThrows
+    public void callBackTask(JSONObject eventJson) {
+        String eventName = eventJson.getString("event");
+        log.info("callBackTask: 未被代理");
+    }
+
+    @Async
+    @Override
+    @SneakyThrows
+    public void callBackProject(JSONObject eventJson) {
+        String eventName = eventJson.getString("event");
+        log.info("callBackProject: 未被代理");
+    }
+}

+ 215 - 0
mjava-RJK/mjava/src/main/java/com/malk/filter/CatchException.java

@@ -0,0 +1,215 @@
+package com.malk.filter;
+
+import com.alibaba.fastjson.JSONException;
+import com.malk.server.common.McException;
+import com.malk.server.common.McR;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.converter.HttpMessageNotReadableException;
+import org.springframework.validation.BindException;
+import org.springframework.validation.FieldError;
+import org.springframework.web.HttpMediaTypeNotSupportedException;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.MissingServletRequestParameterException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
+
+import javax.validation.ConstraintDeclarationException;
+import javax.validation.ConstraintViolationException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 统一错误拦截
+ * -
+ * 参数校验 @Validated【javax.validation】
+ * 1. @Validated 针对实体类进行字段入参校验,可做分组,实现不同接口请求下字段的校验规则
+ * 2. @RestControllerAdvice 搭配 @ExceptionHandler 注解进行错误统一捕获和拦截返回,无需 try…catch 即可格式化返回标准输出::注意类名
+ * 3. 若无分组验证需求,可将@Validated 置于类上,不需要在@requestBody前声明。分组验证,可以在实体类组合好,若是取并集,则可直接在@requestBody上设置多个即可
+ * -
+ * 拦截param自定义message: @RequestParam默认是required,若想使用该检验,请求方法无需添加@RequestParam,且需要在Controller上添加@Validated
+ * 参数@RequestParam,可以配置默认值,指定name,指定name后可以设置是否必填。若是要获取所有(@RequestParam MultiValueMap<String, String> paramMap),每一个字段的值是一个集合
+ * -
+ * 关于在Service上使用校验, 首先需要impl类上添加@Validated [和Controller一致]
+ * 建议注解到方法声明上, 抛出到ConstraintViolationException. 若校验注解到方法实现上, 则错误抛出到 ConstraintDeclarationException, 没有友好的提示
+ * -
+ * -
+ * 若入参是Body,需要使用@RequestBody解析到实体/Map内,校验错误抛出到MethodArgumentNotValidException。@RequestBody @Validated({YDParam.Retrieve_Condition.class}) YDParam param
+ * 如入参是formData,不能使用@RequestBody,参数会自动解析到实体; 若不是实体则需要通过方法转Map, 错会抛出到BindException. @Validated({YDParam.Retrieve_Condition.class}) YDParam param
+ * -
+ * 使用@RestController后,请求方法无需再使用@RequestBody,@RequestParam,直接可对入参进行修饰,效果类似解构,除了正常获取HttpServletRequest外,相关参数自动处理到注解内
+ * RESTFul 风格接口, 参数获取方式均相同, GET 没有 body, @PathVariable 获取 url 占位符, RequestParam 获取 url 参数, @RequestBody 获取 body 数据
+ * -
+ * 关于入参为JSONString类型: body内的json需要传入字符串, 若为对象会解析报错. 若是formData, 传入对象会自动转为字符串, 不会解析错误, 无需转为JSONString
+ * 注解@JsonInclude(JsonInclude.Include.NON_NULL):类注解过滤null字段,包含入参和返回值【参数类搭配 @Data,才可以实例化返回值以及ToString输出】
+ */
+@Slf4j
+@RestControllerAdvice(annotations = RestController.class)
+public class CatchException<T> {
+
+//    @Autowired
+//    private ExceptionNotice notice;
+
+    /**
+     * 通用错误类抛出
+     */
+    @ExceptionHandler(McException.class)
+    public McR McException(McException e) {
+        log.error(e.getMessage(), e);  // 记录错误日志
+        return McR.R(e.isSuccess(), e.getCode(), e.getMessage(), null, e.getSource());  // IGNORE_EXECUTE 为成功
+    }
+
+    /**************** validated不合法 ****************/
+
+    /**
+     * @validated 验证入参对象 @Validated({YDParam.Retrieve_Condition.class}) YDParam param
+     * -
+     * 如入参是formData,不能使用@RequestBody,参数会自动解析到实体; 若不是实体通过方法转Map .报错会抛出到BindException。@Validated({YDParam.Retrieve_Condition.class}) YDParam param
+     */
+    @ExceptionHandler(value = BindException.class)
+    public McR BindException(BindException e) {
+        List errorParam = new ArrayList();
+        e.getFieldErrors().forEach(fieldError -> {
+            errorParam.add(fieldError.getField() + ": " + fieldError.getDefaultMessage());
+        });
+        log.error(e.getMessage(), e);  // 记录错误日志
+        return McR.errorParam("formData参数 -> " + String.join(", ", errorParam));
+    }
+
+    /**
+     * @Validated 关于在Service上使用校验,
+     * -
+     * 首先需要impl类上添加@Validated [和Controller一致]
+     * 建议注解到方法声明上, 抛出到ConstraintViolationException. 若校验注解到方法实现上, 则错误抛出到 ConstraintDeclarationException, 没有友好的提示
+     */
+    @ExceptionHandler(ConstraintDeclarationException.class)
+    public McR ConstraintDeclarationException(ConstraintDeclarationException e) {
+        log.error(e.getMessage(), e);  // 记录错误日志
+        return McR.errorParam("param参数 -> " + e.getMessage());
+    }
+
+    /**
+     * @validated 验证入参字段 @NotNull(message = "cur不能为空") String cur
+     * -
+     * 若入参是Body,需要使用@RequestBody解析到实体内,校验错误抛出到MethodArgumentNotValidException。@RequestBody @Validated({YDParam.Retrieve_Condition.class}) YDParam param
+     * 如入参是formData,不能使用@RequestBody,参数会自动解析到实体; 若不是实体通过方法转Map .报错会抛出到BindException。@Validated({YDParam.Retrieve_Condition.class}) YDParam param
+     * 关于入参为JSONString类型: body内的json需要传入字符串, 若为对象会解析报错. 若是formData, 传入对象会自动转为字符串, 不会解析错误, 无需转为JSONString
+     */
+    @ExceptionHandler(ConstraintViolationException.class)
+    public McR ConstraintViolationException(ConstraintViolationException e) {
+        List errorParam = new ArrayList();
+        e.getConstraintViolations().forEach(fieldError -> {
+            errorParam.add(fieldError.getPropertyPath() + ": " + fieldError.getMessage());
+        });
+        log.error(e.getMessage(), e);  // 记录错误日志
+        return McR.errorParam("param参数 -> " + String.join(", ", errorParam));
+    }
+
+    /**
+     * @validated 验证入参对象 @RequestBody @Validated({YDParam.Retrieve_Condition.class}) YDParam param
+     * -
+     * 若入参是Body,需要使用@RequestBody解析到实体内,校验错误抛出到MethodArgumentNotValidException。@RequestBody @Validated({YDParam.Retrieve_Condition.class}) YDParam param
+     */
+    @ExceptionHandler(MethodArgumentNotValidException.class)
+    public McR MethodArgumentNotValidException(MethodArgumentNotValidException e) {
+        List errorParam = new ArrayList();
+        e.getBindingResult().getAllErrors().forEach(err -> {
+            FieldError fieldError = (FieldError) err;
+            errorParam.add(fieldError.getField() + ": " + fieldError.getDefaultMessage());
+        });
+        log.error(e.getMessage(), e);  // 记录错误日志
+        return McR.errorParam("body参数 -> " + String.join(", ", errorParam));
+    }
+
+    /**************** 请求参数异常 ****************/
+
+    /**
+     * @RequestParam默认是required 验证入参字段 @RequestParam String cur
+     * -
+     * 参数@RequestParam,可以配置默认值,指定name,指定name后可以设置是否必填。若是要获取所有(@RequestParam MultiValueMap<String, String> param
+     */
+    @ExceptionHandler(MissingServletRequestParameterException.class)
+    public McR MissingServletRequestParameterException(MissingServletRequestParameterException e) {
+        log.error(e.getMessage(), e);  // 记录错误日志
+        return McR.errorParam("param参数 -> " + e.getMessage());
+    }
+
+    /**
+     * @RequestParam 参数类型解析异常
+     */
+    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
+    public McR MethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e) {
+        log.error(e.getMessage(), e);  // 记录错误日志
+        return McR.errorParam("param参数 ->  " + e.getName() + "不合法: " + e.getMessage());
+    }
+
+    /**
+     * @RequestBody 序列化字段为非Sting类型
+     * -
+     * 关于入参为JSONString类型: body内的json需要传入字符串, 若为对象会解析报错. 若是formData, 传入对象会自动转为字符串, 不会解析错误, 无需转为JSONString
+     */
+    @ExceptionHandler(HttpMessageNotReadableException.class)
+    public McR HttpMessageNotReadableException(HttpMessageNotReadableException e) {
+        log.error(e.getMessage(), e);  // 记录错误日志
+        if (e.getMessage().contains("Required request body is missing")) {
+            return McR.errorParam("请求体参数不能为空");
+        }
+        return McR.errorParam("请求体参数格式不合法");
+    }
+
+    /**
+     * Content-Type不合法
+     */
+    @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
+    public McR HttpMediaTypeNotSupportedException(HttpMediaTypeNotSupportedException e) {
+        log.error(e.getMessage(), e);  // 记录错误日志
+        return McR.errorParam("Content-Type不合法: " + e.getMessage());
+    }
+
+    /**************** 网络请求异常 ****************/
+
+    /**
+     * http请求返回json解析异常
+     */
+    @ExceptionHandler(JSONException.class)
+    public McR JSONException(JSONException e) {
+        log.error(e.getMessage(), e);  // 记录错误日志
+        return McR.errorParam("返回JSON解析异常: " + e.getMessage());
+    }
+
+    /**************** 数据类型异常 ****************/
+
+    /**
+     * BigDecimal 非数值型字符串, 空字符串取值异常
+     */
+    @ExceptionHandler(NumberFormatException.class)
+    public McR NumberFormatException(NumberFormatException e) {
+        log.error(e.getMessage(), e);  // 记录错误日志
+        return McR.errorParam(e.getMessage() + ": 非数值型字符串, 空字符串取值异常");
+    }
+
+    /**
+     * 反射未匹配到路径
+     */
+    @ExceptionHandler(ClassNotFoundException.class)
+    public McR ClassNotFoundException(ClassNotFoundException e) {
+        log.error(e.getMessage(), e);  // 记录错误日志
+        return McR.errorParam("反射未匹配到路径: " + e.getMessage());
+    }
+
+    /**
+     * 系统错误抛出
+     * -
+     * NOTE: 空指针判定, 日志目前会记录在warn内, 不输出到error内, 做一层包装返回 (空指针message为null)
+     */
+    @ExceptionHandler(Exception.class)
+    public McR Exception(Exception e) {
+        log.error(e.getMessage(), e);       // 记录错误日志
+//        notice.noticeErrorByDingtalk(e);    // 上报错误日志
+        if (e instanceof NullPointerException) {
+            return McR.errorNullPointer();
+        }
+        return McR.errorUnknown(e.getMessage());
+    }
+}

+ 53 - 0
mjava-RJK/mjava/src/main/java/com/malk/filter/ExceptionNotice.java

@@ -0,0 +1,53 @@
+//package com.mcli.filter;
+//
+//import cn.hutool.core.util.ObjectUtil;
+//import com.alibaba.fastjson.JSONObject;
+//import com.mcli.com.mcli.mcli.utils.UtilDateTime;
+//import com.mcli.service.common.McConf;
+//import com.mcli.service.common.McException;
+//import com.mcli.service.dingtalk.DDConf;
+//import lombok.extern.slf4j.Slf4j;
+//import org.springframework.beans.factory.annotation.Autowired;
+//import org.springframework.stereotype.Component;
+//
+//import java.util.Date;
+//
+///**
+// * 报错消息: 发送钉钉工作通知
+// */
+//@Slf4j
+//@Component
+//public class ExceptionNotice {
+//
+//    @Autowired
+//    private DDClient ddClient;
+//
+//    @Autowired
+//    private DDConf ddConf;
+//
+//    @Autowired
+//    private McConf mcConf;
+//
+//    /**
+//     * 上报错误日志 [未知错误]
+//     */
+//    public void noticeErrorByDingtalk(Exception e) {
+//        // 通知人为空为空则忽略
+//        if (ObjectUtil.isNull(mcConf.getEngineers()) || mcConf.getEngineers().isEmpty()) {
+//            return;
+//        }
+//        String content = e.getMessage();
+//        if (e instanceof McException) {
+//            McException mcE = (McException) e;
+//            content = mcE.toString();
+//        }
+//        log.warn("上报错误日志, {}, {}", mcConf.getEngineers(), content);
+//        content += " --> " + UtilDateTime.formatDateTime(new Date());
+//        JSONObject message = new JSONObject();
+//        message.put("msgtype", "text");
+//        JSONObject info = new JSONObject();
+//        info.put("content", content);
+//        message.put("text", info);
+//        ddClient.sendCorpConversationMessage(ddConf.getAgentId(), mcConf.getEngineers(), null, false, message);
+//    }
+//}

+ 32 - 0
mjava-RJK/mjava/src/main/java/com/malk/filter/RequestFilter.java

@@ -0,0 +1,32 @@
+package com.malk.filter;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.*;
+import java.io.IOException;
+
+@Component
+public class RequestFilter implements Filter {
+
+    // 指定类输出日志到指定文件夹
+    private static final Logger logger = LoggerFactory.getLogger("point");
+
+    @Override
+    public void init(FilterConfig filterConfig) throws ServletException {
+        logger.info("过滤器 ▷ 初始化");
+    }
+
+    @Override
+    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
+        logger.trace("过滤器 ▷ 开始执行");
+        chain.doFilter(request, response);
+        logger.trace("过滤器 ▷ 执行结束");
+    }
+
+    @Override
+    public void destroy() {
+        logger.info("过滤器 ▷ 销毁");
+    }
+}

+ 42 - 0
mjava-RJK/mjava/src/main/java/com/malk/filter/RequestInterceptor.java

@@ -0,0 +1,42 @@
+package com.malk.filter;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.annotation.Profile;
+import org.springframework.stereotype.Component;
+import org.springframework.web.servlet.ModelAndView;
+import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * 请求拦截器: 继承 HandlerInterceptor 或实现 HandlerInterceptor
+ */
+@Component
+public class RequestInterceptor extends HandlerInterceptorAdapter {
+
+    // 指定类输出日志到指定文件夹
+    private static final Logger logger = LoggerFactory.getLogger("point");
+
+    // 请求拦截
+    @Override
+    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
+        logger.info("拦截器 ▷ 收到请求: {}", request.getServletPath());
+        return super.preHandle(request, response, handler);
+    }
+
+    // 视图渲染
+    @Override
+    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
+        logger.trace("拦截器 ▷ 视图渲染");
+        super.postHandle(request, response, handler, modelAndView);
+    }
+
+    // 数据返回
+    @Override
+    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
+        logger.trace("拦截器 ▷ 数据返回");
+        super.afterCompletion(request, response, handler, ex);
+    }
+}

+ 13 - 0
mjava-RJK/mjava/src/main/java/com/malk/repository/dao/primary/McAuthorizationDao.java

@@ -0,0 +1,13 @@
+package com.malk.repository.dao.primary;
+
+import com.malk.repository.entity.primary.McAuthorizationPo;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+/**
+ * JAP配置参考BaseDao
+ */
+public interface McAuthorizationDao extends JpaRepository<McAuthorizationPo, Long> {
+
+    // findBy, 可直接添加字段名称
+    McAuthorizationPo findByAppType(String appType);
+}

+ 10 - 0
mjava-RJK/mjava/src/main/java/com/malk/repository/dao/primary/McTableDao.java

@@ -0,0 +1,10 @@
+package com.malk.repository.dao.primary;
+
+import com.malk.repository.entity.primary.McTablePo;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository("primaryMcTableDao")
+public interface McTableDao extends JpaRepository<McTablePo, Long> {
+
+}

+ 11 - 0
mjava-RJK/mjava/src/main/java/com/malk/repository/dao/slave/McTableDao.java

@@ -0,0 +1,11 @@
+package com.malk.repository.dao.slave;
+
+
+import com.malk.repository.entity.slave.McTablePo;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository("slaveMcTableDao")
+public interface McTableDao extends JpaRepository<McTablePo, Long> {
+
+}

+ 51 - 0
mjava-RJK/mjava/src/main/java/com/malk/repository/entity/primary/McAuthorizationPo.java

@@ -0,0 +1,51 @@
+package com.malk.repository.entity.primary;
+
+import com.malk.base.BasePo;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.hibernate.validator.constraints.Range;
+
+import javax.persistence.Entity;
+import javax.persistence.Table;
+import javax.validation.constraints.NotNull;
+
+/**
+ * 宜搭授权信息类
+ **/
+@Entity
+@Data
+@NoArgsConstructor
+@Table(name = "mc_authorization")
+public class McAuthorizationPo extends BasePo {
+
+    @Range(min = 0, max = 1)
+    private int delStatus = 0; // 是否删除: 设置默认值
+
+    @NotNull(message = "应用编码不能为空")
+    private String appType;
+
+    @NotNull(message = "应用秘钥不能为空")
+    private String systemToken;
+
+    @NotNull(message = "应用名称不能为空")
+    private String appName;
+
+    @NotNull(message = "授权企业编号不能为空")
+    private String corpId;
+
+    private String corpName;
+
+    @NotNull(message = "申请人用户编号不能为空")
+    private String userId;
+
+    @NotNull(message = "申请人姓名不能为空")
+    private String userName;
+
+    private String deptId;
+
+    private String deptName;
+
+    private String description;
+
+    private String remark;
+}

+ 34 - 0
mjava-RJK/mjava/src/main/java/com/malk/repository/entity/primary/McTablePo.java

@@ -0,0 +1,34 @@
+package com.malk.repository.entity.primary;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.malk.base.BasePo;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.springframework.data.jpa.domain.support.AuditingEntityListener;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.EntityListeners;
+import javax.persistence.Table;
+
+/**
+ * 主表记录
+ */
+@Entity
+@Table(name = "mc_table")
+@EntityListeners(AuditingEntityListener.class)
+@JsonInclude(value = JsonInclude.Include.NON_NULL)
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class McTablePo extends BasePo {
+
+    @Column
+    private String tName;
+
+    @Column
+    private String tDesc;
+}

+ 34 - 0
mjava-RJK/mjava/src/main/java/com/malk/repository/entity/slave/McTablePo.java

@@ -0,0 +1,34 @@
+package com.malk.repository.entity.slave;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.malk.base.BasePo;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.springframework.data.jpa.domain.support.AuditingEntityListener;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.EntityListeners;
+import javax.persistence.Table;
+
+/**
+ * 从表记录
+ */
+@Entity
+@Table(name = "mc_table")
+@EntityListeners(AuditingEntityListener.class)
+@JsonInclude(value = JsonInclude.Include.NON_NULL)
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class McTablePo extends BasePo {
+
+    @Column
+    private String tName;
+
+    @Column
+    private String tDesc;
+}

+ 58 - 0
mjava-RJK/mjava/src/main/java/com/malk/schedule/McScheduleTask.java

@@ -0,0 +1,58 @@
+package com.malk.schedule;
+
+import com.malk.service.dingtalk.DDClient;
+import com.malk.service.dingtalk.DDClient_Event;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.EnableScheduling;
+
+
+/**
+ * 定时任务ScheduleTask
+ * -
+ * 1. @EnableScheduling:开启定时任务,当前文件文件全局注解【也可单独成为一个控制的配置文件管控全局】
+ * 2. @Scheduled:定时执行的方法,通过入参控制
+ * - 1. @Scheduled(fixedDelay = 5000):单位毫秒,当任务执行完毕后5s后再执行
+ * - 2. @Scheduled(fixedRate = 3000):单位毫秒,表示每隔3秒,不受执行时间影响
+ * - 3. cron表达式:cron一共有7位,但是最后一位是年,可以留空,cron中,还有一些特殊的符号
+ * 3. @component / @Configuration:声明定时任务为组件类。若不声明,定时器无效,因为没有注入
+ */
+
+/**
+ * @EnableScheduling 条件注入, 根据条件确定当前类是否要装载Bean: 如定时器开发环境不启动
+ * -
+ * 示例:注意格式 @ConditionalOnExpression(value = "${spel.scheduling}") 或者 @ConditionalOnProperty(name = "spel.scheduling", havingValue = "false")
+ * *
+ * @ConditionalOnExpression,可多个参数,支持与、或关系,默认匹配bool。注意若对应环境未识别到声明会报错
+ * @ConditionalOnProperty,可多个参数,支持与,若获取值为空识别为false,若有值则将该值与havingValue指定的值进行比较,匹配结果为bool。不支持或
+ */
+
+@Slf4j
+@Configuration
+@EnableScheduling
+@ConditionalOnProperty(name = {"spel.scheduling"}, havingValue = "false")
+public class McScheduleTask {
+
+    @Autowired
+    private DDClient ddClient;
+
+    @Autowired
+    private DDClient_Event ddClient_event;
+
+    /**
+     * 钉钉事件回调 3_3
+     * -
+     * 同步钉钉推送失败记录: 推送失败列表, 获取后记录会被清空
+     */
+//    @Scheduled(cron = "0 0/30 7-23 * * ?")
+    public void syncDingTalkFailedList() {
+        try {
+            ddClient_event.syncFailedList(ddClient.getAccessToken());
+        } catch (Exception e) {
+            // 记录错误信息
+            e.printStackTrace();
+        }
+    }
+}

+ 187 - 0
mjava-RJK/mjava/src/main/java/com/malk/server/aliwork/YDConf.java

@@ -0,0 +1,187 @@
+package com.malk.server.aliwork;
+
+import com.alibaba.fastjson.JSON;
+import com.malk.utils.UtilMap;
+import com.malk.utils.UtilString;
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+@Data
+@Component
+@ConfigurationProperties(prefix = "aliwork")
+@Slf4j
+public class YDConf {
+
+    private String appType;
+
+    private String systemToken;
+
+    /**
+     * 一个切片数量上限
+     */
+    public static final Integer UPPER_LIMIT = 30000;
+
+    /**
+     * 一个分页数量上限
+     */
+    public static final Integer PAGE_SIZE_LIMIT = 100;
+
+    /**
+     * 接口访问账号 [不能触发待办与消息通知, 业务规则亦不能]
+     * -
+     * 非管理员账号只可以查询, 不检验权限
+     * 操作数据需要管理员账号, 或宜搭平台
+     */
+    public static final String PUB_ACCOUNT = "yida_pub_account";
+
+    ////////////////////////// 新版本API //////////////////////////
+
+    /**
+     * 查询表单
+     */
+    public enum FORM_QUERY {
+
+        retrieve_list,              // 全局查询, 不包含子表单
+        retrieve_list_all,          // 全局查询, 包含子表单
+        multi_retrieve_id,
+
+        retrieve_id,                // 单个ID查询 todo 若秘钥不匹配, 返回空, 添加报错说明
+
+        retrieve_search_process,            // 流程列表
+        retrieve_search_form,               // 表单列表
+
+        retrieve_search_process_id,         // 流程列表
+        retrieve_search_form_id,             // 表单列表
+
+        retrieve_details,             // 子表数据[表单]
+        retrieve_changed,    // 变更记录
+        retrieve_definition, // 表单定义
+
+    }
+
+    /**
+     * 表单操作
+     */
+    public enum FORM_OPERATION {
+        create,
+        delete,             // 传入为body, 文档为param
+        update,
+        multi_create,               // 批量操作
+        delete_batch,               // 批量删除
+        multi_update,               // 批量更新
+        start,                      // 发起流程
+        batchSave,                  // 批量创建
+    }
+
+    /**
+     * 关联表单处理
+     *
+     * @param formType: "receipt" 跳转为表单【若是流程,则权限体系会失效,但也会显示审批节点】; formType: "process" 为流程
+     * @apiNote ppExt 接口更新, 传入jsonString或List都可以. 返回数据都是两层json解析, 组件 + _id格式
+     */
+    public List<Map> associationForm(String formUuid, String formInstanceId, String title, String subTitle, boolean isProcess) {
+        return associationForm(appType, formUuid, formInstanceId, title, subTitle, isProcess);
+    }
+
+    // todo 部门ID写入需要是string集合\数组, 封装
+    public static List<Map> associationForm(String appType, String formUuid, String formInstanceId, String title, String subTitle, boolean isProcess) {
+        String formType = isProcess ? "process" : "receipt";
+        return Arrays.asList(UtilMap.map("appType, formUuid, instanceId, title, subTitle, formType", appType, formUuid, formInstanceId, title, subTitle, formType));
+    }
+
+    /**
+     * 读取关联表单: ppExt 两层解析后才是List, 若是赋值取值转一层, 宜搭也可进行写入
+     * - 赋值说明 -
+     * 若是同一个应用,appType与formUuid为空(不指定下formUuid可为错误,但appType只能为空),点击页面链接会自动匹配;
+     * 若是跨应用实例,则不能通过导入,需要通过接口指定appType
+     */
+    public List<Map> associationForm(String associations) {
+        if (UtilString.isBlankCompatNull(associations)) {
+            return new ArrayList<>();
+        }
+        return (List<Map>) JSON.parse(String.valueOf(JSON.parse(String.valueOf(associations))));
+    }
+
+    /**
+     * 高级筛选: ppExt 通过表单\流程 search 宜搭目前下拉框也会被模糊匹配, 为避免事后过滤, 使用高级查询; 且高级查询支持同一个字段多条件
+     */
+    public static Map searchCondition_TextFiled(String compId, Object value, String operator) {
+        return UtilMap.map("key, value, type, operator, componentName", compId, value, "TEXT", operator, "TextField");
+    }
+
+    // todo text组件是否可用于select
+    public static Map searchCondition_processInstanceStatus(List status) {
+        return UtilMap.map("key, value, type, operator, componentName", "processInstanceStatus", status, "ARRAY", "in", "SelectField");
+    }
+
+    /**
+     * 组件格式化取值 [取值] todo 明细表递归, 循环传递入参
+     */
+    public static Object getDataByCompId(Map formData, String compId_cur) {
+        if (compId_cur.contains("associationFormField_")) {
+            // 服务注册 #{_yida_all_data} 直接组件Id
+            if (!formData.containsKey(compId_cur)) {
+                // 接口返回数据, 关联组件ID, 不会返回组件id字段
+                compId_cur = compId_cur + "_id";
+            }
+            return JSON.parse(String.valueOf(formData.get(compId_cur)));
+        } else if (compId_cur.contains("employeeField_")) {
+            if (!formData.containsKey(compId_cur + "_id")) {
+                // 服务注册 #{_yida_all_data} 返回JsonString userid 数组
+                return JSON.parse(String.valueOf(formData.get(compId_cur)));
+            } else {
+                // 成员组件, 接口返回组件id返回为姓名, _id返回是userId
+                return formData.get(compId_cur + "_id");
+            }
+        }
+        return formData.get(compId_cur);
+    }
+
+    ////////////////////////// 老版本API //////////////////////////
+
+    /**
+     * 表单接口适用于流程: 查询和更新以及删除
+     */
+    private static String formatApiForm(String uri) {
+        return "/yida_vpc/form/" + uri + ".json";
+    }
+
+    private static String formatApiProcess(String uri) {
+        return "/yida_vpc/process/" + uri + ".json";
+    }
+
+    /**
+     * 接口地址: 表单
+     */
+    public static final String API_FORM_CREATE = formatApiForm("saveFormData");
+    public static final String API_FORM_DELETE = formatApiForm("deleteFormData");
+    public static final String API_FORM_UPDATE = formatApiForm("updateFormData");
+    public static final String API_FORM_DETAIL = formatApiForm("getFormDataById");
+    public static final String API_FORM_QUERY_ID = formatApiForm("searchFormDataIds");
+    public static final String API_FORM_QUERY_DATA = formatApiForm("searchFormDatas");
+
+    public static final String API_FORM_DEFINITION = "/yida_vpc/formDesign/getFormComponentDefinationList.json";
+
+    /**
+     * 接口地址: 流程
+     */
+    public static final String API_PROCESS_CREATE = formatApiProcess("startInstance");
+    public static final String API_PROCESS_DELETE = formatApiProcess("deleteInstance");
+    public static final String API_PROCESS_UPDATE = formatApiProcess("updateInstance");
+    public static final String API_PROCESS_DETAIL = formatApiProcess("getInstanceById");
+    public static final String API_PROCESS_BATCH_DETAIL = formatApiProcess("getInstancesByIds");
+    public static final String API_PROCESS_QUERY_ID = formatApiProcess("getInstanceIds");
+    public static final String API_PROCESS_QUERY_DATA = formatApiProcess("getInstances");
+
+    /**
+     * 其它接口
+     */
+    public static final String API_OPEN_URL = "/yida_vpc/file/getOpenUrl.json";
+}

+ 225 - 0
mjava-RJK/mjava/src/main/java/com/malk/server/aliwork/YDParam.java

@@ -0,0 +1,225 @@
+package com.malk.server.aliwork;
+
+import com.alibaba.fastjson.JSONObject;
+import com.malk.base.BaseDto;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.apache.commons.lang3.StringUtils;
+
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Null;
+import javax.validation.groups.Default;
+import java.util.List;
+
+/**
+ * 表单接口适用于流程: 查询和更新以及删除 [参数校验参考CatchException]
+ */
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class YDParam extends BaseDto {
+
+    /**
+     * 接口参数
+     */
+    @NotNull(message = "应用编码不能为空")
+    private String appType;
+
+    @Null(message = "应用秘钥无需传递")
+    private String systemToken;
+
+    // 默认宜搭平台, 有操作数据权限
+    @Builder.Default
+    private String userId = YDConf.PUB_ACCOUNT;
+
+    @NotNull(message = "表单编码不能为空", groups = {Create.class, Retrieve_Condition.class, Definition.class, Create_Process.class, Retrieve_Condition_Update.class})
+    private String formUuid;
+
+    /// FIXME: 表单/流程组件查询
+    private String searchFieldJson;
+
+    @Builder.Default
+    private Integer currentPage = 1;
+
+    @Builder.Default
+    private Integer pageSize = YDConf.PAGE_SIZE_LIMIT;
+
+    // 流程暂不支持排序
+    private String dynamicOrder;
+
+    // 修改开始时间, ppExt: 需要同时传递开始和就结束才会生效; 其次日期格式yyyy-MM-dd, 但默认是0点, 结束日期j建议加1
+    private String modifiedFromTimeGMT;
+
+    // 修改结束时间, ppExt: 需要同时传递开始和就结束才会生效; 其次日期格式yyyy-MM-dd, 但默认是0点, 结束日期j建议加1
+    private String modifiedToTimeGMT;
+
+    @NotNull(message = "实例ID不能为空", groups = {Update.class, Delete.class, Retrieve_FormInstId.class})
+    private String formInstId;
+
+    @NotNull(message = "更新内容不能为空", groups = {Update.class, Update_ProcessInstanceId.class, Retrieve_Condition_Update.class})
+    private String updateFormDataJson;
+
+    @NotNull(message = "流程编码不能为空", groups = Create_Process.class)
+    private String processCode;
+
+    // todo: 成员组件未匹配, 接口可以新增 [系统会忽略], 导入会提示无法匹配
+    @NotNull(message = "新增内容不能为空", groups = {Create.class, Create_Process.class})
+    public String formDataJson;
+
+    @NotNull(message = "实例ID不能为空", groups = {Update_ProcessInstanceId.class, Delete_ProcessInstanceId.class, Retrieve_ProcessInstanceId.class})
+    private String processInstanceId;
+
+    @NotNull(message = "实例ID不能为空", groups = Retrieve_ProcessInstanceIds.class)
+    private String processInstanceIds;
+
+    private String instanceStatus;
+
+    private String approvedResult;
+
+    /**
+     * 格式化赋值: 内部使用
+     */
+    public void setDynamicOrder(String dynamicOrder) {
+        this.dynamicOrder = dynamicOrder;
+    }
+
+    public void setDynamicOrder(String rule, String... compIds) {
+        for (String compId : compIds) {
+            JSONObject orderJson = new JSONObject();
+            orderJson.put(compId, rule);
+            this.dynamicOrder = orderJson.toJSONString();
+        }
+    }
+
+    /**
+     * 自定义参数
+     */
+    private String compIdSerial; // 流水号排序字段
+
+    /**
+     * 钉钉新版本接口
+     */
+    @Builder.Default
+    private Integer pageNumber = 1;
+
+    private String formInstanceId;
+
+    // formInstanceId 为新版本实例id字段
+    public String getFormInstanceId() {
+        if (StringUtils.isBlank(formInstanceId)) {
+            return formInstId;
+        }
+        return formInstanceId;
+    }
+
+    /// FIXME: 表单/流程全局查询
+    private String searchCondition;
+
+    // 全局条件查询列表数据 FIXME: 若是查询组件也传searchFieldJson格式
+    public String getSearchCondition() {
+        if (StringUtils.isBlank(searchCondition)) {
+            return searchFieldJson;
+        }
+        return searchCondition;
+    }
+
+    // 查询列表可查询多个指定实例ID | 批量删除 [上限50] | 批量更新
+    private List<String> formInstanceIdList;
+
+    // 是否需要宜搭表单组件格式的实例数据
+    private boolean needFormInstanceValue = false;
+
+    // 查询明细数据列表, 默认仅返回前50
+    private String tableFieldId;
+
+    // 附件转临时免登地址
+    private String fileUrl;
+    // 临时地址失效时间, 默认10分钟, 最大值24小时
+    @Builder.Default
+    private Integer timeout = 600000;
+
+    /**
+     * 批量删除
+     */
+
+    // 是否需要宜搭服务端异步执行该任务
+    @Builder.Default
+    boolean asynchronousExecution = false;
+
+    // 是否需要触发表单绑定的校验规则、关联业务规则和第三方服务回调
+    @Builder.Default
+    boolean executeExpression = false;
+
+    // 是否不触发表单绑定的校验规则、关联业务规则和第三方服务回调。
+    @Builder.Default
+    boolean noExecuteExpression = true;
+
+    // 是否忽略空值。
+    @Builder.Default
+    boolean ignoreEmpty = true;
+
+    // 是否使用最新的表单schema版本。
+    @Builder.Default
+    boolean useLatestFormSchemaVersion = true;
+
+    // 使用最新的表单版本进行更新。
+    @Builder.Default
+    boolean useLatestVersion = false;
+
+    /**
+     * 分组校验
+     *
+     * @RequestBody @Validated(YDParam.Create.class) YDParam ydParam
+     */
+
+    public interface Create extends Default {
+
+    }
+
+    public interface Create_Process extends Default {
+
+    }
+
+    public interface Retrieve_Condition extends Default {
+
+    }
+
+    public interface Retrieve_Condition_Update extends Default {
+
+    }
+
+    public interface Retrieve_FormInstId extends Default {
+
+    }
+
+    public interface Retrieve_ProcessInstanceId extends Default {
+
+    }
+
+    public interface Retrieve_ProcessInstanceIds extends Default {
+
+    }
+
+    public interface Update extends Default {
+
+    }
+
+    public interface Update_ProcessInstanceId extends Default {
+
+    }
+
+    public interface Delete extends Default {
+
+    }
+
+    public interface Delete_ProcessInstanceId extends Default {
+
+    }
+
+    public interface Definition extends Default {
+
+    }
+}

+ 32 - 0
mjava-RJK/mjava/src/main/java/com/malk/server/aliwork/YDR.java

@@ -0,0 +1,32 @@
+package com.malk.server.aliwork;
+
+import com.malk.utils.UtilMap;
+import lombok.Data;
+import org.springframework.data.domain.Page;
+
+import java.util.Map;
+
+/**
+ * * 返回数据_宜搭
+ **/
+@Data
+public class YDR {
+
+    private boolean success;
+
+    private String errorCode;
+
+    private String errorMsg;
+
+    // 查询为data之集合, 新增为实例Id, 删除/更新为空, ....
+    private Object result;
+
+    /**
+     * 宜搭表格分页管理
+     */
+    public static final Map formatPage(Page<Map> page) {
+        Map data = UtilMap.map("currentPage, pageSize, totalCount", page.getNumber() + 1, page.getNumberOfElements(), page.getTotalElements());
+        data.put("data", page.getContent());
+        return data;
+    }
+}

+ 33 - 0
mjava-RJK/mjava/src/main/java/com/malk/server/aliyun/ALYR.java

@@ -0,0 +1,33 @@
+package com.malk.server.aliyun;
+
+import com.malk.server.common.McException;
+import com.malk.server.common.VenR;
+import lombok.Data;
+
+/**
+ * 返回数据_阿里云
+ */
+@Data
+public class ALYR<T> extends VenR {
+
+    private boolean success;
+
+    private String code;
+
+    private String message;
+
+    private String description;
+
+    private T data;
+
+    // 成功状态标记
+    private final static String SUC_CODE = "0";
+
+    /**
+     * 断言错误信息
+     */
+    @Override
+    public void assertSuccess() {
+        McException.assertException(!code.equals(SUC_CODE), code, message + " >>> " + description, "aliyun");
+    }
+}

+ 42 - 0
mjava-RJK/mjava/src/main/java/com/malk/server/common/FilePath.java

@@ -0,0 +1,42 @@
+package com.malk.server.common;
+
+import lombok.Data;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.stereotype.Component;
+
+/**
+ * 读取配置文件参考McConf
+ */
+@Data
+@Component
+@ConfigurationProperties(prefix = "file")
+public class FilePath {
+
+    @Autowired
+    private Path path;
+
+    @Autowired
+    private Source source;
+
+    @Data
+    @Configuration
+    @ConfigurationProperties(prefix = "file.path")
+    public class Path {
+
+        private String file;
+
+        private String image;
+
+        private String tmp;
+    }
+
+    @Data
+    @Configuration
+    @ConfigurationProperties(prefix = "file.source")
+    public class Source {
+
+        private String fonts;
+    }
+}

+ 87 - 0
mjava-RJK/mjava/src/main/java/com/malk/server/common/McConf.java

@@ -0,0 +1,87 @@
+package com.malk.server.common;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Nullable;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * 读取配置文件
+ *
+ * @Value("${ }") :变量读取方式,尤其注意不支持读取yml文件内容,可通过在变量后 :10 设置默认值为 10
+ * 添加的属性前,为该字段赋值:@Value("${dingding.appKey}"
+ * 需要3个前提: 不能使用static或final修饰了tagValue; 类没有加上@Component(或者@service等); 类被new新建了实例,而没有使用@Autowire
+ * * --------------------------------------------------------------------------------- *
+ * @ConfigurationProperties(prefix = "pre") 对象读取方式: 实例化承载对象
+ * 将指定前缀配置实例化为实体类 [添加 spring-boot-configuration-processor依赖避免idea报错]
+ * 尤其注意: 需要为类添加组件注解, 如 @Service, @Component, ...; 引用实体类必须通过 @Autowired 进行注入, 否则识别到为空 [使用注意与 @Value 相同]
+ */
+@Data
+@Component
+@ConfigurationProperties(prefix = "corp")
+public class McConf {
+
+    private int timeOut;
+
+    private int timeAwait;
+
+    private List<String> engineers;
+
+    /**
+     * 最大分页
+     */
+    public static int pageSize = 100;
+
+    /**
+     * 分页数据
+     */
+    public static PageRequest simplePage(@Nullable Map param) {
+        int page = Integer.parseInt(String.valueOf(Optional.ofNullable(param.get("page")).orElse("1")));
+        int size = Integer.parseInt(String.valueOf(Optional.ofNullable(param.get("size")).orElse(pageSize)));
+        page -= 1;
+        if (page < 0) {
+            page = 0;
+        }
+        if (size > pageSize) {
+            size = pageSize;
+        }
+        if (size < 1) {
+            size = 1;
+        }
+        return PageRequest.of(page, size);
+    }
+
+    /**
+     * 全部数据
+     */
+    public static PageRequest allDataPage() {
+        return PageRequest.of(0, Integer.MAX_VALUE);
+    }
+
+    /**
+     * 单条数据
+     */
+    public static PageRequest singleDataPage() {
+        return PageRequest.of(0, 1);
+    }
+
+    /**
+     * 简单分页: 先查询再分页, 用于数据组装场景
+     */
+    public static List manualPage(PageRequest pageRequest, List dataList) {
+        long total = dataList.size();
+        if (pageRequest.getPageSize() < total) {
+            int start = pageRequest.getPageNumber() * pageRequest.getPageSize();
+            if (start > total) start = (int) total;
+            int end = (pageRequest.getPageNumber() + 1) * pageRequest.getPageSize();
+            if (end > total) end = (int) total;
+            dataList = dataList.subList(start, end);
+        }
+        return dataList;
+    }
+}

+ 167 - 0
mjava-RJK/mjava/src/main/java/com/malk/server/common/McException.java

@@ -0,0 +1,167 @@
+package com.malk.server.common;
+
+import com.malk.utils.UtilServlet;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.Map;
+
+/**
+ * 通用错误类 [错误抛出与拦截详见 CatchException]
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class McException extends RuntimeException {
+
+    private static final long serialVersionUID = 5131110381034275224L;
+
+    private boolean success;
+
+    private String code;
+
+    private String message;
+
+    // 错误来源
+    private String source;
+
+    public McException(McREnum rEnum) {
+        this(rEnum.isSuc(), rEnum.getCode(), rEnum.getMsg());
+    }
+
+    public McException(String code, String message) {
+        this(false, code, message);
+    }
+
+    public McException(boolean suc, String code, String message) {
+        super(message);
+        this.success = suc;
+        this.code = code;
+        this.message = message;
+    }
+
+
+    /// 断言错误: 自定义错误信息 ///
+
+    /**
+     * 错误断言: 枚举
+     */
+    public static void assertException(boolean isAssert, McREnum rEnum) {
+        if (isAssert) {
+            throw McException.builder().code(rEnum.getCode()).message(rEnum.getMsg()).build();
+        }
+    }
+
+    /**
+     * 错误断言: 信息
+     */
+    public static void assertException(boolean isAssert, String code, String message) {
+        if (isAssert) {
+            throw McException.builder().code(code).message(message).build();
+        }
+    }
+
+    /**
+     * 错误断言: 来源
+     */
+    public static void assertException(boolean isAssert, String code, String message, String source) {
+        if (isAssert) {
+            throw McException.builder().code(code).message(message).source(source).build();
+        }
+    }
+
+    /**
+     * 断言: 业务校验不通过
+     */
+    public static void
+    assertAccessException(boolean isAssert, String message) {
+        if (isAssert) {
+            throw McException.builder().code(McREnum.VALIDATED_ACCESS.getCode()).message(message).build();
+        }
+    }
+
+    /**
+     * 断言: 参数校验不通过
+     */
+    public static void assertParamException(boolean isAssert, String message) {
+        if (isAssert) {
+            throw McException.builder().code(McREnum.VALIDATED_PARAM.getCode()).message(message).build();
+        }
+    }
+
+    /**
+     * 断言: 参数不合法
+     * -
+     * 实例: McException.assertParamException_Null(UtilServlet.isNull(param, "projectName", "userName"));
+     */
+    public static void assertParamException_Null(String key) {
+        if (StringUtils.isNotBlank(key)) {
+            throw McException.builder().code(McREnum.VALIDATED_PARAM.getCode()).message(("参数不能为空: ").concat(key)).build();
+        }
+    }
+
+    public static void assertParamException_Null(Map param, String... keys) {
+        McException.assertParamException_Null(UtilServlet.isNull(param, keys));
+    }
+
+    public static void assertParamException_Null(Map param, String keys) {
+        McException.assertParamException_Null(UtilServlet.isNull(param, keys));
+    }
+
+    /**
+     * 断言: 参数不能为空
+     */
+    public static void assertParamException_Rule(String key) {
+        if (StringUtils.isNotBlank(key)) {
+            throw McException.builder().code(McREnum.VALIDATED_PARAM.getCode()).message(("参数不合法: ").concat(key)).build();
+        }
+    }
+
+    /// 快速枚举: 自定义错误信息 ///
+
+    /**
+     * 参数不合法
+     */
+    public static McR exceptionParam(String message) {
+        throw McException.builder().code(McREnum.VALIDATED_PARAM.getCode()).message(message).build();
+    }
+
+    /**
+     * 无效token
+     */
+    public static McR exceptionToken(String message) {
+        throw McException.builder().code(McREnum.TOKEN_INVALID.getCode()).message(message).build();
+    }
+
+    /**
+     * 授权无效
+     */
+    public static McR exceptionAuth(String message) {
+        throw McException.builder().code(McREnum.NOT_AUTHORIZED.getCode()).message(message).build();
+    }
+
+    /**
+     * 业务校验不通过
+     */
+    public static McR exceptionAccess(String message) {
+        throw McException.builder().code(McREnum.VALIDATED_ACCESS.getCode()).message(message).build();
+    }
+
+    /**
+     * 方法执行失败
+     */
+    public static McR exceptionExecute(String message) {
+        throw McException.builder().code(McREnum.METHOD_EXECUTE.getCode()).message(message).build();
+    }
+
+    /**
+     * 未知错误
+     */
+    public static McR exceptionUnknown(String message) {
+        throw McException.builder().code(McREnum.UNKNOWN_EXCEPTION.getCode()).message(message).build();
+    }
+}

+ 40 - 0
mjava-RJK/mjava/src/main/java/com/malk/server/common/McPage.java

@@ -0,0 +1,40 @@
+package com.malk.server.common;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.springframework.data.domain.Page;
+
+import java.util.List;
+
+/**
+ * 分页集合数据结构
+ */
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class McPage {
+
+    private int page = 1;
+
+    private int size;
+
+    private long total = 0;
+
+    private List list;
+
+    public static McPage page(Page page) {
+        return page(page, page.getContent());
+    }
+
+    public static McPage page(Page page, List dataList) {
+        return McPage.builder()
+                .total(page.getTotalElements())
+                .page(page.getNumber())
+                .size(page.getSize())
+                .list(dataList)
+                .build();
+    }
+}

+ 105 - 0
mjava-RJK/mjava/src/main/java/com/malk/server/common/McR.java

@@ -0,0 +1,105 @@
+package com.malk.server.common;
+
+import com.malk.base.BaseDto;
+import lombok.Builder;
+import lombok.Data;
+
+/**
+ * 定义返回值结构信息
+ * - java使用了未经检查或不安全的操作,编译会有打印,如泛型 T
+ * 单个文件屏蔽: @SuppressWarnings("unchecked") 。注解在类上编辑就不会有输出
+ * 插件显示警告: 配置 -Xlint:unchecked, 显示具体报错警告位置, 如 Map, List 也会报警告
+ */
+@Data
+@Builder
+public class McR<T> extends BaseDto {
+
+    private boolean success;
+    private String code;
+    private String message;
+    private T data;
+
+    // 错误来源
+    private String source;
+
+    /// 错误实例 ///
+
+    public McR(McREnum rEnum, T data) {
+        this(rEnum.isSuc(), rEnum.getCode(), rEnum.getMsg(), data);
+    }
+
+    public McR(boolean success, String code, String message, T data, String source) {
+        this.success = success;
+        this.code = code;
+        this.message = message;
+        this.data = data;
+        this.source = source;
+    }
+
+    public McR(boolean success, String code, String message, T data) {
+        this(success, code, message, data, null);
+    }
+
+    /// 静态访问 ///
+
+    public static McR R(McREnum rEnum) {
+        return new McR(rEnum, null);
+    }
+
+    public static McR R(McREnum rEnum, Object data) {
+        return new McR(rEnum, data);
+    }
+
+    public static McR R(boolean success, String code, String message, Object data, String source) {
+        return new McR(success, code, message, data, source);
+    }
+
+    /// 快速访问: 实例 ///
+
+    public static McR error(String code, String message) {
+        return R(false, code, message, null, null);
+    }
+
+    public static McR errorParam(String message) {
+        return error(McREnum.VALIDATED_PARAM.getCode(), message);
+    }
+
+    public static McR errorAccess(String message) {
+        return error(McREnum.VALIDATED_ACCESS.getCode(), message);
+    }
+
+    public static McR errorAuth(String message) {
+        return error(McREnum.NOT_AUTHORIZED.getCode(), message);
+    }
+
+    public static McR errorVendor(String message, String source) {
+        return R(false, McREnum.VENDOR_ERROR.getCode(), message, null, source);
+    }
+
+    public static McR errorUnknown(String message) {
+        return error(McREnum.UNKNOWN_EXCEPTION.getCode(), message);
+    }
+
+    /// 快速访问: 枚举 ///
+
+    public static McR success() {
+        return R(McREnum.SUCCESS);
+    }
+
+    public static McR success(Object data) {
+        return R(McREnum.SUCCESS, data);
+    }
+
+    public static McR errorIgnore() {
+        return R(McREnum.IGNORE_EXECUTE);
+    }
+
+    public static McR errorToken() {
+        return R(McREnum.TOKEN_INVALID);
+    }
+
+    public static McR errorNullPointer() {
+        return R(McREnum.NULL_POINTER);
+    }
+}
+

+ 33 - 0
mjava-RJK/mjava/src/main/java/com/malk/server/common/McREnum.java

@@ -0,0 +1,33 @@
+package com.malk.server.common;
+
+import lombok.Getter;
+
+/**
+ * 定义返回值和对应状态的信息
+ */
+public enum McREnum {
+
+    SUCCESS(true, "200", "SUCCESS"),
+    IGNORE_EXECUTE(true, "200", "IGNORE THE CURRENT EXECUTE RESULT"),
+    VALIDATED_PARAM(false, "4001", "PARAMETER VERIFICATION FAILS"),
+    TOKEN_INVALID(false, "4002", "TOKEN INVALID"),
+    NOT_AUTHORIZED(false, "4003", "ERROR INVALID AUTH STATE"),
+    VALIDATED_ACCESS(false, "4004", "VALIDATE IS DISSATISFY THE CONDITION"),
+    METHOD_EXECUTE(false, "5001", "ENCOUNTER AN ERROR WHEN EXECUTE METHOD"),
+    NULL_POINTER(false, "5002", "ENCOUNTER AN ERROR NULL POINTER EXCEPTION"),
+    VENDOR_ERROR(false, "5004", "VENDOR PLATFORM ERROR EXCEPTION"),
+    UNKNOWN_EXCEPTION(false, "6001", "THIS IS AN UNKNOWN EXCEPTION");
+
+    @Getter
+    private String code;
+    @Getter
+    boolean suc;
+    @Getter
+    private String msg;
+
+    McREnum(boolean suc, String code, String msg) {
+        this.suc = suc;
+        this.code = code;
+        this.msg = msg;
+    }
+}

+ 55 - 0
mjava-RJK/mjava/src/main/java/com/malk/server/common/VenR.java

@@ -0,0 +1,55 @@
+package com.malk.server.common;
+
+import com.malk.base.BaseDto;
+import com.malk.utils.UtilHttp;
+import lombok.Data;
+import lombok.SneakyThrows;
+
+import java.util.Map;
+
+/**
+ * 定义第三方返回值结构信息
+ */
+@Data
+public class VenR extends BaseDto {
+
+    /**
+     * 断言错误信息: 子类实现 [静态代理]
+     */
+    public void assertSuccess() {
+
+    }
+
+    ///-- 反射 [通过Class.forName(全类名)方式获取Class]
+
+    public static final String RC_MC = "com.malk.server.common.MCR";
+    public static final String RC_YD = "com.malk.server.aliwork.YDR";
+    public static final String RC_ALY = "com.malk.server.aliyun.ALYR";
+    public static final String RC_DD = "com.malk.server.dingtalk.DDR";
+    public static final String RC_DD_New = "com.malk.server.dingtalk.DDR_New";
+    public static final String RC_EKB = "com.malk.server.ekuaibao.EKBRR";
+    public static final String RC_FXK = "com.malk.server.fxiaoke.FXKR ";
+    public static final String RC_XBB = "com.malk.server.xbongbong.XBBR";
+    public static final String RC_VK = "com.malk.server.vika.VKR";
+
+    /**
+     * 通用post请求
+     */
+    @SneakyThrows
+    public static VenR doPost(String url, Map header, Map param, Map body, String path) {
+        Class rClass = Class.forName(path);
+        VenR rsp = UtilHttp.doPost(url, header, param, body, rClass);
+        return rsp;
+    }
+
+    /**
+     * 通用get请求
+     */
+    @SneakyThrows
+    public static VenR doGet(String url, Map header, Map param, String path) {
+        Class rClass = Class.forName(path);
+        VenR rsp = UtilHttp.doGet(url, header, param, rClass);
+        return rsp;
+    }
+}
+

+ 76 - 0
mjava-RJK/mjava/src/main/java/com/malk/server/dingtalk/DDConf.java

@@ -0,0 +1,76 @@
+package com.malk.server.dingtalk;
+
+import com.malk.utils.UtilMap;
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+
+/**
+ * 读取配置文件参考FilePah
+ */
+@Data
+@Component
+@ConfigurationProperties(prefix = "dingtalk")
+public class DDConf {
+
+    private Number agentId;
+
+    private String appKey;
+
+    private String appSecret;
+
+    private String corpId;
+
+    private String corpToken;
+
+    private String aesKey;
+
+    private String token;
+
+    // 操作人, 需要为OA后台管理员
+    private String operator;
+
+    // 机器人编号
+    private String robotCode;
+
+    /**
+     * 钉钉一级部门: 1
+     */
+    public static final long TOP_DEPARTMENT = 1L;
+
+    /**
+     * 钉钉回调响应
+     */
+    public static final String CALLBACK_RESPONSE = "success";
+
+    /**
+     * 验证注册地址, 通过开发平台配置触发
+     */
+    public static final String CALLBACK_CHECK = "check_url";
+
+    /**
+     * 审批任务回调 [审批任务开始、结束、转交]
+     */
+    public static final String BPMS_TASK_CHANGE = "bpms_task_change";
+
+    /**
+     * 审批实例回调 [审批实例开始、结束]
+     */
+    public static final String BPMS_INSTANCE_CHANGE = "bpms_instance_change";
+
+    /**
+     * token授权参数: 旧版本
+     */
+    public static Map initTokenParams(String access_token) {
+        return UtilMap.map("access_token", access_token);
+    }
+
+    /**
+     * token授权参数: 新版本
+     */
+    public static Map initTokenHeader(String access_token) {
+        return UtilMap.map("x-acs-dingtalk-access-token", access_token);
+    }
+}

+ 78 - 0
mjava-RJK/mjava/src/main/java/com/malk/server/dingtalk/DDConfigSign.java

@@ -0,0 +1,78 @@
+package com.malk.server.dingtalk;
+
+import lombok.SneakyThrows;
+
+import java.net.URL;
+import java.net.URLDecoder;
+import java.security.MessageDigest;
+import java.util.Formatter;
+import java.util.Random;
+
+/**
+ * 计算dd.config的签名参数
+ **/
+public class DDConfigSign {
+
+    /**
+     * 计算dd.config的签名参数
+     *
+     * @param jsticket  通过微应用appKey获取的jsticket
+     * @param nonceStr  自定义固定字符串
+     * @param timeStamp 当前时间戳
+     * @param url       调用dd.config的当前页面URL
+     */
+    @SneakyThrows
+    public static String sign(String jsticket, String nonceStr, long timeStamp, String url) {
+        String plain = "jsapi_ticket=" + jsticket + "&noncestr=" + nonceStr + "&timestamp=" + timeStamp + "&url=" + decodeUrl(url);
+        MessageDigest sha1 = MessageDigest.getInstance("SHA-256");
+        sha1.reset();
+        sha1.update(plain.getBytes("UTF-8"));
+        return byteToHex(sha1.digest());
+    }
+
+    // 字节数组转化成十六进制字符串
+    private static String byteToHex(final byte[] hash) {
+        Formatter formatter = new Formatter();
+        for (byte b : hash) {
+            formatter.format("%02x", b);
+        }
+        String result = formatter.toString();
+        formatter.close();
+        return result;
+    }
+
+    /**
+     * 因为ios端上传递的url是encode过的,android是原始的url。开发者使用的也是原始url,
+     * 所以需要把参数进行一般urlDecode
+     */
+    private static String decodeUrl(String url) throws Exception {
+        URL urler = new URL(url);
+        StringBuilder urlBuffer = new StringBuilder();
+        urlBuffer.append(urler.getProtocol());
+        urlBuffer.append(":");
+        if (urler.getAuthority() != null && urler.getAuthority().length() > 0) {
+            urlBuffer.append("//");
+            urlBuffer.append(urler.getAuthority());
+        }
+        if (urler.getPath() != null) {
+            urlBuffer.append(urler.getPath());
+        }
+        if (urler.getQuery() != null) {
+            urlBuffer.append('?');
+            urlBuffer.append(URLDecoder.decode(urler.getQuery(), "utf-8"));
+        }
+        return urlBuffer.toString();
+    }
+
+    /// test
+    public static String getRandomStr(int count) {
+        String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+        Random random = new Random();
+        StringBuffer sb = new StringBuffer();
+        for (int i = 0; i < count; i++) {
+            int number = random.nextInt(base.length());
+            sb.append(base.charAt(number));
+        }
+        return sb.toString();
+    }
+}

+ 79 - 0
mjava-RJK/mjava/src/main/java/com/malk/server/dingtalk/DDFormComponentDto.java

@@ -0,0 +1,79 @@
+package com.malk.server.dingtalk;
+
+import com.alibaba.fastjson.JSON;
+import com.malk.utils.UtilMap;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 钉钉组件要求
+ * -
+ * 1. 数字组件不能传null或空, 计算字段会自动计算, 不用传值有可. 但打印不支持会不显示
+ * 2. 撤回和提交字段必填设置, 接口调用也会受控. 但若发起页面只读, 审批节点必填则不影响
+ */
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class DDFormComponentDto {
+
+    /**
+     * 组件名称
+     */
+    private String name;
+
+    /**
+     * 组件的值
+     */
+    private String value;
+
+    /**
+     * todo: 钉钉回调, 数字组件不能传null或空, 文本组件null过滤为空; 计算字段胡自动计算, 不用传值, 日期仅支持为年月日或年月日时分两种格式, 人员组件JSON.toJSONString(Arrays.asList(userId))))
+     * 快速格式化, 若value非直接取值需预处理好
+     *
+     * @param formData   表单数据源, 若Value类型为list, 则触发ruleDetail
+     * @param formRule   表单数据格式: { 字段名: 组件名 }
+     * @param detailRule 表单明细数据格式: 单子表 { 字段名: 组件名 }; 多明细 { 明细字段:  { 字段名: 组件名 } }, 明细字段保持和数据字段一致
+     */
+    public static List<DDFormComponentDto> formatComponentValues(Map<String, ?> formData, Map<String, String> formRule, Map detailRule) {
+        // 审批流表单参数,设置各表单项值
+        Map<String, String> ruleDetail = detailRule;
+        List<DDFormComponentDto> formComponentValues = new ArrayList<>();
+        for (String key : formRule.keySet()) {
+            if (formData.get(key) instanceof List) {
+                // 明细组件: 每一个组件都是集合, table为集合嵌套集合
+                List<List<DDFormComponentDto>> formComponentDetailsValues = new ArrayList<>();
+                List<Map> details = (List<Map>) formData.get(key);
+                if (detailRule.get(key) instanceof Map) {
+                    ruleDetail = (Map<String, String>) detailRule.get(key);
+                }
+                for (Map detail : details) {
+                    List<DDFormComponentDto> detailComponentValues = new ArrayList<>();
+                    for (String sub : ruleDetail.keySet()) {
+                        detailComponentValues.add(DDFormComponentDto.builder().name(ruleDetail.get(sub)).value(String.valueOf(detail.get(sub))).build());
+                    }
+                    formComponentDetailsValues.add(detailComponentValues);
+                }
+                formComponentValues.add(DDFormComponentDto.builder().name(formRule.get(key)).value(JSON.toJSONString(formComponentDetailsValues)).build());
+            } else {
+                formComponentValues.add(DDFormComponentDto.builder().name(formRule.get(key)).value(String.valueOf(formData.get(key))).build());
+            }
+        }
+        return formComponentValues;
+    }
+
+    /**
+     * 快速格式化, 上传釘盘的文件转为OA审批对象
+     */
+    public static String formatAttachment(Map dentry) {
+        Map data = UtilMap.map("spaceId, fileName, fileSize, fileType, fileId", "spaceId, name, size, extension, id", dentry);
+        return JSON.toJSONString(Arrays.asList(data));
+    }
+}

+ 24 - 0
mjava-RJK/mjava/src/main/java/com/malk/server/dingtalk/DDInterActiveCard.java

@@ -0,0 +1,24 @@
+package com.malk.server.dingtalk;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.serializer.SerializerFeature;
+import com.malk.utils.UtilMap;
+import org.apache.commons.collections4.map.HashedMap;
+
+import java.util.Map;
+
+public class DDInterActiveCard {
+
+    /**
+     * 格式交互卡片, 表格数据类型 [兼容多表格], 格式详见 DDClient_Extension 发送卡片
+     */
+    public static Map formCardDataForTable(Map data, String... props) {
+
+        Map cardData = new HashedMap();
+        for (String prop : props) {
+            cardData.put(prop, UtilMap.map("data, meta", data.get(prop), data.get("meta")));
+        }
+        /// fastjson 避免循环引用: 当一个对象包含另一个对象时 或 当一个对象和另一个对象完全相同时
+        return UtilMap.map("cardParamMap", UtilMap.map("sys_full_json_obj", JSON.toJSONString(cardData, SerializerFeature.DisableCircularReferenceDetect)));
+    }
+}

+ 101 - 0
mjava-RJK/mjava/src/main/java/com/malk/server/dingtalk/DDR.java

@@ -0,0 +1,101 @@
+package com.malk.server.dingtalk;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.malk.server.common.McException;
+import com.malk.server.common.VenR;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 返回值配置参考McR
+ */
+@Data
+@NoArgsConstructor
+public class DDR<T> extends VenR {
+
+    // 请求状态
+    private boolean success;
+
+    private String errcode;
+
+    private String errmsg;
+
+    private T result;
+
+    /**
+     * token
+     */
+    private String accessToken;
+    private int expiresIn;
+
+    /**
+     * ticket
+     */
+    private String ticket;
+
+    /**
+     * 审批实例ID
+     */
+    private String processInstanceId;
+
+    /**
+     * 审批实例详情
+     */
+    private Map processInstance;
+
+    /**
+     * 回调失败事件
+     */
+    private List<Map> failedList;
+
+    private boolean hasMore;
+
+    private String corpid;
+
+    private List<String> userIdList;
+
+    private Long nextToken;
+
+    /**
+     * 发送工作通知
+     */
+    private String task_id; // 可调用获取工作通知消息的发送结果查询结果
+
+    /**
+     * 考勤打卡数据
+     */
+    private List<Map> recordresult;
+
+    // 成功状态标记
+    private final static String SUC_CODE = "0";
+
+    // 创建用户邀请成功提示
+    private final static String USER_CREATE = "40103";
+
+    /**
+     * 断言错误信息
+     */
+    @Override
+    public void assertSuccess() {
+//        if (!hasMore && ObjectUtil.isNull(userIdList)){
+            McException.assertException(!errcode.equals(SUC_CODE) && !errcode.equals(USER_CREATE), errcode, errmsg, "dingtalk");
+//        }
+    }
+
+    /**
+     * 通用post请求
+     */
+    public static DDR doPost(String url, Map header, Map param, Map body) {
+        return (DDR) DDR.doPost(url, header, param, body, VenR.RC_DD);
+    }
+
+    /**
+     * 通用get请求
+     */
+    public static DDR doGet(String url, Map header, Map param) {
+        return (DDR) DDR.doGet(url, header, param, VenR.RC_DD);
+    }
+}

+ 0 - 0
mjava-RJK/mjava/src/main/java/com/malk/server/dingtalk/DDR_New.java


Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä