瀏覽代碼

Initial commit

CRK 3 天之前
當前提交
2ece8513af
共有 100 個文件被更改,包括 19551 次插入0 次删除
  1. 42 0
      .gitignore
  2. 0 0
      README.md
  3. 二進制
      mjava-rjk/log/2024-10-22/info-0.log.gz
  4. 二進制
      mjava-rjk/log/2024-10-22/point-0.log.gz
  5. 二進制
      mjava-rjk/log/2024-10-22/warn-0.log.gz
  6. 二進制
      mjava-rjk/log/2024-10-28/info-0.log.gz
  7. 二進制
      mjava-rjk/log/2024-10-28/point-0.log.gz
  8. 二進制
      mjava-rjk/log/2024-11-12/info-0.log.gz
  9. 二進制
      mjava-rjk/log/2024-11-12/point-0.log.gz
  10. 二進制
      mjava-rjk/log/2024-11-12/warn-0.log.gz
  11. 二進制
      mjava-rjk/log/2025-02-20/info-0.log.gz
  12. 二進制
      mjava-rjk/log/2025-02-20/point-0.log.gz
  13. 二進制
      mjava-rjk/log/2025-02-20/warn-0.log.gz
  14. 二進制
      mjava-rjk/log/2025-02-26/info-0.log.gz
  15. 二進制
      mjava-rjk/log/2025-02-26/point-0.log.gz
  16. 100 0
      mjava-rjk/pom.xml
  17. 29 0
      mjava-rjk/src/main/java/com/malk/rjk/Boot.java
  18. 5 0
      mjava-rjk/src/main/java/com/malk/rjk/common/bizObjectCommon.java
  19. 35 0
      mjava-rjk/src/main/java/com/malk/rjk/config/YouZanConfig.java
  20. 59 0
      mjava-rjk/src/main/java/com/malk/rjk/controller/HXController.java
  21. 120 0
      mjava-rjk/src/main/java/com/malk/rjk/controller/RJKController.java
  22. 88 0
      mjava-rjk/src/main/java/com/malk/rjk/controller/TBController.java
  23. 27 0
      mjava-rjk/src/main/java/com/malk/rjk/controller/YouZanController.java
  24. 13 0
      mjava-rjk/src/main/java/com/malk/rjk/dto/CallbackBody.java
  25. 151 0
      mjava-rjk/src/main/java/com/malk/rjk/dto/WeilingCallbackDTO.java
  26. 36 0
      mjava-rjk/src/main/java/com/malk/rjk/dto/YouZanTokenResponse.java
  27. 218 0
      mjava-rjk/src/main/java/com/malk/rjk/mapping/DuildBusinessCustom.java
  28. 377 0
      mjava-rjk/src/main/java/com/malk/rjk/mapping/DuildContactCustom.java
  29. 449 0
      mjava-rjk/src/main/java/com/malk/rjk/mapping/LabelToValue.java
  30. 18 0
      mjava-rjk/src/main/java/com/malk/rjk/server/HXClient.java
  31. 80 0
      mjava-rjk/src/main/java/com/malk/rjk/server/KingDee.java
  32. 35 0
      mjava-rjk/src/main/java/com/malk/rjk/server/RjkServer.java
  33. 35 0
      mjava-rjk/src/main/java/com/malk/rjk/server/TBService.java
  34. 9 0
      mjava-rjk/src/main/java/com/malk/rjk/server/YouZanServer.java
  35. 501 0
      mjava-rjk/src/main/java/com/malk/rjk/server/impl/HXImplClient.java
  36. 1242 0
      mjava-rjk/src/main/java/com/malk/rjk/server/impl/KingDeeImpl.java
  37. 1928 0
      mjava-rjk/src/main/java/com/malk/rjk/server/impl/RjkServerImpl.java
  38. 474 0
      mjava-rjk/src/main/java/com/malk/rjk/server/impl/TbServiceImpl.java
  39. 251 0
      mjava-rjk/src/main/java/com/malk/rjk/server/impl/YouZanServerImpl.java
  40. 119 0
      mjava-rjk/src/main/java/com/malk/rjk/timers/timerOne.java
  41. 68 0
      mjava-rjk/src/main/java/com/malk/rjk/util/CryptUtil.java
  42. 141 0
      mjava-rjk/src/main/java/com/malk/rjk/util/DownHelp.java
  43. 37 0
      mjava-rjk/src/main/java/com/malk/rjk/util/HttpRequestUtil.java
  44. 22 0
      mjava-rjk/src/main/java/com/malk/rjk/util/SHA1.java
  45. 164 0
      mjava-rjk/src/main/java/com/malk/rjk/util/YouZanTokenUtil.java
  46. 24 0
      mjava-rjk/src/main/java/com/malk/rjk/util/convertTimestampToDateTimeUtil.java
  47. 42 0
      mjava-rjk/src/main/java/com/malk/rjk/util/convertToTimestampUtil.java
  48. 125 0
      mjava-rjk/src/main/java/com/malk/rjk/util/javaHelp.java
  49. 74 0
      mjava-rjk/src/main/resources/application-dev.yml
  50. 61 0
      mjava-rjk/src/main/resources/application-prod.yml
  51. 414 0
      mjava-rjk/src/test/java/test.java
  52. 57 0
      mjava-wlduijie/pom.xml
  53. 37 0
      mjava-wlduijie/src/main/java/com/malk/Boot.java
  54. 155 0
      mjava-wlduijie/src/main/java/com/malk/config/WlduijieConfig.java
  55. 158 0
      mjava-wlduijie/src/main/java/com/malk/controller/KingdeeWebhookController.java
  56. 65 0
      mjava-wlduijie/src/main/java/com/malk/model/SyncStatus.java
  57. 40 0
      mjava-wlduijie/src/main/java/com/malk/model/h3yun/H3yunQueryRequest.java
  58. 42 0
      mjava-wlduijie/src/main/java/com/malk/model/h3yun/H3yunRequest.java
  59. 46 0
      mjava-wlduijie/src/main/java/com/malk/model/h3yun/H3yunResponse.java
  60. 48 0
      mjava-wlduijie/src/main/java/com/malk/model/kingdee/KingdeeAuthRequest.java
  61. 47 0
      mjava-wlduijie/src/main/java/com/malk/model/kingdee/KingdeeAuthResponse.java
  62. 4 0
      mjava-wlduijie/src/main/java/com/malk/model/kingdee/KingdeeModels.java
  63. 45 0
      mjava-wlduijie/src/main/java/com/malk/model/kingdee/KingdeeQueryRequest.java
  64. 46 0
      mjava-wlduijie/src/main/java/com/malk/model/kingdee/KingdeeSaveRequest.java
  65. 55 0
      mjava-wlduijie/src/main/java/com/malk/model/kingdee/KingdeeSaveResponse.java
  66. 56 0
      mjava-wlduijie/src/main/java/com/malk/model/kingdee/KingdeeWebhookEvent.java
  67. 31 0
      mjava-wlduijie/src/main/java/com/malk/model/wangdian/StockPdItem.java
  68. 61 0
      mjava-wlduijie/src/main/java/com/malk/model/wangdian/WangdianRequest.java
  69. 42 0
      mjava-wlduijie/src/main/java/com/malk/model/wangdian/WangdianResponse.java
  70. 45 0
      mjava-wlduijie/src/main/java/com/malk/model/wangdian/WangdianStockPdRequest.java
  71. 65 0
      mjava-wlduijie/src/main/java/com/malk/schedule/WlduijieTimer.java
  72. 307 0
      mjava-wlduijie/src/main/java/com/malk/service/h3yun/H3yunService.java
  73. 387 0
      mjava-wlduijie/src/main/java/com/malk/service/kingdee/KingdeeService.java
  74. 420 0
      mjava-wlduijie/src/main/java/com/malk/service/sync/SyncService.java
  75. 201 0
      mjava-wlduijie/src/main/java/com/malk/service/wangdian/WangdianService.java
  76. 74 0
      mjava-wlduijie/src/main/resources/application-dev.yml
  77. 61 0
      mjava-wlduijie/src/main/resources/application-prod.yml
  78. 73 0
      mjava-wlduijie/src/main/resources/application.yml
  79. 85 0
      mjava-wlh3tok3/pom.xml
  80. 45 0
      mjava-wlh3tok3/src/main/java/com/malk/Boot.java
  81. 180 0
      mjava-wlh3tok3/src/main/java/com/malk/config/H3tok3Config.java
  82. 180 0
      mjava-wlh3tok3/src/main/java/com/malk/controller/KingdeeWebhookController.java
  83. 383 0
      mjava-wlh3tok3/src/main/java/com/malk/controller/SyncController.java
  84. 198 0
      mjava-wlh3tok3/src/main/java/com/malk/controller/WangdianSyncController.java
  85. 63 0
      mjava-wlh3tok3/src/main/java/com/malk/model/H3yunResponse.java
  86. 44 0
      mjava-wlh3tok3/src/main/java/com/malk/model/KingdeeAuthRequest.java
  87. 44 0
      mjava-wlh3tok3/src/main/java/com/malk/model/KingdeeAuthResponse.java
  88. 17 0
      mjava-wlh3tok3/src/main/java/com/malk/model/KingdeeRequest.java
  89. 45 0
      mjava-wlh3tok3/src/main/java/com/malk/model/KingdeeSaveRequest.java
  90. 200 0
      mjava-wlh3tok3/src/main/java/com/malk/model/KingdeeSaveResponse.java
  91. 101 0
      mjava-wlh3tok3/src/main/java/com/malk/model/KingdeeWebhookEvent.java
  92. 84 0
      mjava-wlh3tok3/src/main/java/com/malk/model/SyncResult.java
  93. 122 0
      mjava-wlh3tok3/src/main/java/com/malk/model/WangdianResponse.java
  94. 247 0
      mjava-wlh3tok3/src/main/java/com/malk/schedule/InventorySyncTask.java
  95. 954 0
      mjava-wlh3tok3/src/main/java/com/malk/service/h3yun/H3yunService.java
  96. 3051 0
      mjava-wlh3tok3/src/main/java/com/malk/service/kingdee/KingdeeService.java
  97. 2988 0
      mjava-wlh3tok3/src/main/java/com/malk/service/sync/SyncService.java
  98. 192 0
      mjava-wlh3tok3/src/main/java/com/malk/service/wangdian/WangdianService.java
  99. 47 0
      mjava-wlh3tok3/src/main/resources/application-dev.yml
  100. 0 0
      mjava-wlh3tok3/src/main/resources/application-prod.yml

+ 42 - 0
.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
README.md


二進制
mjava-rjk/log/2024-10-22/info-0.log.gz


二進制
mjava-rjk/log/2024-10-22/point-0.log.gz


二進制
mjava-rjk/log/2024-10-22/warn-0.log.gz


二進制
mjava-rjk/log/2024-10-28/info-0.log.gz


二進制
mjava-rjk/log/2024-10-28/point-0.log.gz


二進制
mjava-rjk/log/2024-11-12/info-0.log.gz


二進制
mjava-rjk/log/2024-11-12/point-0.log.gz


二進制
mjava-rjk/log/2024-11-12/warn-0.log.gz


二進制
mjava-rjk/log/2025-02-20/info-0.log.gz


二進制
mjava-rjk/log/2025-02-20/point-0.log.gz


二進制
mjava-rjk/log/2025-02-20/warn-0.log.gz


二進制
mjava-rjk/log/2025-02-26/info-0.log.gz


二進制
mjava-rjk/log/2025-02-26/point-0.log.gz


+ 100 - 0
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/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/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/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/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/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/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/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/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/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/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/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("D150508productDetails");// 购买产品明细-子表单
+        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/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("D150508Feaf0c0b14ecc4ab9888f741c106212c1");//会员卡明细-子表单
+        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("D150508F7db8076628f1470aa37e4b9319e3ce09");//客户标签-子表单
+        // 使用列表来收集标签名称
+        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/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/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/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;
+
+}

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

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

+ 35 - 0
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/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/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.");
+//    }
+}

File diff suppressed because it is too large
+ 1242 - 0
mjava-rjk/src/main/java/com/malk/rjk/server/impl/KingDeeImpl.java


File diff suppressed because it is too large
+ 1928 - 0
mjava-rjk/src/main/java/com/malk/rjk/server/impl/RjkServerImpl.java


+ 474 - 0
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/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;
+    }
+
+}

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

@@ -0,0 +1,119 @@
+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.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.scheduling.annotation.Scheduled;
+
+@Slf4j
+@Configuration
+@EnableScheduling
+@ConditionalOnProperty(name = {"spel.scheduling"}, havingValue = "true")
+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);
+//        }
+//    }
+//
+
+
+    /**
+     *抓取氚云20 分钟内 创建/编辑的订单 写入卫瓴
+     */
+    @Scheduled(fixedRateString = "${timer.material.interval:1200000}")
+    public void syncMa() {
+        // 代码保持不变
+        log.info("定时器开始执行订单写入");
+        long startTime = System.currentTimeMillis();
+
+        try {
+
+
+            rjkServer.H3BusinessToWeiling_LIST();
+            log.info("定时器订单写入任务完成,耗时: {}ms", System.currentTimeMillis() - startTime);
+        } catch (McException e) {
+            log.error("定时器订单写入任务失败: {}", e.getMessage(), e);
+        } catch (Exception e) {
+            log.error("定时器订单写入任务发生未预期错误", e);
+        }
+    }
+
+    /**
+     *抓取氚云20 分钟内 创建/编辑的客户 写入卫瓴
+     */
+    @Scheduled(fixedRateString = "${timer.material.interval:1080000}")
+    public void syncUSER() {
+        // 代码保持不变
+        log.info("定时器开始执行客户写入");
+        long startTime = System.currentTimeMillis();
+
+        try {
+
+            rjkServer.H3UserToweiling_LIST();
+
+            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/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/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/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/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/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/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/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/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/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/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/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", "1952cc5e-34c9-4945-bda2-acabfb98301f");
+        // 调用H3UserToweiling方法
+        rjkServer.H3BusinessToWeiling(testData);
+    }
+}
+

+ 57 - 0
mjava-wlduijie/pom.xml

@@ -0,0 +1,57 @@
+<?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>
+    <version>0.0.1</version>
+
+    <artifactId>mjava-wlduijie</artifactId>
+    <description>mjava-wlduijie framework</description>
+
+    <properties>
+        <maven.compiler.source>1.8</maven.compiler.source>
+        <maven.compiler.target>1.8</maven.compiler.target>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.8.1</version>
+                <configuration>
+                    <source>1.8</source>
+                    <target>1.8</target>
+                    <proc>none</proc>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <version>2.1.1.RELEASE</version>
+                <configuration>
+                    <includeSystemScope>true</includeSystemScope>
+                    <fork>false</fork>
+                    <jvmArguments>-Dfile.encoding=UTF-8</jvmArguments>
+                </configuration>
+            </plugin>
+        </plugins>
+        <finalName>${project.artifactId}</finalName>
+    </build>
+</project>

+ 37 - 0
mjava-wlduijie/src/main/java/com/malk/Boot.java

@@ -0,0 +1,37 @@
+package com.malk;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
+import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
+
+/**
+ * 五路对接启动类
+ * 
+ * 功能说明:企业数据集成平台中间件的启动入口
+ * 
+ * 项目说明:
+ * - 模块名称:mjava-wlduijie
+ * - 功能定位:实现氚云、金蝶云·星空、旺店通三大系统的数据互联互通
+ * 
+ * 系统集成架构(需求文档2):
+ * 1. 氚云 -> 金蝶:API主动推送模式
+ * 2. 金蝶 -> 氚云:事件订阅模式(Webhook)
+ * 3. 氚云 -> 旺店通:定时任务/触发器
+ * 
+ * 配置说明:
+ * - 配置文件:application.yml
+ * - 配置前缀:wlduijie
+ * - 需配置:金蝶API地址、氚云认证信息、旺店通API信息
+ * 
+ * 启动说明:
+ * - 排除数据库自动配置(当前模块不依赖数据库)
+ * - 默认端口:9101
+ */
+@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class})
+public class Boot {
+
+    public static void main(String... args) {
+        SpringApplication.run(Boot.class, args);
+    }
+}

+ 155 - 0
mjava-wlduijie/src/main/java/com/malk/config/WlduijieConfig.java

@@ -0,0 +1,155 @@
+package com.malk.config;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * 五路对接配置类
+ * 
+ * 功能说明:配置金蝶云·星空、氚云、旺店通三大系统的连接信息
+ * 
+ * 配置项说明(在application.yml中使用wlduijie前缀):
+ * - wlduijie.kingdee: 金蝶系统配置
+ * - wlduijie.h3yun: 氚云系统配置  
+ * - wlduijie.wangdian: 旺店通系统配置
+ */
+@Configuration
+@ConfigurationProperties(prefix = "wlduijie")
+public class WlduijieConfig {
+
+    /** 金蝶云·星空配置 */
+    private KingdeeConfig kingdee = new KingdeeConfig();
+    
+    /** 氚云配置 */
+    private H3yunConfig h3yun = new H3yunConfig();
+    
+    /** 旺店通配置 */
+    private WangdianConfig wangdian = new WangdianConfig();
+
+    // ==================== getter/setter ====================
+    
+    public KingdeeConfig getKingdee() {
+        return kingdee;
+    }
+
+    public void setKingdee(KingdeeConfig kingdee) {
+        this.kingdee = kingdee;
+    }
+
+    public H3yunConfig getH3yun() {
+        return h3yun;
+    }
+
+    public void setH3yun(H3yunConfig h3yun) {
+        this.h3yun = h3yun;
+    }
+
+    public WangdianConfig getWangdian() {
+        return wangdian;
+    }
+
+    public void setWangdian(WangdianConfig wangdian) {
+        this.wangdian = wangdian;
+    }
+
+    /**
+     * 金蝶云·星空配置类
+     * 
+     * 对接方式:API主动推送(氚云->金蝶)和事件订阅(金蝶->氚云)
+     * 认证方式:AuthService.ValidateUser 获取SessionId或AccessToken
+     * API地址:https://wuliustyle.test.kdgalaxy.com/kapi/v2/{接口名}
+     */
+    public static class KingdeeConfig {
+        /** 金蝶API服务器地址,如:https://wuliustyle.test.kdgalaxy.com/kapi/v2 */
+        private String host;
+        
+        /** 账套ID,用于登录认证 */
+        private String acctId;
+        
+        /** 用户名,用于登录认证 */
+        private String userName;
+        
+        /** 密码,用于登录认证 */
+        private String password;
+        
+        /** 应用ID,用于OAuth认证(可选) */
+        private String appId;
+        
+        /** 应用密钥,用于OAuth认证(可选) */
+        private String appSecret;
+
+        // ==================== getter/setter ====================
+        
+        public String getHost() { return host; }
+        public void setHost(String host) { this.host = host; }
+        public String getAcctId() { return acctId; }
+        public void setAcctId(String acctId) { this.acctId = acctId; }
+        public String getUserName() { return userName; }
+        public void setUserName(String userName) { this.userName = userName; }
+        public String getPassword() { return password; }
+        public void setPassword(String password) { this.password = password; }
+        public String getAppId() { return appId; }
+        public void setAppId(String appId) { this.appId = appId; }
+        public String getAppSecret() { return appSecret; }
+        public void setAppSecret(String appSecret) { this.appSecret = appSecret; }
+    }
+
+    /**
+     * 氚云配置类
+     * 
+     * 对接方式:API调用(CreateBizObject/UpdateFormData等)
+     * 认证方式:Header携带EngineCode和EngineSecret
+     * API地址:https://www.h3yun.com/OpenApi/Invoke
+     */
+    public static class H3yunConfig {
+        /** 氚云API基础地址 */
+        private String baseUrl = "https://www.h3yun.com/OpenApi/Invoke";
+        
+        /** 引擎编码,用于API认证 */
+        private String engineCode;
+        
+        /** 引擎密钥,用于API认证 */
+        private String engineSecret;
+
+        // ==================== getter/setter ====================
+        
+        public String getBaseUrl() { return baseUrl; }
+        public void setBaseUrl(String baseUrl) { this.baseUrl = baseUrl; }
+        public String getEngineCode() { return engineCode; }
+        public void setEngineCode(String engineCode) { this.engineCode = engineCode; }
+        public String getEngineSecret() { return engineSecret; }
+        public void setEngineSecret(String engineSecret) { this.engineSecret = engineSecret; }
+    }
+
+    /**
+     * 旺店通配置类
+     * 
+     * 对接方式:API调用(盘盈盘亏单等)
+     * 认证方式:请求体包含appkey、sid、timestamp、sign(MD5签名)
+     * API地址:https://api.wangdian.cn/openapi2/
+     */
+    public static class WangdianConfig {
+        /** 旺店通API基础地址 */
+        private String baseUrl = "https://api.wangdian.cn/openapi2/";
+        
+        /** 应用Key,用于API认证 */
+        private String appkey;
+        
+        /** 店铺ID,用于API认证 */
+        private String sid;
+        
+        /** 应用密钥,用于生成签名 */
+        private String secret;
+
+        // ==================== getter/setter ====================
+        
+        public String getBaseUrl() { return baseUrl; }
+        public void setBaseUrl(String baseUrl) { this.baseUrl = baseUrl; }
+        public String getAppkey() { return appkey; }
+        public void setAppkey(String appkey) { this.appkey = appkey; }
+        public String getSid() { return sid; }
+        public void setSid(String sid) { this.sid = sid; }
+        public String getSecret() { return secret; }
+        public void setSecret(String secret) { this.secret = secret; }
+    }
+}

+ 158 - 0
mjava-wlduijie/src/main/java/com/malk/controller/KingdeeWebhookController.java

@@ -0,0 +1,158 @@
+package com.malk.controller;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.malk.model.kingdee.KingdeeWebhookEvent;
+import com.malk.service.kingdee.KingdeeService;
+import com.malk.service.sync.SyncService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 金蝶Webhook控制器
+ * 
+ * 功能说明:接收金蝶系统的事件推送,实现金蝶->氚云的数据同步
+ * 
+ * 接口地址:POST /api/kingdee/webhook/receive
+ * 
+ * 工作流程:
+ * 1. 金蝶BOS平台配置事件订阅
+ * 2. 审核操作触发Webhook推送至中间件
+ * 3. 中间件解析事件,判断是否为审核操作(OperateType=Audit)
+ * 4. 根据FormId回调金蝶接口查询完整数据
+ * 5. 数据清洗与映射
+ * 6. 调用氚云API写入数据
+ * 
+ * 事件订阅配置(需求文档3.2.2):
+ * | 业务对象    | FormId          | 订阅事件 | 触发条件           | 处理逻辑                    |
+ * | 采购入库单  | STK_InStock      | 审核     | 单据状态变为'C'   | 获取详情->写入氚云采购入库   |
+ * | 采购退料单  | STK_MisDelivery  | 审核     | 单据状态变为'C'   | 获取详情->写入氚云退料      |
+ * | 销售出库单  | SAL_OUTSTOCK     | 审核     | 单据状态变为'C'   | 获取详情->写入氚云销售出库   |
+ * | 销售退货单  | SAL_RETURNSTOCK  | 审核     | 单据状态变为'C'   | 获取详情->写入氚云销售退货   |
+ * 
+ * 注意事项:
+ * - 需在金蝶BOS平台配置事件推送,并配置过滤条件(FDocumentStatus = 'C')
+ * - 只处理审核(Audit)操作,其他操作跳过
+ * - 需实现幂等性:利用单据编号查询氚云是否已存在
+ */
+@RestController
+@RequestMapping("/api/kingdee")
+public class KingdeeWebhookController {
+
+    private static final Logger log = LoggerFactory.getLogger(KingdeeWebhookController.class);
+
+    @Autowired
+    private KingdeeService kingdeeService;
+
+    @Autowired
+    private SyncService syncService;
+
+    /**
+     * 接收金蝶Webhook事件推送
+     * 
+     * 功能说明:金蝶事件订阅的回调接口,接收审核事件并处理数据同步
+     * 
+     * 请求示例:
+     * {
+     *     "InterId": "xxx",
+     *     "BillType": "STK_InStock",
+     *     "FormId": "STK_InStock",
+     *     "OperateType": "Audit",
+     *     "UserName": "admin",
+     *     "ActionTime": 1234567890,
+     *     "Number": "RK123456"
+     * }
+     * 
+     * @param payload 事件载荷(JSON字符串)
+     * @return 处理结果
+     */
+    @PostMapping("/webhook/receive")
+    public Map<String, Object> receiveWebhook(@RequestBody String payload) {
+        try {
+            log.info("收到金蝶 webhook 推送: {}", payload);
+            
+            // 1. 解析事件数据
+            JSONObject json = JSON.parseObject(payload);
+            String formId = json.getString("FormId");
+            String operateType = json.getString("OperateType");
+            String interId = json.getString("InterId");
+            
+            // 2. 只处理审核操作
+            if (!"Audit".equals(operateType)) {
+                log.info("非审核操作,跳过处理. OperateType: {}", operateType);
+                return buildResponse(true, "非审核操作,已跳过");
+            }
+            
+            // 3. 获取单据编号
+            String billNo = json.getString("Number");
+            if (billNo == null || billNo.isEmpty()) {
+                billNo = json.getString("FBillNo");
+            }
+            
+            // 4. 根据单据类型调用对应的同步方法
+            handleKingdeeAuditEvent(formId, billNo);
+            
+            return buildResponse(true, "处理成功");
+        } catch (Exception e) {
+            log.error("处理金蝶 webhook 异常", e);
+            return buildResponse(false, "处理异常: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 处理金蝶审核事件
+     * 
+     * 功能说明:根据FormId分发到对应的同步处理方法
+     * 
+     * @param formId 金蝶业务对象FormId
+     * @param billNo 单据编号
+     */
+    private void handleKingdeeAuditEvent(String formId, String billNo) {
+        log.info("处理金蝶审核事件, FormId: {}, BillNo: {}", formId, billNo);
+        
+        switch (formId) {
+            // 采购入库单 -> 氚云采购入库
+            case "STK_InStock":
+                syncService.syncPurchaseInstockFromKingdee(billNo);
+                break;
+                
+            // 采购退料单 -> 氚云采购退料
+            case "STK_MisDelivery":
+            case "PUR_MRB":
+                syncService.syncPurchaseReturnFromKingdee(billNo);
+                break;
+                
+            // 销售出库单 -> 氚云销售出库
+            case "SAL_OUTSTOCK":
+                syncService.syncSaleOutstockFromKingdee(billNo);
+                break;
+                
+            // 销售退货单 -> 氚云销售退货
+            case "SAL_RETURNSTOCK":
+                syncService.syncSaleReturnFromKingdee(billNo);
+                break;
+                
+            default:
+                log.warn("未配置的金蝶单据类型: {}", formId);
+        }
+    }
+
+    /**
+     * 构建响应结果
+     * 
+     * @param success 是否成功
+     * @param message 消息
+     * @return 响应Map
+     */
+    private Map<String, Object> buildResponse(boolean success, String message) {
+        Map<String, Object> result = new HashMap<>();
+        result.put("ResultStatus", success ? 1 : 0);
+        result.put("Message", message);
+        return result;
+    }
+}

+ 65 - 0
mjava-wlduijie/src/main/java/com/malk/model/SyncStatus.java

@@ -0,0 +1,65 @@
+package com.malk.model;
+
+/**
+ * 同步状态实体类
+ * 
+ * 功能说明:记录数据同步的状态和信息,用于追溯同步结果
+ * 
+ * 字段说明:
+ * - sourceSystem:源系统(tianyun/kingdee/wangdian)
+ * - targetSystem:目标系统(kingdee/h3yun/wangdian)
+ * - sourceBillNo:源系统单据编号
+ * - targetBillNo:目标系统单据编号
+ * - status:同步状态
+ * - errorMsg:错误信息
+ * - syncTime:同步时间
+ * - bizType:业务类型
+ * 
+ * 状态常量:
+ * - STATUS_PENDING:未同步
+ * - STATUS_SYNCING:同步中
+ * - STATUS_SUCCESS:同步成功
+ * - STATUS_FAILED:同步失败
+ */
+public class SyncStatus {
+    /** 主键ID */
+    public String id;
+    
+    /** 源系统:tianyun(氚云)、kingdee(金蝶)、wangdian(旺店通) */
+    public String sourceSystem;
+    
+    /** 目标系统:kingdee(金蝶)、h3yun(氚云)、wangdian(旺店通) */
+    public String targetSystem;
+    
+    /** 源系统单据编号 */
+    public String sourceBillNo;
+    
+    /** 目标系统单据编号(同步成功后填充) */
+    public String targetBillNo;
+    
+    /** 同步状态:未同步/同步中/同步成功/同步失败 */
+    public String status;
+    
+    /** 错误信息(同步失败时填充) */
+    public String errorMsg;
+    
+    /** 同步时间 */
+    public java.time.LocalDateTime syncTime;
+    
+    /** 业务类型:supplier/material/customer/stock/purchase/sale等 */
+    public String bizType;
+
+    // ==================== 状态常量 ====================
+    
+    /** 未同步 */
+    public static final String STATUS_PENDING = "未同步";
+    
+    /** 同步中 */
+    public static final String STATUS_SYNCING = "同步中";
+    
+    /** 同步成功 */
+    public static final String STATUS_SUCCESS = "同步成功";
+    
+    /** 同步失败 */
+    public static final String STATUS_FAILED = "同步失败";
+}

+ 40 - 0
mjava-wlduijie/src/main/java/com/malk/model/h3yun/H3yunQueryRequest.java

@@ -0,0 +1,40 @@
+package com.malk.model.h3yun;
+
+/**
+ * 氚云查询请求模型
+ * 
+ * 功能说明:用于查询氚云业务对象列表
+ * 
+ * 使用场景:
+ * - 检查单据是否已存在(幂等性设计)
+ * - 查询业务数据
+ */
+public class H3yunQueryRequest {
+    /** 表单编码,标识业务对象类型 */
+    public String SchemaCode;
+    
+    /** 查询过滤条件,格式:字段名 = '值',如:F0000001 = 'PO123' */
+    public String Filter;
+    
+    /** 排序条件,如:CreateTime DESC */
+    public String Sorter;
+    
+    /** 每页记录数 */
+    public Integer PageSize;
+    
+    /** 页码,从0开始 */
+    public Integer PageIndex;
+
+    // ==================== getter/setter ====================
+    
+    public String getSchemaCode() { return SchemaCode; }
+    public void setSchemaCode(String SchemaCode) { this.SchemaCode = SchemaCode; }
+    public String getFilter() { return Filter; }
+    public void setFilter(String Filter) { this.Filter = Filter; }
+    public String getSorter() { return Sorter; }
+    public void setSorter(String Sorter) { this.Sorter = Sorter; }
+    public Integer getPageSize() { return PageSize; }
+    public void setPageSize(Integer PageSize) { this.PageSize = PageSize; }
+    public Integer getPageIndex() { return PageIndex; }
+    public void setPageIndex(Integer PageIndex) { this.PageIndex = PageIndex; }
+}

+ 42 - 0
mjava-wlduijie/src/main/java/com/malk/model/h3yun/H3yunRequest.java

@@ -0,0 +1,42 @@
+package com.malk.model.h3yun;
+
+import java.util.Map;
+
+/**
+ * 氚云请求模型
+ * 
+ * 功能说明:用于调用氚云 OpenApi/Invoke 接口的通用请求模型
+ * 
+ * API地址:https://www.h3yun.com/OpenApi/Invoke
+ * 认证方式:Header携带EngineCode和EngineSecret
+ * 
+ * 常用操作:
+ * - CreateBizObject:创建业务对象
+ * - UpdateBizObject:更新业务对象
+ * - UpdateFormData:更新表单数据
+ * - QueryBizObjects:查询业务对象列表
+ */
+public class H3yunRequest {
+    /** 表单编码(SchemaCode),标识业务对象类型 */
+    public String SchemaCode;
+    
+    /** 操作类型:CreateBizObject/UpdateBizObject/UpdateFormData/QueryBizObjects */
+    public String Action;
+    
+    /** 业务数据(用于CreateBizObject/UpdateBizObject) */
+    public Map<String, Object> Data;
+    
+    /** 表单数据(用于CreateBizObject/UpdateBizObject) */
+    public Map<String, Object> FormData;
+
+    // ==================== getter/setter ====================
+    
+    public String getSchemaCode() { return SchemaCode; }
+    public void setSchemaCode(String SchemaCode) { this.SchemaCode = SchemaCode; }
+    public String getAction() { return Action; }
+    public void setAction(String Action) { this.Action = Action; }
+    public Map<String, Object> getData() { return Data; }
+    public void setData(Map<String, Object> Data) { this.Data = Data; }
+    public Map<String, Object> getFormData() { return FormData; }
+    public void setFormData(Map<String, Object> FormData) { this.FormData = FormData; }
+}

+ 46 - 0
mjava-wlduijie/src/main/java/com/malk/model/h3yun/H3yunResponse.java

@@ -0,0 +1,46 @@
+package com.malk.model.h3yun;
+
+import java.util.Map;
+
+/**
+ * 氚云响应模型
+ * 
+ * 功能说明:氚云 OpenApi/Invoke 接口的通用响应模型
+ * 
+ * 响应示例:
+ * {
+ *     "Success": true,          // 是否成功
+ *     "ErrorCode": 0,          // 错误码,0=成功
+ *     "Message": "操作成功",    // 消息
+ *     "BizObjectId": "xxx"    // 业务对象ID(创建成功时返回)
+ * }
+ */
+public class H3yunResponse {
+    /** 是否成功 */
+    public Boolean Success;
+    
+    /** 错误码,0=成功,其他=失败 */
+    public Integer ErrorCode;
+    
+    /** 错误消息 */
+    public String Message;
+    
+    /** 业务对象ID(创建成功时返回) */
+    public String BizObjectId;
+    
+    /** 业务数据 */
+    public Map<String, Object> Data;
+
+    // ==================== getter/setter ====================
+    
+    public Boolean getSuccess() { return Success; }
+    public void setSuccess(Boolean Success) { this.Success = Success; }
+    public Integer getErrorCode() { return ErrorCode; }
+    public void setErrorCode(Integer ErrorCode) { this.ErrorCode = ErrorCode; }
+    public String getMessage() { return Message; }
+    public void setMessage(String Message) { this.Message = Message; }
+    public String getBizObjectId() { return BizObjectId; }
+    public void setBizObjectId(String BizObjectId) { this.BizObjectId = BizObjectId; }
+    public Map<String, Object> getData() { return Data; }
+    public void setData(Map<String, Object> Data) { this.Data = Data; }
+}

+ 48 - 0
mjava-wlduijie/src/main/java/com/malk/model/kingdee/KingdeeAuthRequest.java

@@ -0,0 +1,48 @@
+package com.malk.model.kingdee;
+
+/**
+ * 金蝶认证请求模型
+ * 
+ * 功能说明:用于调用金蝶 AuthService.ValidateUser 接口进行用户认证
+ * 
+ * 使用场景:
+ * - 调用金蝶API前必须先获取SessionId
+ * - 每次请求需要携带有效的SessionId
+ * 
+ * 认证方式:通过账套ID、用户名、密码获取SessionId
+ */
+public class KingdeeAuthRequest {
+    /** 账套ID,标识要登录的账套 */
+    public String acctId;
+    
+    /** 用户名,金蝶系统用户名 */
+    public String userName;
+    
+    /** 密码,对应用户名的密码 */
+    public String password;
+    
+    /** 验证码(可选),用于需要验证码的场景 */
+    public String authenticationCode;
+    
+    /** 验证密钥(可选),用于二次验证 */
+    public String validateKey;
+    
+    /** 设备类型(可选),如:Mobile、PC */
+
+    public String deviceType;
+
+    // ==================== getter/setter ====================
+    
+    public String getAcctId() { return acctId; }
+    public void setAcctId(String acctId) { this.acctId = acctId; }
+    public String getUserName() { return userName; }
+    public void setUserName(String userName) { this.userName = userName; }
+    public String getPassword() { return password; }
+    public void setPassword(String password) { this.password = password; }
+    public String getAuthenticationCode() { return authenticationCode; }
+    public void setAuthenticationCode(String authenticationCode) { this.authenticationCode = authenticationCode; }
+    public String getValidateKey() { return validateKey; }
+    public void setValidateKey(String validateKey) { this.validateKey = validateKey; }
+    public String getDeviceType() { return deviceType; }
+    public void setDeviceType(String deviceType) { this.deviceType = deviceType; }
+}

+ 47 - 0
mjava-wlduijie/src/main/java/com/malk/model/kingdee/KingdeeAuthResponse.java

@@ -0,0 +1,47 @@
+package com.malk.model.kingdee;
+
+/**
+ * 金蝶认证响应模型
+ * 
+ * 功能说明:AuthService.ValidateUser 接口的响应结果
+ * 
+ * 响应示例:
+ * {
+ *     "ResultStatus": 1,      // 结果状态:1或100=成功
+ *     "Message": "登录成功",   // 结果消息
+ *     "SessionId": "xxx",      // 会话ID,用于后续API调用
+ *     "UserName": "admin",     // 用户名
+ *     "Token": "xxx"           // 令牌(可选)
+ * }
+ * 
+ * @see KingdeeAuthRequest
+ */
+public class KingdeeAuthResponse {
+    /** 结果状态:1或100=成功,其他=失败 */
+    public Integer ResultStatus;
+    
+    /** 结果消息 */
+    public String Message;
+    
+    /** 会话ID,用于后续API调用(成功时返回) */
+    public String SessionId;
+    
+    /** 用户名 */
+    public String UserName;
+    
+    /** 令牌(可选,用于OAuth认证) */
+    public String Token;
+
+    // ==================== getter/setter ====================
+    
+    public Integer getResultStatus() { return ResultStatus; }
+    public void setResultStatus(Integer ResultStatus) { this.ResultStatus = ResultStatus; }
+    public String getMessage() { return Message; }
+    public void setMessage(String Message) { this.Message = Message; }
+    public String getSessionId() { return SessionId; }
+    public void setSessionId(String SessionId) { this.SessionId = SessionId; }
+    public String getUserName() { return UserName; }
+    public void setUserName(String UserName) { this.UserName = UserName; }
+    public String getToken() { return Token; }
+    public void setToken(String Token) { this.Token = Token; }
+}

+ 4 - 0
mjava-wlduijie/src/main/java/com/malk/model/kingdee/KingdeeModels.java

@@ -0,0 +1,4 @@
+package com.malk.model.kingdee;
+
+public class KingdeeModels {
+}

+ 45 - 0
mjava-wlduijie/src/main/java/com/malk/model/kingdee/KingdeeQueryRequest.java

@@ -0,0 +1,45 @@
+package com.malk.model.kingdee;
+
+/**
+ * 金蝶查询请求模型
+ * 
+ * 功能说明:用于查询金蝶单据详情
+ * 
+ * 使用场景:
+ * - 金蝶事件订阅后,根据InterId查询完整单据数据
+ * - 需要解析单据头和单据体信息
+ */
+public class KingdeeQueryRequest {
+    /** 业务对象FormId */
+    public String formId;
+    
+    /** 要查询的字段列表,逗号分隔,如:FNumber,FName,FEntity.FMaterialId */
+    public String fieldKeys;
+    
+    /** 查询过滤条件,SQL格式,如:FId='xxx' */
+    public String filter;
+    
+    /** 排序字段,如:FId DESC */
+    public String orderby;
+    
+    /** 返回前N条记录 */
+    public Integer topRowCount;
+    
+    /** 分页起始行 */
+    public Integer startRow;
+
+    // ==================== getter/setter ====================
+    
+    public String getFormId() { return formId; }
+    public void setFormId(String formId) { this.formId = formId; }
+    public String getFieldKeys() { return fieldKeys; }
+    public void setFieldKeys(String fieldKeys) { this.fieldKeys = fieldKeys; }
+    public String getFilter() { return filter; }
+    public void setFilter(String filter) { this.filter = filter; }
+    public String getOrderby() { return orderby; }
+    public void setOrderby(String orderby) { this.orderby = orderby; }
+    public Integer getTopRowCount() { return topRowCount; }
+    public void setTopRowCount(Integer topRowCount) { this.topRowCount = topRowCount; }
+    public Integer getStartRow() { return startRow; }
+    public void setStartRow(Integer startRow) { this.startRow = startRow; }
+}

+ 46 - 0
mjava-wlduijie/src/main/java/com/malk/model/kingdee/KingdeeSaveRequest.java

@@ -0,0 +1,46 @@
+package com.malk.model.kingdee;
+
+import java.util.Map;
+
+/**
+ * 金蝶保存请求模型
+ * 
+ * 功能说明:用于调用金蝶 DynamicFormService.Save 接口保存单据数据
+ * 
+ * 使用场景:
+ * - 氚云表单触发流程/按钮调用中间件接口,中间件封装数据推送至金蝶
+ * - 支持基础档案(供应商、物料、客户、仓库)和业务单据(采购申请、销售订单等)
+ * 
+ * 请求结构:
+ * {
+ *     "formId": "业务对象FormId",
+ *     "model": { 单据数据 },
+ *     "autoSubmitAndAudit": false  // 是否自动提交审核
+ * }
+ * 
+ * @see KingdeeSaveResponse
+ */
+public class KingdeeSaveRequest {
+    /** 业务对象FormId,如:basedata/bd_supplier/add、PUR_Requisition、SAL_SaleOrder等 */
+    public String formId;
+    
+    /** 单据数据模型,包含单据头和单据体信息 */
+    public Map<String, Object> model;
+    
+    /** 是否自动提交并审核,true=保存后自动审核,false=仅保存 */
+    public Boolean autoSubmitAndAudit;
+    
+    /** 子系统ID,用于多组织架构(可选) */
+    public String subSystemId;
+
+    // ==================== getter/setter ====================
+    
+    public String getFormId() { return formId; }
+    public void setFormId(String formId) { this.formId = formId; }
+    public Map<String, Object> getModel() { return model; }
+    public void setModel(Map<String, Object> model) { this.model = model; }
+    public Boolean getAutoSubmitAndAudit() { return autoSubmitAndAudit; }
+    public void setAutoSubmitAndAudit(Boolean autoSubmitAndAudit) { this.autoSubmitAndAudit = autoSubmitAndAudit; }
+    public String getSubSystemId() { return subSystemId; }
+    public void setSubSystemId(String subSystemId) { this.subSystemId = subSystemId; }
+}

+ 55 - 0
mjava-wlduijie/src/main/java/com/malk/model/kingdee/KingdeeSaveResponse.java

@@ -0,0 +1,55 @@
+package com.malk.model.kingdee;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 金蝶保存响应模型
+ * 
+ * 功能说明:金蝶 DynamicFormService.Save 接口的响应结果
+ * 
+ * 响应示例:
+ * {
+ *     "ResultStatus": 1,      // 结果状态:1=成功,其他=失败
+ *     "Message": "保存成功",  // 结果消息
+ *     "Number": "PO123456",   // 金蝶单据编号
+ *     "Id": "xxx",           // 金蝶单据内码
+ *     "SuccessCount": 1      // 成功数量
+ * }
+ * 
+ * @see KingdeeSaveRequest
+ */
+public class KingdeeSaveResponse {
+    /** 结果状态:1=成功,其他=失败 */
+    public Integer ResultStatus;
+    
+    /** 结果消息,成功时为"保存成功",失败时为错误信息 */
+    public String Message;
+    
+    /** 金蝶单据编号(保存成功后返回) */
+    public String Number;
+    
+    /** 金蝶单据内码(保存成功后返回) */
+    public String Id;
+    
+    /** 成功操作的数量(批量操作时使用) */
+    public Integer SuccessCount;
+    
+    /** 详细的错误信息列表(失败时返回) */
+    public List<Map<String, Object>> details;
+
+    // ==================== getter/setter ====================
+    
+    public Integer getResultStatus() { return ResultStatus; }
+    public void setResultStatus(Integer ResultStatus) { this.ResultStatus = ResultStatus; }
+    public String getMessage() { return Message; }
+    public void setMessage(String Message) { this.Message = Message; }
+    public String getNumber() { return Number; }
+    public void setNumber(String Number) { this.Number = Number; }
+    public String getId() { return Id; }
+    public void setId(String Id) { this.Id = Id; }
+    public Integer getSuccessCount() { return SuccessCount; }
+    public void setSuccessCount(Integer SuccessCount) { this.SuccessCount = SuccessCount; }
+    public List<Map<String, Object>> getDetails() { return details; }
+    public void setDetails(List<Map<String, Object>> details) { this.details = details; }
+}

+ 56 - 0
mjava-wlduijie/src/main/java/com/malk/model/kingdee/KingdeeWebhookEvent.java

@@ -0,0 +1,56 @@
+package com.malk.model.kingdee;
+
+/**
+ * 金蝶Webhook事件模型
+ * 
+ * 功能说明:金蝶事件订阅推送的事件数据
+ * 
+ * 使用场景:
+ * - 金蝶BOS平台配置事件推送,审核操作时触发Webhook
+ * - 中间件接收事件后,根据FormId和OperateType处理对应业务
+ * 
+ * 事件类型说明:
+ * - OperateType=Audit:审核事件,需要处理
+ * - 其他事件:暂不处理
+ * 
+ * 金蝶事件订阅配置清单(需求文档3.2.2):
+ * | 业务对象    | FormId          | 订阅事件 | 触发条件           | 处理逻辑                    |
+ * | 采购入库单  | STK_InStock      | 审核     | 单据状态变为'C'   | 获取详情->写入氚云采购入库   |
+ * | 采购退料单  | STK_MisDelivery  | 审核     | 单据状态变为'C'   | 获取详情->写入氚云退料      |
+ * | 销售出库单  | SAL_OUTSTOCK     | 审核     | 单据状态变为'C'   | 获取详情->写入氚云销售出库   |
+ * | 销售退货单  | SAL_RETURNSTOCK  | 审核     | 单据状态变为'C'   | 获取详情->写入氚云销售退货   |
+ */
+public class KingdeeWebhookEvent {
+    /** 事件唯一标识,用于查询单据详情 */
+    public String InterId;
+    
+    /** 单据类型 */
+    public String BillType;
+    
+    /** 业务对象FormId,标识单据类型 */
+    public String FormId;
+    
+    /** 操作类型:Audit=审核,Submit=提交,Save=保存等 */
+    public String OperateType;
+    
+    /** 操作人用户名 */
+    public String UserName;
+    
+    /** 操作时间戳(毫秒) */
+    public Long ActionTime;
+
+    // ==================== getter/setter ====================
+    
+    public String getInterId() { return InterId; }
+    public void setInterId(String InterId) { this.InterId = InterId; }
+    public String getBillType() { return BillType; }
+    public void setBillType(String BillType) { this.BillType = BillType; }
+    public String getFormId() { return FormId; }
+    public void setFormId(String FormId) { this.FormId = FormId; }
+    public String getOperateType() { return OperateType; }
+    public void setOperateType(String OperateType) { this.OperateType = OperateType; }
+    public String getUserName() { return UserName; }
+    public void setUserName(String UserName) { this.UserName = UserName; }
+    public Long getActionTime() { return ActionTime; }
+    public void setActionTime(Long ActionTime) { this.ActionTime = ActionTime; }
+}

+ 31 - 0
mjava-wlduijie/src/main/java/com/malk/model/wangdian/StockPdItem.java

@@ -0,0 +1,31 @@
+package com.malk.model.wangdian;
+
+/**
+ * 旺店通盘盈盘亏明细项
+ * 
+ * 功能说明:盘盈盘亏单中的明细行数据
+ * 
+ * 字段说明:
+ * - spec_no:商家编码(物料编码),对应氚云的物料编码
+ * - num:数量,正数表示增加,负数表示减少
+ * - remark:备注(可选)
+ */
+public class StockPdItem {
+    /** 商家编码(物料编码),对应氚云物料 */
+    public String spec_no;
+    
+    /** 数量,正数增加库存,负数减少库存 */
+    public Integer num;
+    
+    /** 备注信息(可选) */
+    public String remark;
+
+    // ==================== getter/setter ====================
+    
+    public String getSpec_no() { return spec_no; }
+    public void setSpec_no(String spec_no) { this.spec_no = spec_no; }
+    public Integer getNum() { return num; }
+    public void setNum(Integer num) { this.num = num; }
+    public String getRemark() { return remark; }
+    public void setRemark(String remark) { this.remark = remark; }
+}

+ 61 - 0
mjava-wlduijie/src/main/java/com/malk/model/wangdian/WangdianRequest.java

@@ -0,0 +1,61 @@
+package com.malk.model.wangdian;
+
+import java.util.Map;
+
+/**
+ * 旺店通请求模型
+ * 
+ * 功能说明:旺店通 OpenAPI2.0 接口的通用请求模型
+ * 
+ * API地址:https://api.wangdian.cn/openapi2/
+ * 认证方式:MD5签名
+ * 
+ * 签名算法:
+ * 1. 将所有参数(除sign外)按key升序排列
+ * 2. 拼接成字符串:key1value1key2value2... + secret
+ * 3. MD5加密后转大写
+ */
+public class WangdianRequest {
+    /** 接口方法名,如:stock_pd_add(盘盈盘亏单) */
+    public String method;
+    
+    /** 应用Key */
+    public String appkey;
+    
+    /** 店铺ID */
+    public String sid;
+    
+    /** 时间戳(毫秒) */
+    public String timestamp;
+    
+    /** 签名(MD5大写) */
+    public String sign;
+    
+    /** 响应格式,默认json */
+    public String format = "json";
+    
+    /** API版本,默认1 */
+    public Integer v = 1;
+    
+    /** 业务参数 */
+    public Map<String, Object> params;
+
+    // ==================== getter/setter ====================
+    
+    public String getMethod() { return method; }
+    public void setMethod(String method) { this.method = method; }
+    public String getAppkey() { return appkey; }
+    public void setAppkey(String appkey) { this.appkey = appkey; }
+    public String getSid() { return sid; }
+    public void setSid(String sid) { this.sid = sid; }
+    public String getTimestamp() { return timestamp; }
+    public void setTimestamp(String timestamp) { this.timestamp = timestamp; }
+    public String getSign() { return sign; }
+    public void setSign(String sign) { this.sign = sign; }
+    public String getFormat() { return format; }
+    public void setFormat(String format) { this.format = format; }
+    public Integer getV() { return v; }
+    public void setV(Integer v) { this.v = v; }
+    public Map<String, Object> getParams() { return params; }
+    public void setParams(Map<String, Object> params) { this.params = params; }
+}

+ 42 - 0
mjava-wlduijie/src/main/java/com/malk/model/wangdian/WangdianResponse.java

@@ -0,0 +1,42 @@
+package com.malk.model.wangdian;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 旺店通响应模型
+ * 
+ * 功能说明:旺店通 OpenAPI2.0 接口的通用响应模型
+ * 
+ * 响应示例:
+ * {
+ *     "code": 0,           // 错误码,0=成功
+ *     "message": "success", // 消息
+ *     "success": true,     // 是否成功
+ *     "data": [...]        // 数据列表
+ * }
+ */
+public class WangdianResponse {
+    /** 错误码,0=成功 */
+    public Integer code;
+    
+    /** 错误消息 */
+    public String message;
+    
+    /** 是否成功 */
+    public Boolean success;
+    
+    /** 数据列表 */
+    public List<Map<String, Object>> data;
+
+    // ==================== getter/setter ====================
+    
+    public Integer getCode() { return code; }
+    public void setCode(Integer code) { this.code = code; }
+    public String getMessage() { return message; }
+    public void setMessage(String message) { this.message = message; }
+    public Boolean getSuccess() { return success; }
+    public void setSuccess(Boolean success) { this.success = success; }
+    public List<Map<String, Object>> getData() { return data; }
+    public void setData(List<Map<String, Object>> data) { this.data = data; }
+}

+ 45 - 0
mjava-wlduijie/src/main/java/com/malk/model/wangdian/WangdianStockPdRequest.java

@@ -0,0 +1,45 @@
+package com.malk.model.wangdian;
+
+import java.util.List;
+
+/**
+ * 旺店通盘盈盘亏单请求模型
+ * 
+ * 功能说明:用于创建盘盈盘亏单,调整库存
+ * 
+ * 接口:stock_pd_add
+ * API地址:https://api.wangdian.cn/openapi2/
+ * 请求方式:POST
+ * 
+ * 使用场景(需求文档3.3):
+ * - 氚云盘点表单审核后,根据差异结果同步至旺店通
+ * - 盘盈单:order_type=1,调整库存增加
+ * - 盘亏单:order_type=2,调整库存减少
+ * 
+ * 字段映射(需求文档3.3.1):
+ * | 氚云字段       | 旺店通字段   | 说明       |
+ * | 仓库编码       | warehouse_no | 仓库编号   |
+ * | 明细.物料编码  | spec_no     | 商家编码   |
+ * | 明细.盈余数量  | num         | 数量       |
+ * | 明细.亏损数量  | num         | 数量       |
+ * | order_type    | 1/2         | 1=盘盈,2=盘亏 |
+ */
+public class WangdianStockPdRequest {
+    /** 仓库编号,旺店通系统中的仓库编码 */
+    public String warehouse_no;
+    
+    /** 盘盈盘亏类型:1=盘盈(库存增加),2=盘亏(库存减少) */
+    public Integer order_type;
+    
+    /** 盘盈盘亏明细列表 */
+    public List<StockPdItem> items;
+
+    // ==================== getter/setter ====================
+    
+    public String getWarehouse_no() { return warehouse_no; }
+    public void setWarehouse_no(String warehouse_no) { this.warehouse_no = warehouse_no; }
+    public Integer getOrder_type() { return order_type; }
+    public void setOrder_type(Integer order_type) { this.order_type = order_type; }
+    public List<StockPdItem> getItems() { return items; }
+    public void setItems(List<StockPdItem> items) { this.items = items; }
+}

+ 65 - 0
mjava-wlduijie/src/main/java/com/malk/schedule/WlduijieTimer.java

@@ -0,0 +1,65 @@
+package com.malk.schedule;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.scheduling.annotation.Scheduled;
+
+import java.time.LocalDateTime;
+
+/**
+ * 五路对接定时器
+ * 
+ * 功能说明:定时执行的任务调度类
+ * 
+ * 当前任务:
+ * - 每10秒输出当前时间(用于测试定时任务是否正常启动)
+ * 
+ * 扩展说明:
+ * - 可在此类中添加其他定时任务
+ * - 如:定时同步盘点数据、清理缓存等
+ * 
+ * @EnableScheduling:启用定时任务功能
+ * @Scheduled:配置定时执行规则
+ * 
+ * Cron表达式说明:
+ * - fixedRate:固定频率执行(毫秒)
+ * - fixedDelay:固定间隔执行(上次结束后开始计算)
+ * - cron:Cron表达式
+ */
+@Configuration
+@EnableScheduling
+public class WlduijieTimer {
+
+    private static final Logger log = LoggerFactory.getLogger(WlduijieTimer.class);
+
+    /**
+     * 测试定时任务
+     * 
+     * 功能说明:每10秒执行一次,输出当前时间
+     * 用于验证定时任务功能是否正常工作
+     * 
+     * @Scheduled(fixedRate = 10000) 表示每10000毫秒(10秒)执行一次
+     */
+    @Scheduled(fixedRate = 10000)
+    public void printCurrentTime() {
+        log.info("当前时间: {}", LocalDateTime.now());
+    }
+
+    /**
+     * 时间与季度输出定时任务
+     * 
+     * 功能说明:每15秒执行一次,输出当前时间和季度
+     * 季度计算:1-3月=Q1,4-6月=Q2,7-9月=Q3,10-12月=Q4
+     * 
+     * @Scheduled(fixedRate = 15000) 表示每15000毫秒(15秒)执行一次
+     */
+    @Scheduled(fixedRate = 15000)
+    public void printTimeWithQuarter() {
+        LocalDateTime now = LocalDateTime.now();
+        int month = now.getMonthValue();
+        String quarter = "Q" + ((month - 1) / 3 + 1);
+        log.info("当前时间: {}, 当前季度: {}", now, quarter);
+    }
+}

+ 307 - 0
mjava-wlduijie/src/main/java/com/malk/service/h3yun/H3yunService.java

@@ -0,0 +1,307 @@
+package com.malk.service.h3yun;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.malk.config.WlduijieConfig;
+import com.malk.model.h3yun.H3yunRequest;
+import com.malk.model.h3yun.H3yunResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.*;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 氚云 API 客户端服务
+ * 
+ * 功能说明:提供氚云系统的API调用能力,实现数据从金蝶同步至氚云
+ * 
+ * 对接方式:
+ * - API调用:CreateBizObject/UpdateFormData/QueryBizObjects
+ * - 认证方式:Header携带EngineCode和EngineSecret
+ * 
+ * API地址:https://www.h3yun.com/OpenApi/Invoke
+ * 
+ * 氚云表单编码(需求文档3.2.3):
+ * | 业务对象    | 表单编码(SchemaCode)              | 说明         |
+ * | 采购入库单  | D293655srqj5uui3keso9oraeqm   | 金蝶->氚云   |
+ * | 采购退料单  | D293655sjfn3c62rluwju0mozl3r   | 金蝶->氚云   |
+ * | 销售出库单  | D293655suomjudbplkoxrgs42dqj   | 金蝶->氚云   |
+ * | 销售退货单  | D293655sejcxgmmjpemkhfns2sv    | 金蝶->氚云   |
+ * 
+ * 状态回写字段(需求文档4.5):
+ * | 控件名称   | 控件编码        | 类型         | 说明           |
+ * | 同步状态   | F_SyncStatus   | 单选框/下拉框 | 未同步/同步中/同步成功/同步失败 |
+ * | 返回信息   | F_ResultMsg    | 多行文本框    | 错误详情或第三方单号 |
+ * | 第三方单号 | F_ThirdBillNo  | 文本框       | 金蝶/旺店通单号  |
+ * | 同步时间   | F_SyncTime     | 日期时间     | 最后同步时间    |
+ */
+@Service
+public class H3yunService {
+
+    private static final Logger log = LoggerFactory.getLogger(H3yunService.class);
+
+    @Autowired
+    private WlduijieConfig config;
+
+    private final RestTemplate restTemplate = new RestTemplate();
+
+    /**
+     * 创建业务对象
+     * 
+     * 功能说明:调用CreateBizObject接口创建新的业务对象
+     * 
+     * 使用场景:
+     * - 金蝶审核事件触发后,写入氚云
+     * - 采购入库单、销售出库单等
+     * 
+     * @param schemaCode 表单编码
+     * @param formData 表单数据
+     * @return 响应结果
+     */
+    public H3yunResponse createBizObject(String schemaCode, Map<String, Object> formData) {
+        try {
+            Map<String, Object> body = new HashMap<>();
+            body.put("SchemaCode", schemaCode);
+            body.put("Action", "CreateBizObject");
+            body.put("FormData", formData);
+
+            String response = execute(body);
+            log.info("氚云创建业务对象, schemaCode: {}, 响应: {}", schemaCode, response);
+            
+            return JSON.parseObject(response, H3yunResponse.class);
+        } catch (Exception e) {
+            log.error("氚云创建业务对象异常, schemaCode: {}", schemaCode, e);
+            throw new RuntimeException("氚云创建业务对象异常: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 更新业务对象
+     * 
+     * @param schemaCode 表单编码
+     * @param bizObjectId 业务对象ID
+     * @param formData 表单数据
+     * @return 响应结果
+     */
+    public H3yunResponse updateBizObject(String schemaCode, String bizObjectId, Map<String, Object> formData) {
+        try {
+            Map<String, Object> body = new HashMap<>();
+            body.put("SchemaCode", schemaCode);
+            body.put("Action", "UpdateBizObject");
+            body.put("BizObjectId", bizObjectId);
+            body.put("FormData", formData);
+
+            String response = execute(body);
+            log.info("氚云更新业务对象, schemaCode: {}, bizObjectId: {}, 响应: {}", schemaCode, bizObjectId, response);
+            
+            return JSON.parseObject(response, H3yunResponse.class);
+        } catch (Exception e) {
+            log.error("氚云更新业务对象异常, schemaCode: {}, bizObjectId: {}", schemaCode, bizObjectId, e);
+            throw new RuntimeException("氚云更新业务对象异常: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 更新表单数据
+     * 
+     * 功能说明:更新指定表单的数据,主要用于状态回写
+     * 
+     * 使用场景:
+     * - 同步成功后更新同步状态
+     * - 回写第三方单号和错误信息
+     * 
+     * @param schemaCode 表单编码
+     * @param objectId 数据对象ID
+     * @param data 要更新的字段数据
+     * @return 响应结果
+     */
+    public H3yunResponse updateFormData(String schemaCode, String objectId, Map<String, Object> data) {
+        try {
+            Map<String, Object> body = new HashMap<>();
+            body.put("SchemaCode", schemaCode);
+            body.put("Action", "UpdateFormData");
+            body.put("ObjectId", objectId);
+            body.put("Data", data);
+
+            String response = execute(body);
+            log.info("氚云更新表单数据, schemaCode: {}, objectId: {}, 响应: {}", schemaCode, objectId, response);
+            
+            return JSON.parseObject(response, H3yunResponse.class);
+        } catch (Exception e) {
+            log.error("氚云更新表单数据异常, schemaCode: {}, objectId: {}", schemaCode, objectId, e);
+            throw new RuntimeException("氚云更新表单数据异常: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 查询业务对象列表
+     * 
+     * 功能说明:查询符合条件的数据列表
+     * 
+     * 使用场景:
+     * - 检查单据是否已存在(幂等性设计)
+     * - 获取数据列表
+     * 
+     * @param schemaCode 表单编码
+     * @param filter 过滤条件
+     * @param pageSize 每页数量
+     * @param pageIndex 页码
+     * @return 查询结果JSON
+     */
+    public JSONObject queryBizObjects(String schemaCode, String filter, Integer pageSize, Integer pageIndex) {
+        try {
+            Map<String, Object> body = new HashMap<>();
+            body.put("SchemaCode", schemaCode);
+            body.put("Action", "QueryBizObjects");
+            body.put("PageSize", pageSize);
+            body.put("PageIndex", pageIndex);
+            if (filter != null) {
+                body.put("Filter", filter);
+            }
+
+            String response = execute(body);
+            log.info("氚云查询业务对象, schemaCode: {}, 响应: {}", schemaCode, response);
+            
+            return JSON.parseObject(response);
+        } catch (Exception e) {
+            log.error("氚云查询业务对象异常, schemaCode: {}", schemaCode, e);
+            throw new RuntimeException("氚云查询业务对象异常: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 检查单据是否已存在
+     * 
+     * 功能说明:根据单据编号检查氚云是否已存在该记录(幂等性设计)
+     * 
+     * 需求文档说明:
+     * - 金蝶->氚云:利用单据编号查询氚云是否已存在该记录,防止重复写入
+     * - 氚云->金蝶:利用金蝶Save接口的"编码相同即修改"特性,保证数据不重复
+     * 
+     * @param schemaCode 表单编码
+     * @param billNo 单据编号
+     * @param fieldName 单据编号字段名
+     * @return 是否存在
+     */
+    public boolean checkBillExists(String schemaCode, String billNo, String fieldName) {
+        try {
+            String filter = String.format("%s = '%s'", fieldName, billNo);
+            JSONObject result = queryBizObjects(schemaCode, filter, 1, 0);
+            return result.getJSONArray("data") != null && result.getJSONArray("data").size() > 0;
+        } catch (Exception e) {
+            log.error("氚云检查单据是否存在异常, schemaCode: {}, billNo: {}", schemaCode, billNo, e);
+            return false;
+        }
+    }
+
+    /**
+     * 执行氚云API请求
+     * 
+     * 功能说明:发送HTTP请求到氚云API,包含认证头设置
+     * 
+     * @param body 请求体
+     * @return 响应字符串
+     */
+    private String execute(Map<String, Object> body) {
+        HttpHeaders headers = new HttpHeaders();
+        headers.setContentType(MediaType.APPLICATION_JSON);
+        headers.set("EngineCode", config.getH3yun().getEngineCode());
+        headers.set("EngineSecret", config.getH3yun().getEngineSecret());
+
+        HttpEntity<Map<String, Object>> request = new HttpEntity<>(body, headers);
+        ResponseEntity<String> response = restTemplate.exchange(
+                config.getH3yun().getBaseUrl(),
+                HttpMethod.POST,
+                request,
+                String.class
+        );
+        return response.getBody();
+    }
+
+    /**
+     * 更新同步状态
+     * 
+     * 功能说明:同步完成后,回写同步状态至氚云表单
+     * 
+     * 回写字段(需求文档4.5):
+     * - F_SyncStatus:同步状态
+     * - F_ResultMsg:返回信息
+     * - F_ThirdBillNo:第三方单号
+     * - F_SyncTime:同步时间
+     * 
+     * @param schemaCode 表单编码
+     * @param objectId 对象ID
+     * @param status 同步状态
+     * @param message 返回信息
+     * @param thirdBillNo 第三方单号
+     * @return 响应结果
+     */
+    public H3yunResponse updateSyncStatus(String schemaCode, String objectId, String status, String message, String thirdBillNo) {
+        Map<String, Object> data = new HashMap<>();
+        data.put("F_SyncStatus", status);
+        data.put("F_ResultMsg", message);
+        data.put("F_ThirdBillNo", thirdBillNo);
+        data.put("F_SyncTime", System.currentTimeMillis());
+        
+        return updateFormData(schemaCode, objectId, data);
+    }
+
+    /**
+     * 创建采购入库单
+     * 
+     * 功能说明:金蝶采购入库单审核后,写入氚云采购入库表单
+     * 
+     * 表单编码:D293655srqj5uui3keso9oraeqm
+     * 
+     * 字段映射(需求文档3.2.3):
+     * | 金蝶字段         | 氚云字段   | 说明     |
+     * | FBillNo         | F0000001  | 单据编号 |
+     * | FCustomerId     | F0000003  | 客户     |
+     * | FEntity_FQty    | F0000005  | 明细-数量 |
+     * 
+     * @param billNo 单据编号
+     * @param customerId 客户ID
+     * @param qty 数量
+     * @return 响应结果
+     */
+    public H3yunResponse createPurchaseInstock(String billNo, String customerId, Integer qty) {
+        Map<String, Object> formData = new HashMap<>();
+        formData.put("F0000001", billNo);
+        formData.put("F0000003", customerId);
+        formData.put("F0000005", qty);
+        
+        return createBizObject("D293655srqj5uui3keso9oraeqm", formData);
+    }
+
+    /**
+     * 创建销售出库单
+     * 
+     * 功能说明:金蝶销售出库单审核后,写入氚云销售出库表单
+     * 
+     * 表单编码:D293655suomjudbplkoxrgs42dqj
+     * 
+     * 字段映射(需求文档3.2.3):
+     * | 金蝶字段         | 氚云字段   | 说明     |
+     * | FBillNo         | F0000001  | 单据编号 |
+     * | FCustomerId     | F0000003  | 客户     |
+     * | FEntity_FQty    | F0000005  | 明细-数量 |
+     * 
+     * @param billNo 单据编号
+     * @param customerId 客户ID
+     * @param qty 数量
+     * @return 响应结果
+     */
+    public H3yunResponse createSaleOutstock(String billNo, String customerId, Integer qty) {
+        Map<String, Object> formData = new HashMap<>();
+        formData.put("F0000001", billNo);
+        formData.put("F0000003", customerId);
+        formData.put("F0000005", qty);
+        
+        return createBizObject("D293655suomjudbplkoxrgs42dqj", formData);
+    }
+}

+ 387 - 0
mjava-wlduijie/src/main/java/com/malk/service/kingdee/KingdeeService.java

@@ -0,0 +1,387 @@
+package com.malk.service.kingdee;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.malk.config.WlduijieConfig;
+import com.malk.model.kingdee.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * 金蝶云·星空 API 客户端服务
+ * 
+ * 功能说明:提供金蝶系统的API调用能力,实现数据从氚云同步至金蝶
+ * 
+ * 对接方式:
+ * 1. 氚云 -> 金蝶:API主动推送模式
+ *    - 基础档案:采用KAPI或标准WebAPI推送
+ *    - 业务单据:采用标准WebAPI推送
+ *    - 状态反馈:同步完成后回写状态至氚云
+ * 
+ * 2. 金蝶 -> 氚云:通过Webhook事件订阅(见KingdeeWebhookController)
+ * 
+ * 认证方式:AuthService.ValidateUser 获取SessionId
+ * 
+ * 常用API:
+ * - AuthService.ValidateUser:用户认证
+ * - DynamicFormService.Save:保存单据
+ * - DynamicFormService.GetBill:查询单据详情
+ * 
+ * 基础档案FormId(需求文档3.1.1):
+ * - 供应商:basedata/bd_supplier/add
+ * - 物料:basedata/material/batchAddAll(批量)
+ * - 客户:basedata/bd_customer/add
+ * - 仓库:BD_STOCK
+ * 
+ * 业务单据FormId(需求文档3.1.2):
+ * - 采购申请单:PUR_Requisition
+ * - 收料通知单:PUR_ReceiveNotice
+ * - 采购退料申请:PUR_ReturnRequest
+ * - 销售订单:SAL_SaleOrder
+ * - 发货通知单:SAL_DeliveryNotice
+ * - 退货申请单:SAL_ReturnRequest
+ * - 付款申请单:CN_PayableBill
+ * - 财务应付单:AP_PAYABLE
+ * - 采购发票:IV_PurchaseInvoice
+ * - 销售发票:IV_SalesInvoice
+ * - 收款单:AR_RECEIVEBILL
+ * - 开票申请单:IV_InvoiceApply
+ */
+@Service
+public class KingdeeService {
+
+    private static final Logger log = LoggerFactory.getLogger(KingdeeService.class);
+
+    @Autowired
+    private WlduijieConfig config;
+
+    private final RestTemplate restTemplate = new RestTemplate();
+    
+    /** SessionId缓存,用于避免频繁登录 */
+    private final Map<String, String> sessionCache = new ConcurrentHashMap<>();
+
+    /**
+     * 获取金蝶SessionId
+     * 
+     * 功能说明:获取有效的会话ID,用于后续API调用
+     * 
+     * 逻辑:
+     * 1. 先从缓存获取
+     * 2. 缓存不存在则重新登录
+     * 
+     * @return SessionId
+     */
+    public String getSessionId() {
+        String sessionId = sessionCache.get("session");
+        if (sessionId != null) {
+            return sessionId;
+        }
+        return login();
+    }
+
+    /**
+     * 金蝶登录认证
+     * 
+     * 功能说明:调用AuthService.ValidateUser接口进行用户认证,获取SessionId
+     * 
+     * @return SessionId
+     * @throws RuntimeException 登录失败时抛出
+     */
+    public String login() {
+        try {
+            KingdeeAuthRequest request = new KingdeeAuthRequest();
+            request.setAcctId(config.getKingdee().getAcctId());
+            request.setUserName(config.getKingdee().getUserName());
+            request.setPassword(config.getKingdee().getPassword());
+
+            String url = config.getKingdee().getHost() + "/kapi/v2/AuthService.ValidateUser";
+            String response = restTemplate.postForObject(url, request, String.class);
+            
+            KingdeeAuthResponse authResponse = JSON.parseObject(response, KingdeeAuthResponse.class);
+            
+            // ResultStatus为1或100表示成功
+            if (authResponse.getResultStatus() == 1 || authResponse.getResultStatus() == 100) {
+                sessionCache.put("session", authResponse.getSessionId());
+                log.info("金蝶登录成功, SessionId: {}", authResponse.getSessionId());
+                return authResponse.getSessionId();
+            }
+            
+            log.error("金蝶登录失败: {}", authResponse.getMessage());
+            throw new RuntimeException("金蝶登录失败: " + authResponse.getMessage());
+        } catch (Exception e) {
+            log.error("金蝶登录异常", e);
+            throw new RuntimeException("金蝶登录异常: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 保存单据(通用方法)
+     * 
+     * 功能说明:调用DynamicFormService.Save接口保存数据到金蝶
+     * 
+     * @param formId 业务对象FormId
+     * @param model 单据数据模型
+     * @return 保存结果
+     */
+    public KingdeeSaveResponse save(String formId, Map<String, Object> model) {
+        return save(formId, model, false);
+    }
+
+    /**
+     * 保存单据(可配置是否自动审核)
+     * 
+     * @param formId 业务对象FormId
+     * @param model 单据数据模型
+     * @param autoSubmitAndAudit 是否自动提交审核
+     * @return 保存结果
+     */
+    public KingdeeSaveResponse save(String formId, Map<String, Object> model, boolean autoSubmitAndAudit) {
+        try {
+            String sessionId = getSessionId();
+            
+            KingdeeSaveRequest request = new KingdeeSaveRequest();
+            request.setFormId(formId);
+            request.setModel(model);
+            request.setAutoSubmitAndAudit(autoSubmitAndAudit);
+
+            String url = config.getKingdee().getHost() + "/kapi/v2/DynamicFormService.Save";
+            Map<String, Object> body = new HashMap<>();
+            body.put("SessionId", sessionId);
+            body.put("data", JSON.toJSONString(request));
+
+            String response = restTemplate.postForObject(url, body, String.class);
+            log.info("金蝶保存请求: {}, 响应: {}", JSON.toJSONString(request), response);
+            
+            return JSON.parseObject(response, KingdeeSaveResponse.class);
+        } catch (Exception e) {
+            log.error("金蝶保存异常, formId: {}", formId, e);
+            throw new RuntimeException("金蝶保存异常: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 批量新增物料(基础档案)
+     * 
+     * 功能说明:调用batchAddAll接口批量新增物料档案
+     * 
+     * @param formId 接口路径,如:basedata/material/batchAddAll
+     * @param model 物料数据
+     * @return 保存结果
+     */
+    public KingdeeSaveResponse batchAddMaterial(String formId, Map<String, Object> model) {
+        try {
+            String sessionId = getSessionId();
+            
+            Map<String, Object> body = new HashMap<>();
+            body.put("SessionId", sessionId);
+            body.put("data", JSON.toJSONString(model));
+
+            String url = config.getKingdee().getHost() + "/kapi/v2/" + formId;
+            String response = restTemplate.postForObject(url, body, String.class);
+            log.info("金蝶批量新增请求, 响应: {}", response);
+            
+            return JSON.parseObject(response, KingdeeSaveResponse.class);
+        } catch (Exception e) {
+            log.error("金蝶批量新增异常, formId: {}", formId, e);
+            throw new RuntimeException("金蝶批量新增异常: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 查询单据详情
+     * 
+     * 功能说明:根据单据编号查询完整的单据数据
+     * 
+     * 使用场景:金蝶事件订阅后,获取审核单据的完整信息
+     * 
+     * @param formId 业务对象FormId
+     * @param billNo 单据编号
+     * @return 单据详情JSON
+     */
+    public JSONObject queryBillDetail(String formId, String billNo) {
+        try {
+            String sessionId = getSessionId();
+            
+            String url = config.getKingdee().getHost() + "/kapi/v2/DynamicFormService.GetBill";
+            Map<String, Object> body = new HashMap<>();
+            body.put("SessionId", sessionId);
+            body.put("FormId", formId);
+            body.put("Number", billNo);
+
+            String response = restTemplate.postForObject(url, body, String.class);
+            log.info("金蝶查询单据详情: {}, 响应: {}", billNo, response);
+            
+            return JSON.parseObject(response);
+        } catch (Exception e) {
+            log.error("金蝶查询单据详情异常, formId: {}, billNo: {}", formId, billNo, e);
+            throw new RuntimeException("金蝶查询单据详情异常: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 保存供应商档案
+     * 
+     * 功能说明:将氚云供应商同步至金蝶
+     * 
+     * 字段映射(需求文档3.1.1):
+     * | 氚云字段   | 金蝶字段     | 说明         |
+     * | 供应商编码 | FNumber     | 必填         |
+     * | 供应商名称 | FName       | 必填         |
+     * | 创建组织   | FCreateOrgId| 必填,需转换  |
+     * | 使用组织   | FUseOrgId   | 必填,需转换  |
+     * | 社会信用代码 | FSocCreditCode | 必填     |
+     * 
+     * @param data 供应商数据
+     * @return 保存结果
+     */
+    public KingdeeSaveResponse saveSupplier(Map<String, Object> data) {
+        Map<String, Object> model = new HashMap<>();
+        model.put("FNumber", data.get("code"));
+        model.put("FName", data.get("name"));
+        model.put("FCreateOrgId", data.get("createOrgId"));
+        model.put("FUseOrgId", data.get("useOrgId"));
+        model.put("FSocCreditCode", data.get("creditCode"));
+        
+        return save("basedata/bd_supplier/add", model);
+    }
+
+    /**
+     * 保存客户档案
+     * 
+     * 功能说明:将氚云客户同步至金蝶
+     * 
+     * 字段映射(需求文档3.1.1):
+     * | 氚云字段   | 金蝶字段     | 说明   |
+     * | 客户编码   | FNumber     | 必填   |
+     * | 客户名称   | FName       | 必填   |
+     * | 客户分组   | FGroup      | 必填   |
+     * | 税号       | FTaxRegisterNo | 可选 |
+     * 
+     * @param data 客户数据
+     * @return 保存结果
+     */
+    public KingdeeSaveResponse saveCustomer(Map<String, Object> data) {
+        Map<String, Object> model = new HashMap<>();
+        model.put("FNumber", data.get("code"));
+        model.put("FName", data.get("name"));
+        model.put("FGroup", data.get("group"));
+        model.put("FTaxRegisterNo", data.get("taxNo"));
+        
+        return save("basedata/bd_customer/add", model);
+    }
+
+    /**
+     * 保存物料档案
+     * 
+     * 功能说明:将氚云物料同步至金蝶
+     * 
+     * 字段映射(需求文档3.1.1):
+     * | 氚云字段   | 金蝶字段       | 说明         |
+     * | 物料编码   | FNumber       | 必填         |
+     * | 物料名称   | FName         | 必填         |
+     * | 基本单位   | FBaseUnitId   | 必填,需映射  |
+     * | 物料分组   | FMaterialGroup| 必填,需映射  |
+     * | 创建组织   | FCreateOrgId  | 必填,需转换  |
+     * 
+     * @param data 物料数据
+     * @return 保存结果
+     */
+    public KingdeeSaveResponse saveMaterial(Map<String, Object> data) {
+        Map<String, Object> model = new HashMap<>();
+        model.put("FNumber", data.get("code"));
+        model.put("FName", data.get("name"));
+        model.put("FBaseUnitId", data.get("baseUnitId"));
+        model.put("FMaterialGroup", data.get("materialGroup"));
+        model.put("FCreateOrgId", data.get("createOrgId"));
+        
+        return save("basedata/material/add", model);
+    }
+
+    /**
+     * 保存仓库档案
+     * 
+     * 功能说明:将氚云仓库同步至金蝶
+     * 
+     * 字段映射(需求文档3.1.1):
+     * | 氚云字段   | 金蝶字段       | 说明   |
+     * | 仓库编码   | FNumber       | 必填   |
+     * | 仓库名称   | FName         | 必填   |
+     * | 使用组织   | FUseOrgId     | 必填   |
+     * | 仓库属性   | FStockProperty| 必填   |
+     * 
+     * @param data 仓库数据
+     * @return 保存结果
+     */
+    public KingdeeSaveResponse saveStock(Map<String, Object> data) {
+        Map<String, Object> model = new HashMap<>();
+        model.put("FNumber", data.get("code"));
+        model.put("FName", data.get("name"));
+        model.put("FUseOrgId", data.get("useOrgId"));
+        model.put("FStockProperty", data.get("stockProperty"));
+        
+        return save("BD_STOCK", model);
+    }
+
+    /**
+     * 保存采购申请单
+     * 
+     * 功能说明:将氚云采购申请单同步至金蝶
+     * 
+     * 字段映射(需求文档3.1.2):
+     * | 氚云字段   | 金蝶字段           | 说明     |
+     * | 申请组织   | FApplicationOrgId  | 必填     |
+     * | 申请日期   | FDate              | 必填     |
+     * | 明细.物料  | FEntity.FMaterialId | 必填   |
+     * | 明细.数量  | FEntity.FReqQty    | 必填    |
+     * 
+     * @param data 采购申请单数据
+     * @return 保存结果
+     */
+    public KingdeeSaveResponse savePurchaseRequisition(Map<String, Object> data) {
+        Map<String, Object> model = new HashMap<>();
+        model.put("FApplicationOrgId", data.get("applicationOrgId"));
+        model.put("FDate", data.get("date"));
+        
+        Map<String, Object> entity = new HashMap<>();
+        entity.put("FMaterialId", data.get("materialId"));
+        entity.put("FReqQty", data.get("reqQty"));
+        model.put("FEntity", entity);
+        
+        return save("PUR_Requisition", model);
+    }
+
+    /**
+     * 保存销售订单
+     * 
+     * 功能说明:将氚云销售订单同步至金蝶
+     * 
+     * 字段映射(需求文档3.1.2):
+     * | 氚云字段   | 金蝶字段           | 说明     |
+     * | 销售组织   | FSaleOrgId         | 必填     |
+     * | 客户       | FCustomerId        | 必填     |
+     * | 明细.物料  | FEntity.FMaterialId | 必填    |
+     * | 明细.数量  | FEntity.FQty        | 必填    |
+     * 
+     * @param data 销售订单数据
+     * @return 保存结果
+     */
+    public KingdeeSaveResponse saveSaleOrder(Map<String, Object> data) {
+        Map<String, Object> model = new HashMap<>();
+        model.put("FSaleOrgId", data.get("saleOrgId"));
+        model.put("FCustomerId", data.get("customerId"));
+        
+        Map<String, Object> entity = new HashMap<>();
+        entity.put("FMaterialId", data.get("materialId"));
+        entity.put("FQty", data.get("qty"));
+        model.put("FEntity", entity);
+        
+        return save("SAL_SaleOrder", model);
+    }
+}

+ 420 - 0
mjava-wlduijie/src/main/java/com/malk/service/sync/SyncService.java

@@ -0,0 +1,420 @@
+package com.malk.service.sync;
+
+import com.alibaba.fastjson.JSONObject;
+import com.malk.model.kingdee.KingdeeSaveResponse;
+import com.malk.model.wangdian.WangdianResponse;
+import com.malk.service.h3yun.H3yunService;
+import com.malk.service.kingdee.KingdeeService;
+import com.malk.service.wangdian.WangdianService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 数据同步服务
+ * 
+ * 功能说明:实现氚云、金蝶、旺店通三大系统之间的数据互联互通
+ * 
+ * 集成架构(需求文档2):
+ * 1. 氚云 -> 金蝶:API主动推送模式
+ *    - 基础档案:供应商、物料、客户、仓库
+ *    - 业务单据:采购申请单、销售订单等
+ * 
+ * 2. 金蝶 -> 氚云:事件订阅模式
+ *    - 采购入库单、采购退料单
+ *    - 销售出库单、销售退货单
+ * 
+ * 3. 氚云 -> 旺店通:定时任务/触发器
+ *    - 盘盈单、盘亏单
+ * 
+ * 开发规范(需求文档4):
+ * 1. 异常处理机制:重试机制(指数退避)、日志记录
+ * 2. 数据一致性:幂等性设计、基础资料映射表
+ * 3. 性能优化:批量处理、事件过滤
+ * 4. 状态回写:同步状态、返回信息、第三方单号、同步时间
+ */
+@Service
+public class SyncService {
+
+    private static final Logger log = LoggerFactory.getLogger(SyncService.class);
+
+    @Autowired
+    private KingdeeService kingdeeService;
+
+    @Autowired
+    private H3yunService h3yunService;
+
+    @Autowired
+    private WangdianService wangdianService;
+
+    // ==================== 氚云 -> 金蝶 同步方法 ====================
+
+    /**
+     * 同步供应商到金蝶
+     * 
+     * 功能说明:将氚云供应商档案同步至金蝶
+     * 
+     * 字段映射:
+     * | 氚云字段   | 金蝶字段     | 说明         |
+     * | 供应商编码 | FNumber     | 必填         |
+     * | 供应商名称 | FName       | 必填         |
+     * | 创建组织   | FCreateOrgId| 必填,需转换  |
+     * | 使用组织   | FUseOrgId   | 必填,需转换  |
+     * | 社会信用代码 | FSocCreditCode | 必填     |
+     * 
+     * @param code 供应商编码
+     * @param name 供应商名称
+     * @param createOrgId 创建组织ID
+     * @param useOrgId 使用组织ID
+     * @param creditCode 社会信用代码
+     * @return 金蝶保存结果
+     */
+    public KingdeeSaveResponse syncSupplierToKingdee(String code, String name, String createOrgId, 
+                                                     String useOrgId, String creditCode) {
+        try {
+            Map<String, Object> data = new HashMap<>();
+            data.put("code", code);
+            data.put("name", name);
+            data.put("createOrgId", createOrgId);
+            data.put("useOrgId", useOrgId);
+            data.put("creditCode", creditCode);
+            
+            return kingdeeService.saveSupplier(data);
+        } catch (Exception e) {
+            log.error("同步供应商到金蝶失败, code: {}", code, e);
+            throw e;
+        }
+    }
+
+    /**
+     * 同步物料到金蝶
+     * 
+     * 功能说明:将氚云物料档案同步至金蝶(支持批量)
+     * 
+     * 字段映射:
+     * | 氚云字段   | 金蝶字段       | 说明         |
+     * | 物料编码   | FNumber       | 必填         |
+     * | 物料名称   | FName         | 必填         |
+     * | 基本单位   | FBaseUnitId   | 必填,需映射  |
+     * | 物料分组   | FMaterialGroup| 必填,需映射  |
+     * | 创建组织   | FCreateOrgId  | 必填,需转换  |
+     * 
+     * @param code 物料编码
+     * @param name 物料名称
+     * @param baseUnitId 基本单位ID
+     * @param materialGroup 物料分组
+     * @param createOrgId 创建组织ID
+     * @return 金蝶保存结果
+     */
+    public KingdeeSaveResponse syncMaterialToKingdee(String code, String name, String baseUnitId,
+                                                      String materialGroup, String createOrgId) {
+        try {
+            Map<String, Object> data = new HashMap<>();
+            data.put("code", code);
+            data.put("name", name);
+            data.put("baseUnitId", baseUnitId);
+            data.put("materialGroup", materialGroup);
+            data.put("createOrgId", createOrgId);
+            
+            return kingdeeService.saveMaterial(data);
+        } catch (Exception e) {
+            log.error("同步物料到金蝶失败, code: {}", code, e);
+            throw e;
+        }
+    }
+
+    /**
+     * 同步客户到金蝶
+     * 
+     * 功能说明:将氚云客户档案同步至金蝶
+     * 
+     * @param code 客户编码
+     * @param name 客户名称
+     * @param group 客户分组
+     * @param taxNo 税号
+     * @return 金蝶保存结果
+     */
+    public KingdeeSaveResponse syncCustomerToKingdee(String code, String name, String group, String taxNo) {
+        try {
+            Map<String, Object> data = new HashMap<>();
+            data.put("code", code);
+            data.put("name", name);
+            data.put("group", group);
+            data.put("taxNo", taxNo);
+            
+            return kingdeeService.saveCustomer(data);
+        } catch (Exception e) {
+            log.error("同步客户到金蝶失败, code: {}", code, e);
+            throw e;
+        }
+    }
+
+    /**
+     * 同步仓库到金蝶
+     * 
+     * 功能说明:将氚云仓库档案同步至金蝶
+     * 
+     * @param code 仓库编码
+     * @param name 仓库名称
+     * @param useOrgId 使用组织ID
+     * @param stockProperty 仓库属性
+     * @return 金蝶保存结果
+     */
+    public KingdeeSaveResponse syncStockToKingdee(String code, String name, String useOrgId, String stockProperty) {
+        try {
+            Map<String, Object> data = new HashMap<>();
+            data.put("code", code);
+            data.put("name", name);
+            data.put("useOrgId", useOrgId);
+            data.put("stockProperty", stockProperty);
+            
+            return kingdeeService.saveStock(data);
+        } catch (Exception e) {
+            log.error("同步仓库到金蝶失败, code: {}", code, e);
+            throw e;
+        }
+    }
+
+    /**
+     * 同步采购申请单到金蝶
+     * 
+     * 功能说明:将氚云采购申请单同步至金蝶
+     * 
+     * @param data 采购申请单数据
+     * @return 金蝶保存结果
+     */
+    public KingdeeSaveResponse syncPurchaseRequisitionToKingdee(Map<String, Object> data) {
+        try {
+            return kingdeeService.savePurchaseRequisition(data);
+        } catch (Exception e) {
+            log.error("同步采购申请单到金蝶失败", e);
+            throw e;
+        }
+    }
+
+    /**
+     * 同步销售订单到金蝶
+     * 
+     * 功能说明:将氚云销售订单同步至金蝶
+     * 
+     * @param data 销售订单数据
+     * @return 金蝶保存结果
+     */
+    public KingdeeSaveResponse syncSaleOrderToKingdee(Map<String, Object> data) {
+        try {
+            return kingdeeService.saveSaleOrder(data);
+        } catch (Exception e) {
+            log.error("同步销售订单到金蝶失败", e);
+            throw e;
+        }
+    }
+
+    // ==================== 金蝶 -> 氚云 同步方法 ====================
+
+    /**
+     * 从金蝶同步采购入库单到氚云
+     * 
+     * 功能说明:金蝶采购入库单审核后,通过事件订阅触发,同步至氚云
+     * 
+     * 金蝶事件配置:
+     * - FormId: STK_InStock
+     * - 事件: 审核
+     * - 触发条件: 单据状态变为'C'
+     * 
+     * 氚云表单编码:D293655srqj5uui3keso9oraeqm
+     * 
+     * 字段映射:
+     * | 金蝶字段         | 氚云字段   | 说明     |
+     * | FBillNo         | F0000001  | 单据编号 |
+     * | FCustomerId     | F0000003  | 客户     |
+     * | FEntity_FQty    | F0000005  | 明细-数量 |
+     * 
+     * @param billNo 金蝶单据编号
+     */
+    public void syncPurchaseInstockFromKingdee(String billNo) {
+        try {
+            // 1. 查询金蝶单据详情
+            JSONObject billDetail = kingdeeService.queryBillDetail("STK_InStock", billNo);
+            JSONObject model = billDetail.getJSONObject("Model");
+            
+            // 2. 提取单据数据
+            String customerId = model.getString("FCustomerId");
+            List<Map<String, Object>> entities = (List<Map<String, Object>>) model.get("FEntity");
+            
+            // 3. 逐行写入氚云
+            for (Map<String, Object> entity : entities) {
+                Integer qty = ((Number) entity.get("FQty")).intValue();
+                
+                h3yunService.createPurchaseInstock(billNo, customerId, qty);
+            }
+            
+            log.info("同步采购入库单到氚云成功, billNo: {}", billNo);
+        } catch (Exception e) {
+            log.error("同步采购入库单到氚云失败, billNo: {}", billNo, e);
+            throw e;
+        }
+    }
+
+    /**
+     * 从金蝶同步采购退料单到氚云
+     * 
+     * 功能说明:金蝶采购退料单审核后,同步至氚云
+     * 
+     * 金蝶事件配置:
+     * - FormId: STK_MisDelivery (或 PUR_MRB)
+     * - 事件: 审核
+     * 
+     * 氚云表单编码:D293655sjfn3c62rluwju0mozl3r
+     * 
+     * @param billNo 金蝶单据编号
+     */
+    public void syncPurchaseReturnFromKingdee(String billNo) {
+        try {
+            JSONObject billDetail = kingdeeService.queryBillDetail("STK_MisDelivery", billNo);
+            JSONObject model = billDetail.getJSONObject("Model");
+            
+            String customerId = model.getString("FCustomerId");
+            List<Map<String, Object>> entities = (List<Map<String, Object>>) model.get("FEntity");
+            
+            for (Map<String, Object> entity : entities) {
+                Integer qty = ((Number) entity.get("FQty")).intValue();
+                
+                Map<String, Object> formData = new HashMap<>();
+                formData.put("F0000001", billNo);
+                formData.put("F0000003", customerId);
+                formData.put("F0000005", qty);
+                
+                h3yunService.createBizObject("D293655sjfn3c62rluwju0mozl3r", formData);
+            }
+            
+            log.info("同步采购退料单到氚云成功, billNo: {}", billNo);
+        } catch (Exception e) {
+            log.error("同步采购退料单到氚云失败, billNo: {}", billNo, e);
+            throw e;
+        }
+    }
+
+    /**
+     * 从金蝶同步销售出库单到氚云
+     * 
+     * 功能说明:金蝶销售出库单审核后,同步至氚云
+     * 
+     * 金蝶事件配置:
+     * - FormId: SAL_OUTSTOCK
+     * - 事件: 审核
+     * 
+     * 氚云表单编码:D293655suomjudbplkoxrgs42dqj
+     * 
+     * @param billNo 金蝶单据编号
+     */
+    public void syncSaleOutstockFromKingdee(String billNo) {
+        try {
+            JSONObject billDetail = kingdeeService.queryBillDetail("SAL_OUTSTOCK", billNo);
+            JSONObject model = billDetail.getJSONObject("Model");
+            
+            String customerId = model.getString("FCustomerId");
+            List<Map<String, Object>> entities = (List<Map<String, Object>>) model.get("FEntity");
+            
+            for (Map<String, Object> entity : entities) {
+                Integer qty = ((Number) entity.get("FQty")).intValue();
+                
+                h3yunService.createSaleOutstock(billNo, customerId, qty);
+            }
+            
+            log.info("同步销售出库单到氚云成功, billNo: {}", billNo);
+        } catch (Exception e) {
+            log.error("同步销售出库单到氚云失败, billNo: {}", billNo, e);
+            throw e;
+        }
+    }
+
+    /**
+     * 从金蝶同步销售退货单到氚云
+     * 
+     * 功能说明:金蝶销售退货单审核后,同步至氚云
+     * 
+     * 金蝶事件配置:
+     * - FormId: SAL_RETURNSTOCK
+     * - 事件: 审核
+     * 
+     * 氚云表单编码:D293655sejcxgmmjpemkhfns2sv
+     * 
+     * @param billNo 金蝶单据编号
+     */
+    public void syncSaleReturnFromKingdee(String billNo) {
+        try {
+            JSONObject billDetail = kingdeeService.queryBillDetail("SAL_RETURNSTOCK", billNo);
+            JSONObject model = billDetail.getJSONObject("Model");
+            
+            String customerId = model.getString("FCustomerId");
+            List<Map<String, Object>> entities = (List<Map<String, Object>>) model.get("FEntity");
+            
+            for (Map<String, Object> entity : entities) {
+                Integer qty = ((Number) entity.get("FQty")).intValue();
+                
+                Map<String, Object> formData = new HashMap<>();
+                formData.put("F0000001", billNo);
+                formData.put("F0000003", customerId);
+                formData.put("F0000005", qty);
+                
+                h3yunService.createBizObject("D293655sejcxgmmjpemkhfns2sv", formData);
+            }
+            
+            log.info("同步销售退货单到氚云成功, billNo: {}", billNo);
+        } catch (Exception e) {
+            log.error("同步销售退货单到氚云失败, billNo: {}", billNo, e);
+            throw e;
+        }
+    }
+
+    // ==================== 氚云 -> 旺店通 同步方法 ====================
+
+    /**
+     * 同步盘盈单到旺店通
+     * 
+     * 功能说明:将氚云盘点表单的盘盈结果同步至旺店通
+     * 
+     * 接口:stock_pd_add
+     * 类型:order_type = 1(盘盈)
+     * 
+     * @param warehouseNo 仓库编号
+     * @param specNo 商家编码(物料编码)
+     * @param num 盈余数量
+     * @return 旺店通响应
+     */
+    public WangdianResponse syncInventoryProfitToWangdian(String warehouseNo, String specNo, Integer num) {
+        try {
+            return wangdianService.stockPdAdd(warehouseNo, 1, specNo, num, "盘盈同步");
+        } catch (Exception e) {
+            log.error("同步盘盈单到旺店通失败, specNo: {}", specNo, e);
+            throw e;
+        }
+    }
+
+    /**
+     * 同步盘亏单到旺店通
+     * 
+     * 功能说明:将氚云盘点表单的盘亏结果同步至旺店通
+     * 
+     * 接口:stock_pd_add
+     * 类型:order_type = 2(盘亏)
+     * 
+     * @param warehouseNo 仓库编号
+     * @param specNo 商家编码(物料编码)
+     * @param num 亏损数量
+     * @return 旺店通响应
+     */
+    public WangdianResponse syncInventoryLossToWangdian(String warehouseNo, String specNo, Integer num) {
+        try {
+            return wangdianService.stockPdAdd(warehouseNo, 2, specNo, num, "盘亏同步");
+        } catch (Exception e) {
+            log.error("同步盘亏单到旺店通失败, specNo: {}", specNo, e);
+            throw e;
+        }
+    }
+}

+ 201 - 0
mjava-wlduijie/src/main/java/com/malk/service/wangdian/WangdianService.java

@@ -0,0 +1,201 @@
+package com.malk.service.wangdian;
+
+import com.alibaba.fastjson.JSON;
+import com.malk.config.WlduijieConfig;
+import com.malk.model.wangdian.WangdianRequest;
+import com.malk.model.wangdian.WangdianResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * 旺店通 API 客户端服务
+ * 
+ * 功能说明:提供旺店通ERP系统的API调用能力,实现数据从氚云同步至旺店通
+ * 
+ * 对接方式:API调用(盘盈盘亏单)
+ * 认证方式:MD5签名认证
+ * 
+ * API地址:https://api.wangdian.cn/openapi2/
+ * 
+ * 签名算法:
+ * 1. 将所有参数(除sign外)按key升序排列
+ * 2. 拼接成字符串:key1value1key2value2... + secret
+ * 3. MD5加密后转大写
+ * 
+ * 接口清单(需求文档3.3.1):
+ * | 业务对象 | 源系统 | 目标接口  | 方法 | 核心字段映射                    |
+ * | 盘盈单   | 氚云   | stock_pd_add | POST | 仓库编码->warehouse_no, 物料编码->spec_no, 盈余数量->num, order_type->1 |
+ * | 盘亏单   | 氚云   | stock_pd_add | POST | 仓库编码->warehouse_no, 物料编码->spec_no, 亏损数量->num, order_type->2 |
+ * 
+ * 使用场景:
+ * - 氚云盘点表单审核后,根据差异结果同步至旺店通ERP
+ * - 盘盈单调整库存增加,盘亏单调整库存减少
+ */
+@Service
+public class WangdianService {
+
+    private static final Logger log = LoggerFactory.getLogger(WangdianService.class);
+
+    @Autowired
+    private WlduijieConfig config;
+
+    private final RestTemplate restTemplate = new RestTemplate();
+
+    /**
+     * 创建盘盈盘亏单(单个物料)
+     * 
+     * 功能说明:根据盘点结果调整库存
+     * 
+     * @param warehouseNo 仓库编号
+     * @param orderType 盘盈盘亏类型:1=盘盈,2=盘亏
+     * @param specNo 商家编码(物料编码)
+     * @param num 数量
+     * @param remark 备注
+     * @return 响应结果
+     */
+    public WangdianResponse stockPdAdd(String warehouseNo, Integer orderType, 
+                                       String specNo, Integer num, String remark) {
+        try {
+            TreeMap<String, Object> params = new TreeMap<>();
+            params.put("warehouse_no", warehouseNo);
+            params.put("order_type", orderType);
+            
+            Map<String, Object> item = new HashMap<>();
+            item.put("spec_no", specNo);
+            item.put("num", num);
+            if (remark != null) {
+                item.put("remark", remark);
+            }
+            params.put("items", JSON.toJSONString(new Object[]{item}));
+
+            String response = execute("stock_pd_add", params);
+            log.info("旺店通盘盈盘亏单, warehouseNo: {}, orderType: {}, 响应: {}", 
+                    warehouseNo, orderType, response);
+            
+            return JSON.parseObject(response, WangdianResponse.class);
+        } catch (Exception e) {
+            log.error("旺店通盘盈盘亏单异常, warehouseNo: {}, orderType: {}", warehouseNo, orderType, e);
+            throw new RuntimeException("旺店通盘盈盘亏单异常: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 创建盘盈盘亏单(多个物料)
+     * 
+     * 功能说明:批量创建盘盈盘亏单,提高效率
+     * 
+     * @param warehouseNo 仓库编号
+     * @param orderType 盘盈盘亏类型:1=盘盈,2=盘亏
+     * @param items 明细列表
+     * @return 响应结果
+     */
+    public WangdianResponse stockPdAdd(String warehouseNo, Integer orderType,
+                                       java.util.List<Map<String, Object>> items) {
+        try {
+            TreeMap<String, Object> params = new TreeMap<>();
+            params.put("warehouse_no", warehouseNo);
+            params.put("order_type", orderType);
+            params.put("items", JSON.toJSONString(items));
+
+            String response = execute("stock_pd_add", params);
+            log.info("旺店通批量盘盈盘亏单, warehouseNo: {}, orderType: {}, 响应: {}",
+                    warehouseNo, orderType, response);
+
+            return JSON.parseObject(response, WangdianResponse.class);
+        } catch (Exception e) {
+            log.error("旺店通批量盘盈盘亏单异常, warehouseNo: {}, orderType: {}", warehouseNo, orderType, e);
+            throw new RuntimeException("旺店通批量盘盈盘亏单异常: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 执行旺店通API请求
+     * 
+     * 功能说明:发送HTTP请求到旺店通API,包含签名计算
+     * 
+     * 签名算法说明:
+     * 1. 收集所有参数(method, appkey, sid, timestamp, v)
+     * 2. 按key升序排列
+     * 3. 拼接:key1value1key2value2... + secret
+     * 4. MD5加密后转大写
+     * 
+     * @param method 接口方法名
+     * @param params 业务参数
+     * @return 响应字符串
+     */
+    private String execute(String method, Map<String, Object> params) {
+        long timestamp = System.currentTimeMillis();
+        
+        // 构建签名前的参数(包含认证参数和业务参数)
+        TreeMap<String, Object> signParams = new TreeMap<>();
+        signParams.put("appkey", config.getWangdian().getAppkey());
+        signParams.put("method", method);
+        signParams.put("sid", config.getWangdian().getSid());
+        signParams.put("timestamp", timestamp);
+        signParams.put("v", 1);
+        signParams.putAll(params);
+        
+        // 生成签名
+        String sign = generateSign(signParams);
+
+        // 构建请求体
+        Map<String, Object> body = new HashMap<>();
+        body.put("appkey", config.getWangdian().getAppkey());
+        body.put("method", method);
+        body.put("sid", config.getWangdian().getSid());
+        body.put("timestamp", timestamp);
+        body.put("sign", sign);
+        body.put("v", 1);
+        body.put("format", "json");
+        body.putAll(params);
+
+        String url = config.getWangdian().getBaseUrl();
+        return restTemplate.postForObject(url, body, String.class);
+    }
+
+    /**
+     * 生成MD5签名
+     * 
+     * 功能说明:计算旺店通API请求签名
+     * 
+     * 算法:
+     * 1. 将所有参数按key升序排列
+     * 2. 拼接成字符串:key1value1key2value2...
+     * 3. 追加密钥:+ secret
+     * 4. MD5加密后转大写
+     * 
+     * @param params 签名参数
+     * @return 签名字符串(大写)
+     */
+    private String generateSign(TreeMap<String, Object> params) {
+        StringBuilder sb = new StringBuilder();
+        for (Map.Entry<String, Object> entry : params.entrySet()) {
+            sb.append(entry.getKey()).append(entry.getValue());
+        }
+        // 追加密钥
+        sb.append(config.getWangdian().getSecret());
+        
+        try {
+            java.security.MessageDigest md = java.security.MessageDigest.getInstance("MD5");
+            byte[] digest = md.digest(sb.toString().getBytes("UTF-8"));
+            StringBuilder hexString = new StringBuilder();
+            for (byte b : digest) {
+                String hex = Integer.toHexString(0xff & b);
+                if (hex.length() == 1) {
+                    hexString.append("0");
+                }
+                hexString.append(hex);
+            }
+            return hexString.toString().toUpperCase();
+        } catch (Exception e) {
+            throw new RuntimeException("MD5签名异常", e);
+        }
+    }
+}

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

@@ -0,0 +1,74 @@
+# 环境配置
+server:
+  port: 8088
+  servlet:
+    context-path: /api/wuliu
+
+# 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-wlduijie/src/main/resources/application-prod.yml

@@ -0,0 +1,61 @@
+server:
+  port: 8088
+  servlet:
+    context-path: /api/wuliu
+
+# 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==

+ 73 - 0
mjava-wlduijie/src/main/resources/application.yml

@@ -0,0 +1,73 @@
+# 五路对接模块配置文件
+# 
+# 功能说明:配置氚云、金蝶云·星空、旺店通三大系统的连接信息
+
+server:
+  # 服务端口
+  port: 9101
+
+spring:
+  profiles:
+    # 激活的生产环境配置
+    active: prod
+  devtools:
+    restart:
+      # 禁用热重启(开发时可选开启)
+      enabled: false
+
+# ==================== 五路对接系统配置 ====================
+wlduijie:
+  # 金蝶云·星空配置
+  # 对接方式:API主动推送(氚云->金蝶)和事件订阅(金蝶->氚云)
+  # 认证方式:AuthService.ValidateUser 获取SessionId
+  kingdee:
+    # API服务器地址,如:https://wuliustyle.test.kdgalaxy.com/kapi/v2
+    host: https://wuliustyle.test.kdgalaxy.com/kapi/v2
+    # 账套ID,用于登录认证
+    acctId: your_acct_id
+    # 用户名
+    userName: your_username
+    # 密码
+    password: your_password
+    # 应用ID(可选)
+    appId: your_app_id
+    # 应用密钥(可选)
+    appSecret: your_app_secret
+
+  # 氚云配置
+  # 对接方式:API调用(CreateBizObject/UpdateFormData等)
+  # 认证方式:Header携带EngineCode和EngineSecret
+  h3yun:
+    baseUrl: https://www.h3yun.com/OpenApi/Invoke
+    # 引擎编码
+    engineCode: your_engine_code
+    # 引擎密钥
+    engineSecret: your_engine_secret
+
+  # 旺店通配置
+  # 对接方式:API调用(盘盈盘亏单等)
+  # 认证方式:MD5签名(appkey + sid + timestamp + secret)
+  wangdian:
+    baseUrl: https://api.wangdian.cn/openapi2/
+    # 应用Key
+    appkey: your_appkey
+    # 店铺ID
+    sid: your_sid
+    # 应用密钥(用于生成MD5签名)
+    secret: your_secret
+
+# ==================== 定时任务配置 ====================
+spel:
+  # 定时任务开关(true=启用,false=禁用)
+  scheduling: true
+
+# ==================== 日志配置 ====================
+logging:
+  level:
+    # 项目日志级别
+    com.malk: info
+    # Spring框架日志级别
+    org.springframework: warn
+  file:
+    # 日志文件目录(相对于项目根目录)
+    path: ./log

+ 85 - 0
mjava-wlh3tok3/pom.xml

@@ -0,0 +1,85 @@
+<?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>
+    <version>0.0.1</version>
+
+    <artifactId>mjava-wlh3tok3</artifactId>
+    <description>氚云-金蝶-旺店通三系统集成中间件</description>
+
+    <properties>
+        <maven.compiler.source>1.8</maven.compiler.source>
+        <maven.compiler.target>1.8</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.squareup.okhttp3</groupId>
+            <artifactId>okhttp</artifactId>
+            <version>4.10.0</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.8.1</version>
+                <configuration>
+                    <source>1.8</source>
+                    <target>1.8</target>
+                    <encoding>UTF-8</encoding>
+                    <proc>none</proc>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <version>2.2.13.RELEASE</version>
+                <configuration>
+                    <mainClass>com.malk.Boot</mainClass>
+                    <includeSystemScope>true</includeSystemScope>
+                    <fork>false</fork>
+                    <jvmArguments>-Dfile.encoding=UTF-8</jvmArguments>
+                </configuration>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+        <finalName>${project.artifactId}</finalName>
+    </build>
+</project>

+ 45 - 0
mjava-wlh3tok3/src/main/java/com/malk/Boot.java

@@ -0,0 +1,45 @@
+package com.malk;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
+import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
+import org.springframework.scheduling.annotation.EnableScheduling;
+
+/**
+ * 氚云-金蝶-旺店通三系统集成中间件启动类
+ * 
+ * 功能说明:企业数据集成平台中间件的启动入口
+ * 
+ * 项目说明:
+ * - 模块名称:mjava-wlh3tok3
+ * - 功能定位:实现氚云、金蝶云·星空、旺店通三大系统的数据互联互通
+ * 
+ * 系统集成架构(需求文档2):
+ * 1. 氚云 -> 金蝶:API主动推送模式
+ *    - 基础档案:采用KAPI或标准WebAPI推送
+ *    - 业务单据:采用标准WebAPI推送
+ *    - 状态反馈:同步完成后回写状态至氚云
+ * 2. 金蝶 -> 氚云:事件订阅模式(Webhook)
+ *    - 金蝶审核操作触发Webhook推送至中间件
+ *    - 中间件查询详情并写入氚云
+ * 3. 氚云 -> 旺店通:定时任务/触发推送模式
+ *    - 抓取氚云盘点数据,推送至旺店通
+ * 
+ * 配置说明:
+ * - 配置文件:application.yml
+ * - 配置前缀:h3tok3
+ * - 需配置:金蝶API地址、氚云认证信息、旺店通API信息
+ * 
+ * 启动说明:
+ * - 排除数据库自动配置(当前模块不依赖数据库)
+ * - 默认端口:9102
+ */
+@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class})
+@EnableScheduling
+public class Boot {
+//netstat -ano 查看服务器实际端口
+    public static void main(String... args) {
+        SpringApplication.run(Boot.class, args);
+    }
+}

+ 180 - 0
mjava-wlh3tok3/src/main/java/com/malk/config/H3tok3Config.java

@@ -0,0 +1,180 @@
+package com.malk.config;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * 氚云-金蝶-旺店通三系统集成配置类
+ * 
+ * 功能说明:配置金蝶云·星空、氚云、旺店通三大系统的连接信息
+ * 
+ * 配置项说明(在application.yml中使用h3tok3前缀):
+ * - h3tok3.kingdee: 金蝶系统配置
+ * - h3tok3.h3yun: 氚云系统配置  
+ * - h3tok3.wangdian: 旺店通系统配置
+ */
+@Configuration
+@ConfigurationProperties(prefix = "h3tok3")
+public class H3tok3Config {
+
+    /** 金蝶云·星空配置 */
+    private KingdeeConfig kingdee = new KingdeeConfig();
+    
+    /** 氚云配置 */
+    private H3yunConfig h3yun = new H3yunConfig();
+    
+    /** 旺店通配置 */
+    private WangdianConfig wangdian = new WangdianConfig();
+
+    // ==================== getter/setter ====================
+    
+    public KingdeeConfig getKingdee() {
+        return kingdee;
+    }
+
+    public void setKingdee(KingdeeConfig kingdee) {
+        this.kingdee = kingdee;
+    }
+
+    public H3yunConfig getH3yun() {
+        return h3yun;
+    }
+
+    public void setH3yun(H3yunConfig h3yun) {
+        this.h3yun = h3yun;
+    }
+
+    public WangdianConfig getWangdian() {
+        return wangdian;
+    }
+
+    public void setWangdian(WangdianConfig wangdian) {
+        this.wangdian = wangdian;
+    }
+
+    /**
+     * 金蝶云·星空配置类
+     * 
+     * 对接方式:API主动推送(氚云->金蝶)和事件订阅(金蝶->氚云)
+     * 
+     * 认证流程(新的AccessToken认证方式):
+     * 1. 调用 /api/getAppToken.do 获取 app_token
+     * 2. 调用 /api/login.do 获取 access_token
+     * 3. 在后续请求头中添加 AccessToken
+     * 
+     * API地址:https://wuliustyle.test.kdgalaxy.com/kapi/v2/{接口名}
+     */
+    public static class KingdeeConfig {
+        /** 金蝶API服务器地址,如:https://wuliustyle.test.kdgalaxy.com */
+        private String host;
+        
+        /** API基础路径,如:/kapi/v2 */
+        private String basePath = "/kapi/v2";
+        
+        /** 账套ID,用于登录认证 */
+        private String acctId;
+        
+        /** 用户名,用于登录认证 */
+        private String userName;
+        
+        /** 密码,用于登录认证 */
+        private String password;
+        
+        /** 应用ID(AppId),用于获取app_token */
+        private String appId;
+        
+        /** 应用密钥(AppSecret),用于获取app_token */
+        private String appSecret;
+        
+        /** 租户ID(tenantid),如:wuliustyle.test */
+        private String tenantId;
+        
+        /** 账户ID(accountId),如:2375638172857100288 */
+        private String accountId;
+
+        /** x-acgw-identity:调用API的身份标识,第三方应用启用后自动颁发 */
+        private String acgwIdentity;
+
+        // ==================== getter/setter ====================
+
+        public String getHost() { return host; }
+        public void setHost(String host) { this.host = host; }
+        public String getBasePath() { return basePath; }
+        public void setBasePath(String basePath) { this.basePath = basePath; }
+        public String getAcctId() { return acctId; }
+        public void setAcctId(String acctId) { this.acctId = acctId; }
+        public String getUserName() { return userName; }
+        public void setUserName(String userName) { this.userName = userName; }
+        public String getPassword() { return password; }
+        public void setPassword(String password) { this.password = password; }
+        public String getAppId() { return appId; }
+        public void setAppId(String appId) { this.appId = appId; }
+        public String getAppSecret() { return appSecret; }
+        public void setAppSecret(String appSecret) { this.appSecret = appSecret; }
+        public String getTenantId() { return tenantId; }
+        public void setTenantId(String tenantId) { this.tenantId = tenantId; }
+        public String getAccountId() { return accountId; }
+        public void setAccountId(String accountId) { this.accountId = accountId; }
+        public String getAcgwIdentity() { return acgwIdentity; }
+        public void setAcgwIdentity(String acgwIdentity) { this.acgwIdentity = acgwIdentity; }
+    }
+
+    /**
+     * 氚云配置类
+     * 
+     * 对接方式:API调用(CreateBizObject/UpdateFormData等)
+     * 认证方式:Header携带EngineCode和EngineSecret
+     * API地址:https://www.h3yun.com/OpenApi/Invoke
+     */
+    public static class H3yunConfig {
+        /** 氚云API基础地址 */
+        private String baseUrl = "https://www.h3yun.com/OpenApi/Invoke";
+        
+        /** 引擎编码,用于API认证 */
+        private String engineCode;
+        
+        /** 引擎密钥,用于API认证 */
+        private String engineSecret;
+
+        // ==================== getter/setter ====================
+        
+        public String getBaseUrl() { return baseUrl; }
+        public void setBaseUrl(String baseUrl) { this.baseUrl = baseUrl; }
+        public String getEngineCode() { return engineCode; }
+        public void setEngineCode(String engineCode) { this.engineCode = engineCode; }
+        public String getEngineSecret() { return engineSecret; }
+        public void setEngineSecret(String engineSecret) { this.engineSecret = engineSecret; }
+    }
+
+    /**
+     * 旺店通配置类
+     * 
+     * 对接方式:API调用(盘盈盘亏单等)
+     * 认证方式:请求体包含appkey、sid、timestamp、sign(MD5签名)
+     * API地址:https://api.wangdian.cn/openapi2/
+     */
+    public static class WangdianConfig {
+        /** 旺店通API基础地址 */
+        private String baseUrl = "https://api.wangdian.cn/openapi2/";
+        
+        /** 应用Key,用于API认证 */
+        private String appkey;
+        
+        /** 店铺ID,用于API认证 */
+        private String sid;
+        
+        /** 应用密钥,用于生成签名 */
+        private String secret;
+
+        // ==================== getter/setter ====================
+        
+        public String getBaseUrl() { return baseUrl; }
+        public void setBaseUrl(String baseUrl) { this.baseUrl = baseUrl; }
+        public String getAppkey() { return appkey; }
+        public void setAppkey(String appkey) { this.appkey = appkey; }
+        public String getSid() { return sid; }
+        public void setSid(String sid) { this.sid = sid; }
+        public String getSecret() { return secret; }
+        public void setSecret(String secret) { this.secret = secret; }
+    }
+}

+ 180 - 0
mjava-wlh3tok3/src/main/java/com/malk/controller/KingdeeWebhookController.java

@@ -0,0 +1,180 @@
+package com.malk.controller;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.malk.service.sync.SyncService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.*;
+
+/**
+ * 金蝶Webhook控制器
+ *
+ * 功能说明:接收金蝶系统的事件推送,实现金蝶->氚云的数据同步
+ *
+ * 接口地址:POST /api/kingdee/webhook/receive
+ *
+ * 工作流程:
+ * 1. 金蝶BOS平台配置事件订阅
+ * 2. 审核操作触发Webhook推送至中间件
+ * 3. 中间件解析事件,判断是否为审核操作(operation=audit)
+ * 4. 立即返回200,延迟2秒后异步执行数据同步
+ * 5. 根据单据类型名称分发到对应的同步方法
+ * 6. 数据清洗与映射
+ * 7. 调用氚云API写入数据
+ *
+ * 单据类型判断规则(根据billtype.name包含关键字):
+ * | 关键字      | 单据类型       | 同步方法                       |
+ * | 采购入库    | 采购入库单     | syncPurchaseInstockFromKingdee |
+ * | 采购退料    | 采购退料单     | syncPurchaseReturnFromKingdee  |
+ * | 销售出库    | 销售出库单     | syncSaleOutstockFromKingdee    |
+ * | 销售退货    | 销售退货单     | syncSaleReturnFromKingdee      |
+ *
+ * 注意事项:
+ * - 需在金蝶BOS平台配置事件推送
+ * - 只处理审核(operation=audit)操作,其他操作跳过
+ * - 需实现幂等性:利用单据编号查询氚云是否已存在
+ */
+@RestController
+@RequestMapping("/api/kingdee")
+public class KingdeeWebhookController {
+
+    private static final Logger log = LoggerFactory.getLogger(KingdeeWebhookController.class);
+
+    /** 专用异步线程池:核心4线程,最大8线程,队列容量100,超出直接丢弃并记录日志 */
+    private final ExecutorService webhookExecutor = new ThreadPoolExecutor(
+            4, 8, 60L, TimeUnit.SECONDS,
+            new LinkedBlockingQueue<>(100),
+            (r, executor) -> log.warn("Webhook异步任务队列已满,丢弃任务")
+    );
+
+    @Autowired
+    private SyncService syncService;
+
+    /**
+     * 接收金蝶Webhook事件推送
+     *
+     * 功能说明:金蝶事件订阅的回调接口,立即返回后异步处理数据同步
+     *
+     * 工作流程:
+     * 1. 接收金蝶审核事件推送
+     * 2. 立即返回200给金蝶,避免超时
+     * 3. 延迟2秒后异步执行数据同步
+
+     */
+    @PostMapping("/webhook/receive")
+    public Map<String, Object> receiveWebhook(@RequestBody String payload) {
+        try {
+            log.info("收到金蝶 webhook 推送: {}", payload);
+
+            // 1. 解析事件数据
+            JSONObject json = JSON.parseObject(payload);
+
+            // 2. 获取操作类型(operation字段表示审核操作)
+            String operation = json.getString("operation");
+            if (!"audit".equalsIgnoreCase(operation)) {
+                log.info("非审核操作,跳过处理. operation: {}", operation);
+                return buildResponse(true, "非审核操作,已跳过");
+            }
+
+            // 3. 从data节点获取单据信息
+            JSONObject data = json.getJSONObject("data");
+            if (data == null) {
+                log.warn("未获取到data节点,跳过处理");
+                return buildResponse(false, "未获取到data节点");
+            }
+
+            // 4. 获取单据编号
+            String billNo = data.getString("billno");
+            if (billNo == null || billNo.isEmpty()) {
+                log.warn("未获取到单据编号,跳过处理");
+                return buildResponse(false, "未获取到单据编号");
+            }
+
+            // 5. 获取单据类型信息
+            JSONObject billtype = data.getJSONObject("billtype");
+            String billtypeId = data.getString("id") ;//billtype != null ? billtype.getString("id") : null;
+            String billtypeName = billtype != null ? billtype.getString("name") : null;
+
+            log.info("解析单据信息, billNo: {}, billtypeId: {}, billtypeName: {}", billNo, billtypeId, billtypeName);
+
+            if (billtypeName == null || billtypeName.isEmpty()) {
+                log.warn("未获取到单据类型名称,跳过处理");
+                return buildResponse(false, "未获取到单据类型名称");
+            }
+
+            // 6. 立即返回200,异步执行业务逻辑(延迟2秒)
+            final String finalBillNo = billNo;
+            final String finalBilltypeId = billtypeId;
+            final String finalBilltypeName = billtypeName;
+
+            CompletableFuture.runAsync(() -> {
+                try {
+                    TimeUnit.SECONDS.sleep(2);
+                    handleKingdeeAuditEvent(data, finalBillNo, finalBilltypeId, finalBilltypeName);
+                } catch (Exception e) {
+                    log.error("异步处理金蝶 webhook 异常, billNo: {}", finalBillNo, e);
+                }
+            }, webhookExecutor);
+
+            return buildResponse(true, "已接收,正在异步处理");
+        } catch (Exception e) {
+            log.error("处理金蝶 webhook 异常", e);
+            return buildResponse(false, "处理异常: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 处理金蝶审核事件
+     *
+     * 功能说明:根据单据类型名称包含的关键字分发到对应的同步处理方法
+     *
+     * @param json 原始JSON对象
+     * @param billNo 单据编号
+     * @param billtypeId 单据类型ID
+     * @param billtypeName 单据类型名称
+     */
+    private void handleKingdeeAuditEvent(JSONObject json, String billNo, String billtypeId, String billtypeName) {
+        log.info("处理金蝶审核事件, billNo: {}, billtypeId: {}, billtypeName: {}", billNo, billtypeId, billtypeName);
+
+        // 根据单据类型名称包含的关键字判断走哪个方法
+        if (billtypeName.contains("采购入库")) {
+            // 采购入库单 -> 氚云采购入库
+            log.info("匹配到采购入库单, 调用syncPurchaseInstockFromKingdee");
+            syncService.syncPurchaseInstockFromKingdee(billtypeId, billNo,json,billtypeName);
+
+        } else if (billtypeName.contains("采购退料")) {
+            // 采购退料单 -> 氚云采购退料
+            log.info("匹配到采购退料单, 调用syncPurchaseReturnFromKingdee");
+            syncService.syncPurchaseReturnFromKingdee(billtypeId, billNo,json,billtypeName);
+
+        } else if (billtypeName.contains("销售出库")) {
+            // 销售出库单 -> 氚云销售出库
+            log.info("匹配到销售出库单, 调用syncSaleOutstockFromKingdee");
+            syncService.syncSaleOutstockFromKingdee(billtypeId, billNo,json,billtypeName);
+
+        } else if (billtypeName.contains("销售退货")) {
+            // 销售退货单 -> 氚云销售退货
+            log.info("匹配到销售退货单, 调用syncSaleReturnFromKingdee");
+            syncService.syncSaleReturnFromKingdee(billtypeId, billNo,json,billtypeName);
+
+        } else {
+            log.warn("未匹配的单据类型名称: {}", billtypeName);
+        }
+    }
+
+    /**
+     * 构建响应结果
+     */
+    private Map<String, Object> buildResponse(boolean success, String message) {
+        Map<String, Object> result = new HashMap<>();
+        result.put("ResultStatus", success ? 1 : 0);
+        result.put("Message", message);
+        return result;
+    }
+}

+ 383 - 0
mjava-wlh3tok3/src/main/java/com/malk/controller/SyncController.java

@@ -0,0 +1,383 @@
+package com.malk.controller;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.malk.model.KingdeeSaveResponse;
+import com.malk.service.h3yun.H3yunService;
+import com.malk.service.sync.SyncService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 数据同步控制器
+ *
+ * 功能说明:提供氚云到金蝶的数据同步接口
+ *
+ * 接口说明:
+ * 1. 基础档案同步(供应商、客户、物料、仓库)
+ * 2. 业务单据同步(采购申请单、销售订单等)
+ *
+ * 状态回写(需求文档4.5):
+ * 所有同步操作完成后,都需要将同步结果回写至氚云表单
+ */
+@RestController
+@RequestMapping("/api/sync")
+public class SyncController {
+
+    private static final Logger log = LoggerFactory.getLogger(SyncController.class);
+
+    /** 异步任务执行器 */
+    private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(4);
+
+    @Autowired
+    private SyncService syncService;
+
+    @Autowired
+    private H3yunService h3yunService;
+
+    /**
+     * 同步供应商档案到金蝶
+     *
+     * @param code 供应商编码
+     * @param name 供应商名称
+     * @param createOrgId 创建组织ID
+     * @param useOrgId 使用组织ID
+     * @param creditCode 社会信用代码
+     * @param h3yunObjectId 氚云对象ID(用于回写状态)
+     * @return 同步结果
+     */
+    @PostMapping("/supplier")
+    public Map<String, Object> syncSupplier(
+            @RequestParam String code,
+            @RequestParam String name,
+            @RequestParam(required = false) String createOrgId,
+            @RequestParam(required = false) String useOrgId,
+            @RequestParam(required = false) String creditCode,
+            @RequestParam(required = false) String h3yunObjectId) {
+        try {
+            KingdeeSaveResponse response = syncService.syncSupplierToKingdee(
+                    code, name, createOrgId, useOrgId, creditCode);
+
+            // 回写状态到氚云
+            if (h3yunObjectId != null) {
+                writeBackToH3yun(h3yunObjectId, H3yunService.SCHEMA_PURCHASE_INSTOCK,
+                        response.isSuccess(), response.getBillNo(), response.getMessage());
+            }
+
+            return buildResponse(response.isSuccess(), response.getMessage(), response.getBillNo());
+        } catch (Exception e) {
+            log.error("同步供应商档案异常, code: {}", code, e);
+            return buildResponse(false, e.getMessage(), null);
+        }
+    }
+
+    /**
+     * 同步客户档案到金蝶
+     */
+    @PostMapping("/customer")
+    public Map<String, Object> syncCustomer(
+            @RequestParam String code,
+            @RequestParam String name,
+            @RequestParam(required = false) String group,
+            @RequestParam(required = false) String taxNo,
+            @RequestParam(required = false) String h3yunObjectId) {
+        try {
+            KingdeeSaveResponse response = syncService.syncCustomerToKingdee(code, name, group, taxNo);
+
+            if (h3yunObjectId != null) {
+                writeBackToH3yun(h3yunObjectId, H3yunService.SCHEMA_SALE_OUTSTOCK,
+                        response.isSuccess(), response.getBillNo(), response.getMessage());
+            }
+
+            return buildResponse(response.isSuccess(), response.getMessage(), response.getBillNo());
+        } catch (Exception e) {
+            log.error("同步客户档案异常, code: {}", code, e);
+            return buildResponse(false, e.getMessage(), null);
+        }
+    }
+
+    /**
+     * 同步物料档案到金蝶
+     */
+    @PostMapping("/material")
+    public Map<String, Object> syncMaterial(
+            @RequestParam String code,
+            @RequestParam String name,
+            @RequestParam(required = false) String baseUnitId,
+            @RequestParam(required = false) String materialGroup,
+            @RequestParam(required = false) String createOrgId,
+            @RequestParam(required = false) String h3yunObjectId) {
+        try {
+            KingdeeSaveResponse response = syncService.syncMaterialToKingdee(
+                    code, name, baseUnitId, materialGroup, createOrgId);
+
+            if (h3yunObjectId != null) {
+                writeBackToH3yun(h3yunObjectId, H3yunService.SCHEMA_PURCHASE_INSTOCK,
+                        response.isSuccess(), response.getBillNo(), response.getMessage());
+            }
+
+            return buildResponse(response.isSuccess(), response.getMessage(), response.getBillNo());
+        } catch (Exception e) {
+            log.error("同步物料档案异常, code: {}", code, e);
+            return buildResponse(false, e.getMessage(), null);
+        }
+    }
+
+    /**
+     * 同步仓库档案到金蝶
+     */
+    @PostMapping("/stock")
+    public Map<String, Object> syncStock(
+            @RequestParam String code,
+            @RequestParam String name,
+            @RequestParam(required = false) String useOrgId,
+            @RequestParam(required = false) String stockProperty,
+            @RequestParam(required = false) String h3yunObjectId) {
+        try {
+            KingdeeSaveResponse response = syncService.syncStockToKingdee(code, name, useOrgId, stockProperty);
+
+            if (h3yunObjectId != null) {
+                writeBackToH3yun(h3yunObjectId, H3yunService.SCHEMA_PURCHASE_INSTOCK,
+                        response.isSuccess(), response.getBillNo(), response.getMessage());
+            }
+
+            return buildResponse(response.isSuccess(), response.getMessage(), response.getBillNo());
+        } catch (Exception e) {
+            log.error("同步仓库档案异常, code: {}", code, e);
+            return buildResponse(false, e.getMessage(), null);
+        }
+    }
+
+    /**
+     * 同步采购申请单到金蝶
+     */
+    @PostMapping("/purchase-requisition")
+    public Map<String, Object> syncPurchaseRequisition(
+            @RequestParam String applicationOrgId,
+            @RequestParam String date,
+            @RequestParam String materialId,
+            @RequestParam Double reqQty,
+            @RequestParam(required = false) String h3yunObjectId) {
+        try {
+            KingdeeSaveResponse response = syncService.syncPurchaseRequisitionToKingdee(
+                    applicationOrgId, date, materialId, reqQty);
+
+            if (h3yunObjectId != null) {
+                writeBackToH3yun(h3yunObjectId, H3yunService.SCHEMA_PURCHASE_INSTOCK,
+                        response.isSuccess(), response.getBillNo(), response.getMessage());
+            }
+
+            return buildResponse(response.isSuccess(), response.getMessage(), response.getBillNo());
+        } catch (Exception e) {
+            log.error("同步采购申请单异常", e);
+            return buildResponse(false, e.getMessage(), null);
+        }
+    }
+
+    /**
+     * 同步销售订单到金蝶
+     */
+    @PostMapping("/sale-order")
+    public Map<String, Object> syncSaleOrder(
+            @RequestParam String saleOrgId,
+            @RequestParam String customerId,
+            @RequestParam String materialId,
+            @RequestParam Double qty,
+            @RequestParam(required = false) String h3yunObjectId) {
+        try {
+            KingdeeSaveResponse response = syncService.syncSaleOrderToKingdee(
+                    saleOrgId, customerId, materialId, qty);
+
+            if (h3yunObjectId != null) {
+                writeBackToH3yun(h3yunObjectId, H3yunService.SCHEMA_SALE_OUTSTOCK,
+                        response.isSuccess(), response.getBillNo(), response.getMessage());
+            }
+
+            return buildResponse(response.isSuccess(), response.getMessage(), response.getBillNo());
+        } catch (Exception e) {
+            log.error("同步销售订单异常", e);
+            return buildResponse(false, e.getMessage(), null);
+        }
+    }
+
+    /**
+     * 氚云->金蝶统一同步接口
+     *
+     * 功能说明:根据氚云表单ID和数据ID,查询氚云数据后根据对接类型同步到金蝶
+     *
+     * 请求参数:
+     * - schemaCode: 氚云表单编码
+     * - objectId: 氚云数据ID
+     * - syncType: 对接类型
+     *   - SUPPLIER: 供应商档案
+     *   - CUSTOMER: 客户档案
+     *   - MATERIAL: 物料档案
+     *   - STOCK: 仓库档案
+     *   - PURCHASE_REQUISITION: 采购申请单
+     *   - SALE_ORDER: 销售订单
+     *   - PURCHASE_RECEIVE: 收料通知单
+     *   - PURCHASE_RETURN: 采购退料申请
+     *   - DELIVERY_NOTICE: 发货通知单
+     *   - RETURN_REQUEST: 退货申请单
+     *   - PAYMENT_REQUEST: 付款申请单
+     *   - PAYABLE_BILL: 财务应付单
+     *   - PURCHASE_INVOICE: 采购发票
+     *   - SALES_INVOICE: 销售发票
+     *   - RECEIVE_BILL: 收款单
+     *   - INVOICE_APPLY: 开票申请单
+     *
+     * @return 立即返回接收成功,3秒后异步执行同步
+     */
+    @PostMapping("/h3yun-to-kingdee")
+    public Map<String, Object> syncH3yunToKingdee(
+            @RequestBody Map<String, String> data) {
+        final String schemaCode = data.get("schemaCode");
+        final String objectId = data.get("objectId");
+        final String syncType = data.get("syncType");
+        log.info("收到氚云->金蝶同步请求, schemaCode: {}, objectId: {}, syncType: {}, 3秒后异步执行",
+                schemaCode, objectId, syncType);
+        // 立即返回成功
+        Map<String, Object> immediateResponse = new HashMap<>();
+        immediateResponse.put("success", true);
+        immediateResponse.put("message", "请求已接收,3秒后异步执行");
+        immediateResponse.put("schemaCode", schemaCode);
+        immediateResponse.put("objectId", objectId);
+
+
+        if ("获取分仓库存".equals(syncType)) {
+            Map<String, Object> result = syncService.syncKingdeeInventorySumToH3yun();
+            log.info("金蝶库存汇总同步完成, result: {}", JSONObject.toJSONString(result));
+            return immediateResponse; // 这里按你当前接口返回类型直接结束
+        }
+
+    // 原有逻辑继续
+        // 异步执行同步任务
+        executor.schedule(() -> {
+            try {
+                log.info("开始执行异步同步任务, objectId: {}", objectId);
+
+                // 1. 先更新氚云数据状态为"同步中"
+                h3yunService.updateSyncStatus(schemaCode, objectId,
+                        H3yunService.SYNC_STATUS_SYNCING, "同步中", null);
+
+                // 2. 执行同步
+                KingdeeSaveResponse response = syncService.syncToKingdeeByType(schemaCode, objectId, syncType);
+
+                // 3. 回写同步结果到氚云
+                if (response.isSuccess()) {
+                    String k3Id = response.getFirstSuccessId();
+                    h3yunService.updateSyncStatus(schemaCode, objectId,
+                            H3yunService.SYNC_STATUS_SUCCESS, "Success", response.getBillNo(), k3Id);
+                    log.info("氚云->金蝶同步成功, objectId: {}, billNo: {}, k3Id: {}", objectId, response.getBillNo(), k3Id);
+                } else {
+                    String errorMsg = response.getErrorInfo();
+                    h3yunService.updateSyncStatus(schemaCode, objectId,
+                            H3yunService.SYNC_STATUS_FAIL, errorMsg, null);
+                    log.error("氚云->金蝶同步失败, objectId: {}, error: {}", objectId, errorMsg);
+                }
+            } catch (Exception e) {
+                log.error("异步同步任务异常, objectId: {}", objectId, e);
+                try {
+                    h3yunService.updateSyncStatus(schemaCode, objectId,
+                            H3yunService.SYNC_STATUS_FAIL, e.getMessage(), null);
+                } catch (Exception ex) {
+                    log.error("回写失败状态异常, objectId: {}", objectId, ex);
+                }
+            }
+        }, 3, TimeUnit.SECONDS);
+
+        return immediateResponse;
+    }
+
+    /**
+     * 获取支持的对接类型列表
+     */
+    @GetMapping("/sync-types")
+    public Map<String, Object> getSyncTypes() {
+        Map<String, Object> result = new HashMap<>();
+        result.put("success", true);
+
+        List<Map<String, String>> types = new ArrayList<>();
+        types.add(createSyncTypeInfo(H3yunService.SYNC_TYPE_SUPPLIER, "供应商档案", "basedata/bd_supplier/add"));
+        types.add(createSyncTypeInfo(H3yunService.SYNC_TYPE_CUSTOMER, "客户档案", "basedata/bd_customer/add"));
+        types.add(createSyncTypeInfo(H3yunService.SYNC_TYPE_MATERIAL, "物料档案", "basedata/material/add"));
+        types.add(createSyncTypeInfo(H3yunService.SYNC_TYPE_STOCK, "仓库档案", "BD_STOCK"));
+        types.add(createSyncTypeInfo(H3yunService.SYNC_TYPE_PURCHASE_REQUISITION, "采购申请单", "PUR_Requisition"));
+        types.add(createSyncTypeInfo(H3yunService.SYNC_TYPE_SALE_ORDER, "销售订单", "SAL_SaleOrder"));
+        types.add(createSyncTypeInfo(H3yunService.SYNC_TYPE_PURCHASE_RECEIVE, "收料通知单", "PUR_ReceiveNotice"));
+        types.add(createSyncTypeInfo(H3yunService.SYNC_TYPE_PURCHASE_RETURN, "采购退料申请", "PUR_ReturnRequest"));
+        types.add(createSyncTypeInfo(H3yunService.SYNC_TYPE_DELIVERY_NOTICE, "发货通知单", "SAL_DeliveryNotice"));
+        types.add(createSyncTypeInfo(H3yunService.SYNC_TYPE_RETURN_REQUEST, "销售退货单", "SAL_ReturnRequest"));
+        types.add(createSyncTypeInfo(H3yunService.SYNC_TYPE_PAYMENT_REQUEST, "付款申请单", "CN_PayableBill"));
+        types.add(createSyncTypeInfo(H3yunService.SYNC_TYPE_PAYABLE_BILL, "财务应付单", "AP_PAYABLE"));
+        types.add(createSyncTypeInfo(H3yunService.SYNC_TYPE_PURCHASE_INVOICE, "采购发票", "IV_PurchaseInvoice"));
+        types.add(createSyncTypeInfo(H3yunService.SYNC_TYPE_SALES_INVOICE, "销售发票", "IV_SalesInvoice"));
+        types.add(createSyncTypeInfo(H3yunService.SYNC_TYPE_RECEIVE_BILL, "收款单", "AR_RECEIVEBILL"));
+        types.add(createSyncTypeInfo(H3yunService.SYNC_TYPE_INVOICE_APPLY, "开票申请单", "IV_InvoiceApply"));
+
+        result.put("data", types);
+        return result;
+    }
+
+    /**
+     * 手动触发:全量同步金蝶库存汇总到氚云分仓库存表
+     */
+    @PostMapping("/kingdee-inventory/full-sync")
+    public Map<String, Object> syncKingdeeInventoryFull() {
+        try {
+            return syncService.syncKingdeeInventorySumToH3yun();
+        } catch (Exception e) {
+            log.error("手动同步金蝶库存汇总到氚云异常", e);
+            Map<String, Object> result = new HashMap<>();
+            result.put("success", false);
+            result.put("message", e.getMessage());
+            return result;
+        }
+    }
+
+    /**
+     * 创建对接类型信息
+     */
+    private Map<String, String> createSyncTypeInfo(String type, String name, String formId) {
+        Map<String, String> info = new HashMap<>();
+        info.put("type", type);
+        info.put("name", name);
+        info.put("formId", formId);
+        return info;
+    }
+
+    /**
+     * 回写同步状态到氚云
+     */
+    private void writeBackToH3yun(String objectId, String schemaCode, boolean success,
+                                  String thirdBillNo, String message) {
+        try {
+            String status = success ? H3yunService.SYNC_STATUS_SUCCESS : H3yunService.SYNC_STATUS_FAIL;
+            String resultMsg = success ? "Success" : message;
+            h3yunService.updateSyncStatus(schemaCode, objectId, status, resultMsg, thirdBillNo);
+        } catch (Exception e) {
+            log.error("回写氚云状态异常, objectId: {}", objectId, e);
+        }
+    }
+
+    /**
+     * 构建响应结果
+     */
+    private Map<String, Object> buildResponse(boolean success, String message, String thirdBillNo) {
+        Map<String, Object> result = new HashMap<>();
+        result.put("success", success);
+        result.put("message", message);
+        result.put("thirdBillNo", thirdBillNo);
+        return result;
+    }
+}

+ 198 - 0
mjava-wlh3tok3/src/main/java/com/malk/controller/WangdianSyncController.java

@@ -0,0 +1,198 @@
+package com.malk.controller;
+
+import com.alibaba.fastjson.JSON;
+import com.malk.model.WangdianResponse;
+import com.malk.service.h3yun.H3yunService;
+import com.malk.service.sync.SyncService;
+import com.malk.service.wangdian.WangdianService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 旺店通同步控制器
+ * 
+ * 功能说明:提供氚云到旺店通的数据同步接口
+ * 
+ * 接口说明:
+ * - 盘盈盘亏单同步
+ * 
+ * 字段映射(需求文档3.3.1):
+ * | 氚云字段     | 旺店通字段    | 说明           |
+ * | 仓库编码     | warehouse_no | 仓库编号       |
+ * | 物料编码     | spec_no      | 商家编码       |
+ * | 盈余数量     | num          | 盘盈数量       |
+ * | 亏损数量     | num          | 盘亏数量       |
+ * | order_type  | order_type   | 1=盘盈, 2=盘亏 |
+ */
+@RestController
+@RequestMapping("/api/wangdian")
+public class WangdianSyncController {
+
+    private static final Logger log = LoggerFactory.getLogger(WangdianSyncController.class);
+
+    @Autowired
+    private SyncService syncService;
+
+    @Autowired
+    private WangdianService wangdianService;
+
+    @Autowired
+    private H3yunService h3yunService;
+
+    /**
+     * 同步盘盈单到旺店通
+     * 
+     * @param warehouseNo 仓库编号
+     * @param specNo 商家编码(物料编码)
+     * @param num 盘盈数量
+     * @param remark 备注
+     * @param h3yunObjectId 氚云对象ID(用于回写状态)
+     * @return 同步结果
+     */
+    @PostMapping("/profit")
+    public Map<String, Object> syncProfit(
+            @RequestParam String warehouseNo,
+            @RequestParam String specNo,
+            @RequestParam Integer num,
+            @RequestParam(required = false) String remark,
+            @RequestParam(required = false) String h3yunObjectId) {
+        try {
+            WangdianResponse response = wangdianService.createProfitOrder(warehouseNo, specNo, num, remark);
+            
+            // 回写状态
+            if (h3yunObjectId != null) {
+                writeBackToH3yun(h3yunObjectId, response.isSuccess(), 
+                        response.isSuccess() ? "Success" : response.getErrorMessage());
+            }
+            
+            return buildResponse(response.isSuccess(), response.getErrorMessage());
+        } catch (Exception e) {
+            log.error("同步盘盈单异常, warehouseNo: {}, specNo: {}", warehouseNo, specNo, e);
+            
+            if (h3yunObjectId != null) {
+                writeBackToH3yun(h3yunObjectId, false, e.getMessage());
+            }
+            
+            return buildResponse(false, e.getMessage());
+        }
+    }
+
+    /**
+     * 同步盘亏单到旺店通
+     */
+    @PostMapping("/loss")
+    public Map<String, Object> syncLoss(
+            @RequestParam String warehouseNo,
+            @RequestParam String specNo,
+            @RequestParam Integer num,
+            @RequestParam(required = false) String remark,
+            @RequestParam(required = false) String h3yunObjectId) {
+        try {
+            WangdianResponse response = wangdianService.createLossOrder(warehouseNo, specNo, num, remark);
+            
+            if (h3yunObjectId != null) {
+                writeBackToH3yun(h3yunObjectId, response.isSuccess(),
+                        response.isSuccess() ? "Success" : response.getErrorMessage());
+            }
+            
+            return buildResponse(response.isSuccess(), response.getErrorMessage());
+        } catch (Exception e) {
+            log.error("同步盘亏单异常, warehouseNo: {}, specNo: {}", warehouseNo, specNo, e);
+            
+            if (h3yunObjectId != null) {
+                writeBackToH3yun(h3yunObjectId, false, e.getMessage());
+            }
+            
+            return buildResponse(false, e.getMessage());
+        }
+    }
+
+    /**
+     * 批量同步盘盈盘亏单
+     */
+    @PostMapping("/batch")
+    public Map<String, Object> syncBatch(
+            @RequestParam String warehouseNo,
+            @RequestBody String itemsJson) {
+        try {
+            log.info("批量同步盘点单到旺店通, warehouseNo: {}, items: {}", warehouseNo, itemsJson);
+            
+            List<Map<String, Object>> items = JSON.parseObject(itemsJson,
+                    new com.alibaba.fastjson.TypeReference<List<Map<String, Object>>>(){});
+            
+            boolean allSuccess = true;
+            String errorMsg = null;
+            
+            // 分拣盘盈和盘亏物料
+            List<Map<String, Object>> profitItems = new java.util.ArrayList<>();
+            List<Map<String, Object>> lossItems = new java.util.ArrayList<>();
+            
+            for (Map<String, Object> item : items) {
+                Integer orderType = (Integer) item.get("order_type");
+                Map<String, Object> wangdianItem = new HashMap<>();
+                wangdianItem.put("spec_no", item.get("spec_no"));
+                wangdianItem.put("num", item.get("num"));
+                if (item.containsKey("remark")) {
+                    wangdianItem.put("remark", item.get("remark"));
+                }
+                
+                if (WangdianService.ORDER_TYPE_PROFIT==orderType) {
+                    profitItems.add(wangdianItem);
+                } else if (WangdianService.ORDER_TYPE_LOSS== orderType) {
+                    lossItems.add(wangdianItem);
+                }
+            }
+            
+            // 同步盘盈单
+            if (!profitItems.isEmpty()) {
+                WangdianResponse profitResponse = wangdianService.createProfitOrder(warehouseNo, profitItems);
+                if (!profitResponse.isSuccess()) {
+                    allSuccess = false;
+                    errorMsg = "盘盈单同步失败: " + profitResponse.getErrorMessage();
+                }
+            }
+            
+            // 同步盘亏单
+            if (!lossItems.isEmpty()) {
+                WangdianResponse lossResponse = wangdianService.createLossOrder(warehouseNo, lossItems);
+                if (!lossResponse.isSuccess()) {
+                    allSuccess = false;
+                    errorMsg = (errorMsg != null ? errorMsg + "; " : "") + "盘亏单同步失败: " + lossResponse.getErrorMessage();
+                }
+            }
+            
+            return buildResponse(allSuccess, errorMsg);
+        } catch (Exception e) {
+            log.error("批量同步盘点单异常, warehouseNo: {}", warehouseNo, e);
+            return buildResponse(false, e.getMessage());
+        }
+    }
+
+    /**
+     * 回写同步状态到氚云
+     */
+    private void writeBackToH3yun(String objectId, boolean success, String message) {
+        try {
+            String status = success ? H3yunService.SYNC_STATUS_SUCCESS : H3yunService.SYNC_STATUS_FAIL;
+            h3yunService.updateSyncStatus(H3yunService.SCHEMA_PURCHASE_INSTOCK, objectId, status, message, null);
+        } catch (Exception e) {
+            log.error("回写氚云状态异常, objectId: {}", objectId, e);
+        }
+    }
+
+    /**
+     * 构建响应结果
+     */
+    private Map<String, Object> buildResponse(boolean success, String message) {
+        Map<String, Object> result = new HashMap<>();
+        result.put("success", success);
+        result.put("message", message);
+        return result;
+    }
+}

+ 63 - 0
mjava-wlh3tok3/src/main/java/com/malk/model/H3yunResponse.java

@@ -0,0 +1,63 @@
+package com.malk.model;
+
+import com.alibaba.fastjson.annotation.JSONField;
+
+/**
+ * 氚云API响应
+ */
+public class H3yunResponse {
+    /** 响应状态码 */
+    private Integer StatusCode;
+    
+    /** 响应消息 */
+    private String Message;
+    
+    /** 业务对象ID */
+    private String BizObjectId;
+    
+    /** 是否成功 */
+    private Boolean Success;
+
+    @JSONField(name = "StatusCode")
+    public Integer getStatusCode() {
+        return StatusCode;
+    }
+
+    public void setStatusCode(Integer statusCode) {
+        StatusCode = statusCode;
+    }
+
+    @JSONField(name = "Message")
+    public String getMessage() {
+        return Message;
+    }
+
+    public void setMessage(String message) {
+        Message = message;
+    }
+
+    @JSONField(name = "BizObjectId")
+    public String getBizObjectId() {
+        return BizObjectId;
+    }
+
+    public void setBizObjectId(String bizObjectId) {
+        BizObjectId = bizObjectId;
+    }
+
+    @JSONField(name = "Success")
+    public Boolean getSuccess() {
+        return Success;
+    }
+
+    public void setSuccess(Boolean success) {
+        Success = success;
+    }
+    
+    /**
+     * 判断是否成功
+     */
+    public boolean isSuccess() {
+        return Success != null && Success;
+    }
+}

+ 44 - 0
mjava-wlh3tok3/src/main/java/com/malk/model/KingdeeAuthRequest.java

@@ -0,0 +1,44 @@
+package com.malk.model;
+
+import com.alibaba.fastjson.annotation.JSONField;
+
+/**
+ * 金蝶API认证请求
+ */
+public class KingdeeAuthRequest {
+    /** 账套ID */
+    private String acctId;
+    
+    /** 用户名 */
+    private String UserName;
+    
+    /** 密码 */
+    private String Password;
+    
+    @JSONField(name = "acctId")
+    public String getAcctId() {
+        return acctId;
+    }
+    
+    public void setAcctId(String acctId) {
+        this.acctId = acctId;
+    }
+    
+    @JSONField(name = "UserName")
+    public String getUserName() {
+        return UserName;
+    }
+    
+    public void setUserName(String userName) {
+        UserName = userName;
+    }
+    
+    @JSONField(name = "Password")
+    public String getPassword() {
+        return Password;
+    }
+    
+    public void setPassword(String password) {
+        Password = password;
+    }
+}

+ 44 - 0
mjava-wlh3tok3/src/main/java/com/malk/model/KingdeeAuthResponse.java

@@ -0,0 +1,44 @@
+package com.malk.model;
+
+import com.alibaba.fastjson.annotation.JSONField;
+
+/**
+ * 金蝶API认证响应
+ */
+public class KingdeeAuthResponse {
+    /** 结果状态:1或100表示成功 */
+    private Integer ResultStatus;
+    
+    /** 会话ID */
+    private String SessionId;
+    
+    /** 错误消息 */
+    private String Message;
+    
+    @JSONField(name = "ResultStatus")
+    public Integer getResultStatus() {
+        return ResultStatus;
+    }
+    
+    public void setResultStatus(Integer resultStatus) {
+        ResultStatus = resultStatus;
+    }
+    
+    @JSONField(name = "SessionId")
+    public String getSessionId() {
+        return SessionId;
+    }
+    
+    public void setSessionId(String sessionId) {
+        SessionId = sessionId;
+    }
+    
+    @JSONField(name = "Message")
+    public String getMessage() {
+        return Message;
+    }
+    
+    public void setMessage(String message) {
+        Message = message;
+    }
+}

+ 17 - 0
mjava-wlh3tok3/src/main/java/com/malk/model/KingdeeRequest.java

@@ -0,0 +1,17 @@
+package com.malk.model;
+
+/**
+ * 金蝶API请求基类
+ */
+public class KingdeeRequest {
+    /** 认证会话ID */
+    private String SessionId;
+    
+    public String getSessionId() {
+        return SessionId;
+    }
+    
+    public void setSessionId(String sessionId) {
+        SessionId = sessionId;
+    }
+}

+ 45 - 0
mjava-wlh3tok3/src/main/java/com/malk/model/KingdeeSaveRequest.java

@@ -0,0 +1,45 @@
+package com.malk.model;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import java.util.Map;
+
+/**
+ * 金蝶单据保存请求
+ */
+public class KingdeeSaveRequest {
+    /** 业务对象FormId */
+    private String FormId;
+    
+    /** 单据数据模型 */
+    private Map<String, Object> Model;
+    
+    /** 是否自动提交审核 */
+    private boolean AutoSubmitAndAudit;
+    
+    @JSONField(name = "FormId")
+    public String getFormId() {
+        return FormId;
+    }
+    
+    public void setFormId(String formId) {
+        FormId = formId;
+    }
+    
+    @JSONField(name = "Model")
+    public Map<String, Object> getModel() {
+        return Model;
+    }
+    
+    public void setModel(Map<String, Object> model) {
+        Model = model;
+    }
+    
+    @JSONField(name = "AutoSubmitAndAudit")
+    public boolean isAutoSubmitAndAudit() {
+        return AutoSubmitAndAudit;
+    }
+    
+    public void setAutoSubmitAndAudit(boolean autoSubmitAndAudit) {
+        AutoSubmitAndAudit = autoSubmitAndAudit;
+    }
+}

+ 200 - 0
mjava-wlh3tok3/src/main/java/com/malk/model/KingdeeSaveResponse.java

@@ -0,0 +1,200 @@
+package com.malk.model;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import java.util.List;
+
+/**
+ * 金蝶单据保存响应
+ */
+public class KingdeeSaveResponse {
+    /** 结果状态:1或100表示成功 */
+    private Integer ResultStatus;
+
+    /** 错误代码,0表示成功 */
+    private String ErrorCode;
+
+    /** 错误消息 */
+    private String Message;
+
+    /** 保存成功的单据编号 */
+    private String BillNo;
+
+    /** 需要重新提交的ID列表 */
+    private List<String> NeedReturnData;
+
+    /** 金蝶返回的ID(如物料ID、单据ID) */
+    private String Id;
+
+    /** 状态(用于某些接口的status字段判断) */
+    private Boolean Status;
+
+    /** data节点完整数据 */
+    private JSONObject Data;
+
+    @JSONField(name = "ResultStatus")
+    public Integer getResultStatus() {
+        return ResultStatus;
+    }
+
+    public void setResultStatus(Integer resultStatus) {
+        ResultStatus = resultStatus;
+    }
+
+    @JSONField(name = "ErrorCode")
+    public String getErrorCode() {
+        return ErrorCode;
+    }
+
+    public void setErrorCode(String errorCode) {
+        ErrorCode = errorCode;
+    }
+
+    @JSONField(name = "Message")
+    public String getMessage() {
+        return Message;
+    }
+
+    public void setMessage(String message) {
+        Message = message;
+    }
+
+    @JSONField(name = "BillNo")
+    public String getBillNo() {
+        return BillNo;
+    }
+
+    public void setBillNo(String billNo) {
+        BillNo = billNo;
+    }
+
+    @JSONField(name = "NeedReturnData")
+    public List<String> getNeedReturnData() {
+        return NeedReturnData;
+    }
+
+    public void setNeedReturnData(List<String> needReturnData) {
+        NeedReturnData = needReturnData;
+    }
+
+    @JSONField(name = "Id")
+    public String getId() {
+        return Id;
+    }
+
+    public void setId(String id) {
+        Id = id;
+    }
+
+    @JSONField(name = "Status")
+    public Boolean getStatus() {
+        return Status;
+    }
+
+    public void setStatus(Boolean status) {
+        Status = status;
+    }
+
+    @JSONField(name = "Data")
+    public JSONObject getData() {
+        return Data;
+    }
+
+    public void setData(JSONObject data) {
+        Data = data;
+    }
+
+    /**
+     * 判断是否成功(兼容多种响应格式)
+     * 1. errorCode 为 "0"
+     * 2. status 为 true
+     * 3. ResultStatus 为 1 或 100
+     */
+    public boolean isSuccess() {
+        // 判断 errorCode 为 "0"
+        if ("0".equals(ErrorCode)) {
+            return true;
+        }
+        // 判断 status 为 true
+        if (Status != null && Status) {
+            return true;
+        }
+        // 兼容旧逻辑
+        return ResultStatus != null && (ResultStatus == 1 || ResultStatus == 100);
+    }
+
+    /**
+     * 获取错误信息
+     * 优先从 errors 数组中获取
+     */
+    public String getErrorInfo() {
+        // 如果有 Message,优先使用
+        if (Message != null && !Message.isEmpty()) {
+            return Message;
+        }
+        // 从 data.result[].errors 中获取
+        if (Data != null) {
+            Object resultObj = Data.get("result");
+            if (resultObj instanceof JSONArray) {
+                JSONArray resultArray = (JSONArray) resultObj;
+                if (resultArray != null && !resultArray.isEmpty()) {
+                    for (int i = 0; i < resultArray.size(); i++) {
+                        Object item = resultArray.get(i);
+                        if (item instanceof JSONObject) {
+                            JSONObject resultItem = (JSONObject) item;
+                            JSONArray errors = resultItem.getJSONArray("errors");
+                            if (errors != null && !errors.isEmpty()) {
+                                return errors.getString(0);
+                            }
+                        }
+                    }
+                }
+            } else if (resultObj instanceof JSONObject) {
+                JSONObject dataObj = (JSONObject) resultObj;
+                JSONArray errors = dataObj.getJSONArray("errors");
+                if (errors != null && !errors.isEmpty()) {
+                    return errors.getString(0);
+                }
+            }
+            // 获取 failCount
+            String failCount = Data.getString("failCount");
+            if (failCount != null && !"0".equals(failCount)) {
+                return "处理失败,failCount: " + failCount;
+            }
+        }
+        return "同步失败";
+    }
+
+    /**
+     * 获取第一个成功记录的ID
+     */
+    public String getFirstSuccessId() {
+        if (Data != null) {
+            Object resultObj = Data.get("result");
+            if (resultObj instanceof JSONArray) {
+                JSONArray resultArray = (JSONArray) resultObj;
+                if (resultArray != null && !resultArray.isEmpty()) {
+                    for (int i = 0; i < resultArray.size(); i++) {
+                        Object item = resultArray.get(i);
+                        if (item instanceof JSONObject) {
+                            JSONObject resultItem = (JSONObject) item;
+                            Boolean billStatus = resultItem.getBoolean("billStatus");
+                            if (billStatus != null && billStatus) {
+                                return resultItem.getString("id");
+                            }
+                        }
+                    }
+                    // 如果没有 billStatus 为 true 的,取第一个
+                    Object firstItem = resultArray.get(0);
+                    if (firstItem instanceof JSONObject) {
+                        return ((JSONObject) firstItem).getString("id");
+                    }
+                }
+            } else if (resultObj instanceof JSONObject) {
+                return ((JSONObject) resultObj).getString("id");
+            }
+        }
+        return Id;
+    }
+}

+ 101 - 0
mjava-wlh3tok3/src/main/java/com/malk/model/KingdeeWebhookEvent.java

@@ -0,0 +1,101 @@
+package com.malk.model;
+
+import com.alibaba.fastjson.annotation.JSONField;
+
+/**
+ * 金蝶Webhook事件
+ * 
+ * 功能说明:接收金蝶事件订阅推送的数据结构
+ * 
+ * 字段说明(需求文档3.2.1):
+ * | 字段        | 类型   | 说明                     |
+ * | InterId     | String | 事件唯一标识             |
+ * | FormId       | String | 业务对象ID               |
+ * | OperateType  | String | 操作类型(如:Audit)    |
+ * | Number       | String | 单据编号                 |
+ */
+public class KingdeeWebhookEvent {
+    /** 事件唯一标识 */
+    private String InterId;
+    
+    /** 业务对象ID */
+    private String FormId;
+    
+    /** 单据类型 */
+    private String BillType;
+    
+    /** 操作类型(Audit=审核) */
+    private String OperateType;
+    
+    /** 用户名 */
+    private String UserName;
+    
+    /** 操作时间戳 */
+    private Long ActionTime;
+    
+    /** 单据编号 */
+    private String Number;
+
+    @JSONField(name = "InterId")
+    public String getInterId() {
+        return InterId;
+    }
+
+    public void setInterId(String interId) {
+        InterId = interId;
+    }
+
+    @JSONField(name = "FormId")
+    public String getFormId() {
+        return FormId;
+    }
+
+    public void setFormId(String formId) {
+        FormId = formId;
+    }
+
+    @JSONField(name = "BillType")
+    public String getBillType() {
+        return BillType;
+    }
+
+    public void setBillType(String billType) {
+        BillType = billType;
+    }
+
+    @JSONField(name = "OperateType")
+    public String getOperateType() {
+        return OperateType;
+    }
+
+    public void setOperateType(String operateType) {
+        OperateType = operateType;
+    }
+
+    @JSONField(name = "UserName")
+    public String getUserName() {
+        return UserName;
+    }
+
+    public void setUserName(String userName) {
+        UserName = userName;
+    }
+
+    @JSONField(name = "ActionTime")
+    public Long getActionTime() {
+        return ActionTime;
+    }
+
+    public void setActionTime(Long actionTime) {
+        ActionTime = actionTime;
+    }
+
+    @JSONField(name = "Number")
+    public String getNumber() {
+        return Number;
+    }
+
+    public void setNumber(String number) {
+        Number = number;
+    }
+}

+ 84 - 0
mjava-wlh3tok3/src/main/java/com/malk/model/SyncResult.java

@@ -0,0 +1,84 @@
+package com.malk.model;
+
+import com.alibaba.fastjson.annotation.JSONField;
+
+/**
+ * 同步结果封装
+ */
+public class SyncResult {
+    /** 是否成功 */
+    private boolean success;
+    
+    /** 结果消息 */
+    private String message;
+    
+    /** 第三方单据编号 */
+    private String thirdBillNo;
+    
+    /** 错误代码 */
+    private String errorCode;
+
+    public SyncResult() {
+    }
+    
+    public SyncResult(boolean success, String message) {
+        this.success = success;
+        this.message = message;
+    }
+    
+    public static SyncResult success(String message) {
+        return new SyncResult(true, message);
+    }
+    
+    public static SyncResult success(String message, String thirdBillNo) {
+        SyncResult result = new SyncResult(true, message);
+        result.setThirdBillNo(thirdBillNo);
+        return result;
+    }
+    
+    public static SyncResult fail(String message) {
+        return new SyncResult(false, message);
+    }
+    
+    public static SyncResult fail(String errorCode, String message) {
+        SyncResult result = new SyncResult(false, message);
+        result.setErrorCode(errorCode);
+        return result;
+    }
+
+    @JSONField(name = "success")
+    public boolean isSuccess() {
+        return success;
+    }
+
+    public void setSuccess(boolean success) {
+        this.success = success;
+    }
+
+    @JSONField(name = "message")
+    public String getMessage() {
+        return message;
+    }
+
+    public void setMessage(String message) {
+        this.message = message;
+    }
+
+    @JSONField(name = "thirdBillNo")
+    public String getThirdBillNo() {
+        return thirdBillNo;
+    }
+
+    public void setThirdBillNo(String thirdBillNo) {
+        this.thirdBillNo = thirdBillNo;
+    }
+
+    @JSONField(name = "errorCode")
+    public String getErrorCode() {
+        return errorCode;
+    }
+
+    public void setErrorCode(String errorCode) {
+        this.errorCode = errorCode;
+    }
+}

+ 122 - 0
mjava-wlh3tok3/src/main/java/com/malk/model/WangdianResponse.java

@@ -0,0 +1,122 @@
+package com.malk.model;
+
+import com.alibaba.fastjson.annotation.JSONField;
+
+/**
+ * 旺店通API响应
+ */
+public class WangdianResponse {
+    /** 响应代码 */
+    private Integer code;
+    
+    /** 响应消息 */
+    private String message;
+    
+    /** 错误信息 */
+    private String error_msg;
+    
+    /** 分页信息 */
+    private WangdianPage page;
+
+    @JSONField(name = "code")
+    public Integer getCode() {
+        return code;
+    }
+
+    public void setCode(Integer code) {
+        this.code = code;
+    }
+
+    @JSONField(name = "message")
+    public String getMessage() {
+        return message;
+    }
+
+    public void setMessage(String message) {
+        this.message = message;
+    }
+
+    @JSONField(name = "error_msg")
+    public String getError_msg() {
+        return error_msg;
+    }
+
+    public void setError_msg(String error_msg) {
+        this.error_msg = error_msg;
+    }
+
+    @JSONField(name = "page")
+    public WangdianPage getPage() {
+        return page;
+    }
+
+    public void setPage(WangdianPage page) {
+        this.page = page;
+    }
+    
+    /**
+     * 判断是否成功(code为0表示成功)
+     */
+    public boolean isSuccess() {
+        return code != null && code == 0;
+    }
+    
+    /**
+     * 获取错误信息
+     */
+    public String getErrorMessage() {
+        if (error_msg != null) {
+            return error_msg;
+        }
+        if (code != null && code != 0) {
+            return message;
+        }
+        return null;
+    }
+    
+    /**
+     * 旺店通分页信息
+     */
+    public static class WangdianPage {
+        private Integer page_no;
+        private Integer page_size;
+        private Integer page_count;
+        private Integer total_count;
+
+        @JSONField(name = "page_no")
+        public Integer getPage_no() {
+            return page_no;
+        }
+
+        public void setPage_no(Integer page_no) {
+            this.page_no = page_no;
+        }
+
+        @JSONField(name = "page_size")
+        public Integer getPage_size() {
+            return page_size;
+        }
+
+        public void setPage_size(Integer page_size) {
+            this.page_size = page_size;
+        }
+
+        @JSONField(name = "page_count")
+        public Integer getPage_count() {
+            return page_count;
+        }
+
+        public void setPage_count(Integer page_count) {
+            this.page_count = page_count;
+        }
+
+        @JSONField(name = "total_count")
+        public Integer getTotal_count() {
+            return total_count;
+        }
+
+        public void setTotal_count(Integer total_count) {
+            this.total_count = total_count;
+        }
+    }
+}

+ 247 - 0
mjava-wlh3tok3/src/main/java/com/malk/schedule/InventorySyncTask.java

@@ -0,0 +1,247 @@
+package com.malk.schedule;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.malk.service.h3yun.H3yunService;
+import com.malk.service.sync.SyncService;
+import com.malk.service.wangdian.WangdianService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.context.event.ApplicationReadyEvent;
+import org.springframework.context.event.EventListener;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 盘点数据同步定时任务
+ * 
+ * 功能说明:定时从氚云抓取盘点数据,同步至旺店通
+ * 
+ * 同步逻辑(需求文档3.3):
+ * 1. 从氚云查询盘点差异结果
+ * 2. 分拣盘盈和盘亏物料
+ * 3. 调用旺店通API同步
+ * 4. 回写同步状态至氚云
+ * 
+ * 执行频率:默认每5分钟执行一次(可配置)
+ */
+@Component
+public class InventorySyncTask {
+
+    private static final Logger log = LoggerFactory.getLogger(InventorySyncTask.class);
+
+    @Autowired
+    private H3yunService h3yunService;
+
+    @Autowired
+    private SyncService syncService;
+
+    @Autowired
+    private WangdianService wangdianService;
+
+    /** 盘点表单SchemaCode(需要根据实际配置) */
+    private static final String INVENTORY_SCHEMA_CODE = "D293655sinventory"; // 示例SchemaCode
+    private static final String Warehouse_division_CODE = "D293655sinventory"; // 示例SchemaCode
+
+//    /**
+//     * 定时同步盘点数据
+//     *
+//     * cron表达式:默认每5分钟执行一次
+//     * 可在配置文件中通过 h3tok3.schedule.inventory-cron 进行配置
+//     */
+////    @Scheduled(cron = "${h3tok3.schedule.inventory-cron:0 */5 * * * ?}")
+////    public void syncInventoryData() {
+////        log.info("开始执行盘点数据同步任务");
+////        try {
+////            // 1. 从氚云查询待同步的盘点单
+////            List<JSONObject> pendingInventoryList = queryPendingInventoryList();
+////
+////            if (pendingInventoryList == null || pendingInventoryList.isEmpty()) {
+////                log.info("没有待同步的盘点数据");
+////                return;
+////            }
+////
+////            log.info("发现 {} 条待同步的盘点数据", pendingInventoryList.size());
+////
+////            // 2. 处理每条盘点单
+////            int successCount = 0;
+////            int failCount = 0;
+////
+////            for (JSONObject inventory : pendingInventoryList) {
+////                boolean success = processInventoryOrder(inventory);
+////                if (success) {
+////                    successCount++;
+////                } else {
+////                    failCount++;
+////                }
+////            }
+////
+////            log.info("盘点数据同步完成, 成功: {}, 失败: {}", successCount, failCount);
+////        } catch (Exception e) {
+////            log.error("执行盘点数据同步任务异常", e);
+////        }
+////    }
+//
+//    /**
+//     * 查询待同步的盘点单列表
+//     */
+//    private List<JSONObject> queryPendingInventoryList() {
+//        try {
+//            // 查询同步状态为"未同步"或"同步失败"的盘点单
+//            String filter = "F_SyncStatus = '未同步' OR F_SyncStatus = '同步失败'";
+//
+//            JSONObject result = h3yunService.queryBizObjects(INVENTORY_SCHEMA_CODE, filter, 100, 0);
+//
+//            // 根据参考案例,响应格式是 ReturnData.BizObjectArray
+//            JSONObject returnData = result.getJSONObject("ReturnData");
+//            if (returnData != null) {
+//                JSONArray bizObjectArray = returnData.getJSONArray("BizObjectArray");
+//                if (bizObjectArray != null) {
+//                    log.info("查询到 {} 条待同步盘点数据", bizObjectArray.size());
+//                    return bizObjectArray.toJavaList(JSONObject.class);
+//                }
+//            }
+//
+//            return new ArrayList<>();
+//        } catch (Exception e) {
+//            log.error("查询待同步盘点单列表异常", e);
+//            return new ArrayList<>();
+//        }
+//    }
+
+    /**
+     * 处理单条盘点单
+     */
+//    private boolean processInventoryOrder(JSONObject inventory) {
+//        String objectId = inventory.getString("ObjectId");
+//        String billNo = inventory.getString("F0000001"); // 单据编号
+//        String warehouseNo = inventory.getString("F0000002"); // 仓库编码
+//
+//        try {
+//            log.info("开始处理盘点单, billNo: {}, warehouse: {}", billNo, warehouseNo);
+//
+//            // 1. 更新状态为"同步中"
+//            updateInventoryStatus(objectId, H3yunService.SYNC_STATUS_SYNCING, null, null);
+//
+//            // 2. 获取明细数据(盘盈和盘亏物料)
+//            JSONArray detailItems = inventory.getJSONArray("F0000010"); // 假设明细字段
+//
+//            if (detailItems == null || detailItems.isEmpty()) {
+//                updateInventoryStatus(objectId, H3yunService.SYNC_STATUS_FAIL, "明细数据为空", null);
+//                return false;
+//            }
+//
+//            // 3. 分拣盘盈和盘亏物料
+//            List<Map<String, Object>> profitItems = new ArrayList<>();
+//            List<Map<String, Object>> lossItems = new ArrayList<>();
+//
+//            for (int i = 0; i < detailItems.size(); i++) {
+//                JSONObject item = detailItems.getJSONObject(i);
+//                String specNo = item.getString("F0000003"); // 物料编码
+//                Integer profitQty = item.getInteger("F0000004"); // 盈余数量
+//                Integer lossQty = item.getInteger("F0000005"); // 亏损数量
+//                String remark = item.getString("F0000006"); // 备注
+//
+//                Map<String, Object> wangdianItem = new HashMap<>();
+//                wangdianItem.put("spec_no", specNo);
+//                wangdianItem.put("remark", remark != null ? remark : "");
+//
+//                if (profitQty != null && profitQty > 0) {
+//                    wangdianItem.put("num", profitQty);
+//                    profitItems.add(wangdianItem);
+//                }
+//
+//                if (lossQty != null && lossQty > 0) {
+//                    wangdianItem = new HashMap<>();
+//                    wangdianItem.put("spec_no", specNo);
+//                    wangdianItem.put("num", lossQty);
+//                    wangdianItem.put("remark", remark != null ? remark : "");
+//                    lossItems.add(wangdianItem);
+//                }
+//            }
+//
+//            // 4. 调用旺店通同步
+//            boolean allSuccess = syncService.syncInventoryToWangdian(warehouseNo, profitItems, lossItems);
+//
+//            // 5. 更新同步结果
+//            if (allSuccess) {
+//                updateInventoryStatus(objectId, H3yunService.SYNC_STATUS_SUCCESS, "Success", billNo);
+//                log.info("盘点单同步成功, billNo: {}", billNo);
+//                return true;
+//            } else {
+//                updateInventoryStatus(objectId, H3yunService.SYNC_STATUS_FAIL, "同步失败", null);
+//                log.error("盘点单同步失败, billNo: {}", billNo);
+//                return false;
+//            }
+//        } catch (Exception e) {
+//            log.error("处理盘点单异常, objectId: {}", objectId, e);
+//            updateInventoryStatus(objectId, H3yunService.SYNC_STATUS_FAIL, e.getMessage(), null);
+//            return false;
+//        }
+//    }
+
+    /**
+     * 更新盘点单同步状态
+     */
+
+
+    /**
+     * 应用启动完成后立即执行一次金蝶库存汇总同步
+     */
+//    @EventListener(ApplicationReadyEvent.class)
+//    public void runInventorySyncOnStartup() {
+//        log.info("应用启动完成,立即执行一次金蝶库存汇总同步任务");
+//        executeKingdeeInventorySync();
+//    }
+
+    /**
+     * 每30分钟全量同步一次金蝶库存汇总到氚云分仓库存表
+     */
+//    @Scheduled(cron = "0 0/30 * * * ?")
+//    public void syncKingdeeInventorySumToH3yun() {
+//        executeKingdeeInventorySync();
+//    }
+
+    /**
+     * 每60分钟全量同步一次金蝶银行行名行号到氚云表
+     */
+//    @Scheduled(cron = "0 0 * * * ?")
+//    public void syncKingdeeBankListToH3yun() {
+//        executeKingdeeBankSync();
+//    }
+
+//    private void executeKingdeeInventorySync() {
+//        log.info("开始执行金蝶库存汇总同步任务");
+//        try {
+//            Map<String, Object> result = syncService.syncKingdeeInventorySumToH3yun();
+//            log.info("金蝶库存汇总同步完成, result: {}", JSONObject.toJSONString(result));
+//        } catch (Exception e) {
+//            log.error("执行金蝶库存汇总同步任务异常", e);
+//        }
+//    }
+//
+//    /**
+//     * 应用启动完成后立即执行一次金蝶银行行名行号同步
+//     */
+//    @EventListener(ApplicationReadyEvent.class)
+//    public void runBankSyncOnStartup() {
+//        log.info("应用启动完成,立即执行一次金蝶银行行名行号同步任务");
+//        executeKingdeeBankSync();
+//    }
+//
+//    private void executeKingdeeBankSync() {
+//        log.info("开始执行金蝶银行行名行号同步任务");
+//        try {
+//            Map<String, Object> result = syncService.syncKingdeeBankListToH3yun();
+//            log.info("金蝶银行行名行号同步完成, result: {}", JSONObject.toJSONString(result));
+//        } catch (Exception e) {
+//            log.error("执行金蝶银行行名行号同步任务异常", e);
+//        }
+//    }
+}

+ 954 - 0
mjava-wlh3tok3/src/main/java/com/malk/service/h3yun/H3yunService.java

@@ -0,0 +1,954 @@
+package com.malk.service.h3yun;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.malk.config.H3tok3Config;
+import com.malk.model.H3yunResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.io.*;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 氚云 API 客户端服务
+ *
+ * 功能说明:提供氚云系统的API调用能力,实现数据从金蝶同步至氚云
+ *
+ * 对接方式:
+ * - API调用:CreateBizObject/UpdateFormData/QueryBizObjects
+ * - 认证方式:Header携带EngineCode和EngineSecret
+ *
+ * API地址:https://www.h3yun.com/OpenApi/Invoke
+ *
+ * 氚云表单编码(需求文档3.2.3):
+ * | 业务对象    | 表单编码(SchemaCode)              | 说明         |
+ * | 采购入库单  | D293655srqj5uui3keso9oraeqm   | 金蝶->氚云   |
+ * | 采购退料单  | D293655sjfn3c62rluwju0mozl3r   | 金蝶->氚云   |
+ * | 销售出库单  | D293655suomjudbplkoxrgs42dqj   | 金蝶->氚云   |
+ * | 销售退货单  | D293655sejcxgmmjpemkhfns2sv    | 金蝶->氚云   |
+ *
+ * 状态回写字段(需求文档4.5):
+ * | 控件名称   | 控件编码        | 类型         | 说明           |
+ * | 同步状态   | F_SyncStatus   | 单选框/下拉框 | 未同步/同步中/同步成功/同步失败 |
+ * | 返回信息   | F_ResultMsg    | 多行文本框    | 错误详情或第三方单号 |
+ * | 第三方单号 | F_ThirdBillNo  | 文本框       | 金蝶/旺店通单号  |
+ * | 同步时间   | F_SyncTime     | 日期时间     | 最后同步时间    |
+ */
+@Service
+public class H3yunService {
+
+    //    @Autowired
+//    private H3yunService h3yunService;
+    private static final Logger log = LoggerFactory.getLogger(H3yunService.class);
+
+    @Autowired
+    private H3tok3Config config;
+
+    /** 连接超时时间(毫秒) */
+    private static final int CONNECT_TIMEOUT = 30000;
+
+    /** 读取超时时间(毫秒) */
+    private static final int READ_TIMEOUT = 60000;
+
+    // 氚云表单编码常量
+    /** 采购入库单表单编码 */
+    public static final String SCHEMA_PURCHASE_INSTOCK = "D293655srqj5uui3keso9oraeqm";
+    /** 采购退料单表单编码 */
+    public static final String SCHEMA_PURCHASE_RETURN = "D293655sjfn3c62rluwju0mozl3r";
+    /** 销售出库单表单编码 */
+    public static final String SCHEMA_SALE_OUTSTOCK = "D293655suomjudbplkoxrgs42dqj";
+    /** 销售退货单表单编码 */
+    public static final String SCHEMA_SALE_RETURN = "D293655sejcxgmmjpemkhfns2sv";
+
+    // 同步状态常量
+    /** 同步状态-未同步 */
+    public static final String SYNC_STATUS_PENDING = "未同步";
+    /** 同步状态-同步中 */
+    public static final String SYNC_STATUS_SYNCING = "同步中";
+    /** 同步状态-同步成功 */
+    public static final String SYNC_STATUS_SUCCESS = "同步成功";
+    /** 同步状态-同步失败 */
+    public static final String SYNC_STATUS_FAIL = "同步失败";
+
+    // 对接类型常量(用于氚云->金蝶同步)
+    /** 对接类型-供应商 */
+    public static final String SYNC_TYPE_SUPPLIER = "供应商档案";
+
+    /** 对接类型-客户 */
+    public static final String SYNC_TYPE_CUSTOMER = "客户档案";
+
+    /** 对接类型-物料 */
+    public static final String SYNC_TYPE_MATERIAL = "物料档案";
+
+    /** 对接类型-仓库 */
+    public static final String SYNC_TYPE_STOCK = "仓库档案";
+
+    /** 对接类型-采购申请单 */
+    public static final String SYNC_TYPE_PURCHASE_REQUISITION = "采购订单";
+
+    /** 对接类型-销售订单 */
+    public static final String SYNC_TYPE_SALE_ORDER = "销售订单";
+
+    /** 对接类型-收料通知单 */
+    public static final String SYNC_TYPE_PURCHASE_RECEIVE = "收料通知单";
+
+    /** 对接类型-采购退料申请 */
+    public static final String SYNC_TYPE_PURCHASE_RETURN = "采购退料申请";
+
+    /** 对接类型-发货通知单 */
+    public static final String SYNC_TYPE_DELIVERY_NOTICE = "发货通知单";
+
+    /** 对接类型-销售退货单 */
+    public static final String SYNC_TYPE_RETURN_REQUEST = "退货申请单";
+
+    /** 对接类型-付款申请单 */
+    public static final String SYNC_TYPE_PAYMENT_REQUEST = "采购付款单";
+
+    /** 对接类型-财务应付单 */
+    public static final String SYNC_TYPE_PAYABLE_BILL = "财务应付单";
+
+    /** 对接类型-采购发票 */
+    public static final String SYNC_TYPE_PURCHASE_INVOICE = "采购发票";
+
+    /** 对接类型-销售发票 */
+    public static final String SYNC_TYPE_SALES_INVOICE = "销售发票";
+
+    /** 对接类型-收款单 */
+    public static final String SYNC_TYPE_RECEIVE_BILL = "销售收款单";
+
+    /** 对接类型-开票申请单 */
+    public static final String SYNC_TYPE_INVOICE_APPLY = "开票申请@财务应收单";
+
+    public static final String SYNC_TYPE_PROJECT = "项目立项";
+
+
+    /**
+     * 创建业务对象(标准格式)
+     *
+     * 氚云API标准格式:
+     * - ActionName: CreateBizObject
+     * - SchemaCode: 表单编码
+     * - BizObject: 表单数据JSON字符串
+     * - IsSubmit: 是否立即提交(可选)
+     *
+     * @param schemaCode 表单编码
+     * @param formData 表单数据
+     * @param isSubmit 是否立即提交(可选,默认为false)
+     * @return H3yunResponse
+     */
+    public H3yunResponse createBizObject(String schemaCode, Map<String, Object> formData, boolean isSubmit) {
+        try {
+            Map<String, Object> body = new HashMap<>();
+            body.put("ActionName", "CreateBizObject");
+            body.put("SchemaCode", schemaCode);
+            body.put("BizObject", JSON.toJSONString(formData));
+            body.put("IsSubmit", "true");
+
+
+            String response = execute(body);
+            log.info("氚云创建业务对象, schemaCode: {}, isSubmit: {}, 响应: {}", schemaCode, isSubmit, response);
+
+            return JSON.parseObject(response, H3yunResponse.class);
+        } catch (Exception e) {
+            log.error("氚云创建业务对象异常, schemaCode: {}", schemaCode, e);
+            throw new RuntimeException("氚云创建业务对象异常: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 创建业务对象(默认不提交)
+     */
+    public H3yunResponse createBizObject(String schemaCode, Map<String, Object> formData) {
+        return createBizObject(schemaCode, formData, true);
+    }
+
+    /**
+     * 更新业务对象(标准格式)
+     *
+     * @param schemaCode 表单编码
+     * @param bizObjectId 业务对象ID
+     * @param formData 表单数据
+     * @param isSubmit 是否立即提交(可选,默认为false)
+     * @return H3yunResponse
+     */
+    public H3yunResponse updateBizObject(String schemaCode, String bizObjectId, Map<String, Object> formData, boolean isSubmit) {
+        try {
+            Map<String, Object> body = new HashMap<>();
+            body.put("ActionName", "UpdateBizObject");
+            body.put("SchemaCode", schemaCode);
+            body.put("BizObjectId", bizObjectId);
+            body.put("BizObject", JSON.toJSONString(formData));
+            if (isSubmit) {
+                body.put("IsSubmit", "true");
+            }
+
+            String response = execute(body);
+            log.info("氚云更新业务对象, schemaCode: {}, bizObjectId: {}, isSubmit: {}, 响应: {}", schemaCode, bizObjectId, isSubmit, response);
+
+            return JSON.parseObject(response, H3yunResponse.class);
+        } catch (Exception e) {
+            log.error("氚云更新业务对象异常, schemaCode: {}, bizObjectId: {}", schemaCode, bizObjectId, e);
+            throw new RuntimeException("氚云更新业务对象异常: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 更新业务对象(默认不提交)
+     */
+    public H3yunResponse updateBizObject(String schemaCode, String bizObjectId, Map<String, Object> formData) {
+        return updateBizObject(schemaCode, bizObjectId, formData, false);
+    }
+
+    /**
+     * 更新表单数据(标准格式)
+     *
+     * @param schemaCode 表单编码
+     * @param objectId 业务对象ID
+     * @param data 表单数据
+     * @param isSubmit 是否立即提交(可选,默认为false)
+     * @return H3yunResponse
+     */
+    public H3yunResponse updateFormData(String schemaCode, String objectId, Map<String, Object> data, boolean isSubmit) {
+        try {
+            Map<String, Object> body = new HashMap<>();
+            body.put("SchemaCode", schemaCode);
+            body.put("ActionName", "UpdateBizObject");
+            body.put("BizObjectId", objectId);
+            body.put("BizObject", JSON.toJSONString(data));
+            if (isSubmit) {
+                body.put("IsSubmit", "true");
+            }
+
+            String response = execute(body);
+            log.info("氚云更新表单数据, schemaCode: {}, objectId: {}, isSubmit: {}, 响应: {}", schemaCode, objectId, isSubmit, response);
+
+            return JSON.parseObject(response, H3yunResponse.class);
+        } catch (Exception e) {
+            log.error("氚云更新表单数据异常, schemaCode: {}, objectId: {}", schemaCode, objectId, e);
+            throw new RuntimeException("氚云更新表单数据异常: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 更新表单数据(默认不提交)
+     */
+    public H3yunResponse updateFormData(String schemaCode, String objectId, Map<String, Object> data) {
+        return updateFormData(schemaCode, objectId, data, false);
+    }
+
+    /**
+     * 查询业务对象列表
+     */
+    public JSONObject queryBizObjects(String schemaCode, String filter, Integer pageSize, Integer pageIndex) {
+        try {
+            Map<String, Object> body = new HashMap<>();
+            body.put("SchemaCode", schemaCode);
+            body.put("ActionName", "LoadBizObjects");
+
+            // 构建分页和筛选参数
+            Map<String, Object> filterObj = new HashMap<>();
+            if (pageIndex != null && pageSize != null) {
+                filterObj.put("FromRowNum", pageIndex * pageSize);
+                filterObj.put("ToRowNum", pageIndex * pageSize + pageSize);
+            } else {
+                filterObj.put("FromRowNum", 0);
+                filterObj.put("ToRowNum", 500);
+            }
+            filterObj.put("RequireCount", false);
+            filterObj.put("ReturnItems", new JSONArray());
+            filterObj.put("SortByCollection", new JSONArray());
+
+            // 如果有自定义 filter,解析 Matcher 部分
+            if (filter != null && !filter.isEmpty()) {
+                // filter 格式: "F_SyncStatus = '未同步'"
+                // 转换为 Matcher 格式
+                filterObj.put("Matcher", parseSimpleFilterToMatcher(filter));
+            } else {
+                filterObj.put("Matcher", new JSONObject());
+            }
+
+            body.put("Filter", JSON.toJSONString(filterObj));
+
+            String response = execute(body);
+            log.info("氚云查询业务对象, schemaCode: {}, 响应: {}", schemaCode, response);
+
+            return JSON.parseObject(response);
+        } catch (Exception e) {
+            log.error("氚云查询业务对象异常, schemaCode: {}", schemaCode, e);
+            throw new RuntimeException("氚云查询业务对象异常: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 将简单 filter 转换为氚云 Matcher 格式
+     * 支持格式: "F_SyncStatus = '未同步'" 或 "F_SyncStatus = '未同步' OR F_SyncStatus = '同步失败'"
+     */
+    private JSONObject parseSimpleFilterToMatcher(String filter) {
+        JSONObject matcher = new JSONObject();
+        if (filter.contains(" OR ")) {
+            // 多个 OR 条件
+            matcher.put("Type", "Or");
+            JSONArray matchers = new JSONArray();
+            String[] conditions = filter.split(" OR ");
+            for (String condition : conditions) {
+                matchers.add(parseSingleCondition(condition.trim()));
+            }
+            matcher.put("Matchers", matchers);
+        } else if (filter.contains(" AND ")) {
+            // 多个 AND 条件
+            matcher.put("Type", "And");
+            JSONArray matchers = new JSONArray();
+            String[] conditions = filter.split(" AND ");
+            for (String condition : conditions) {
+                matchers.add(parseSingleCondition(condition.trim()));
+            }
+            matcher.put("Matchers", matchers);
+        } else {
+            // 单个条件
+            matcher.put("Type", "And");
+            JSONArray matchers = new JSONArray();
+            matchers.add(parseSingleCondition(filter.trim()));
+            matcher.put("Matchers", matchers);
+        }
+        return matcher;
+    }
+
+    /**
+     * 解析单个条件为 Matcher Item
+     */
+    private JSONObject parseSingleCondition(String condition) {
+        JSONObject item = new JSONObject();
+        item.put("Type", "Item");
+
+        // 解析 field = 'value' 格式
+        String[] parts = condition.split("=");
+        if (parts.length == 2) {
+            item.put("Name", parts[0].trim());
+            item.put("Operator", 2); // 2 表示等于
+            item.put("Value", parts[1].trim().replace("'", ""));
+        }
+
+        return item;
+    }
+
+    /**
+     * 检查单据是否已存在(幂等性设计)
+     */
+    public boolean checkBillExists(String schemaCode, String billNo, String fieldName) {
+        try {
+            String filter = String.format("%s = '%s'", fieldName, billNo);
+            JSONObject result = queryBizObjects(schemaCode, filter, 1, 0);
+            JSONObject returnData = result.getJSONObject("ReturnData");
+            if (returnData != null) {
+                JSONArray bizObjectArray = returnData.getJSONArray("BizObjectArray");
+                return bizObjectArray != null && bizObjectArray.size() > 0;
+            }
+            return false;
+        } catch (Exception e) {
+            log.error("氚云检查单据是否存在异常, schemaCode: {}, billNo: {}", schemaCode, billNo, e);
+            return false;
+        }
+    }
+
+    /**
+     * 获取已存在的单据ID
+     */
+    public String getExistingBillId(String schemaCode, String billNo, String fieldName) {
+        try {
+            String filter = String.format("%s = '%s'", fieldName, billNo);
+            JSONObject result = queryBizObjects(schemaCode, filter, 1, 0);
+            JSONObject returnData = result.getJSONObject("ReturnData");
+            if (returnData != null) {
+                JSONArray bizObjectArray = returnData.getJSONArray("BizObjectArray");
+                if (bizObjectArray != null && bizObjectArray.size() > 0) {
+                    return bizObjectArray.getJSONObject(0).getString("ObjectId");
+                }
+            }
+            return null;
+        } catch (Exception e) {
+            log.error("氚云获取已存在单据ID异常, schemaCode: {}, billNo: {}", schemaCode, billNo, e);
+            return null;
+        }
+    }
+
+    /**
+     * 根据ObjectId查询单条业务对象
+     *
+     * 功能说明:根据氚云数据ID查询完整的业务对象数据
+     *
+     * @param schemaCode 表单编码
+     * @param objectId 数据对象ID
+     * @return 业务对象JSON数据
+     */
+    public JSONObject getBizObjectById(String schemaCode, String objectId) {
+        try {
+            // 先查询列表,再筛选ObjectId
+            String filter = String.format("ObjectId = '%s'", objectId);
+            JSONObject result = queryBizObjects(schemaCode, filter, 1, 0);
+
+            JSONObject returnData = result.getJSONObject("ReturnData");
+            if (returnData != null) {
+                JSONArray bizObjectArray = returnData.getJSONArray("BizObjectArray");
+                if (bizObjectArray != null && bizObjectArray.size() > 0) {
+                    log.info("查询到氚云数据, schemaCode: {}, objectId: {}", schemaCode, objectId);
+                    return bizObjectArray.getJSONObject(0);
+                }
+            }
+
+            log.warn("未查询到氚云数据, schemaCode: {}, objectId: {}", schemaCode, objectId);
+            return null;
+        } catch (Exception e) {
+            log.error("查询氚云数据异常, schemaCode: {}, objectId: {}", schemaCode, objectId, e);
+            throw new RuntimeException("查询氚云数据异常: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 获取业务对象中的字段值
+     *
+     * @param bizObject 业务对象JSON
+     * @param fieldName 字段名
+     * @return 字段值
+     */
+    public Object getFieldValue(JSONObject bizObject, String fieldName) {
+        if (bizObject == null || fieldName == null) {
+            return null;
+        }
+        return bizObject.get(fieldName);
+    }
+
+    /**
+     * 获取氚云业务对象中指定字段的值
+     *
+     * 功能说明:根据表单编码、数据ID、字段编码获取该字段的值
+     *
+     * 使用示例:
+     * <pre>
+     * // 获取采购申请单的单号
+     * Object billNo = h3yunService.getFieldValueById("采购申请单SchemaCode", "objectId", "F0000001");
+     *
+     * // 获取客户名称
+     * Object customerName = h3yunService.getFieldValueById("销售订单SchemaCode", "objectId", "F0000003");
+     * </pre>
+     *
+     * @param schemaCode 表单编码(H3yunFormId)
+     * @param objectId 数据对象ID
+     * @param fieldId 字段编码(如F0000001、F0000002等)
+     * @return 字段值,如果未找到返回null
+     */
+    public Object getFieldValueById(String schemaCode, String objectId, String fieldId) {
+        JSONObject bizObject = getBizObjectById(schemaCode, objectId);
+        return getFieldValue(bizObject, fieldId);
+    }
+
+    /**
+     * 根据查询字段和字段值获取ObjectId
+     *
+     * 功能说明:根据表单编码、查询字段名、字段值,查询匹配的数据并返回其ObjectId
+     *
+     * 使用示例:
+     * <pre>
+     * // 根据单据编号查询ObjectId
+     * String objectId = h3yunService.getObjectIdByFieldValue("D293655srqj5uui3keso9oraeqm", "F0000001", "CGRK-001");
+     *
+     * // 根据客户编码查询ObjectId
+     * String objectId = h3yunService.getObjectIdByFieldValue("D293655sxvsttpe7re2tep6gvsdg", "F0000002", "KH001");
+     * </pre>
+     *
+     * @param schemaCode 表单编码
+     * @param fieldName 查询字段名(如F0000001等)
+     * @param fieldValue 查询字段的值
+     * @return 匹配数据的ObjectId,未找到返回null
+     */
+    public String getObjectIdByFieldValue(String schemaCode, String fieldName, String fieldValue) {
+        if (schemaCode == null || fieldName == null || fieldValue == null) {
+            return null;
+        }
+        try {
+            String filter = String.format("%s = '%s'", fieldName, fieldValue);
+            JSONObject result = queryBizObjects(schemaCode, filter, 1, 0);
+            JSONObject returnData = result.getJSONObject("ReturnData");
+            if (returnData != null) {
+                JSONArray bizObjectArray = returnData.getJSONArray("BizObjectArray");
+                if (bizObjectArray != null && bizObjectArray.size() > 0) {
+                    String objectId = bizObjectArray.getJSONObject(0).getString("ObjectId");
+                    log.info("根据字段查询到ObjectId, schemaCode: {}, {}={}, objectId: {}", schemaCode, fieldName, fieldValue, objectId);
+                    return objectId;
+                }
+            }
+            log.warn("未查询到匹配数据, schemaCode: {}, {}={}", schemaCode, fieldName, fieldValue);
+            return null;
+        } catch (Exception e) {
+            log.error("根据字段查询ObjectId异常, schemaCode: {}, {}={}", schemaCode, fieldName, fieldValue, e);
+            return null;
+        }
+    }
+
+    /**
+     * 执行氚云API请求
+     */
+    private String execute(Map<String, Object> body) throws IOException {
+        String jsonBody = JSON.toJSONString(body);
+        HttpURLConnection conn = null;
+        BufferedReader br = null;
+        StringBuilder result = new StringBuilder();
+
+        try {
+            URL url = new URL(config.getH3yun().getBaseUrl());
+            conn = (HttpURLConnection) url.openConnection();
+
+            conn.setRequestMethod("POST");
+            conn.setConnectTimeout(CONNECT_TIMEOUT);
+            conn.setReadTimeout(READ_TIMEOUT);
+            conn.setDoOutput(true);
+            conn.setDoInput(true);
+            conn.setUseCaches(false);
+            conn.setRequestProperty("accept", "*/*");
+            conn.setRequestProperty("connection", "Keep-Alive");
+            conn.setRequestProperty("Content-Type", "application/json;charset=utf-8");
+            conn.setRequestProperty("EngineCode", config.getH3yun().getEngineCode());
+            conn.setRequestProperty("EngineSecret", config.getH3yun().getEngineSecret());
+
+            conn.connect();
+
+            // 写入请求体
+            log.info("氚云API请求, url: {}, body: {}", config.getH3yun().getBaseUrl(), jsonBody);
+            try (OutputStream os = conn.getOutputStream()) {
+                os.write(jsonBody.getBytes(StandardCharsets.UTF_8));
+                os.flush();
+            }
+
+            // 获取响应码
+            int responseCode = conn.getResponseCode();
+            log.info("氚云API响应码: {}", responseCode);
+
+            // 根据响应码读取对应流
+            InputStream inputStream;
+            if (responseCode >= 200 && responseCode < 300) {
+                inputStream = conn.getInputStream();
+            } else {
+                inputStream = conn.getErrorStream();
+            }
+
+            if (inputStream != null) {
+                br = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
+                String line;
+                while ((line = br.readLine()) != null) {
+                    result.append(line);
+                }
+            }
+
+            String responseStr = result.toString();
+            log.info("氚云API响应: {}", responseStr);
+
+            // 如果是错误响应,抛出异常
+            if (responseCode >= 400) {
+                throw new RuntimeException("氚云API请求失败, responseCode: " + responseCode + ", response: " + responseStr);
+            }
+
+            return responseStr;
+        } finally {
+            if (br != null) {
+                try {
+                    br.close();
+                } catch (IOException e) {
+                    log.warn("关闭BufferedReader异常", e);
+                }
+            }
+            if (conn != null) {
+                conn.disconnect();
+            }
+        }
+    }
+
+    /**
+     * 更新同步状态(通用方法)
+     */
+    public H3yunResponse updateSyncStatus(String schemaCode, String objectId, String status, String message, String thirdBillNo) {
+        return updateSyncStatus(schemaCode, objectId, status, message, thirdBillNo, null);
+    }
+
+    /**
+     * 更新同步状态(带K3ID)
+     */
+    public H3yunResponse updateSyncStatus(String schemaCode, String objectId, String status, String message, String thirdBillNo, String k3Id) {
+        Map<String, Object> data = new HashMap<>();
+        data.put("SyncStatus", status);
+        data.put("ResultMsg", message);
+        data.put("ThirdBillNo", thirdBillNo);
+        data.put("SyncTime", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
+        if (k3Id != null) {
+            data.put("K3id", k3Id);
+        }
+
+        return updateFormData(schemaCode, objectId, data);
+    }
+
+    /**
+     * 创建采购入库单
+     */
+    public H3yunResponse createPurchaseInstock(String billNo, String billtypeId, JSONObject webhookData, String finalBilltypeName) {
+        Map<String, Object> formData = new HashMap<>();
+        formData.put("F0000041", billNo);
+        formData.put("F0000038", billtypeId);
+        formData.put("F0000023", finalBilltypeName);
+        formData.put("F0000028", webhookData.getString("biztime"));//业务日期 *
+        formData.put("F0000031", webhookData.getString("bookdate"));//记账日期
+        JSONObject org = webhookData.getJSONObject("bizorg");//采购组织
+        String orgNumber = org != null ? org.getString("number") : null;
+
+
+        formData.put("F0000030", this.getObjectIdByFieldValue("D29365537feb4e5e8644b21b7fd938dd322dab3", "F0000002", orgNumber));
+        formData.put("F0000024", this.getObjectIdByFieldValue("D29365537feb4e5e8644b21b7fd938dd322dab3", "F0000002", orgNumber));
+        //根据编号获取氚云集团公司ID
+        JSONObject supplier = webhookData.getJSONObject("supplier");//供货供应商
+        String supplierrNumber = supplier != null ? supplier.getString("number") : null;
+        formData.put("F0000029", this.getObjectIdByFieldValue("D293655b4a0a34e44914aee9613a7bd3957df83", "SeqNo", supplierrNumber));//根据编号获取氚云供应商ID
+
+
+        JSONArray billentry = webhookData.getJSONArray("billentry");//采购明细
+        List<Map<String, Object>> detailList = new ArrayList<>();
+        if (billentry != null && !billentry.isEmpty()) {
+            for (int i = 0; i < billentry.size(); i++) {
+                JSONObject entry = billentry.getJSONObject(i);
+                Map<String, Object> detail = new LinkedHashMap<>();
+                detail.put("F0000042", entry.getString("id"));//id
+                // 采购单
+                detail.put("F0000040", this.getObjectIdByFieldValue("D2936559700b1d66a0e4ee69a84e13da8dbbe95", "SeqNo", entry.getString("mainbillnumber")));
+
+                // 数量
+                Double qty = entry.getDouble("qty");
+                detail.put("F0000021", qty != null ? qty : 0);
+                detail.put("F0000036", qty != null ? qty : 0);
+                // 单位编码
+                detail.put("F0000043", entry.getString("actualtaxprice"));//单价
+                detail.put("F0000044", entry.getString("amountandtax"));//价税合计金额
+                JSONObject unit = entry.getJSONObject("unit");
+                detail.put("F0000034", unit != null ? this.getObjectIdByFieldValue("D293655248b1d9bf6c448f0a291341ec58bb943", "F0000001", unit.getString("number")) : null);
+                detail.put("F0000035", unit != null ? this.getObjectIdByFieldValue("D293655248b1d9bf6c448f0a291341ec58bb943", "F0000001", unit.getString("number")) : null);
+
+                // 仓库编码
+                JSONObject warehouse = entry.getJSONObject("warehouse");
+                detail.put("F0000037", warehouse != null ? this.getObjectIdByFieldValue("D293655scvrhqr64jemxdkqk6gf", "SeqNo", warehouse.getString("number")) : null);
+                String id = this.getObjectIdByFieldValue("D293655fc1a38f7956f400a886f376911a54a30", "K3id", entry.getString("materialmasterid"));
+                // 物料ID
+                detail.put("F0000032", id);
+                detail.put("F0000005", this.getFieldValueById("D293655fc1a38f7956f400a886f376911a54a30", id, "SeqNo"));
+                // 物料名称
+                detail.put("F0000004", entry.getString("materialname"));
+                detailList.add(detail);
+            }
+        }
+        formData.put("D293655sdbobhrfwjkqhpapod1d3", detailList);
+
+
+        // 先创建采购入库单
+        H3yunResponse returnResponse = createBizObject(SCHEMA_PURCHASE_INSTOCK, formData);
+
+
+        return returnResponse;
+
+
+    }
+
+    /**
+     * 创建采购退料单(金蝶->氚云)
+     */
+    public H3yunResponse createPurchaseReturn(String billNo, String billtypeId, JSONObject webhookData, String finalBilltypeName) {
+        Map<String, Object> formData = new HashMap<>();
+        // 1. 单据基本信息
+        formData.put("F0000042", billNo); // 单据编号
+        formData.put("K3id", billtypeId); // 单据ID
+        formData.put("F0000038", finalBilltypeName); // 单据类型名称
+        formData.put("F0000028", webhookData.getString("biztime")); // 业务日期
+
+        formData.put("F0000044", webhookData.getString("bookdate")); // 记账日期
+
+        // 2. 采购组织(从bizorg获取)
+        JSONObject org = webhookData.getJSONObject("bizorg");
+        String orgNumber = org != null ? org.getString("number") : null;
+        formData.put("F0000030", this.getObjectIdByFieldValue("D29365537feb4e5e8644b21b7fd938dd322dab3", "F0000002", orgNumber));
+        formData.put("F0000024", this.getObjectIdByFieldValue("D29365537feb4e5e8644b21b7fd938dd322dab3", "F0000002", orgNumber));
+
+        // 3. 供应商(从supplier获取)
+        JSONObject supplier = webhookData.getJSONObject("supplier");
+        String supplierNumber = supplier != null ? supplier.getString("number") : null;
+        formData.put("F0000029", this.getObjectIdByFieldValue("D293655b4a0a34e44914aee9613a7bd3957df83", "SeqNo", supplierNumber));
+
+        // 4. 业务类型
+        JSONObject biztype = webhookData.getJSONObject("biztype");
+        if (biztype != null) {
+            if (biztype.getString("number").equals("1101")) {
+                formData.put("F0000039", biztype.getString("number")); // 业务类型编码
+            } else {
+                formData.put("F0000039", biztype.getString("number")); // 业务类型编码
+            }
+
+        }
+
+
+//        // 5. 单据状态
+//        formData.put("F0000044", webhookData.getString("billstatus")); // 单据状态
+
+        // 6. 明细行数据
+        JSONArray billentry = webhookData.getJSONArray("billentry");
+        List<Map<String, Object>> detailList = new ArrayList<>();
+        if (billentry != null && !billentry.isEmpty()) {
+            for (int i = 0; i < billentry.size(); i++) {
+                JSONObject entry = billentry.getJSONObject(i);
+                Map<String, Object> detail = new LinkedHashMap<>();
+                detail.put("F0000046", entry.getString("id"));//id
+                // 6.1 物料信息
+                JSONObject material = entry.getJSONObject("material");
+                String materialMasterId = entry.getString("materialmasterid");
+                if (materialMasterId != null) {
+                    // 根据materialMasterId获取氚云物料ID
+                    String materialId = this.getObjectIdByFieldValue("D293655fc1a38f7956f400a886f376911a54a30", "K3id", materialMasterId);
+                    detail.put("F0000032", materialId); // 物料ID
+                    detail.put("F0000005", this.getFieldValueById("D293655fc1a38f7956f400a886f376911a54a30", materialId, "SeqNo")); // 物料编码
+                }
+                // 物料名称
+                detail.put("F0000004", entry.getString("materialname"));
+                detail.put("F0000045", this.getObjectIdByFieldValue("D293655sdqwl9gil5u6s48bnv5f", "SeqNo", entry.getString("srcbillnumber")));
+
+                // 6.2 数量(退料数量为负数)
+                Double qty = entry.getDouble("qty");
+                detail.put("F0000043", qty != null ? qty : 0);
+                detail.put("F0000036", qty != null ? qty : 0);
+
+                // 6.3 单位
+                JSONObject unit = entry.getJSONObject("unit");
+                if (unit != null) {
+                    String unitNumber = unit.getString("number");
+                    detail.put("F0000034", this.getObjectIdByFieldValue("D293655248b1d9bf6c448f0a291341ec58bb943", "F0000001", unitNumber));
+                    detail.put("F0000035", this.getObjectIdByFieldValue("D293655248b1d9bf6c448f0a291341ec58bb943", "F0000001", unitNumber));
+                }
+
+                // 6.4 仓库
+                JSONObject warehouse = entry.getJSONObject("warehouse");
+                if (warehouse != null) {
+                    String warehouseNumber = warehouse.getString("number");
+                    detail.put("F0000037", this.getObjectIdByFieldValue("D293655scvrhqr64jemxdkqk6gf", "SeqNo", warehouseNumber));
+                }
+
+//                // 6.5 供应商(明细行供应商)
+//                JSONObject invoiceSupplier = entry.getJSONObject("invoicesupplier");
+//                if (invoiceSupplier != null) {
+//                    String invoiceSupplierNumber = invoiceSupplier.getString("number");
+//                    detail.put("F0000045", this.getObjectIdByFieldValue("D293655b4a0a34e44914aee9613a7bd3957df83", "SeqNo", invoiceSupplierNumber));
+//                }
+
+//                // 6.6 批次相关信息
+//                detail.put("F0000046", entry.getString("lotnumber")); // 批次号
+//                detail.put("F0000047", entry.getString("serialnumber")); // 序列号
+//
+//                // 6.7 金额信息
+//                detail.put("F0000048", entry.getDouble("amount")); // 金额
+//                detail.put("F0000049", entry.getDouble("taxamount")); // 税额
+
+                detailList.add(detail);
+            }
+        }
+        // 采购退料标明细(需要确认实际的子表字段编码)
+        formData.put("D293655sai676rs7hecwmz3mxdvf", detailList);
+
+        return createBizObject(SCHEMA_PURCHASE_RETURN, formData);
+    }
+
+    /**
+     * 创建销售出库单(金蝶->氚云)
+     */
+    public H3yunResponse createSaleOutstock(String billNo, String billtypeId, JSONObject webhookData, String finalBilltypeName) {
+        Map<String, Object> formData = new HashMap<>();
+        // 1. 单据基本信息
+        formData.put("F0000053", billNo); // 单据编号
+        formData.put("K3id", billtypeId); // 单据ID
+        formData.put("F0000054", finalBilltypeName); // 单据类型名称
+        formData.put("F0000021", webhookData.getString("biztime")); // 业务日期
+        //  formData.put("F0000044", webhookData.getString("bookdate")); // 记账日期
+
+        // 2. 销售组织(从bizorg获取)
+        JSONObject org = webhookData.getJSONObject("bizorg");
+        String orgNumber = org != null ? org.getString("number") : null;
+        formData.put("F0000018", this.getObjectIdByFieldValue("D29365537feb4e5e8644b21b7fd938dd322dab3", "F0000002", orgNumber));
+        formData.put("F0000049", this.getObjectIdByFieldValue("D29365537feb4e5e8644b21b7fd938dd322dab3", "F0000002", orgNumber));
+
+        // 3. 客户(从customer获取,使用客户档案表)
+        JSONObject customer = webhookData.getJSONObject("customer");
+        String customerNumber = customer != null ? customer.getString("number") : null;
+        formData.put("F0000024", this.getObjectIdByFieldValue("D293655shuyz9ttzgkgmhaa4rglr", "SeqNo", customerNumber));
+        // 3. 店铺(从customer获取,使用客户档案表)
+        JSONObject Dianpu = webhookData.getJSONObject("al95_customer");
+        String al95_customer = Dianpu != null ? Dianpu.getString("number") : null;
+        formData.put("F0000023", this.getObjectIdByFieldValue("D293655shuyz9ttzgkgmhaa4rglr", "SeqNo", al95_customer));
+        // 4. 业务类型
+        JSONObject biztype = webhookData.getJSONObject("biztype");
+        if (biztype != null) {
+            formData.put("F0000055", biztype.getString("name")); // 业务类型
+        }
+
+        // 5. 明细行数据
+        JSONArray billentry = webhookData.getJSONArray("billentry");
+        List<Map<String, Object>> detailList = new ArrayList<>();
+        if (billentry != null && !billentry.isEmpty()) {
+            for (int i = 0; i < billentry.size(); i++) {
+                JSONObject entry = billentry.getJSONObject(i);
+                Map<String, Object> detail = new LinkedHashMap<>();
+                detail.put("F0000061", entry.getString("id"));//id
+                // 5.1 物料信息
+                String materialMasterId = entry.getString("materialmasterid");
+                String materialname = entry.getString("materialname");
+                if (materialMasterId != null) {
+                    String materialId = this.getObjectIdByFieldValue("D293655fc1a38f7956f400a886f376911a54a30", "K3id", materialMasterId);
+                    detail.put("F0000027", materialId); // 物料ID
+                    detail.put("F0000058", this.getFieldValueById("D293655fc1a38f7956f400a886f376911a54a30", materialId, "SeqNo")); // 物料编码
+                    detail.put("F0000028", materialname); // 物料名称
+                    detail.put("F0000029", this.getFieldValueById("D293655fc1a38f7956f400a886f376911a54a30", materialId, "F0000010")); // 物料规格型号
+//
+                }
+
+
+                // 5.2 源单关联(销售订单)
+                String srcbillnumber = entry.getString("srcbillnumber");
+                if (srcbillnumber != null && !srcbillnumber.isEmpty()) {
+                    detail.put("F0000059", this.getObjectIdByFieldValue("D293655sv9ijeqmiakgclnrk7cwq", "SeqNo", srcbillnumber));
+                    detail.put("F0000063", srcbillnumber);
+                }
+
+                // 5.3 数量
+                Double qty = entry.getDouble("qty");
+                detail.put("F0000031", qty != null ? qty : 0);
+                detail.put("F0000062", qty != null ? qty : 0);
+
+                // 5.4 单位
+                JSONObject unit = entry.getJSONObject("unit");
+                if (unit != null) {
+                    String unitNumber = unit.getString("number");
+                    detail.put("F0000030", this.getObjectIdByFieldValue("D293655248b1d9bf6c448f0a291341ec58bb943", "F0000001", unitNumber));
+
+                }
+
+                // 5.5 仓库
+                JSONObject warehouse = entry.getJSONObject("warehouse");
+                if (warehouse != null) {
+                    String warehouseNumber = warehouse.getString("number");
+                    detail.put("F0000060", this.getObjectIdByFieldValue("D293655scvrhqr64jemxdkqk6gf", "SeqNo", warehouseNumber));
+                }
+
+                detailList.add(detail);
+            }
+        }
+        formData.put("D293655sn7kin8gllkybwjwvmksy", detailList);
+
+        // 先创建销售出库单
+        H3yunResponse returnResponse = createBizObject(SCHEMA_SALE_OUTSTOCK, formData);
+
+
+        return returnResponse;
+
+    }
+
+    /**
+     * 创建销售退货单(金蝶->氚云)
+     */
+    public H3yunResponse createSaleReturn(String billNo, String billtypeId, JSONObject webhookData, String finalBilltypeName) {
+        Map<String, Object> formData = new HashMap<>();
+        // 1. 单据基本信息
+        formData.put("F0000063", billNo); // 单据编号
+        formData.put("K3id", billtypeId); // 单据ID
+        formData.put("F0000059", finalBilltypeName); // 单据类型名称
+        formData.put("F0000021", webhookData.getString("biztime")); // 业务日期
+
+
+        // 2. 销售组织(从bizorg获取)
+        JSONObject org = webhookData.getJSONObject("bizorg");
+        String orgNumber = org != null ? org.getString("number") : null;
+        formData.put("F0000018", this.getObjectIdByFieldValue("D29365537feb4e5e8644b21b7fd938dd322dab3", "F0000002", orgNumber));
+        formData.put("F0000049", this.getObjectIdByFieldValue("D29365537feb4e5e8644b21b7fd938dd322dab3", "F0000002", orgNumber));
+
+        // 3. 客户(从customer获取,使用客户档案表)
+        JSONObject customer = webhookData.getJSONObject("customer");
+        String customerNumber = customer != null ? customer.getString("number") : null;
+        formData.put("F0000024", this.getObjectIdByFieldValue("D293655shuyz9ttzgkgmhaa4rglr", "SeqNo", customerNumber));
+
+        // 4. 店铺(从al95_customer获取,使用客户档案表)
+        JSONObject al95Customer = webhookData.getJSONObject("al95_customer");
+        String al95CustomerNumber = al95Customer != null ? al95Customer.getString("number") : null;
+        formData.put("F0000023", this.getObjectIdByFieldValue("D293655shuyz9ttzgkgmhaa4rglr", "SeqNo", al95CustomerNumber));
+
+        // 5. 业务类型
+        JSONObject biztype = webhookData.getJSONObject("biztype");
+        if (biztype != null) {
+            formData.put("F0000060", biztype.getString("name")); // 业务类型
+        }
+
+        // 6. 明细行数据
+        JSONArray billentry = webhookData.getJSONArray("billentry");
+        List<Map<String, Object>> detailList = new ArrayList<>();
+        if (billentry != null && !billentry.isEmpty()) {
+            for (int i = 0; i < billentry.size(); i++) {
+                JSONObject entry = billentry.getJSONObject(i);
+                Map<String, Object> detail = new LinkedHashMap<>();
+                detail.put("F0000067", entry.getString("id"));//id
+                // 6.1 物料信息
+                String materialMasterId = entry.getString("materialmasterid");
+                String materialName = entry.getString("materialname");
+                if (materialMasterId != null) {
+                    String materialId = this.getObjectIdByFieldValue("D293655fc1a38f7956f400a886f376911a54a30", "K3id", materialMasterId);
+                    detail.put("F0000027", materialId); // 物料ID
+                    detail.put("F0000065", this.getFieldValueById("D293655fc1a38f7956f400a886f376911a54a30", materialId, "SeqNo")); // 物料编码
+                    detail.put("F0000028", materialName); // 物料名称
+                    detail.put("F0000029", this.getFieldValueById("D293655fc1a38f7956f400a886f376911a54a30", materialId, "F0000010")); // 物料规格型号
+                }
+
+                // 6.2 源单关联(退货申请单)
+                String srcBillNumber = entry.getString("srcbillnumber");
+                if (srcBillNumber != null && !srcBillNumber.isEmpty()) {
+                    detail.put("F0000066", this.getObjectIdByFieldValue("D293655sidsnhobdgeq6aauvwpu", "SeqNo", srcBillNumber));
+                }
+
+                // 6.3 数量(销售退货数量通常为负数)
+                Double qty = entry.getDouble("qty");
+                detail.put("F0000031", qty != null ? qty : 0);
+
+                // 6.4 单位
+                JSONObject unit = entry.getJSONObject("unit");
+                if (unit != null) {
+                    String unitNumber = unit.getString("number");
+                    detail.put("F0000030", this.getObjectIdByFieldValue("D293655248b1d9bf6c448f0a291341ec58bb943", "F0000001", unitNumber));
+                }
+
+                // 6.5 仓库
+                JSONObject warehouse = entry.getJSONObject("warehouse");
+                if (warehouse != null) {
+                    String warehouseNumber = warehouse.getString("number");
+                    detail.put("F0000062", this.getObjectIdByFieldValue("D293655scvrhqr64jemxdkqk6gf", "SeqNo", warehouseNumber));
+                }
+
+                detailList.add(detail);
+            }
+        }
+        // TODO: 请确认销售退货单明细子表的实际SchemaCode
+        formData.put("D293655sdverxsph60edltzkvgwg", detailList);
+
+        // 先创建销售退货单
+        H3yunResponse returnResponse = createBizObject(SCHEMA_SALE_RETURN, formData);
+
+
+        return returnResponse;
+
+    }
+}

File diff suppressed because it is too large
+ 3051 - 0
mjava-wlh3tok3/src/main/java/com/malk/service/kingdee/KingdeeService.java


File diff suppressed because it is too large
+ 2988 - 0
mjava-wlh3tok3/src/main/java/com/malk/service/sync/SyncService.java


+ 192 - 0
mjava-wlh3tok3/src/main/java/com/malk/service/wangdian/WangdianService.java

@@ -0,0 +1,192 @@
+package com.malk.service.wangdian;
+
+import com.alibaba.fastjson.JSON;
+import com.malk.config.H3tok3Config;
+import com.malk.model.WangdianResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.*;
+
+/**
+ * 旺店通 API 客户端服务
+ * 
+ * 功能说明:提供旺店通ERP系统的API调用能力,实现数据从氚云同步至旺店通
+ * 
+ * 对接方式:API调用(盘盈盘亏单)
+ * 认证方式:MD5签名认证
+ * 
+ * API地址:https://api.wangdian.cn/openapi2/
+ * 
+ * 签名算法:
+ * 1. 将所有参数(除sign外)按key升序排列
+ * 2. 拼接成字符串:key1value1key2value2... + secret
+ * 3. MD5加密后转大写
+ * 
+ * 接口清单(需求文档3.3.1):
+ * | 业务对象 | 源系统 | 目标接口  | 方法 | 核心字段映射                    |
+ * | 盘盈单   | 氚云   | stock_pd_add | POST | 仓库编码->warehouse_no, 物料编码->spec_no, 盈余数量->num, order_type->1 |
+ * | 盘亏单   | 氚云   | stock_pd_add | POST | 仓库编码->warehouse_no, 物料编码->spec_no, 亏损数量->num, order_type->2 |
+ * 
+ * 使用场景:
+ * - 氚云盘点表单审核后,根据差异结果同步至旺店通ERP
+ * - 盘盈单调整库存增加,盘亏单调整库存减少
+ */
+@Service
+public class WangdianService {
+
+    private static final Logger log = LoggerFactory.getLogger(WangdianService.class);
+
+    @Autowired
+    private H3tok3Config config;
+
+    private final RestTemplate restTemplate = new RestTemplate();
+
+    /** 盘盈单类型 */
+    public static final int ORDER_TYPE_PROFIT = 1;
+    /** 盘亏单类型 */
+    public static final int ORDER_TYPE_LOSS = 2;
+
+    /**
+     * 创建盘盈盘亏单(单个物料)
+     */
+    public WangdianResponse stockPdAdd(String warehouseNo, Integer orderType, 
+                                       String specNo, Integer num, String remark) {
+        try {
+            TreeMap<String, Object> params = new TreeMap<>();
+            params.put("warehouse_no", warehouseNo);
+            params.put("order_type", orderType);
+            
+            Map<String, Object> item = new HashMap<>();
+            item.put("spec_no", specNo);
+            item.put("num", num);
+            if (remark != null) {
+                item.put("remark", remark);
+            }
+            params.put("items", JSON.toJSONString(new Object[]{item}));
+
+            String response = execute("stock_pd_add", params);
+            log.info("旺店通盘盈盘亏单, warehouseNo: {}, orderType: {}, 响应: {}", 
+                    warehouseNo, orderType, response);
+            
+            return JSON.parseObject(response, WangdianResponse.class);
+        } catch (Exception e) {
+            log.error("旺店通盘盈盘亏单异常, warehouseNo: {}, orderType: {}", warehouseNo, orderType, e);
+            throw new RuntimeException("旺店通盘盈盘亏单异常: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 创建盘盈盘亏单(多个物料)
+     */
+    public WangdianResponse stockPdAdd(String warehouseNo, Integer orderType,
+                                       List<Map<String, Object>> items) {
+        try {
+            TreeMap<String, Object> params = new TreeMap<>();
+            params.put("warehouse_no", warehouseNo);
+            params.put("order_type", orderType);
+            params.put("items", JSON.toJSONString(items));
+
+            String response = execute("stock_pd_add", params);
+            log.info("旺店通批量盘盈盘亏单, warehouseNo: {}, orderType: {}, 响应: {}",
+                    warehouseNo, orderType, response);
+
+            return JSON.parseObject(response, WangdianResponse.class);
+        } catch (Exception e) {
+            log.error("旺店通批量盘盈盘亏单异常, warehouseNo: {}, orderType: {}", warehouseNo, orderType, e);
+            throw new RuntimeException("旺店通批量盘盈盘亏单异常: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 创建盘盈单(从盘点表单数据)
+     */
+    public WangdianResponse createProfitOrder(String warehouseNo, String specNo, Integer num, String remark) {
+        return stockPdAdd(warehouseNo, ORDER_TYPE_PROFIT, specNo, num, remark);
+    }
+
+    /**
+     * 创建盘盈单(批量)
+     */
+    public WangdianResponse createProfitOrder(String warehouseNo, List<Map<String, Object>> items) {
+        return stockPdAdd(warehouseNo, ORDER_TYPE_PROFIT, items);
+    }
+
+    /**
+     * 创建盘亏单(从盘点表单数据)
+     */
+    public WangdianResponse createLossOrder(String warehouseNo, String specNo, Integer num, String remark) {
+        return stockPdAdd(warehouseNo, ORDER_TYPE_LOSS, specNo, num, remark);
+    }
+
+    /**
+     * 创建盘亏单(批量)
+     */
+    public WangdianResponse createLossOrder(String warehouseNo, List<Map<String, Object>> items) {
+        return stockPdAdd(warehouseNo, ORDER_TYPE_LOSS, items);
+    }
+
+    /**
+     * 执行旺店通API请求
+     */
+    private String execute(String method, Map<String, Object> params) {
+        long timestamp = System.currentTimeMillis();
+        
+        // 构建签名前的参数(包含认证参数和业务参数)
+        TreeMap<String, Object> signParams = new TreeMap<>();
+        signParams.put("appkey", config.getWangdian().getAppkey());
+        signParams.put("method", method);
+        signParams.put("sid", config.getWangdian().getSid());
+        signParams.put("timestamp", timestamp);
+        signParams.put("v", 1);
+        signParams.putAll(params);
+        
+        // 生成签名
+        String sign = generateSign(signParams);
+
+        // 构建请求体
+        Map<String, Object> body = new HashMap<>();
+        body.put("appkey", config.getWangdian().getAppkey());
+        body.put("method", method);
+        body.put("sid", config.getWangdian().getSid());
+        body.put("timestamp", timestamp);
+        body.put("sign", sign);
+        body.put("v", 1);
+        body.put("format", "json");
+        body.putAll(params);
+
+        String url = config.getWangdian().getBaseUrl();
+        return restTemplate.postForObject(url, body, String.class);
+    }
+
+    /**
+     * 生成MD5签名
+     */
+    private String generateSign(TreeMap<String, Object> params) {
+        StringBuilder sb = new StringBuilder();
+        for (Map.Entry<String, Object> entry : params.entrySet()) {
+            sb.append(entry.getKey()).append(entry.getValue());
+        }
+        // 追加密钥
+        sb.append(config.getWangdian().getSecret());
+        
+        try {
+            java.security.MessageDigest md = java.security.MessageDigest.getInstance("MD5");
+            byte[] digest = md.digest(sb.toString().getBytes("UTF-8"));
+            StringBuilder hexString = new StringBuilder();
+            for (byte b : digest) {
+                String hex = Integer.toHexString(0xff & b);
+                if (hex.length() == 1) {
+                    hexString.append("0");
+                }
+                hexString.append(hex);
+            }
+            return hexString.toString().toUpperCase();
+        } catch (Exception e) {
+            throw new RuntimeException("MD5签名异常", e);
+        }
+    }
+}

+ 47 - 0
mjava-wlh3tok3/src/main/resources/application-dev.yml

@@ -0,0 +1,47 @@
+# =============================================
+# 开发环境配置
+# =============================================
+
+server:
+  port: 9102
+
+h3tok3:
+  # 金蝶云·星空配置(开发环境)
+  kingdee:
+    host: https://wuliustyle.test.kdgalaxy.com
+    basePath: /kapi/v2
+    accountId: 2375638172857100288
+    tenantId: wuliustyle.test
+    # 金蝶系统登录用户名 user
+    userName: 18930688861
+    # 第三方应用ID
+    appId: OATOK3
+    # 第三方应用密钥
+    appSecret: WULIU2026HYUNTOOA-v1
+    # 网关身份标识(x-acgw-identity)
+    acgw-identity: djF8MTlkNzE0NWExZmUwMTUwMzk0MDF8NDkyOTMyMTg4NDY5MHzbhdLwVwWpcJ98t9HTUi5cmvdRV7o2FVDS4wf2HFAxunw=
+
+  # 氚云配置(开发环境)
+  h3yun:
+    baseUrl: https://www.h3yun.com/OpenApi/Invoke
+    # 引擎编码
+    engineCode: rktm7p12earnk4typxiwruj02
+    # 引擎密钥
+    engineSecret: Tg2Bd5081SDtX9YiMy7dayLEmhHBFwNzXzoIFKWZJCwMeBr5Ap59DQ==
+
+  # 旺店通配置(开发环境)
+  wangdian:
+    baseUrl: https://api.wangdian.cn/openapi2/
+    appkey: dev_appkey
+    sid: dev_sid
+    secret: dev_secret
+
+  # 定时任务配置
+  schedule:
+    # 开发环境:每分钟执行一次
+    inventory-cron: 0 */1 * * * ?
+
+logging:
+  level:
+    root: INFO
+    com.malk: DEBUG

+ 0 - 0
mjava-wlh3tok3/src/main/resources/application-prod.yml


Some files were not shown because too many files changed in this diff