浏览代码

提交东方新华和医保局的代码

zfc 1 年之前
父节点
当前提交
6d4cdcd9b6
共有 100 个文件被更改,包括 5240 次插入8 次删除
  1. 23 0
      .idea/compiler.xml
  2. 13 0
      .idea/encodings.xml
  3. 20 0
      .idea/jarRepositories.xml
  4. 9 0
      .idea/misc.xml
  5. 0 8
      .idea/modules.xml
  6. 61 0
      mjava-dongfangxinhua/pom.xml
  7. 32 0
      mjava-dongfangxinhua/src/main/java/com/malk/dongfangxinhua/Boot.java
  8. 211 0
      mjava-dongfangxinhua/src/main/java/com/malk/dongfangxinhua/controller/DFXHController.java
  9. 48 0
      mjava-dongfangxinhua/src/main/java/com/malk/dongfangxinhua/schedule/ScheduleTask.java
  10. 251 0
      mjava-dongfangxinhua/src/main/java/com/malk/dongfangxinhua/service/Impl/PayServiceImpl.java
  11. 63 0
      mjava-dongfangxinhua/src/main/java/com/malk/dongfangxinhua/service/Impl/UpdateProofServerImpl.java
  12. 31 0
      mjava-dongfangxinhua/src/main/java/com/malk/dongfangxinhua/service/PayService.java
  13. 8 0
      mjava-dongfangxinhua/src/main/java/com/malk/dongfangxinhua/service/UpdateProofServer.java
  14. 65 0
      mjava-dongfangxinhua/src/main/resources/application-dev.yml
  15. 38 0
      mjava-dongfangxinhua/src/main/resources/application-prod.yml
  16. 39 0
      mjava-dongfangxinhua/src/test/resource/server.sh
  17. 65 0
      mjava-dongfangxinhua/target/classes/application-dev.yml
  18. 38 0
      mjava-dongfangxinhua/target/classes/application-prod.yml
  19. 二进制
      mjava-dongfangxinhua/target/classes/com/malk/dongfangxinhua/Boot.class
  20. 二进制
      mjava-dongfangxinhua/target/classes/com/malk/dongfangxinhua/controller/DFXHController.class
  21. 二进制
      mjava-dongfangxinhua/target/classes/com/malk/dongfangxinhua/schedule/ScheduleTask.class
  22. 二进制
      mjava-dongfangxinhua/target/classes/com/malk/dongfangxinhua/service/Impl/PayServiceImpl.class
  23. 二进制
      mjava-dongfangxinhua/target/classes/com/malk/dongfangxinhua/service/Impl/UpdateProofServerImpl.class
  24. 二进制
      mjava-dongfangxinhua/target/classes/com/malk/dongfangxinhua/service/PayService.class
  25. 二进制
      mjava-dongfangxinhua/target/classes/com/malk/dongfangxinhua/service/UpdateProofServer.class
  26. 3 0
      mjava-dongfangxinhua/target/maven-archiver/pom.properties
  27. 7 0
      mjava-dongfangxinhua/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
  28. 7 0
      mjava-dongfangxinhua/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
  29. 二进制
      mjava-dongfangxinhua/target/mjava-dongfangxinhua.jar
  30. 二进制
      mjava-dongfangxinhua/target/mjava-dongfangxinhua.jar.original
  31. 54 0
      mjava-yibaoju/pom.xml
  32. 32 0
      mjava-yibaoju/src/main/java/com/malk/yibaoju/Boot.java
  33. 274 0
      mjava-yibaoju/src/main/java/com/malk/yibaoju/controller/YBJController.java
  34. 49 0
      mjava-yibaoju/src/main/java/com/malk/yibaoju/schedule/ScheduleTask.java
  35. 21 0
      mjava-yibaoju/src/main/java/com/malk/yibaoju/service/YBJService.java
  36. 557 0
      mjava-yibaoju/src/main/java/com/malk/yibaoju/service/impl/YBJServiceImpl.java
  37. 52 0
      mjava-yibaoju/src/main/resources/application-dev.yml
  38. 39 0
      mjava-yibaoju/src/main/resources/application-prod.yml
  39. 67 0
      mjava-yibaoju/src/test/resources/sample/CPTimer.cs
  40. 85 0
      mjava-yibaoju/src/test/resources/sample/CP_Utils.cs
  41. 43 0
      mjava-yibaoju/src/test/resources/sample/附件同步钉盘.cs
  42. 59 0
      mjava-yibaoju/src/test/resources/sample/附件同步钉盘.js
  43. 35 0
      mjava-yibaoju/src/test/resources/server.sh
  44. 52 0
      mjava-yibaoju/target/classes/application-dev.yml
  45. 39 0
      mjava-yibaoju/target/classes/application-prod.yml
  46. 二进制
      mjava-yibaoju/target/classes/com/malk/yibaoju/Boot.class
  47. 二进制
      mjava-yibaoju/target/classes/com/malk/yibaoju/controller/YBJController.class
  48. 二进制
      mjava-yibaoju/target/classes/com/malk/yibaoju/schedule/ScheduleTask.class
  49. 二进制
      mjava-yibaoju/target/classes/com/malk/yibaoju/service/YBJService.class
  50. 二进制
      mjava-yibaoju/target/classes/com/malk/yibaoju/service/impl/YBJServiceImpl.class
  51. 3 0
      mjava-yibaoju/target/maven-archiver/pom.properties
  52. 5 0
      mjava-yibaoju/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
  53. 5 0
      mjava-yibaoju/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
  54. 二进制
      mjava-yibaoju/target/mjava-yibaoju.jar
  55. 二进制
      mjava-yibaoju/target/mjava-yibaoju.jar.original
  56. 51 0
      mjava/pom.xml
  57. 29 0
      mjava/src/main/java/com/malk/Boot.java
  58. 29 0
      mjava/src/main/java/com/malk/base/BaseDao.java
  59. 112 0
      mjava/src/main/java/com/malk/base/BaseDto.java
  60. 64 0
      mjava/src/main/java/com/malk/base/BasePo.java
  61. 9 0
      mjava/src/main/java/com/malk/base/BaseRepository.java
  62. 78 0
      mjava/src/main/java/com/malk/base/JpaMap.java
  63. 35 0
      mjava/src/main/java/com/malk/config/JpaConfiguration.java
  64. 58 0
      mjava/src/main/java/com/malk/config/WebConfiguration.java
  65. 41 0
      mjava/src/main/java/com/malk/config/mutilSource/DataSourceConfig.java
  66. 80 0
      mjava/src/main/java/com/malk/config/mutilSource/PrimaryConfig.java
  67. 76 0
      mjava/src/main/java/com/malk/config/mutilSource/SlaveConfig.java
  68. 73 0
      mjava/src/main/java/com/malk/controller/DDCallbackController.java
  69. 101 0
      mjava/src/main/java/com/malk/core/AsyncConfig.java
  70. 36 0
      mjava/src/main/java/com/malk/delegate/DDEvent.java
  71. 14 0
      mjava/src/main/java/com/malk/delegate/McDelegate.java
  72. 49 0
      mjava/src/main/java/com/malk/delegate/impl/DDImplEvent.java
  73. 21 0
      mjava/src/main/java/com/malk/delegate/impl/McImplDelegate.java
  74. 215 0
      mjava/src/main/java/com/malk/filter/CatchException.java
  75. 53 0
      mjava/src/main/java/com/malk/filter/ExceptionNotice.java
  76. 32 0
      mjava/src/main/java/com/malk/filter/RequestFilter.java
  77. 41 0
      mjava/src/main/java/com/malk/filter/RequestInterceptor.java
  78. 13 0
      mjava/src/main/java/com/malk/repository/dao/primary/McAuthorizationDao.java
  79. 10 0
      mjava/src/main/java/com/malk/repository/dao/primary/McTableDao.java
  80. 11 0
      mjava/src/main/java/com/malk/repository/dao/slave/McTableDao.java
  81. 51 0
      mjava/src/main/java/com/malk/repository/entity/primary/McAuthorizationPo.java
  82. 34 0
      mjava/src/main/java/com/malk/repository/entity/primary/McTablePo.java
  83. 34 0
      mjava/src/main/java/com/malk/repository/entity/slave/McTablePo.java
  84. 58 0
      mjava/src/main/java/com/malk/schedule/McScheduleTask.java
  85. 159 0
      mjava/src/main/java/com/malk/server/aliwork/YDConf.java
  86. 219 0
      mjava/src/main/java/com/malk/server/aliwork/YDParam.java
  87. 32 0
      mjava/src/main/java/com/malk/server/aliwork/YDR.java
  88. 33 0
      mjava/src/main/java/com/malk/server/aliyun/ALYR.java
  89. 42 0
      mjava/src/main/java/com/malk/server/common/FilePath.java
  90. 87 0
      mjava/src/main/java/com/malk/server/common/McConf.java
  91. 167 0
      mjava/src/main/java/com/malk/server/common/McException.java
  92. 40 0
      mjava/src/main/java/com/malk/server/common/McPage.java
  93. 105 0
      mjava/src/main/java/com/malk/server/common/McR.java
  94. 33 0
      mjava/src/main/java/com/malk/server/common/McREnum.java
  95. 55 0
      mjava/src/main/java/com/malk/server/common/VenR.java
  96. 76 0
      mjava/src/main/java/com/malk/server/dingtalk/DDConf.java
  97. 78 0
      mjava/src/main/java/com/malk/server/dingtalk/DDConfigSign.java
  98. 79 0
      mjava/src/main/java/com/malk/server/dingtalk/DDFormComponentDto.java
  99. 24 0
      mjava/src/main/java/com/malk/server/dingtalk/DDInterActiveCard.java
  100. 0 0
      mjava/src/main/java/com/malk/server/dingtalk/DDR.java

+ 23 - 0
.idea/compiler.xml

@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="CompilerConfiguration">
+    <annotationProcessing>
+      <profile name="Maven default annotation processors profile" enabled="true">
+        <sourceOutputDir name="target/generated-sources/annotations" />
+        <sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
+        <outputRelativeToContentRoot value="true" />
+        <module name="mjava" />
+        <module name="mjava-dongfangxinhua" />
+        <module name="mjava-yibaoju" />
+      </profile>
+    </annotationProcessing>
+  </component>
+  <component name="JavacSettings">
+    <option name="ADDITIONAL_OPTIONS_OVERRIDE">
+      <module name="java-mcli" options="-Xlint:unchecked" />
+      <module name="mjava" options="-Xlint:unchecked" />
+      <module name="mjava-dongfangxinhua" options="-Xlint:unchecked" />
+      <module name="mjava-yibaoju" options="-Xlint:unchecked" />
+    </option>
+  </component>
+</project>

+ 13 - 0
.idea/encodings.xml

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="Encoding">
+    <file url="file://$PROJECT_DIR$/mjava-dongfangxinhua/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/mjava-dongfangxinhua/src/main/resources" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/mjava-yibaoju/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/mjava-yibaoju/src/main/resources" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/mjava/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/mjava/src/main/resources" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
+  </component>
+</project>

+ 20 - 0
.idea/jarRepositories.xml

@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="RemoteRepositoriesConfiguration">
+    <remote-repository>
+      <option name="id" value="central" />
+      <option name="name" value="Central Repository" />
+      <option name="url" value="https://repo.maven.apache.org/maven2" />
+    </remote-repository>
+    <remote-repository>
+      <option name="id" value="central" />
+      <option name="name" value="Maven Central repository" />
+      <option name="url" value="https://repo1.maven.org/maven2" />
+    </remote-repository>
+    <remote-repository>
+      <option name="id" value="jboss.community" />
+      <option name="name" value="JBoss Community repository" />
+      <option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
+    </remote-repository>
+  </component>
+</project>

+ 9 - 0
.idea/misc.xml

@@ -1,5 +1,14 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <project version="4">
+  <component name="ExternalStorageConfigurationManager" enabled="true" />
+  <component name="MavenProjectsManager">
+    <option name="originalFiles">
+      <list>
+        <option value="$PROJECT_DIR$/pom.xml" />
+      </list>
+    </option>
+    <option name="workspaceImportForciblyTurnedOn" value="true" />
+  </component>
   <component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
     <output url="file://$PROJECT_DIR$/out" />
   </component>

+ 0 - 8
.idea/modules.xml

@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
-  <component name="ProjectModuleManager">
-    <modules>
-      <module fileurl="file://$PROJECT_DIR$/.idea/mjava-5.iml" filepath="$PROJECT_DIR$/.idea/mjava-5.iml" />
-    </modules>
-  </component>
-</project>

+ 61 - 0
mjava-dongfangxinhua/pom.xml

@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 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-dongfangxinhua</artifactId>
+    <description>东方新华财资接口</description>
+
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+    </properties>
+
+    <dependencies>
+        <!-- 核心模块-->
+        <dependency>
+            <groupId>com.malk</groupId>
+            <artifactId>mjava</artifactId>
+            <version>${mjava.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.xerial</groupId>
+            <artifactId>sqlite-jdbc</artifactId>
+            <version>3.41.2.1</version>
+        </dependency>
+
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <version>2.1.1.RELEASE</version>
+                <configuration>
+                    <includeSystemScope>true</includeSystemScope>
+                    <!-- 如果没有该配置,devtools不会生效: 打包时关闭 -->
+                    <fork>false</fork>
+                    <!-- 避免中文乱码 -->
+                    <jvmArguments>-Dfile.encoding=UTF-8</jvmArguments>
+                </configuration>
+                <!-- 允许生成可运行jar -->
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+        <finalName>${project.artifactId}</finalName>
+    </build>
+</project>

+ 32 - 0
mjava-dongfangxinhua/src/main/java/com/malk/dongfangxinhua/Boot.java

@@ -0,0 +1,32 @@
+package com.malk.dongfangxinhua;
+
+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;
+
+/**
+ * corp项目: 扫描公共模块
+ * -
+ * 若是无需数据库模块, 配置无效地址也可启动, 引入mjava不支持直接 @SpringBootApplication(exclude = DataSourceAutoConfiguration.class) 配置
+ * 需要配置 jpa.hibernate.ddl-auto 为 none. 标识对表没有任何操作. 若不设置为 none, flyway.enabled 配置会无效, 在没有数库连接情况下程序无法启动
+ */
+@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);
+    }
+}

+ 211 - 0
mjava-dongfangxinhua/src/main/java/com/malk/dongfangxinhua/controller/DFXHController.java

@@ -0,0 +1,211 @@
+package com.malk.dongfangxinhua.controller;
+
+/**
+ * 错误抛出与拦截详见 CatchException
+ */
+
+import cn.hutool.core.util.XmlUtil;
+import cn.hutool.http.HttpRequest;
+import cn.hutool.json.JSONArray;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.XML;
+import com.alibaba.fastjson.JSON;
+import com.malk.dongfangxinhua.service.PayService;
+import com.malk.dongfangxinhua.service.UpdateProofServer;
+import com.malk.server.aliwork.YDConf;
+import com.malk.server.aliwork.YDParam;
+import com.malk.server.common.McException;
+import com.malk.server.common.McR;
+import com.malk.service.aliwork.YDClient;
+import com.malk.utils.UtilHttp;
+import com.malk.utils.UtilMap;
+import com.malk.utils.UtilServlet;
+import lombok.SneakyThrows;
+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;
+
+import javax.servlet.http.HttpServletRequest;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.stream.Collectors;
+
+@Slf4j
+@RestController
+@RequestMapping
+public class DFXHController {
+
+    /// doc: http://47.111.179.216/interface/main.html#
+    private static final String API = "http://27.115.15.66:8098/BisOutWeb/payCenter/dealReq.srv";
+
+
+    @Autowired
+    UpdateProofServer updateProofServer;
+
+    @Autowired
+    private YDClient ydClient;
+
+    @Autowired
+    private PayService payService;
+
+    // xml请求
+    private JSONObject _doPost(String tradeCode, Map condition) {
+
+        // xml请求格式数据处理
+        Map head = UtilMap.map("erpSysCode, custNo, tradeName", "XYYGLXT", "300200632", tradeCode);
+        Map body = UtilMap.map("head, map", head, condition);
+        String data = XmlUtil.mapToXmlStr(body, "body");
+        log.info("请求入参, {}", XML.toJSONObject(data));
+
+        /// prd 财资固定格式, xml头数据前需要添加类型code, 且不能换行
+        data = "0000012001" + tradeCode + data;
+        String rsp = HttpRequest.post(API).body(data).execute().body();
+        JSONObject result = XML.toJSONObject(rsp);
+        log.info("请求响应, {}", result);
+        result = result.getJSONObject("body");
+
+        // 错误拦截, 统一返回
+        String message = result.getJSONObject("head").getStr("retMsg");
+        McException.assertAccessException(!"查询成功".equals(message), message);
+
+        return result;
+    }
+
+    /**
+     * 查询联行号
+     */
+    @PostMapping("bank")
+    McR bank(String name) {
+        JSONObject rsp = _doPost("BY0004", UtilMap.map("bankName, isPreciseQuery", name, 0));
+        JSONArray list = rsp.getJSONObject("loopData").getJSONArray("row");
+        return McR.success(list);
+    }
+
+
+    /**
+     * 发起付款申请
+     */
+    @PostMapping("payment")
+    McR payment(@RequestBody Map data) {
+        payService.payMent(data);
+        return McR.success();
+    }
+
+    /**
+     * 手工付款
+     */
+    @PostMapping("handWorkPayment")
+    McR handWorkPayment(@RequestBody Map data) {
+        payService.handWorkPayment(data);
+        return McR.success();
+    }
+
+
+    /**
+     * 付款状态
+     */
+    @PostMapping("state")
+    McR payment(String payNo) {
+        JSONObject rsp = _doPost("BY0003", UtilMap.map("erpReqNo", payNo));
+        JSONObject state = rsp.getJSONObject("loopData").getJSONObject("row");
+        return McR.success(state);
+    }
+
+
+    /**
+     * 付款银行信息同步
+     */
+    @PostMapping("sync")
+    @SneakyThrows
+    McR sync(HttpServletRequest request) {
+        Thread.sleep(2000);
+        Map data = UtilServlet.getParamMap(request);
+        log.info("付款银行信息同步, {}", data);
+        payService.syncPayBankInfo(data);
+        return McR.success();
+    }
+
+    /**
+     * 付款状态同步
+     */
+    @PostMapping("syncPay")
+    McR syncPay(HttpServletRequest request) {
+
+        Map data = UtilServlet.getParamMap(request);
+        log.info("付款状态同步, {}", data);
+
+        payService.syncPayStatus();
+
+        return McR.success();
+    }
+
+
+    /**
+     *推送凭证
+     * @param request
+     * @return
+     */
+    @PostMapping("syncErp")
+    McR pushErp(HttpServletRequest request) {
+        Map pushData = UtilServlet.getParamMap(request);
+        log.info("同步Erp凭证数据, {}", pushData);
+        //调用凭证推送   实例ID   状态:1成功   2失败    失败信息
+        updateProofServer.pdateProof("","1","");
+        return McR.success();
+    }
+
+    @PostMapping(value = "test")
+    McR test(String name, HttpServletRequest request) {
+
+//        XmlUtil.;
+
+//        String rsp = UtilHttp.doPost(API, null, null, null, null);
+
+//        String xmlData = "";
+//        Document document = XmlUtil.parseXml(xmlData);
+
+        StringBuffer reqXmlData = new StringBuffer();
+        try {
+            InputStream inputStream = request.getInputStream();
+            String s;
+            BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
+            while ((s = in.readLine()) != null) {
+                reqXmlData.append(s);
+            }
+            in.close();
+            inputStream.close();
+        } catch (IOException e) {
+            System.out.println("流解析xml数据异常!");
+            e.printStackTrace();
+        }
+        //判断请求数据是否为空
+        if (reqXmlData.length() <= 0) {
+            System.out.println("请求数据为空!");
+        }
+
+        //json类型数据 [自动去掉标识]
+        JSONObject jsonObject = XML.toJSONObject(reqXmlData.toString());
+        log.info("xxxx,, {}", jsonObject);
+
+        String data = reqXmlData.toString();
+//        UtilHttp.doRequest(UtilHttp.METHOD.POST, API, null, null, jsonObject);
+
+        log.info("xxx, {}", "0000012001BY0004<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\"?>\\n\"" + jsonObject.toString());
+        String rsp = HttpRequest.post(API).body(data).execute().body();
+        log.info("xxx, {}", rsp);
+
+        return McR.success(jsonObject.toString());
+    }
+
+
+}

+ 48 - 0
mjava-dongfangxinhua/src/main/java/com/malk/dongfangxinhua/schedule/ScheduleTask.java

@@ -0,0 +1,48 @@
+package com.malk.dongfangxinhua.schedule;
+
+import com.malk.dongfangxinhua.service.PayService;
+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"})
+public class ScheduleTask {
+
+
+     @Autowired
+    PayService payService;
+
+    /**
+     * ,每天1点同步付款状态
+     */
+    @Scheduled(cron = " 0 0 1 * * ? ")
+    public void syncPayStatus() {
+        try {
+            payService.syncPayStatus();
+        } catch (Exception e) {
+            // 记录错误信息
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * 每天1点05执行凭证推送
+     */
+    @Scheduled(cron = " 0 5 1 * * ? ")
+    public void syncErpStatus() {
+        try {
+//            payService.syncPayStatus();
+        } catch (Exception e) {
+            // 记录错误信息
+            e.printStackTrace();
+        }
+    }
+
+
+}

+ 251 - 0
mjava-dongfangxinhua/src/main/java/com/malk/dongfangxinhua/service/Impl/PayServiceImpl.java

@@ -0,0 +1,251 @@
+package com.malk.dongfangxinhua.service.Impl;
+
+import cn.hutool.core.util.XmlUtil;
+import cn.hutool.http.HttpRequest;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.XML;
+import com.alibaba.fastjson.JSON;
+import com.malk.dongfangxinhua.service.PayService;
+import com.malk.server.aliwork.YDConf;
+import com.malk.server.aliwork.YDParam;
+import com.malk.server.common.McException;
+import com.malk.service.aliwork.YDClient;
+import com.malk.utils.UtilHttp;
+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;
+import java.util.Random;
+
+@Service
+@Slf4j
+public class PayServiceImpl implements PayService {
+
+    @Autowired
+    private YDClient ydClient;
+
+    private static final String API = "http://27.115.15.66:8098/BisOutWeb/payCenter/dealReq.srv";
+
+
+
+    /**
+     * 不做错误拦截
+     * @param tradeCode
+     * @param condition
+     * @return
+     */
+    private JSONObject _doPosts(String tradeCode, Map condition) {
+
+        // xml请求格式数据处理
+        Map head = UtilMap.map("erpSysCode, custNo, tradeName", "XYYGLXT", "300200632", tradeCode);
+        Map body = UtilMap.map("head, map", head, condition);
+        String data = XmlUtil.mapToXmlStr(body, "body");
+        log.info("请求入参, {}", XML.toJSONObject(data));
+
+        /// prd 财资固定格式, xml头数据前需要添加类型code, 且不能换行
+        data = "0000012001" + tradeCode + data;
+        String rsp = HttpRequest.post(API).body(data).execute().body();
+        JSONObject result = XML.toJSONObject(rsp);
+        log.info("请求响应, {}", result);
+        return result;
+    }
+
+
+    /**
+     *  付款状态同步
+     * @param
+     */
+    @Override
+    public void syncPayStatus() {
+        // 查询付款账户  (获取跟付款申请表,查询状态为付款中的数据)
+        YDParam ydParam = YDParam.builder()
+                .formUuid("FORM-6L966171YHZA0OJOAUKWG89JTZA635EV3Y1ILR")
+                .searchFieldJson(JSON.toJSONString(UtilMap.map("textField_lr08rrpc", "付款中")))
+                .build();
+        //获取所有 状态为付款中的数据
+        List<Map> dataList = (List<Map>) ydClient.queryData(ydParam, YDConf.FORM_QUERY.retrieve_search_form).getData();
+
+        dataList.forEach(item -> {
+            String payState = "";
+            String payMsg = "";
+            //formInstanceId
+            //获取erpReqNo 和实例ID  循环去获取付款状态
+            JSONObject rsp  = _doPosts("BY0003", (Map) UtilMap.map("erpReqNo, billCode, erpBatchNo", null,((Map) item.get("formData")).get("textField_lra9fqfv").toString(),null));
+            //获取头部信息
+            JSONObject head = rsp.getJSONObject("body").getJSONObject("head");
+            //判断retMsg     retCode =2 时,仅代表查询失败
+            if ("2".equals(head.getStr("retCode"))){
+                //失败信息
+                payMsg = head.getStr("retMsg");
+
+                // 更新跟踪单状态
+                ydClient.operateData(YDParam.builder()
+                        .formInstanceId((String) item.get("formInstanceId"))
+                        .updateFormDataJson(JSON.toJSONString(UtilMap.map("textField_loeg6mse, textareaField_loeg6msh",((Map) item.get("formData")).get("textField_lra9fqfv").toString(),payMsg))).build(), YDConf.FORM_OPERATION.update);
+
+            }else{
+                JSONObject state = rsp.getJSONObject("body").getJSONObject("loopData").getJSONObject("row");
+                /**
+                 * 00付款成功
+                 * 01付款待提交
+                 * 02付款流程中
+                 * 03等待银行结果
+                 * 95付款已删除
+                 * 96付款已打回
+                 * 98付款失败
+                 * 99其他状态
+                 */
+                payState = state.getStr("payState");
+                payMsg =  state.getStr("payMsg");
+                //获取付款成功的
+                if (payState.equals("00")) {
+
+                    payMsg = "付款成功";
+
+                    String  sqly = ((Map) item.get("formData")).get("selectField_lqalbx96").toString();   //申请来源
+                    String guid = ((Map) item.get("formData")).get("textField_li1y52yh").toString();        //PayGUID
+                    String BillCode = ((Map) item.get("formData")).get("textField_lra9fqfv").toString();   //结算流水号
+
+                    //数据来源明源云则回写
+                    if ("明源ERP".equals(sqly)){
+                        //付款成功的更新Erp状态
+                        String erpRsp = UtilHttp.doPost("https://gateway.dslink.net.cn/api/webhook/E185908E63CF407B911FA870642A39A5",
+                                null,null, UtilMap.map("PayGUID, BillCode",guid,BillCode),new HashMap());
+                        //通过 http status 判定, 200 即为成功
+                        Map result = (Map) JSON.parse(erpRsp);
+                        Boolean status = (Boolean) result.get("status");
+                        if (status) {
+                            log.info("回写ERP成功{}",result);
+                        }else{
+                            log.info("回写ERP失败{}",result);
+                        }
+                    }
+                    // 更新付款申请单状态
+                    ydClient.operateData(YDParam.builder()
+                            .formInstanceId((String) item.get("formInstanceId"))
+                            .updateFormDataJson(JSON.toJSONString(UtilMap.map("textField_lr08rrpc, textField_lof4wiqt", "付款成功","凭证未生成")))
+                            .build(), YDConf.FORM_OPERATION.update);
+
+
+                    // 更新跟踪单状态
+                    ydClient.operateData(YDParam.builder()
+                            .formInstanceId(((Map) item.get("formData")).get("textField_lr0ct6da").toString())
+                            .updateFormDataJson(JSON.toJSONString(UtilMap.map("textField_loeg6mse, textField_loeg6msg",((Map) item.get("formData")).get("textField_lra9fqfv").toString(),payMsg)))
+                            .build(), YDConf.FORM_OPERATION.update);
+
+                }
+                //付款已删除   付款已打回   付款失败跟踪单   其他状态
+                else if (payState.equals("95")||payState.equals("96")||payState.equals("98")||payState.equals("99")){
+
+                    // 更新付款申请单状态
+                    ydClient.operateData(YDParam.builder()
+                            .formInstanceId((String) item.get("formInstanceId"))
+                            .updateFormDataJson(JSON.toJSONString(UtilMap.map("textField_lr08rrpc, textField_lraevvod", "待付款",((Map) item.get("formData")).get("textField_lra9fqfv").toString()+"-"+new Random().nextInt(1000))))
+                            .build(), YDConf.FORM_OPERATION.update);
+
+                    // 更新跟踪单状态
+                    ydClient.operateData(YDParam.builder()
+                            .formInstanceId(((Map) item.get("formData")).get("textField_lr0ct6da").toString())
+                            .updateFormDataJson(JSON.toJSONString(UtilMap.map("textField_loeg6mse, textField_loeg6msg",((Map) item.get("formData")).get("textField_lra9fqfv").toString(),payMsg)))
+                            .build(), YDConf.FORM_OPERATION.update);
+
+                }
+
+            }
+        });
+    }
+
+    /**
+     * 同步付款银行信息
+     * @param map
+     */
+    @Override
+    public void syncPayBankInfo(Map map) {
+        //根据企业源系统业务编码   查询付款账号表信息
+        YDParam ydParam = YDParam.builder()
+                .formUuid("FORM-OS566L91HS1B78LW7GMUQ6AQA6C9288Y2W1IL4")
+                .searchFieldJson(JSON.toJSONString(UtilMap.map("textField_lmhlzvdl", map.get("code"))))
+                .build();
+
+        List<Map> mapList = (List<Map>) ydClient.queryData(ydParam, YDConf.FORM_QUERY.retrieve_search_form).getData();
+        Map fkmap = (Map) mapList.get(0).get("formData");
+
+
+        log.info("fkmap:{}",fkmap);
+
+        //根据收款账户  查询收款信息
+        YDParam ydParam2 = YDParam.builder()
+                .formUuid("FORM-NO9667913LKF3GE09L0EHA3YMDGU3GF9NEEOLX1")
+                .searchFieldJson(JSON.toJSONString(UtilMap.map("textField_li1w31c2", map.get("account"))))
+                .build();
+        List<Map> mapList1 = (List<Map>) ydClient.queryData(ydParam2, YDConf.FORM_QUERY.retrieve_search_form).getData();
+
+        Map skmap = (Map) mapList1.get(0).get("formData");
+
+        //收款联行银行号  付款银行联行号  收款银行实例    企业编码                       付款银行简称  付款账户名称   付款开户行    付款银行账号   直连方式
+        ydClient.operateData(YDParam.builder().formInstId(map.get("formInstId").toString())
+                .updateFormDataJson(JSON.toJSONString(UtilMap.map("textField_lr7tmvy9, textField_lr7tmvy8, textField_lr7tmvya, textField_lmhln79w, " +
+                                "selectField_lqalbx97, textField_li1y52z5, textField_li1y52z4, selectField_li1y52z3, selectField_lqalbx98",
+                        skmap.get("selectField_lof5vr7d"),fkmap.get("textField_li1w31c4"),mapList1.get(0).get("formInstanceId"),fkmap.get("textField_lmhln79w"),
+                        fkmap.get("textField_li1w31c5"),fkmap.get("textField_li1w31c1"),fkmap.get("textField_li1w31c3"),fkmap.get("textField_li1w31c2"),fkmap.get("selectField_li1w31c8"))))
+                .build(), YDConf.FORM_OPERATION.update);
+    }
+
+    /**
+     * 发起付款
+     * @param data
+     */
+    @Override
+    public void payMent(Map data) {
+
+        // 付款: payerAccNo, payerCorpName; 领款: payeeAccNo, payeeAccName, payeeBankName, payeeBankCode; 金额: payMoney, payPurpose, erpReqNo
+        JSONObject rsp = _doPosts("BY0001", data);
+        JSONObject head = rsp.getJSONObject("body").getJSONObject("head");
+        McException.assertAccessException(!"交易成功".equals(head.get("retMsg")),head.get("retMsg").toString());
+        if ("交易成功".equals(head.get("retMsg"))){
+            JSONObject objectMap = rsp.getJSONObject("body").getJSONObject("map");
+            String billCode = (String) objectMap.get("billCode");
+            // 更新付款申请单状态
+            ydClient.operateData(YDParam.builder()
+                    .formInstanceId(data.get("formInstId").toString())
+                    .updateFormDataJson(JSON.toJSONString(UtilMap.map("textField_lra9fqfv, textareaField_lraa9mhe, textField_lr08rrpc", billCode,head.get("retMsg"),"付款中"))).build(), YDConf.FORM_OPERATION.update);
+        }
+
+    }
+
+    @Override
+    public void handWorkPayment(Map data) {
+        log.info("手工付款单{}",data);
+        //根据实例ID  查询付款申请单
+        YDParam ydParam = YDParam.builder()
+                .formInstId(data.get("formInstId").toString())
+                .build();
+        Map formData = ydClient.queryData(ydParam, YDConf.FORM_QUERY.retrieve_id).getFormData();
+
+        log.info("formData{}",formData);
+
+        //数据来源明源云则回写
+        if ("明源ERP".equals(formData.get("selectField_lqalbx96")) && "稠州银行金华6486".equals(formData.get("textField_loe5he57"))){
+            //付款成功的更新Erp状态
+            String erpRsp = UtilHttp.doPost("https://gateway.dslink.net.cn/api/webhook/E185908E63CF407B911FA870642A39A5",
+                    null,null, UtilMap.map("PayGUID, BillCode",formData.get("textField_li1y52yh"),""),new HashMap());
+            //通过 http status 判定, 200 即为成功
+            Map result = (Map) JSON.parse(erpRsp);
+            Boolean status = (Boolean) result.get("status");
+            if (status) {
+                log.info("回写ERP成功{}",result);
+            }else{
+                log.info("回写ERP失败{}",result);
+            }
+        }
+
+
+
+    }
+
+
+}

+ 63 - 0
mjava-dongfangxinhua/src/main/java/com/malk/dongfangxinhua/service/Impl/UpdateProofServerImpl.java

@@ -0,0 +1,63 @@
+package com.malk.dongfangxinhua.service.Impl;
+
+import com.malk.dongfangxinhua.service.UpdateProofServer;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import java.sql.*;
+
+@Service
+@Slf4j
+public class UpdateProofServerImpl implements UpdateProofServer {
+
+
+    //配置客户的sqlist URL
+    private static final String SQLITE_URL ="jdbc:sqlite:/D:/JavaSpace/fbi.dll";
+
+    /**
+     * 凭证同步
+     *
+     * @param formInstId
+     */
+
+    @Override
+    public void pdateProof(String formInstId,String sta,String errMsg) {
+
+        Connection connection = null;
+        Statement statement = null;
+        try {
+            // 加载SQLite的JDBC驱动程序
+            Class.forName("org.sqlite.JDBC");
+            //创建链接
+            connection = DriverManager.getConnection(SQLITE_URL);
+            // 创建Statement对象
+            statement = connection.createStatement();
+            // 执行UPDATE语句
+            String sql = "UPDATE rest_drrecord SET status = sta,generate_time=CURRENT_TIMESTAMP,note=errMsg WHERE instance_id ="+formInstId; // 替换为你的UPDATE语句
+
+
+
+
+
+            int rowsAffected = statement.executeUpdate(sql);
+            // 输出受影响的行数
+            System.out.println("实例ID:"+formInstId+"受影响的行数: " + rowsAffected);
+        } catch (ClassNotFoundException | SQLException e) {
+            e.printStackTrace();
+        } finally {
+            // 关闭连接和资源
+            try {
+                if (statement != null) {
+                    statement.close();
+                }
+                if (connection != null) {
+                    connection.close();
+                }
+            } catch (SQLException e) {
+                e.printStackTrace();
+            }
+        }
+
+
+    }
+}

+ 31 - 0
mjava-dongfangxinhua/src/main/java/com/malk/dongfangxinhua/service/PayService.java

@@ -0,0 +1,31 @@
+package com.malk.dongfangxinhua.service;
+
+import java.util.Map;
+
+public interface PayService {
+
+    /**
+     *付款状态同步
+     * @param
+     */
+    void syncPayStatus();
+
+    /**
+     * 付款银行信息同步
+     * @param map
+     */
+    void syncPayBankInfo(Map map);
+
+    /**
+     * 发起付款
+     * @param data
+     */
+    void payMent(Map data);
+
+
+    /**
+     * 手工付款
+     *
+     */
+    void handWorkPayment(Map data);
+}

+ 8 - 0
mjava-dongfangxinhua/src/main/java/com/malk/dongfangxinhua/service/UpdateProofServer.java

@@ -0,0 +1,8 @@
+package com.malk.dongfangxinhua.service;
+
+public interface UpdateProofServer {
+
+    void pdateProof(String formInstId,String status,String errMsg);
+
+
+}

+ 65 - 0
mjava-dongfangxinhua/src/main/resources/application-dev.yml

@@ -0,0 +1,65 @@
+# 环境配置
+server:
+  port: 9001
+  servlet:
+    context-path: /api/dongfangxinhua
+
+# condition
+spel:
+  scheduling: false        # 定时任务是否执行
+  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: mu123
+    url: jdbc:mysql://127.0.0.1:3306/mjava?serverTimezone=Asia/Shanghai&useUnicode=yes&characterEncoding=UTF-8&useSSL=true
+    # 主库
+    primary:
+      username: root
+      password: mu123
+      jdbc-url: jdbc:mysql://127.0.0.1:3306/mjava?serverTimezone=Asia/Shanghai&useUnicode=yes&characterEncoding=UTF-8&useSSL=true
+    # 从库
+    slave:
+      username: root
+      password: mu123
+      jdbc-url: jdbc:mysql://127.0.0.1:3306/mjava_slave?serverTimezone=Asia/Shanghai&useUnicode=yes&characterEncoding=UTF-8&useSSL=true
+  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: 2660236361
+  appKey: dinguuieqv4lkvp3vkaf
+  appSecret: N5JjPU9RDk77pTze5vRWmiWLDjPKeYJV3sQrmYgN_SC57nOALmj570rVB0SGGcQQ
+  corpId: dingec9ee223c2b3a671
+  aesKey:
+  token:
+  operator: ""   # OA管理员账号
+
+# aliwork
+aliwork:
+  appType: "APP_LNBUWW7ZBXRUWE29PCOV"
+  systemToken: "WC866ZA1PI1B2L1JDNL334F44KI03GREIV1ILZ13"
+
+

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

@@ -0,0 +1,38 @@
+# 环境配置
+server:
+  port: 9022
+  servlet:
+    context-path: /api/dongfangxinhua
+
+# condition
+spel:
+  scheduling: false        # 定时任务是否执行
+  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: mu123
+    url: jdbc:mysql://127.0.0.1:3306/mjava?serverTimezone=Asia/Shanghai&useUnicode=yes&characterEncoding=UTF-8&useSSL=true
+  jpa:
+    database: MYSQL
+    database-platform: org.hibernate.dialect.MySQL57Dialect
+
+# dingtalk
+dingtalk:
+  agentId: 2660236361
+  appKey: dinguuieqv4lkvp3vkaf
+  appSecret: N5JjPU9RDk77pTze5vRWmiWLDjPKeYJV3sQrmYgN_SC57nOALmj570rVB0SGGcQQ
+  corpId: dingec9ee223c2b3a671
+  aesKey:
+  token:
+  operator: ""   # OA管理员账号
+
+# aliwork
+aliwork:
+  appType: "APP_LNBUWW7ZBXRUWE29PCOV"
+  systemToken: "WC866ZA1PI1B2L1JDNL334F44KI03GREIV1ILZ13"

+ 39 - 0
mjava-dongfangxinhua/src/test/resource/server.sh

@@ -0,0 +1,39 @@
+#!/bin/bash
+
+appname='mjava-dongfangxinhua'
+
+if [ "$1" == "dev" ]; then
+  java -Xms256m -Xmx256m -jar $appname.jar --spring.profiles.active=dev
+else
+  if [ "$1" == "start" ]; then
+    nohup java -Xms256m -Xmx256m -jar $appname.jar &
+    echo "server prod is starting"
+    tail -f log/info.log
+  else
+    if [ "$1" == "test" ]; then
+      nohup java -Xms256m -Xmx256m -jar $appname.jar --spring.profiles.active=test &
+      echo "server test is starting"
+      tail -f log/info.log
+    else
+      if [ "$1" == "stop" ]; then
+        PID=$(ps -ef | grep $appname.jar | grep -v grep | awk '{ print $2 }')
+        if [ -z "$PID" ]; then
+          echo "server is already stopped"
+        else
+          echo kill $PID
+          kill $PID
+        fi
+      else
+        if [ "$1" == "status" ]; then
+          PID=$(ps -ef | grep $appname.jar | grep -v grep | awk '{ print $2 }')
+          if [ -z "$PID" ]; then
+            echo "server is stopped"
+          else
+            echo "server is running"
+            echo $PID
+          fi
+        fi
+      fi
+    fi
+  fi
+fi

+ 65 - 0
mjava-dongfangxinhua/target/classes/application-dev.yml

@@ -0,0 +1,65 @@
+# 环境配置
+server:
+  port: 9001
+  servlet:
+    context-path: /api/dongfangxinhua
+
+# condition
+spel:
+  scheduling: false        # 定时任务是否执行
+  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: mu123
+    url: jdbc:mysql://127.0.0.1:3306/mjava?serverTimezone=Asia/Shanghai&useUnicode=yes&characterEncoding=UTF-8&useSSL=true
+    # 主库
+    primary:
+      username: root
+      password: mu123
+      jdbc-url: jdbc:mysql://127.0.0.1:3306/mjava?serverTimezone=Asia/Shanghai&useUnicode=yes&characterEncoding=UTF-8&useSSL=true
+    # 从库
+    slave:
+      username: root
+      password: mu123
+      jdbc-url: jdbc:mysql://127.0.0.1:3306/mjava_slave?serverTimezone=Asia/Shanghai&useUnicode=yes&characterEncoding=UTF-8&useSSL=true
+  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: 2660236361
+  appKey: dinguuieqv4lkvp3vkaf
+  appSecret: N5JjPU9RDk77pTze5vRWmiWLDjPKeYJV3sQrmYgN_SC57nOALmj570rVB0SGGcQQ
+  corpId: dingec9ee223c2b3a671
+  aesKey:
+  token:
+  operator: ""   # OA管理员账号
+
+# aliwork
+aliwork:
+  appType: "APP_LNBUWW7ZBXRUWE29PCOV"
+  systemToken: "WC866ZA1PI1B2L1JDNL334F44KI03GREIV1ILZ13"
+
+

+ 38 - 0
mjava-dongfangxinhua/target/classes/application-prod.yml

@@ -0,0 +1,38 @@
+# 环境配置
+server:
+  port: 9022
+  servlet:
+    context-path: /api/dongfangxinhua
+
+# condition
+spel:
+  scheduling: false        # 定时任务是否执行
+  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: mu123
+    url: jdbc:mysql://127.0.0.1:3306/mjava?serverTimezone=Asia/Shanghai&useUnicode=yes&characterEncoding=UTF-8&useSSL=true
+  jpa:
+    database: MYSQL
+    database-platform: org.hibernate.dialect.MySQL57Dialect
+
+# dingtalk
+dingtalk:
+  agentId: 2660236361
+  appKey: dinguuieqv4lkvp3vkaf
+  appSecret: N5JjPU9RDk77pTze5vRWmiWLDjPKeYJV3sQrmYgN_SC57nOALmj570rVB0SGGcQQ
+  corpId: dingec9ee223c2b3a671
+  aesKey:
+  token:
+  operator: ""   # OA管理员账号
+
+# aliwork
+aliwork:
+  appType: "APP_LNBUWW7ZBXRUWE29PCOV"
+  systemToken: "WC866ZA1PI1B2L1JDNL334F44KI03GREIV1ILZ13"

二进制
mjava-dongfangxinhua/target/classes/com/malk/dongfangxinhua/Boot.class


二进制
mjava-dongfangxinhua/target/classes/com/malk/dongfangxinhua/controller/DFXHController.class


二进制
mjava-dongfangxinhua/target/classes/com/malk/dongfangxinhua/schedule/ScheduleTask.class


二进制
mjava-dongfangxinhua/target/classes/com/malk/dongfangxinhua/service/Impl/PayServiceImpl.class


二进制
mjava-dongfangxinhua/target/classes/com/malk/dongfangxinhua/service/Impl/UpdateProofServerImpl.class


二进制
mjava-dongfangxinhua/target/classes/com/malk/dongfangxinhua/service/PayService.class


二进制
mjava-dongfangxinhua/target/classes/com/malk/dongfangxinhua/service/UpdateProofServer.class


+ 3 - 0
mjava-dongfangxinhua/target/maven-archiver/pom.properties

@@ -0,0 +1,3 @@
+artifactId=mjava-dongfangxinhua
+groupId=com.malk
+version=1.0-SNAPSHOT

+ 7 - 0
mjava-dongfangxinhua/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst

@@ -0,0 +1,7 @@
+com\malk\dongfangxinhua\service\UpdateProofServer.class
+com\malk\dongfangxinhua\schedule\ScheduleTask.class
+com\malk\dongfangxinhua\service\Impl\PayServiceImpl.class
+com\malk\dongfangxinhua\service\PayService.class
+com\malk\dongfangxinhua\controller\DFXHController.class
+com\malk\dongfangxinhua\Boot.class
+com\malk\dongfangxinhua\service\Impl\UpdateProofServerImpl.class

+ 7 - 0
mjava-dongfangxinhua/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst

@@ -0,0 +1,7 @@
+D:\JavaSpace\mjava-3\mjava-dongfangxinhua\src\main\java\com\malk\dongfangxinhua\service\Impl\UpdateProofServerImpl.java
+D:\JavaSpace\mjava-3\mjava-dongfangxinhua\src\main\java\com\malk\dongfangxinhua\controller\DFXHController.java
+D:\JavaSpace\mjava-3\mjava-dongfangxinhua\src\main\java\com\malk\dongfangxinhua\service\Impl\PayServiceImpl.java
+D:\JavaSpace\mjava-3\mjava-dongfangxinhua\src\main\java\com\malk\dongfangxinhua\Boot.java
+D:\JavaSpace\mjava-3\mjava-dongfangxinhua\src\main\java\com\malk\dongfangxinhua\service\UpdateProofServer.java
+D:\JavaSpace\mjava-3\mjava-dongfangxinhua\src\main\java\com\malk\dongfangxinhua\service\PayService.java
+D:\JavaSpace\mjava-3\mjava-dongfangxinhua\src\main\java\com\malk\dongfangxinhua\schedule\ScheduleTask.java

二进制
mjava-dongfangxinhua/target/mjava-dongfangxinhua.jar


二进制
mjava-dongfangxinhua/target/mjava-dongfangxinhua.jar.original


+ 54 - 0
mjava-yibaoju/pom.xml

@@ -0,0 +1,54 @@
+<?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-yibaoju</artifactId>
+    <description>医保局开发</description>
+
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+    </properties>
+
+    <dependencies>
+        <!-- 核心模块-->
+        <dependency>
+            <groupId>com.malk</groupId>
+            <artifactId>mjava</artifactId>
+            <version>${mjava.version}</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <version>2.1.1.RELEASE</version>
+                <configuration>
+                    <includeSystemScope>true</includeSystemScope>
+                    <!-- 如果没有该配置,devtools不会生效: 打包时关闭 -->
+                    <fork>false</fork>
+                    <!-- 避免中文乱码 -->
+                    <jvmArguments>-Dfile.encoding=UTF-8</jvmArguments>
+                </configuration>
+                <!-- 允许生成可运行jar -->
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+        <finalName>${project.artifactId}</finalName>
+    </build>
+</project>

+ 32 - 0
mjava-yibaoju/src/main/java/com/malk/yibaoju/Boot.java

@@ -0,0 +1,32 @@
+package com.malk.yibaoju;
+
+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;
+
+/**
+ * corp项目: 扫描公共模块
+ * -
+ * 若是无需数据库模块, 配置无效地址也可启动, 引入mjava不支持直接 @SpringBootApplication(exclude = DataSourceAutoConfiguration.class) 配置
+ * 需要配置 jpa.hibernate.ddl-auto 为 none. 标识对表没有任何操作. 若不设置为 none, flyway.enabled 配置会无效, 在没有数库连接情况下程序无法启动
+ */
+@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);
+    }
+}

+ 274 - 0
mjava-yibaoju/src/main/java/com/malk/yibaoju/controller/YBJController.java

@@ -0,0 +1,274 @@
+package com.malk.yibaoju.controller;
+
+
+import com.alibaba.fastjson.JSON;
+import com.malk.server.aliwork.YDConf;
+import com.malk.server.aliwork.YDParam;
+import com.malk.server.common.McException;
+import com.malk.server.common.McR;
+import com.malk.service.aliwork.YDClient;
+import com.malk.service.aliwork.YDService;
+import com.malk.utils.UtilDateTime;
+import com.malk.utils.UtilMap;
+import com.malk.utils.UtilServlet;
+import com.malk.yibaoju.service.YBJService;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletRequest;
+import java.time.LocalDate;
+import java.time.format.TextStyle;
+import java.time.temporal.TemporalAdjusters;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 错误抛出与拦截详见 CatchException
+ */
+@Slf4j
+@RestController
+@RequestMapping()
+public class YBJController {
+
+    @Autowired
+    private YBJService ybjService;
+
+    @Autowired
+    private YDClient ydClient;
+
+    /**
+     * 图片免登
+     */
+    @PostMapping("temporaryUrl")
+    McR temporaryUrl(@RequestBody Map data) {
+
+        log.info("图片免登, {}", data);
+        return McR.success(ydClient.convertTemporaryUrl(UtilMap.getString(data, "url")));
+    }
+
+    /**
+     * 根据创建的护理计划生成本月多条护理数据
+     */
+    @SneakyThrows
+    @PostMapping("createPlan")
+    McR createPlan(HttpServletRequest request){
+
+        Map data = UtilServlet.getParamMap(request);
+        log.info("createPlan 护理计划{}",data);
+
+        LocalDate currentDate = LocalDate.now();
+        //获取当前日期是本月几号
+        int dayOfMonth = currentDate.getDayOfMonth();
+
+        if ("1".equals(data.get("status"))){
+            ybjService.creatInfo(data.get("formInstId").toString());
+            //删除睡眠两秒  在执行创建
+            if (dayOfMonth>24){
+                Thread.sleep(2000);
+                ybjService.creatNextInfo(data.get("formInstId").toString());
+            }
+        }else{
+            ybjService.updateInfo(data.get("formInstId").toString());
+            if (dayOfMonth>24){
+                Thread.sleep(2000);
+                ybjService.creatNextInfo(data.get("formInstId").toString());
+            }
+        }
+        return McR.success();
+    }
+
+
+
+    /**
+     * 暂停/恢复护理对象 删除/创建护理计划
+     * @param request
+     * @return
+     */
+    @SneakyThrows
+    @PostMapping("deletePlan")
+    McR deletePlan(HttpServletRequest request){
+
+        Map data = UtilServlet.getParamMap(request);
+        log.info("deletePlan 暂停/恢复护理对象 删除/创建护理计划{}",data);
+
+        LocalDate currentDate = LocalDate.now();
+        //获取当前日期是本月几号
+        int dayOfMonth = currentDate.getDayOfMonth();
+
+        if ("1".equals(data.get("code"))){
+            //获取护理对象CID
+            ybjService.deletePlan(data.get("cId").toString());
+        }else if ("2".equals(data.get("code"))){
+            //通过护理对象CID获取护理计划实例ID
+            YDParam ydParam = YDParam.builder()
+                    .formUuid("FORM-RK966E7105DFD27FA7EQHAOH9IFS2RN6MP3OL2")
+                    .searchFieldJson(JSON.toJSONString(UtilMap.map("textField_lnyhv5tn",data.get("cId"))))
+                    .build();
+            List<Map> dataList = (List<Map>) ydClient.queryData(ydParam, YDConf.FORM_QUERY.retrieve_search_form).getData();
+            if(dataList.size()>0){
+                Map map = (Map) dataList.get(0);
+                ybjService.creatInfo(map.get("formInstanceId").toString());
+                if (dayOfMonth>24){
+                    Thread.sleep(2000);
+                    ybjService.creatNextInfo(map.get("formInstanceId").toString());
+                }
+
+                Thread.sleep(2000);
+                ybjService.creatNextInfo(map.get("formInstanceId").toString());
+            }
+        }else{   //编辑护理对象信息
+
+            //先删除
+            ybjService.deletePlan(data.get("cId").toString());
+            //删除睡眠两秒  在执行创建
+            Thread.sleep(2000);
+            //然后重新创建
+            //通过护理对象CID获取护理计划实例ID
+            YDParam ydParam = YDParam.builder()
+                    .formUuid("FORM-RK966E7105DFD27FA7EQHAOH9IFS2RN6MP3OL2")
+                    .searchFieldJson(JSON.toJSONString(UtilMap.map("textField_lnyhv5tn",data.get("cId"))))
+                    .build();
+            List<Map> dataList = (List<Map>) ydClient.queryData(ydParam, YDConf.FORM_QUERY.retrieve_search_form).getData();
+            if(dataList.size()>0){
+                Map map = (Map) dataList.get(0);
+                ybjService.creatInfo(map.get("formInstanceId").toString());
+
+                if (dayOfMonth>24){
+                    Thread.sleep(2000);
+                    ybjService.creatNextInfo(map.get("formInstanceId").toString());
+                }
+
+            }
+
+        }
+        return McR.success();
+    }
+
+
+    @Autowired
+    private YDService ydService;
+
+    /**
+     * 更新子表关联表单 fixme 1.28 子表单关联表单更新
+     */
+    @GetMapping("update")
+    McR update() {
+
+        List<Map> dataList = ydService.queryFormData_all(YDParam.builder()
+                .formUuid("FORM-RK966E7105DFD27FA7EQHAOH9IFS2RN6MP3OL2")
+//                .searchFieldJson(JSON.toJSONString(UtilMap.map("textField_lnyhv5tn", "310226193301123225")))
+                .build());
+
+        for (Map formData: dataList) {
+
+            List<Map> details = (List<Map>) formData.get("tableField_lo3pmohx");
+            for (Map row: details) {
+                row.put("employeeField_lo3pmoi6", "employeeField_lo3pmoi6_id");
+                row.put("associationFormField_lo3pmohz", YDConf.associationForm("APP_YIDJ71B8QORD2YHNZ26Q", "FORM-RK966E7105DFD27FA7EQHAOH9IFS2RN6MP3OL2" ,UtilMap.getString(row,"textField_lrvtfzng"), UtilMap.getString(row, "textField_lrvtfznf"), "", false));
+            }
+            formData.put("tableField_lo3pmohx", details);
+
+            try {
+                ydClient.operateData(YDParam.builder()
+                        .formInstanceId(UtilMap.getString(formData, "instanceId"))
+                        .updateFormDataJson(JSON.toJSONString(formData))
+                        .build(), YDConf.FORM_OPERATION.update);
+            } catch (McException e) {
+                log.info("更新异常, {}",UtilMap.getString(formData, "instanceId") );
+            }
+        }
+
+        log.info("更新子表关联表单, {}", dataList.size());
+
+        return McR.success();
+    }
+
+    /**
+     * 通过安排创建护理任务
+     * @param request
+     * @return
+     */
+    @PostMapping("syncCreatePlan")
+    McR syncCreatePlan(HttpServletRequest request) {
+        ybjService.syncNursePlan();
+        return McR.success();
+    }
+
+    /**
+     * 修改打卡数据中的护理计划名称
+     * @param request
+     * @return
+     */
+    @PostMapping("updatePlanName")
+    McR updatePlanName(HttpServletRequest request) {
+        Map data = UtilServlet.getParamMap(request);
+        log.info("修改打卡数据中的护理计划名称{}",data);
+
+//        List<Map> mapList = Arrays.asList(UtilMap.map("appType, formUuid, instanceId, title, subTitle, formType","APP_YIDJ71B8QORD2YHNZ26Q","FORM-IY966L71PJ8FV10D61M1HBHU6FB320M2765OLM",data.get("relevanceInstanceId").toString(),data.get("titleName").toString(),"",""));;
+
+        Map hashMap = new HashMap();
+        hashMap.put("associationFormField_lpgi5wv5",YDConf.associationForm("APP_YIDJ71B8QORD2YHNZ26Q","FORM-IY966L71PJ8FV10D61M1HBHU6FB320M2765OLM",data.get("relevanceInstanceId").toString(),data.get("titleName").toString(),"",false));
+
+        ydClient.operateData(YDParam.builder()
+                .formInstanceId(data.get("currentInstanceId").toString())
+                .updateFormDataJson(JSON.toJSONString(hashMap))
+                .build(), YDConf.FORM_OPERATION.update);
+
+        return McR.success();
+    }
+
+
+
+
+    @RequestMapping("test")
+    McR test(HttpServletRequest request) {
+
+//        Map data = UtilServlet.getParamMap(request);
+//
+//        log.info("xxxx, {}", data);
+
+//        ybjService.syncUpdatePlanStatus();
+//        LocalDate currentDate = LocalDate.now();
+//        LocalDate nextDay = currentDate.plusDays(0);
+//        //通过护理时间  护理对象ID    证数据是否已经存在
+//        YDParam ydParam1 = YDParam.builder()
+//                .formUuid("FORM-IY966L71PJ8FV10D61M1HBHU6FB320M2765OLM")
+//                .searchFieldJson(JSON.toJSONString(UtilMap.map("dateField_lred6eoj, textField_lnyhv5tn, textField_lombve2g",Arrays.asList(UtilDateTime.parse( currentDate+ " 00:00:01","yyyy-MM-dd HH:mm:ss"),UtilDateTime.parse( currentDate.with(TemporalAdjusters.lastDayOfMonth())+ " 23:59:59","yyyy-MM-dd HH:mm:ss")),"310226193107268888","510702196511294820")))
+//                .build();
+//        List<Map> dataList = (List<Map>) ydClient.queryData(ydParam1, YDConf.FORM_QUERY.retrieve_search_form).getData();
+//        log.info("dataList, {}", dataList);
+
+//        ybjService.syncUpdatePlanStatus();
+
+
+//        LocalDate currentDate = LocalDate.now();
+//
+//
+//        LocalDate firstDayOfNextMonth = currentDate.plusMonths(1).with(TemporalAdjusters.firstDayOfMonth());
+//
+//        int dayOfMonth = firstDayOfNextMonth.getDayOfMonth();
+//
+//        int daysInMonth = firstDayOfNextMonth.lengthOfMonth();
+//
+//        //获取是周几
+//        String dayOfWeek = firstDayOfNextMonth.plusDays(1).getDayOfWeek().getDisplayName(TextStyle.FULL, new Locale("zh", "CN"));
+//        log.info("日期{}",dayOfWeek);
+//        //获取循环每天的日期
+//        LocalDate nextDay = firstDayOfNextMonth.plusDays(1);
+//
+//        log.info("日期{}",nextDay);
+        ybjService.syncCreatePlan();
+
+        return McR.success();
+    }
+
+
+
+
+
+
+
+
+}

+ 49 - 0
mjava-yibaoju/src/main/java/com/malk/yibaoju/schedule/ScheduleTask.java

@@ -0,0 +1,49 @@
+package com.malk.yibaoju.schedule;
+
+import com.malk.yibaoju.service.YBJService;
+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;
+
+/**
+ * @EnableScheduling 开启定时任务 [配置参考McScheduleTask]
+ */
+@Slf4j
+@Configuration
+@EnableScheduling
+@ConditionalOnProperty(name = {"spel.scheduling"})
+public class ScheduleTask {
+
+    @Autowired
+    private YBJService ybjService;
+
+    /**
+     * 每月25日0点5分执行定时任务
+     */
+    @Scheduled(cron = "0 5 0 25 * ?")
+    public void syncDingTalkFailedList() {
+        try {
+            ybjService.syncCreatePlan();
+        } catch (Exception e) {
+            // 记录错误信息
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * 每天凌晨1-4点更新计划护理的任务状态
+     */
+    @Scheduled(cron = " 0 0/20 1,4 * * ? ")
+    public void syncDingTalkStatus() {
+        try {
+            ybjService.syncUpdatePlanStatus();
+        } catch (Exception e) {
+            // 记录错误信息
+            e.printStackTrace();
+        }
+    }
+
+}

+ 21 - 0
mjava-yibaoju/src/main/java/com/malk/yibaoju/service/YBJService.java

@@ -0,0 +1,21 @@
+package com.malk.yibaoju.service;
+
+public interface YBJService {
+
+    /**
+     * 护理计划
+     */
+    void syncNursePlan();
+
+    void syncUpdatePlanStatus();
+
+    void creatInfo(String formInstId);
+
+    void creatNextInfo(String formInstId);
+
+    void updateInfo(String formInstId);
+
+    void deletePlan(String cId);
+
+    void syncCreatePlan();
+}

+ 557 - 0
mjava-yibaoju/src/main/java/com/malk/yibaoju/service/impl/YBJServiceImpl.java

@@ -0,0 +1,557 @@
+package com.malk.yibaoju.service.impl;
+
+import com.alibaba.fastjson.JSON;
+import com.malk.server.aliwork.YDConf;
+import com.malk.server.aliwork.YDParam;
+import com.malk.service.aliwork.YDClient;
+import com.malk.service.aliwork.YDService;
+import com.malk.utils.UtilDateTime;
+import com.malk.utils.UtilMap;
+import com.malk.yibaoju.service.YBJService;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDate;
+import java.time.format.TextStyle;
+import java.time.temporal.TemporalAdjusters;
+import java.util.*;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
+
+
+@Service
+@Slf4j
+public class YBJServiceImpl implements YBJService {
+
+
+
+    @Autowired
+    private YDClient ydClient;
+
+    @Autowired
+    private YDService ydService;
+
+    /**
+     * 每月创建护理计划
+     */
+    @Override
+    @SneakyThrows
+    public void syncNursePlan() {
+
+        YDParam ydParam = new YDParam();
+        log.info("创建护理计划定时任务Start",new Date());
+
+        ydParam = YDParam.builder()
+                .formUuid("FORM-RK966E7105DFD27FA7EQHAOH9IFS2RN6MP3OL2")  //获取护理计划
+                .searchFieldJson(JSON.toJSONString(UtilMap.map("selectField_lo2b6bvf","正常")))//护理对象暂停和正常、月度计划创建只查询正常的护理计划
+                .build();
+        //pagesize设为1获取总数
+        ydParam.setPageSize(1);
+        long totalCount = ydClient.queryData(ydParam, YDConf.FORM_QUERY.retrieve_search_form).getTotalCount();
+
+        float pageSize = 50;
+        ydParam.setCurrentPage(1);
+        ydParam.setPageSize((int) pageSize);
+        List<Map> dataList = new ArrayList<>();
+        for (int page = 1; page <= Math.ceil(totalCount / pageSize); page++) {
+            ydParam.setCurrentPage(page);
+            //创建护理计划  1次获取50条
+            dataList = (List<Map>) ydClient.queryData(ydParam, YDConf.FORM_QUERY.retrieve_search_form).getData();
+
+            log.info("dataList:{}-----page:"+page+"---",dataList.size());
+
+            LocalDate currentDate = LocalDate.now();
+            //获取当前日期是本月几号
+            int dayOfMonth = currentDate.getDayOfMonth();
+            //获取本月天数
+            int daysInMonth = currentDate.lengthOfMonth();
+
+            dataList.forEach(dataItem -> {
+
+                Map formMap = (Map) dataItem.get("formData");
+
+                List<Map> mapList = (List<Map>) formMap.get("tableField_lo3pmohx");
+
+                for (int i =0 ; i <=(daysInMonth-dayOfMonth) ; i++) {
+                    //获取当前日期是本月几号
+                    LocalDate currentDay = LocalDate.now();
+                    //获取是周几
+                    String dayOfWeek = currentDay.plusDays(i).getDayOfWeek().getDisplayName(TextStyle.FULL, new Locale("zh", "CN"));
+                    //获取循环每天的日期
+                    LocalDate nextDay = currentDate.plusDays(i);
+                    List<Map> maps =  mapList.stream().filter(items -> items.get("selectField_lo3pmohy").equals(dayOfWeek.replace("星期","周"))).collect(Collectors.toList());
+
+                    for (Map map : maps) {
+                        Map hashMap = new HashMap();
+                        try {
+                            Thread.sleep(100);
+                        } catch (InterruptedException e) {
+                            throw new RuntimeException(e);
+                        }
+                        //通过护理时间  护理对象  护理人员验证数据是否已经存在
+                        YDParam ydParam1 = YDParam.builder()
+                                .formUuid("FORM-IY966L71PJ8FV10D61M1HBHU6FB320M2765OLM")
+                                .searchFieldJson(JSON.toJSONString(UtilMap.map("dateField_lred6eoj, textField_lnyhv5tn, textField_lombve2g",Arrays.asList(UtilDateTime.parse( nextDay+ " 00:00:01","yyyy-MM-dd HH:mm:ss"),UtilDateTime.parse( currentDate.with(TemporalAdjusters.lastDayOfMonth())+ " 23:59:59","yyyy-MM-dd HH:mm:ss")),formMap.get("textField_lnyhv5tn"),map.get("textField_lomchzi3"))))
+                                .build();
+                        List<Map> dataLists = (List<Map>) ydClient.queryData(ydParam1, YDConf.FORM_QUERY.retrieve_search_form).getData();
+
+
+                        if (dataLists.size()<1){
+
+                            hashMap.put("associationFormField_lo3pmohz",JSON.parse(map.get("associationFormField_lo3pmohz_id").toString()));  //护理人员名称
+                            hashMap.put("textField_lo3pmoi0",map.get("textField_lo3pmoi0"));  //护理人员所属机构
+                            hashMap.put("textField_lombve2g",map.get("textField_lomchzi3"));  //护理人员身份证
+                            hashMap.put("textField_lo3pmoi3",map.get("textField_lo3pmoi3"));  //护理人员手机号
+                            hashMap.put("associationFormField_lo3pmohw",JSON.parse(formMap.get("associationFormField_lo3pmohw_id").toString()));  //护理对象姓名
+                            hashMap.put("textField_lnyhv5tn",formMap.get("textField_lnyhv5tn")); //护理对象身份证
+                            hashMap.put("selectField_lnyhv5tp",formMap.get("selectField_lnyhv5tp"));  //护理对象等级
+                            hashMap.put("textField_lnyhv5tr",formMap.get("textField_lnyhv5tr"));  //护理对象住址
+                            hashMap.put("selectField_lo3pmohy",dayOfWeek.replace("星期","周"));  //护理时间  (星期几)
+                            hashMap.put("dateField_lred6eoj", UtilDateTime.parse(String.valueOf(nextDay)+" "+map.get("selectField_lr1uxpu3"),"yyyy-MM-dd HH:mm")); //护理开始时间。 用当前日期+护理计划的  时分    转成时间戳
+                            hashMap.put("dateField_lred6eok", UtilDateTime.parse(String.valueOf(nextDay)+" "+map.get("selectField_lr1uxpu4"),"yyyy-MM-dd HH:mm")); //护理结束时间。 用当前日期+护理计划的  时分    转成时间戳
+                            hashMap.put("selectField_lqcgsbw2","未执行");  //执行情况
+                            hashMap.put("selectField_lo56u5fn","待上报");  //任务状态
+
+
+                            hashMap.put("textField_lrx0r38x",String.valueOf(nextDay));  //护理日期-文本
+                            hashMap.put("selectField_lr36z6hd","是");  //任务状态
+                            hashMap.put("textField_lo55rupj",formMap.get("textField_lo55rupj"));  //护理对象姓名-文本
+                            hashMap.put("textField_lo3pmoi4",map.get("textField_lo3pmoi4"));  //护理证书类型
+                            hashMap.put("employeeField_lo3pmoi6",JSON.parse(map.get("employeeField_lo3pmoi6_id").toString()));  //护理人员姓名-成员
+                            hashMap.put("textField_lpgi8scj",map.get("textField_lo701pwy"));  //计划明细id
+                            hashMap.put("textField_lq9llmhl",formMap.get("textField_lop4v4qx"));  //护理对象ID
+
+                            try {
+                                ydClient.operateData(YDParam.builder()
+                                        .formUuid("FORM-IY966L71PJ8FV10D61M1HBHU6FB320M2765OLM")
+                                        .formDataJson(JSON.toJSONString(hashMap))
+                                        .build(), YDConf.FORM_OPERATION.create);
+                            } catch (Exception e) {
+                                log.info("异常数据{}",hashMap);
+                            }
+                        }
+                    }
+
+                }
+            });
+        }
+
+        log.info("创建护理计划定时任务end",new Date());
+    }
+
+
+    /**
+     *每天同步护理状态
+     */
+    @Override
+    public void syncUpdatePlanStatus() {
+        log.info("每天同步护理状态开始{}",new Date());
+        syncPlanStatus();
+        log.info("每天同步护理状态结束{}",new Date());
+    }
+
+
+    @SneakyThrows
+    public void syncPlanStatus() {
+
+        LocalDate currentDate = LocalDate.now();
+        //获取需要更新的护理任务   护理日期是当天      且状态为未上报
+        YDParam ydParam = YDParam.builder()
+                .formUuid("FORM-IY966L71PJ8FV10D61M1HBHU6FB320M2765OLM")
+                .searchFieldJson(JSON.toJSONString(UtilMap.map("selectField_lo56u5fn, dateField_lred6eoj","待上报",Arrays.asList(UtilDateTime.parse( currentDate+ " 00:00:59","yyyy-MM-dd HH:mm:ss"),UtilDateTime.parse( currentDate+ " 23:59:59","yyyy-MM-dd HH:mm:ss")))))
+                .build();
+        //pagesize设为1获取总数
+        ydParam.setPageSize(1);
+        long totalCount = ydClient.queryData(ydParam, YDConf.FORM_QUERY.retrieve_search_form).getTotalCount();
+        log.info("totalCount{}",totalCount);
+
+        //如果条数不为0 就一直递归
+        if (totalCount>0){
+            ydParam.setPageSize(50);
+            List<Map> dataList = new ArrayList<>();
+            dataList = (List<Map>) ydClient.queryData(ydParam, YDConf.FORM_QUERY.retrieve_search_form).getData();
+            log.info("dataList:{}",dataList.size());
+            //循环处理修改状态为已上报
+            dataList.forEach(item -> {
+                //修改状态为已上报
+                ydClient.operateData(YDParam.builder()
+                        .formInstanceId(item.get("formInstanceId").toString())
+                        .updateFormDataJson(JSON.toJSONString(UtilMap.map("selectField_lo56u5fn","已上报")))
+                        .build(), YDConf.FORM_OPERATION.update);
+            });
+            Thread.sleep(5000);
+            syncUpdatePlanStatus();
+        }
+    }
+
+    /**
+     * 新增护理计划1条  生成本月护理数据
+     * @param formInstId
+     */
+    @Override
+    public void creatInfo(String formInstId) {
+        log.info("新增护理计划",new Date());
+        //根据实例ID  查询护理计划
+        YDParam ydParam = YDParam.builder()
+                .formInstId(formInstId)
+                .build();
+        Map formData = ydClient.queryData(ydParam, YDConf.FORM_QUERY.retrieve_id).getFormData();
+        log.info("formData{}",formData);
+        List<Map> mapList = (List<Map>) formData.get("tableField_lo3pmohx");
+
+        LocalDate currentDate = LocalDate.now();
+        //获取当前日期是本月几号
+        int dayOfMonth = currentDate.getDayOfMonth();
+        //获取本月天数
+        int daysInMonth = currentDate.lengthOfMonth();
+
+        for (int i = 0; i <(daysInMonth-dayOfMonth)+1 ; i++) {
+            //获取当前日期是本月几号
+            LocalDate currentDay = LocalDate.now();
+            //获取是周几
+            String dayOfWeek = currentDay.plusDays(i).getDayOfWeek().getDisplayName(TextStyle.FULL, new Locale("zh", "CN"));
+            //获取循环每天的日期
+            LocalDate nextDay = currentDate.plusDays(i);
+            List<Map> maps =  mapList.stream().filter(item -> item.get("selectField_lo3pmohy").equals(dayOfWeek.replace("星期","周"))).collect(Collectors.toList());
+            for (Map map : maps) {
+
+                Map hashMap = new HashMap();
+                hashMap.put("associationFormField_lo3pmohz",JSON.parse(map.get("associationFormField_lo3pmohz_id").toString()));  //护理人员名称
+                hashMap.put("textField_lo3pmoi0",map.get("textField_lo3pmoi0"));  //护理人员所属机构
+                hashMap.put("textField_lombve2g",map.get("textField_lomchzi3"));  //护理人员身份证
+                hashMap.put("textField_lo3pmoi3",map.get("textField_lo3pmoi3"));  //护理人员手机号
+                hashMap.put("associationFormField_lo3pmohw",JSON.parse(formData.get("associationFormField_lo3pmohw_id").toString()));  //护理对象姓名
+                hashMap.put("textField_lnyhv5tn",formData.get("textField_lnyhv5tn")); //护理对象身份证
+                hashMap.put("selectField_lnyhv5tp",formData.get("selectField_lnyhv5tp"));  //护理对象等级
+                hashMap.put("textField_lnyhv5tr",formData.get("textField_lnyhv5tr"));  //护理对象住址
+                hashMap.put("selectField_lo3pmohy",dayOfWeek.replace("星期","周"));  //护理时间  (星期几)
+                hashMap.put("dateField_lred6eoj", UtilDateTime.parse(String.valueOf(nextDay)+" "+map.get("selectField_lr1uxpu3"),"yyyy-MM-dd HH:mm")); //护理开始时间。 用当前日期+护理计划的  时分    转成时间戳
+                hashMap.put("dateField_lred6eok",UtilDateTime.parse(String.valueOf(nextDay)+" "+map.get("selectField_lr1uxpu4"),"yyyy-MM-dd HH:mm")); //护理结束时间。 用当前日期+护理计划的  时分    转成时间戳
+                hashMap.put("selectField_lqcgsbw2","未执行");  //执行情况
+                hashMap.put("selectField_lo56u5fn","待上报");  //任务状态
+
+                hashMap.put("textField_lrx0r38x",String.valueOf(nextDay));  //护理日期-文本
+                hashMap.put("selectField_lr36z6hd","是");  //任务状态
+                hashMap.put("textField_lo55rupj",formData.get("textField_lo55rupj"));  //护理对象姓名-文本
+                hashMap.put("textField_lo3pmoi4",map.get("textField_lo3pmoi4"));  //护理证书类型
+                hashMap.put("employeeField_lo3pmoi6",map.get("employeeField_lo3pmoi6_id"));  //护理人员姓名-成员
+                hashMap.put("textField_lpgi8scj",map.get("textField_lo701pwy"));  //计划明细id
+                hashMap.put("textField_lq9llmhl",formData.get("textField_lop4v4qx"));  //护理对象ID
+
+                //通过护理时间  护理对象ID    证数据是否已经存在
+                YDParam ydParam1 = YDParam.builder()
+                        .formUuid("FORM-IY966L71PJ8FV10D61M1HBHU6FB320M2765OLM")
+                        .searchFieldJson(JSON.toJSONString(UtilMap.map("dateField_lred6eoj, textField_lnyhv5tn, textField_lombve2g",Arrays.asList(UtilDateTime.parse( nextDay+ " 00:00:01","yyyy-MM-dd HH:mm:ss"),UtilDateTime.parse( currentDate.with(TemporalAdjusters.lastDayOfMonth())+ " 23:59:59","yyyy-MM-dd HH:mm:ss")),formData.get("textField_lnyhv5tn"),map.get("textField_lomchzi3"))))
+                        .build();
+                List<Map> dataList = (List<Map>) ydClient.queryData(ydParam1, YDConf.FORM_QUERY.retrieve_search_form).getData();
+
+                //判断是否存在
+                if (dataList.size()<1){
+                    try {
+                        ydClient.operateData(YDParam.builder()
+                                .formUuid("FORM-IY966L71PJ8FV10D61M1HBHU6FB320M2765OLM")
+                                .formDataJson(JSON.toJSONString(hashMap))
+                                .build(), YDConf.FORM_OPERATION.create);
+                    } catch (Exception e) {
+                        log.info("定时任务创建计划失败:{}",e);
+                    }
+                }
+            }
+        }
+    }
+
+
+
+    /**
+     * 新增护理计划下月月数据
+     * @param formInstId
+     */
+    @Override
+    public void creatNextInfo(String formInstId) {
+        log.info("新增护理计划下月数据",new Date());
+        //根据实例ID  查询护理计划
+        YDParam ydParam = YDParam.builder()
+                .formInstId(formInstId)
+                .build();
+        Map formData = ydClient.queryData(ydParam, YDConf.FORM_QUERY.retrieve_id).getFormData();
+
+        List<Map> mapList = (List<Map>) formData.get("tableField_lo3pmohx");
+
+        LocalDate currentDate = LocalDate.now();
+        //获取下月1号的日期
+        LocalDate firstDayOfNextMonth  = currentDate.plusMonths(1).with(TemporalAdjusters.firstDayOfMonth());
+        //获取下月1号
+        int dayOfMonth = firstDayOfNextMonth.getDayOfMonth();
+        //获取下月月天数
+        int daysInMonth = firstDayOfNextMonth.lengthOfMonth();
+
+        for (int i = 0; i <=(daysInMonth-dayOfMonth)+1 ; i++) {
+            //获取是周几
+            String dayOfWeek = firstDayOfNextMonth.plusDays(i).getDayOfWeek().getDisplayName(TextStyle.FULL, new Locale("zh", "CN"));
+            //获取循环每天的日期
+            LocalDate nextDay = firstDayOfNextMonth.plusDays(i);
+
+            List<Map> maps =  mapList.stream().filter(item -> item.get("selectField_lo3pmohy").equals(dayOfWeek.replace("星期","周"))).collect(Collectors.toList());
+            for (Map map : maps) {
+
+                Map hashMap = new HashMap();
+                hashMap.put("associationFormField_lo3pmohz",JSON.parse(map.get("associationFormField_lo3pmohz_id").toString()));  //护理人员名称
+                hashMap.put("textField_lo3pmoi0",map.get("textField_lo3pmoi0"));  //护理人员所属机构
+                hashMap.put("textField_lombve2g",map.get("textField_lomchzi3"));  //护理人员身份证
+                hashMap.put("textField_lo3pmoi3",map.get("textField_lo3pmoi3"));  //护理人员手机号
+                hashMap.put("associationFormField_lo3pmohw",JSON.parse(formData.get("associationFormField_lo3pmohw_id").toString()));  //护理对象姓名
+                hashMap.put("textField_lnyhv5tn",formData.get("textField_lnyhv5tn")); //护理对象身份证
+                hashMap.put("selectField_lnyhv5tp",formData.get("selectField_lnyhv5tp"));  //护理对象等级
+                hashMap.put("textField_lnyhv5tr",formData.get("textField_lnyhv5tr"));  //护理对象住址
+                hashMap.put("selectField_lo3pmohy",dayOfWeek.replace("星期","周"));  //护理时间  (星期几)
+                hashMap.put("dateField_lred6eoj", UtilDateTime.parse(String.valueOf(nextDay)+" "+map.get("selectField_lr1uxpu3"),"yyyy-MM-dd HH:mm")); //护理开始时间。 用当前日期+护理计划的  时分    转成时间戳
+                hashMap.put("dateField_lred6eok",UtilDateTime.parse(String.valueOf(nextDay)+" "+map.get("selectField_lr1uxpu4"),"yyyy-MM-dd HH:mm")); //护理结束时间。 用当前日期+护理计划的  时分    转成时间戳
+                hashMap.put("selectField_lqcgsbw2","未执行");  //执行情况
+                hashMap.put("selectField_lo56u5fn","待上报");  //任务状态
+
+                hashMap.put("textField_lrx0r38x",String.valueOf(nextDay));  //护理日期-文本
+                hashMap.put("selectField_lr36z6hd","是");  //任务状态
+                hashMap.put("textField_lo55rupj",formData.get("textField_lo55rupj"));  //护理对象姓名-文本
+                hashMap.put("textField_lo3pmoi4",map.get("textField_lo3pmoi4"));  //护理证书类型
+                hashMap.put("employeeField_lo3pmoi6",map.get("employeeField_lo3pmoi6_id"));  //护理人员姓名-成员
+                hashMap.put("textField_lpgi8scj",map.get("textField_lo701pwy"));  //计划明细id
+                hashMap.put("textField_lq9llmhl",formData.get("textField_lop4v4qx"));  //护理对象ID
+
+                //通过护理时间  护理对象ID    证数据是否已经存在
+                YDParam ydParam1 = YDParam.builder()
+                        .formUuid("FORM-IY966L71PJ8FV10D61M1HBHU6FB320M2765OLM")
+                        .searchFieldJson(JSON.toJSONString(UtilMap.map("dateField_lred6eoj, textField_lnyhv5tn, textField_lombve2g",Arrays.asList(UtilDateTime.parse( nextDay+ " 00:00:01","yyyy-MM-dd HH:mm:ss"),UtilDateTime.parse( currentDate.with(TemporalAdjusters.lastDayOfMonth())+ " 23:59:59","yyyy-MM-dd HH:mm:ss")),formData.get("textField_lnyhv5tn"),map.get("textField_lomchzi3"))))
+                        .build();
+                List<Map> dataList = (List<Map>) ydClient.queryData(ydParam1, YDConf.FORM_QUERY.retrieve_search_form).getData();
+
+                //判断是否存在
+                if (dataList.size()<1){
+                    try {
+                        ydClient.operateData(YDParam.builder()
+                                .formUuid("FORM-IY966L71PJ8FV10D61M1HBHU6FB320M2765OLM")
+                                .formDataJson(JSON.toJSONString(hashMap))
+                                .build(), YDConf.FORM_OPERATION.create);
+                    } catch (Exception e) {
+                        log.info("定时任务创建计划失败:{}",e);
+                    }
+                }
+            }
+        }
+    }
+
+
+
+    /**
+     * 编辑护理计划
+     * @param formInstId
+     */
+    @SneakyThrows
+    @Override
+    @Async
+    public void updateInfo(String formInstId) {
+        Thread.sleep(2000);
+        //根据实例ID  查询护理计划
+        YDParam ydParam = YDParam.builder()
+                .formInstId(formInstId)
+                .build();
+        Map formData = ydClient.queryData(ydParam, YDConf.FORM_QUERY.retrieve_id).getFormData();
+
+        List<Map> mapList = (List<Map>) formData.get("tableField_lo3pmohx");
+
+        LocalDate currentDate = LocalDate.now();
+
+        //获取下月1号的日期
+        LocalDate firstDayOfNextMonth  = currentDate.plusMonths(1).with(TemporalAdjusters.firstDayOfMonth());
+        //删除当前时间节点以后得数据
+        //获取需要更新的护理任务  状态为待上报  护理对象身份证   护理日期是当天
+        YDParam ydParamDelete = YDParam.builder()
+                .formUuid("FORM-IY966L71PJ8FV10D61M1HBHU6FB320M2765OLM")
+                .searchFieldJson(JSON.toJSONString(UtilMap.map("selectField_lo56u5fn, textField_lnyhv5tn, dateField_lred6eoj","待上报",formData.get("textField_lnyhv5tn"),Arrays.asList(UtilDateTime.parse( currentDate+ " 00:00:01","yyyy-MM-dd HH:mm:ss"),UtilDateTime.parse( firstDayOfNextMonth.with(TemporalAdjusters.lastDayOfMonth())+ " 23:59:59","yyyy-MM-dd HH:mm:ss")))))
+                .build();
+
+        List<Map> queryAllFormData = ydService.queryAllFormData(ydParamDelete);
+        queryAllFormData.forEach(item -> {
+            //删除
+            ydClient.operateData(YDParam.builder()
+                    .formInstanceId(item.get("formInstanceId").toString())
+                    .build(), YDConf.FORM_OPERATION.delete);
+        });
+        //获取当前日期是本月几号
+        int dayOfMonth = currentDate.getDayOfMonth();
+        //获取本月天数
+        int daysInMonth = currentDate.lengthOfMonth();
+            //重新创建计划任务
+            for (int i = 0; i <(daysInMonth-dayOfMonth)+1 ; i++) {
+                //获取是周几
+                String dayOfWeek = currentDate.plusDays(i).getDayOfWeek().getDisplayName(TextStyle.FULL, new Locale("zh", "CN"));
+                //获取循环每天的日期
+                LocalDate nextDay = currentDate.plusDays(i);
+                List<Map> maps =  mapList.stream().filter(item -> item.get("selectField_lo3pmohy").equals(dayOfWeek.replace("星期","周"))).collect(Collectors.toList());
+                for (Map map : maps) {
+                    Map hashMap = new HashMap();
+                    hashMap.put("associationFormField_lo3pmohz",JSON.parse(map.get("associationFormField_lo3pmohz_id").toString()));  //护理人员名称
+                    hashMap.put("textField_lo3pmoi0",map.get("textField_lo3pmoi0"));  //护理人员所属机构
+                    hashMap.put("textField_lombve2g",map.get("textField_lomchzi3"));  //护理人员身份证
+                    hashMap.put("textField_lo3pmoi3",map.get("textField_lo3pmoi3"));  //护理人员手机号
+                    hashMap.put("associationFormField_lo3pmohw",JSON.parse(formData.get("associationFormField_lo3pmohw_id").toString()));  //护理对象姓名
+                    hashMap.put("textField_lnyhv5tn",formData.get("textField_lnyhv5tn")); //护理对象身份证
+                    hashMap.put("selectField_lnyhv5tp",formData.get("selectField_lnyhv5tp"));  //护理对象等级
+                    hashMap.put("textField_lnyhv5tr",formData.get("textField_lnyhv5tr"));  //护理对象住址
+                    hashMap.put("selectField_lo3pmohy",dayOfWeek.replace("星期","周"));  //护理时间  (星期几)
+                    hashMap.put("dateField_lred6eoj", UtilDateTime.parse(String.valueOf(nextDay)+" "+map.get("selectField_lr1uxpu3"),"yyyy-MM-dd HH:mm")); //护理开始时间。 用当前日期+护理计划的  时分    转成时间戳
+                    hashMap.put("dateField_lred6eok",UtilDateTime.parse(String.valueOf(nextDay)+" "+map.get("selectField_lr1uxpu4"),"yyyy-MM-dd HH:mm")); //护理结束时间。 用当前日期+护理计划的  时分    转成时间戳
+                    hashMap.put("selectField_lqcgsbw2","未执行");  //执行情况
+                    hashMap.put("selectField_lo56u5fn","待上报");  //任务状态
+
+
+                    hashMap.put("textField_lrx0r38x",String.valueOf(nextDay));  //护理日期-文本
+                    hashMap.put("selectField_lr36z6hd","是");  //任务状态
+                    hashMap.put("textField_lo55rupj",formData.get("textField_lo55rupj"));  //护理对象姓名-文本
+                    hashMap.put("textField_lo3pmoi4",map.get("textField_lo3pmoi4"));  //护理证书类型
+                    hashMap.put("employeeField_lo3pmoi6",JSON.parse(map.get("employeeField_lo3pmoi6_id").toString()));  //护理人员姓名-成员
+                    hashMap.put("textField_lpgi8scj",map.get("textField_lo701pwy"));  //计划明细id
+                    hashMap.put("textField_lq9llmhl",formData.get("textField_lop4v4qx"));  //护理对象ID
+
+                    //通过护理时间  护理对象CID  护理人员CID验证数据是否已经存在
+                    YDParam ydParam1 = YDParam.builder()
+                            .formUuid("FORM-IY966L71PJ8FV10D61M1HBHU6FB320M2765OLM")
+                            .searchFieldJson(JSON.toJSONString(UtilMap.map("dateField_lred6eoj, textField_lnyhv5tn, textField_lombve2g",Arrays.asList(UtilDateTime.parse( nextDay+ " 00:00:01","yyyy-MM-dd HH:mm:ss"),UtilDateTime.parse( currentDate.with(TemporalAdjusters.lastDayOfMonth())+ " 23:59:59","yyyy-MM-dd HH:mm:ss")),formData.get("textField_lnyhv5tn"),map.get("textField_lomchzi3"))))
+                            .build();
+                    List<Map> dataList = (List<Map>) ydClient.queryData(ydParam1, YDConf.FORM_QUERY.retrieve_search_form).getData();
+
+                    if (dataList.size()<1){
+                        ydClient.operateData(YDParam.builder()
+                                .formUuid("FORM-IY966L71PJ8FV10D61M1HBHU6FB320M2765OLM")
+                                .formDataJson(JSON.toJSONString(hashMap))
+                                .build(), YDConf.FORM_OPERATION.create);
+                    }
+                }
+            }
+
+    }
+
+    /**
+     *  删除护理任务
+     * @param cId 护理对象身份证
+     */
+    @Override
+    public void deletePlan(String cId) {
+
+        LocalDate currentDate = LocalDate.now();
+
+        //获取下月1号的日期
+        LocalDate firstDayOfNextMonth  = currentDate.plusMonths(1).with(TemporalAdjusters.firstDayOfMonth());
+
+        //获取需要删除护理任务  执行状态为未执行  护理对象身份证   护理日期是当天以后
+        YDParam ydParamDelete = YDParam.builder()
+                .formUuid("FORM-IY966L71PJ8FV10D61M1HBHU6FB320M2765OLM")
+                .searchFieldJson(JSON.toJSONString(UtilMap.map("selectField_lqcgsbw2, textField_lnyhv5tn, dateField_lred6eoj","未执行",cId,Arrays.asList(UtilDateTime.parse( currentDate+ " 00:00:01","yyyy-MM-dd HH:mm:ss"),UtilDateTime.parse( firstDayOfNextMonth.with(TemporalAdjusters.lastDayOfMonth())+ " 23:59:59","yyyy-MM-dd HH:mm:ss")))))
+                .build();
+        List<Map> mapList = ydService.queryAllFormData(ydParamDelete);
+        //删除当前时间节点以后得数据
+        mapList.forEach(item -> {
+            //删除
+            ydClient.operateData(YDParam.builder()
+                    .formInstanceId(item.get("formInstanceId").toString())
+                    .build(), YDConf.FORM_OPERATION.delete);
+        });
+    }
+
+
+    /**
+     *定时任务每月25号创建下月的数据
+     */
+    @Override
+    @SneakyThrows
+    public void syncCreatePlan() {
+
+        YDParam ydParam = new YDParam();
+        log.info("创建护理计划定时任务Start",new Date());
+
+        ydParam = YDParam.builder()
+                .formUuid("FORM-RK966E7105DFD27FA7EQHAOH9IFS2RN6MP3OL2")  //获取护理计划
+                .searchFieldJson(JSON.toJSONString(UtilMap.map("selectField_lo2b6bvf","正常")))//护理对象暂停和正常、月度计划创建只查询正常的护理计划
+                .build();
+        //pagesize设为1获取总数
+        ydParam.setPageSize(1);
+        long totalCount = ydClient.queryData(ydParam, YDConf.FORM_QUERY.retrieve_search_form).getTotalCount();
+        float pageSize = 50;
+        ydParam.setCurrentPage(1);
+        ydParam.setPageSize((int) pageSize);
+        List<Map> dataList = new ArrayList<>();
+        for (int page = 1; page <= Math.ceil(totalCount / pageSize); page++) {
+            ydParam.setCurrentPage(page);
+            //创建护理计划  1次获取50条
+            dataList = (List<Map>) ydClient.queryData(ydParam, YDConf.FORM_QUERY.retrieve_search_form).getData();
+            log.info("dataList:{}-----page:"+page+"---",dataList.size());
+            LocalDate currentDate = LocalDate.now();
+            //获取下月1号的日期
+            LocalDate firstDayOfNextMonth  = currentDate.plusMonths(1).with(TemporalAdjusters.firstDayOfMonth());
+            //获取当前日期是本月几号
+            int dayOfMonth = firstDayOfNextMonth .getDayOfMonth();
+            //获取本月天数
+            int daysInMonth = firstDayOfNextMonth.lengthOfMonth();
+            dataList.forEach(dataItem -> {
+                Map formMap = (Map) dataItem.get("formData");
+                List<Map> mapList = (List<Map>) formMap.get("tableField_lo3pmohx");
+                for (int i =0 ; i <=(daysInMonth-dayOfMonth) ; i++) {
+                    //获取是周几
+                    String dayOfWeek = firstDayOfNextMonth.plusDays(i).getDayOfWeek().getDisplayName(TextStyle.FULL, new Locale("zh", "CN"));
+                    //获取循环每天的日期
+                    LocalDate nextDay = firstDayOfNextMonth.plusDays(i);
+                    List<Map> maps =  mapList.stream().filter(items -> items.get("selectField_lo3pmohy").equals(dayOfWeek.replace("星期","周"))).collect(Collectors.toList());
+                    for (Map map : maps) {
+                        Map hashMap = new HashMap();
+                        try {
+                            Thread.sleep(100);
+                        } catch (InterruptedException e) {
+                            throw new RuntimeException(e);
+                        }
+                        //通过护理时间  护理对象  护理人员验证数据是否已经存在
+                        YDParam ydParam1 = YDParam.builder()
+                                .formUuid("FORM-IY966L71PJ8FV10D61M1HBHU6FB320M2765OLM")
+                                .searchFieldJson(JSON.toJSONString(UtilMap.map("dateField_lred6eoj, textField_lnyhv5tn, textField_lombve2g",Arrays.asList(UtilDateTime.parse( nextDay+ " 00:00:01","yyyy-MM-dd HH:mm:ss"),UtilDateTime.parse( nextDay.with(TemporalAdjusters.lastDayOfMonth())+ " 23:59:59","yyyy-MM-dd HH:mm:ss")),formMap.get("textField_lnyhv5tn"),map.get("textField_lomchzi3"))))
+                                .build();
+                        List<Map> dataLists = (List<Map>) ydClient.queryData(ydParam1, YDConf.FORM_QUERY.retrieve_search_form).getData();
+                        if (dataLists.size()<1){
+                            hashMap.put("associationFormField_lo3pmohz",JSON.parse(map.get("associationFormField_lo3pmohz_id").toString()));  //护理人员名称
+                            hashMap.put("textField_lo3pmoi0",map.get("textField_lo3pmoi0"));  //护理人员所属机构
+                            hashMap.put("textField_lombve2g",map.get("textField_lomchzi3"));  //护理人员身份证
+                            hashMap.put("textField_lo3pmoi3",map.get("textField_lo3pmoi3"));  //护理人员手机号
+                            hashMap.put("associationFormField_lo3pmohw",JSON.parse(formMap.get("associationFormField_lo3pmohw_id").toString()));  //护理对象姓名
+                            hashMap.put("textField_lnyhv5tn",formMap.get("textField_lnyhv5tn")); //护理对象身份证
+                            hashMap.put("selectField_lnyhv5tp",formMap.get("selectField_lnyhv5tp"));  //护理对象等级
+                            hashMap.put("textField_lnyhv5tr",formMap.get("textField_lnyhv5tr"));  //护理对象住址
+                            hashMap.put("selectField_lo3pmohy",dayOfWeek.replace("星期","周"));  //护理时间  (星期几)
+                            hashMap.put("dateField_lred6eoj", UtilDateTime.parse(String.valueOf(nextDay)+" "+map.get("selectField_lr1uxpu3"),"yyyy-MM-dd HH:mm")); //护理开始时间。 用当前日期+护理计划的  时分    转成时间戳
+                            hashMap.put("dateField_lred6eok", UtilDateTime.parse(String.valueOf(nextDay)+" "+map.get("selectField_lr1uxpu4"),"yyyy-MM-dd HH:mm")); //护理结束时间。 用当前日期+护理计划的  时分    转成时间戳
+                            hashMap.put("selectField_lqcgsbw2","未执行");  //执行情况
+                            hashMap.put("selectField_lo56u5fn","待上报");  //任务状态
+                            hashMap.put("textField_lrx0r38x",String.valueOf(nextDay));  //护理日期-文本
+                            hashMap.put("selectField_lr36z6hd","是");  //任务状态
+                            hashMap.put("textField_lo55rupj",formMap.get("textField_lo55rupj"));  //护理对象姓名-文本
+                            hashMap.put("textField_lo3pmoi4",map.get("textField_lo3pmoi4"));  //护理证书类型
+                            hashMap.put("employeeField_lo3pmoi6",JSON.parse(map.get("employeeField_lo3pmoi6_id").toString()));  //护理人员姓名-成员
+                            hashMap.put("textField_lpgi8scj",map.get("textField_lo701pwy"));  //计划明细id
+                            hashMap.put("textField_lq9llmhl",formMap.get("textField_lop4v4qx"));  //护理对象ID
+                            try {
+                                ydClient.operateData(YDParam.builder()
+                                        .formUuid("FORM-IY966L71PJ8FV10D61M1HBHU6FB320M2765OLM")
+                                        .formDataJson(JSON.toJSONString(hashMap))
+                                        .build(), YDConf.FORM_OPERATION.create);
+                            } catch (Exception e) {
+                                log.info("异常数据{}",hashMap);
+                            }
+                        }
+                    }
+                }
+            });
+        }
+        log.info("创建护理计划定时任务end",new Date());
+    }
+
+
+}

+ 52 - 0
mjava-yibaoju/src/main/resources/application-dev.yml

@@ -0,0 +1,52 @@
+# 环境配置
+server:
+  port: 9001
+  servlet:
+    context-path: /api/yibaoju
+
+# condition
+spel:
+  scheduling: false        # 定时任务是否执行
+  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: mu123
+    url: jdbc:mysql://127.0.0.1:3306/mjava?serverTimezone=Asia/Shanghai&useUnicode=yes&characterEncoding=UTF-8&useSSL=true
+  jpa:
+    database: MYSQL
+    database-platform: org.hibernate.dialect.MySQL57Dialect
+
+# filepath
+file:
+  path:
+    file: C:/Users/fly/server/_Tool/var/mjava/tmp/file/
+    image: C:/Users/fly/server/_Tool/var/mjava/tmp/image/
+    tmp: C:/Users/fly/server/_Tool/var/mjava/tmp/
+  source:
+    fonts: C:/Users/fly/server/_Tool/fonts/simsun.ttc
+logging:
+  file:
+    path: C:/Users/fly/server/_Tool/var/mjava/log
+
+
+# dingtalk
+dingtalk:
+  agentId: 2855972914
+  appKey: dingb1kydxxkseemaybo
+  appSecret: 1dm8Mxmd-CAxQQQpEs4HfHzdS-hngd6G-dnddz0eSEEs0I8BzB7TLh9adGx6TNPJ
+  corpId: dinge169dfb705de47a535c2f4657eb6378f
+  aesKey:
+  token:
+  robotCode:
+  operator: ""   # 管理员 [开头0, 需要转一下字符串]
+
+# aliwork
+aliwork:
+  appType: "APP_YIDJ71B8QORD2YHNZ26Q"
+  systemToken: "RH766AC1707FCUZQAKET09YV8I8K3VZA1GVNLN2"

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

@@ -0,0 +1,39 @@
+# 环境配置
+server:
+  port: 9023
+  servlet:
+    context-path: /api/yibaoju
+
+# condition
+spel:
+  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: mu123
+    url: jdbc:mysql://127.0.0.1:3306/mjava?serverTimezone=Asia/Shanghai&useUnicode=yes&characterEncoding=UTF-8&useSSL=true
+  jpa:
+    database: MYSQL
+    database-platform: org.hibernate.dialect.MySQL57Dialect
+# dingtalk
+dingtalk:
+  agentId: 2855972914
+  appKey: dingb1kydxxkseemaybo
+  appSecret: 1dm8Mxmd-CAxQQQpEs4HfHzdS-hngd6G-dnddz0eSEEs0I8BzB7TLh9adGx6TNPJ
+  corpId: dinge169dfb705de47a535c2f4657eb6378f
+  aesKey:
+  token:
+  robotCode:
+  operator: ""   # 管理员 [开头0, 需要转一下字符串]
+
+# aliwork
+aliwork:
+  appType: "APP_YIDJ71B8QORD2YHNZ26Q"
+  systemToken: "RH766AC1707FCUZQAKET09YV8I8K3VZA1GVNLN2"
+

+ 67 - 0
mjava-yibaoju/src/test/resources/sample/CPTimer.cs

@@ -0,0 +1,67 @@
+/**
+ * 获取用户数据
+ * -
+ * ppExt 氚云组织架构只有全量查询接口, 且部门和用户ID, 有单独ID. 更甚接口未返回钉钉userId与手机号登信息
+ * 1. todo: 后续考虑新建氚云webservice进行查询
+ * 2. 临时方案为, H_User系统表, 字段DingTalkAccount「返回格式为 userId.corpId」, 通过氚云定时任务储存与钉钉对照关系 [详见 test/sample/CPTimer]
+ */
+
+//定义一个定时器类(类名格式:自定义功能名 + _Timer),且继承H3.SmartForm.Timer类
+public class CPTimer: H3.SmartForm.Timer
+{
+    //构造方法,跟类名保持一致,里面不必书写代码,但是必须存在
+    public CPTimer() { }
+
+    //重写定时器引擎执行的方法,必须存在,且方法名必须为OnWork
+    protected override void OnWork(H3.IEngine engine)
+    {
+        //此方法每隔4小时调用一次
+        Execute_1(engine);
+
+        DateTime now = DateTime.Now;//获取当前时间
+        DateTime sTime = DateTime.Parse(now.ToString("yyyy-MM-dd 00:00:00"));//获取今天的10点
+        DateTime eTime = DateTime.Parse(now.ToString("yyyy-MM-dd 04:00:00"));//获取今天的14点
+
+        if(sTime <= now && eTime >= now)//判断当前时间是否处于10点-14点间
+        {
+            //每天0点-4点间调用一次Execute_2方法(根据氚云定时器每隔4小时执行一次的规则,0点-4点间隔4小时,所以这个范围内必定会执行一次)
+            Execute_2(engine);
+        }
+    }
+
+    //此方法内书写你要定时执行的功能代码,非必须存在,只是为了封装,方法名自定义
+    //调试本方法:在列表后端OnLoad方法中书写:new MyTest_Timer().Execute_1(this.Engine)
+    public void Execute_1(H3.IEngine engine)
+    {
+
+        //此处无当前表单业务对象,所以不能使用this.Request.BizObject,请另行查询出需要的业务对象
+
+        //此处无请求对象,所以请将this.Request.Engine替换为engine
+
+        //此处无当前登录人,所以请将this.Request.UserContext.UserId替换为指定的人员id或系统默认用户Id(即:H3.Organization.User.SystemUserId)
+
+    }
+
+    ///  临时方案为, H_User系统表, 字段DingTalkAccount「返回格式为 userId.corpId」, 通过氚云定时任务储存与钉钉对照关系
+    public void Execute_2(H3.IEngine engine)
+    {
+        // 清除表单数据
+        engine.BizObjectManager.Clear("D1489118960387db0274aa18b9168cde213c3d3");
+        // 任务处理同步
+        System.Data.DataTable dtAccount = engine.Query.QueryTable("select * from  H_User", null);
+        if(dtAccount != null && dtAccount.Rows != null && dtAccount.Rows.Count > 0)
+        {
+            foreach(System.Data.DataRow row in dtAccount.Rows)
+            {
+                // DingTalkAccount「返回格式为 userId.corpId」
+                string userId = row["DingTalkAccount"] + string.Empty;
+                string dUserId = userId.Split('.')[0];
+                string cUserId = row["ObjectId"] + string.Empty;
+                string userName = row["Name"] + string.Empty;
+                engine.Query.QueryTable(string.Format("insert into i_D1489118960387db0274aa18b9168cde213c3d3 (F0000001, F0000002, F0000003, ObjectId) values ('{0}', '{1}', '{2}', '{3}')", userName, cUserId, dUserId, cUserId), null);
+            }
+        }
+    }
+
+}
+

+ 85 - 0
mjava-yibaoju/src/test/resources/sample/CP_Utils.cs

@@ -0,0 +1,85 @@
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using H3;
+
+public class CP_Utils
+{
+
+    /********** 莱蒂业务代码 **********/
+
+    /// 附件同步到钉盘
+    public static string syncDingDriveForProject(H3.IEngine engine, string projectName, string attachments) {
+        Dictionary < string, object > bodys = new Dictionary<string, object>();
+        bodys.Add("projectName", projectName); // 项目名称
+        bodys.Add("attachments", attachments);  // 同步附件
+        List < H3.BizBus.ItemSchema > structures = new List<H3.BizBus.ItemSchema>(); // 响应类型
+        structures.Add(new H3.BizBus.ItemSchema("status", "同步状态", H3.Data.BizDataType.String, null));
+        H3.BizBus.BizStructure data = CP_Utils.invokeVendorService(engine, "sync", bodys, structures); // 请求处理
+        // 传递同步状态
+        return data["status"] + string.Empty;
+    }
+
+    /**
+     * 氚云HTTP
+     * 1. code 公用请求,code区分业务逻辑
+     * 2. bodys 请求参数,默认POST,application/json
+     * 3. structures 返回数据格式定义, 统一使用对象响应
+     */
+    public static H3.BizBus.BizStructure invokeVendorService(H3.IEngine engine, string code, Dictionary < string, object > bodys, List < H3.BizBus.ItemSchema > structures) {
+
+        // 请求信息
+        Dictionary < string, string > headers = new Dictionary<string, string>();
+        Dictionary < string, string > querys = new Dictionary<string, string>();
+        querys.Add("code", code);
+
+        // 定义响应数据整体结构体
+        H3.BizBus.BizStructureSchema structureSchema = new H3.BizBus.BizStructureSchema();
+        structureSchema.Add(new H3.BizBus.ItemSchema("code", "结果状态码", H3.Data.BizDataType.Int, null));
+        structureSchema.Add(new H3.BizBus.ItemSchema("success", "请求状态位", H3.Data.BizDataType.Bool, null));
+        structureSchema.Add(new H3.BizBus.ItemSchema("message", "描述信息", H3.Data.BizDataType.String, null));
+        // 定义响应数据的 data 属性 的结构体
+        H3.BizBus.BizStructureSchema dataSchema = new H3.BizBus.BizStructureSchema();
+        foreach(H3.BizBus.ItemSchema itemSchame in structures)
+        {
+            dataSchema.Add(itemSchame);
+        }
+        //将 data 属性的结构体添加进整体的响应数据结构体 [H3.Data.BizDataType.BizStructureArray 集合,H3.Data.BizDataType.BizStructure 对象]
+        structureSchema.Add(new H3.BizBus.ItemSchema("data", "响应数据", H3.Data.BizDataType.BizStructure, dataSchema));
+
+        //调用Invoke接口,系统底层访问第三方接口的Invoke方法
+        H3.BizBus.InvokeResult response = engine.BizBus.InvokeApi(
+            H3.Organization.User.SystemUserId, //固定值,无需改变
+            H3.BizBus.AccessPointType.ThirdConnection, //固定值,无需改变
+            "h3yun-http", //连接编码,对应 插件中心 中配置的连接的编码
+            "POST", //请求方式,取值:GET / POST
+            "application/json", //请求数据类型: json - "application/json";xml - "text/html;charset=utf-8"
+            headers, querys, bodys, structureSchema);
+        if(response != null)
+        {
+            // 氚云调用是否成功
+            if(response.Code == 0)
+            {
+                // 获取返回数据,此实例对应完整的响应JSON
+                H3.BizBus.BizStructure rspData = response.Data;
+                if(((bool) rspData["success"]) == true)
+                {
+                    // 对象响应【氚云自定义字段,需要通过[]直接取值才有效,序列化后返回前端,仅会保留结构】
+                    // H3.BizBus.BizStructure[] datas = rspData["data"] as H3.BizBus.BizStructure[]; // 集合解析
+                    // H3.BizBus.BizStructure data = rspData["data"] as H3.BizBus.BizStructure; // 对象解析
+                    return rspData["data"] as H3.BizBus.BizStructure;
+                } else
+            {
+                throw new Exception("响应异常," + rspData["message"]);
+            }
+        } else
+        {
+            throw new Exception("氚云异常," + response.Message);
+        }
+    } else
+    {
+        throw new Exception("系统异常,接口无响应");
+    }
+}
+}

+ 43 - 0
mjava-yibaoju/src/test/resources/sample/附件同步钉盘.cs

@@ -0,0 +1,43 @@
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using H3;
+
+public class D000867Sscjmr10zg51xaa22ib6infyy2: H3.SmartForm.SmartFormController
+{
+    public D000867Sscjmr10zg51xaa22ib6infyy2(H3.SmartForm.SmartFormRequest request): base(request)
+    {
+    }
+
+    protected override void OnLoad(H3.SmartForm.LoadSmartFormResponse response)
+    {
+        base.OnLoad(response);
+    }
+
+    protected override void OnSubmit(string actionName, H3.SmartForm.SmartFormPostValue postValue, H3.SmartForm.SubmitSmartFormResponse response)
+    {
+        // 表单提交:由于附件控件新增的时候获取不了ID,因此通过前端传递传递。注意1:非Submit的情况下,不能通过 this.Request.BizObject["组件ID"] 取值。注意2:前端 AfterSubmit 提交后事件 调用服务不执行
+        if(actionName == "sync")
+        {
+            // 附件控件 [注意:前端直接获取的附件ID,无文件相关信息。若需要如文件名称,需要单独再查询SQL]
+            System.Data.DataTable attachments = this.Engine.Query.QueryTable(string.Format("select * from H_bizobjectfile where ObjectId in ({0})", this.Request["attachmentIds"]), null);
+            // 请求处理 [项目名称,附件] - 异步
+            string status = CP_Utils.syncDingDriveForProject(this.Engine, this.Request["projectName"] + string.Empty, this.Serialize(attachments));
+        }
+        // 审批节点:后端通过获取即可得到附件对象,返回值与H_bizobjectfile 结构一致
+        if(actionName == "Submit" && this.Request.ActivityCode == "Activity3")  // 流程修改1:审批节点标识
+        {
+            string[] compIds = { "F0000001", "F0000003" }; // 流程修改2:附件组件id
+            foreach(string compId in compIds)
+            {
+                H3.DataModel.BizObjectFileHeader[] files = (H3.DataModel.BizObjectFileHeader[]) this.Request.BizObject[compId];
+                if(files != null) {
+                    // 请求处理 [项目名称,附件] - 异步
+                    CP_Utils.syncDingDriveForProject(this.Engine, this.Request.BizObject["F0000002"] + string.Empty, this.Serialize(files));  // 流程修改3:项目名称组件id
+                }
+            }
+        }
+        base.OnSubmit(actionName, postValue, response);
+    }
+}

+ 59 - 0
mjava-yibaoju/src/test/resources/sample/附件同步钉盘.js

@@ -0,0 +1,59 @@
+/* 控件接口说明:
+ * 1. 读取控件: this.***,*号输入控件编码;
+ * 2. 读取控件的值: this.***.GetValue();
+ * 3. 设置控件的值: this.***.SetValue(???);
+ * 4. 绑定控件值变化事件: this.***.BindChange(key,function(){}),key是定义唯一的方法名;
+ * 5. 解除控件值变化事件: this.***.UnbindChange(key);
+ * 6. CheckboxList、DropDownList、RadioButtonList: $.***.AddItem(value,text),$.***.ClearItems();
+ */
+/* 公共接口:
+ * 1. ajax:$.SmartForm.PostForm(actionName,data,callBack,errorBack,async),
+ *          actionName:提交的ActionName;data:提交后台的数据;callback:回调函数;errorBack:错误回调函数;async:是否异步;
+ * 2. 打开表单:$.IShowForm(schemaCode, objectId, checkIsChange),
+ *          schemaCode:表单编码;objectId;表单数据Id;checkIsChange:关闭时,是否感知变化;
+ * 3. 定位接口:$.ILocation();
+ */
+
+// 表单插件代码
+$.extend($.JForm, {
+    // 加载事件
+    OnLoad: function () {
+
+        //获取params{ SeasonObjectiveID: objId} 中的SeasonObjectiveID值
+        var listData = $.IGetParams("SeasonObjectiveID")
+
+        //判断加载时是否存在数据,如果不存在则不执行弹窗事件
+        if (listData + "" != "") {
+            //控件赋值
+            this.F0000001.SetValue(listData);
+            console.log(listData)
+        }
+
+    },
+
+    // 按钮事件
+    OnLoadActions: function (actions) {
+    },
+
+    // 提交校验
+    OnValidate: function (actionControl) {
+        return true;
+    },
+
+    // 提交前事件
+    BeforeSubmit: function (action, postValue) {
+        // 表单提交:由于附件控件新增的时候获取不了ID,因此通过前端传递传递。注意1:非Submit的情况下,不能通过 this.Request.BizObject["组件ID"] 取值。注意2:前端 AfterSubmit 提交后事件 调用服务不执行
+        // const compIds = ["F0000001", "F0000003"] // 表单修改1:附件组件id
+        // compIds.forEach(compId => {
+        //     const attachmentIds = this.F0000001.GetValue().AttachmentIds.split(";").map(item => `'${item}'`);
+        //     $.SmartForm.PostForm("sync", {
+        //         "attachmentIds": attachmentIds.join(","),
+        //         "projectName": this.F0000002.GetValue()  // 表单修改2:项目名称id
+        //     })
+        // })
+    },
+
+    // 提交后事件
+    AfterSubmit: function (action, responseValue) {
+    }
+});

+ 35 - 0
mjava-yibaoju/src/test/resources/server.sh

@@ -0,0 +1,35 @@
+#!/bin/bash
+appname='mjava-yibaoju'
+if [ "$1" == "dev" ]; then
+  java -Xms256m -Xmx256m -jar $appname.jar --spring.profiles.active=dev
+else
+  if [ "$1" == "start" ]; then
+    nohup java -Xms256m -Xmx256m -jar $appname.jar &
+    echo "server prod is starting"
+  else
+    if [ "$1" == "test" ]; then
+      nohup java -Xms256m -Xmx256m -jar $appname.jar --spring.profiles.active=test &
+      echo "server test is starting"
+    else
+      if [ "$1" == "stop" ]; then
+        PID=$(ps -ef | grep $appname.jar | grep -v grep | awk '{ print $2 }')
+        if [ -z "$PID" ]; then
+          echo "server is already stopped"
+        else
+          echo kill $PID
+          kill $PID
+        fi
+      else
+        if [ "$1" == "status" ]; then
+          PID=$(ps -ef | grep $appname.jar | grep -v grep | awk '{ print $2 }')
+          if [ -z "$PID" ]; then
+            echo "server is stopped"
+          else
+            echo "server is running"
+            echo $PID
+          fi
+        fi
+      fi
+    fi
+  fi
+fi

+ 52 - 0
mjava-yibaoju/target/classes/application-dev.yml

@@ -0,0 +1,52 @@
+# 环境配置
+server:
+  port: 9001
+  servlet:
+    context-path: /api/yibaoju
+
+# condition
+spel:
+  scheduling: false        # 定时任务是否执行
+  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: mu123
+    url: jdbc:mysql://127.0.0.1:3306/mjava?serverTimezone=Asia/Shanghai&useUnicode=yes&characterEncoding=UTF-8&useSSL=true
+  jpa:
+    database: MYSQL
+    database-platform: org.hibernate.dialect.MySQL57Dialect
+
+# filepath
+file:
+  path:
+    file: C:/Users/fly/server/_Tool/var/mjava/tmp/file/
+    image: C:/Users/fly/server/_Tool/var/mjava/tmp/image/
+    tmp: C:/Users/fly/server/_Tool/var/mjava/tmp/
+  source:
+    fonts: C:/Users/fly/server/_Tool/fonts/simsun.ttc
+logging:
+  file:
+    path: C:/Users/fly/server/_Tool/var/mjava/log
+
+
+# dingtalk
+dingtalk:
+  agentId: 2855972914
+  appKey: dingb1kydxxkseemaybo
+  appSecret: 1dm8Mxmd-CAxQQQpEs4HfHzdS-hngd6G-dnddz0eSEEs0I8BzB7TLh9adGx6TNPJ
+  corpId: dinge169dfb705de47a535c2f4657eb6378f
+  aesKey:
+  token:
+  robotCode:
+  operator: ""   # 管理员 [开头0, 需要转一下字符串]
+
+# aliwork
+aliwork:
+  appType: "APP_YIDJ71B8QORD2YHNZ26Q"
+  systemToken: "RH766AC1707FCUZQAKET09YV8I8K3VZA1GVNLN2"

+ 39 - 0
mjava-yibaoju/target/classes/application-prod.yml

@@ -0,0 +1,39 @@
+# 环境配置
+server:
+  port: 9023
+  servlet:
+    context-path: /api/yibaoju
+
+# condition
+spel:
+  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: mu123
+    url: jdbc:mysql://127.0.0.1:3306/mjava?serverTimezone=Asia/Shanghai&useUnicode=yes&characterEncoding=UTF-8&useSSL=true
+  jpa:
+    database: MYSQL
+    database-platform: org.hibernate.dialect.MySQL57Dialect
+# dingtalk
+dingtalk:
+  agentId: 2855972914
+  appKey: dingb1kydxxkseemaybo
+  appSecret: 1dm8Mxmd-CAxQQQpEs4HfHzdS-hngd6G-dnddz0eSEEs0I8BzB7TLh9adGx6TNPJ
+  corpId: dinge169dfb705de47a535c2f4657eb6378f
+  aesKey:
+  token:
+  robotCode:
+  operator: ""   # 管理员 [开头0, 需要转一下字符串]
+
+# aliwork
+aliwork:
+  appType: "APP_YIDJ71B8QORD2YHNZ26Q"
+  systemToken: "RH766AC1707FCUZQAKET09YV8I8K3VZA1GVNLN2"
+

二进制
mjava-yibaoju/target/classes/com/malk/yibaoju/Boot.class


二进制
mjava-yibaoju/target/classes/com/malk/yibaoju/controller/YBJController.class


二进制
mjava-yibaoju/target/classes/com/malk/yibaoju/schedule/ScheduleTask.class


二进制
mjava-yibaoju/target/classes/com/malk/yibaoju/service/YBJService.class


二进制
mjava-yibaoju/target/classes/com/malk/yibaoju/service/impl/YBJServiceImpl.class


+ 3 - 0
mjava-yibaoju/target/maven-archiver/pom.properties

@@ -0,0 +1,3 @@
+artifactId=mjava-yibaoju
+groupId=com.malk
+version=1.0-SNAPSHOT

+ 5 - 0
mjava-yibaoju/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst

@@ -0,0 +1,5 @@
+com\malk\yibaoju\service\YBJService.class
+com\malk\yibaoju\service\impl\YBJServiceImpl.class
+com\malk\yibaoju\controller\YBJController.class
+com\malk\yibaoju\schedule\ScheduleTask.class
+com\malk\yibaoju\Boot.class

+ 5 - 0
mjava-yibaoju/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst

@@ -0,0 +1,5 @@
+D:\JavaSpace\mjava-3\mjava-yibaoju\src\main\java\com\malk\yibaoju\controller\YBJController.java
+D:\JavaSpace\mjava-3\mjava-yibaoju\src\main\java\com\malk\yibaoju\schedule\ScheduleTask.java
+D:\JavaSpace\mjava-3\mjava-yibaoju\src\main\java\com\malk\yibaoju\service\impl\YBJServiceImpl.java
+D:\JavaSpace\mjava-3\mjava-yibaoju\src\main\java\com\malk\yibaoju\service\YBJService.java
+D:\JavaSpace\mjava-3\mjava-yibaoju\src\main\java\com\malk\yibaoju\Boot.java

二进制
mjava-yibaoju/target/mjava-yibaoju.jar


二进制
mjava-yibaoju/target/mjava-yibaoju.jar.original


+ 51 - 0
mjava/pom.xml

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@@ -0,0 +1,14 @@
+package com.malk.delegate;
+
+import org.springframework.scheduling.annotation.Async;
+
+public interface McDelegate {
+
+    @Async
+    void setTimeout(Invoke fn, long millis);
+
+    @FunctionalInterface
+    interface Invoke {
+        void execute();
+    }
+}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@@ -0,0 +1,159 @@
+package com.malk.server.aliwork;
+
+import com.alibaba.fastjson.JSON;
+import com.malk.utils.UtilMap;
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+@Data
+@Component
+@ConfigurationProperties(prefix = "aliwork")
+@Slf4j
+public class YDConf {
+
+    private String appType;
+
+    private String systemToken;
+
+    /**
+     * 一个切片数量上限
+     */
+    public static final Integer UPPER_LIMIT = 30000;
+
+    /**
+     * 一个分页数量上限
+     */
+    public static final Integer PAGE_SIZE_LIMIT = 100;
+
+    /**
+     * 接口访问账号 [不能触发待办与消息通知, 业务规则亦不能]
+     * -
+     * 非管理员账号只可以查询, 不检验权限
+     * 操作数据需要管理员账号, 或宜搭平台
+     */
+    public static final String PUB_ACCOUNT = "yida_pub_account";
+
+    ////////////////////////// 新版本API //////////////////////////
+
+    /**
+     * 查询表单
+     */
+    public enum FORM_QUERY {
+
+        retrieve_list,              // 全局查询, 不包含子表单
+        retrieve_list_all,          // 全局查询, 包含子表单
+        multi_retrieve_id,
+
+        retrieve_id,                // 单个ID查询
+
+        retrieve_search_process,            // 流程列表
+        retrieve_search_form,               // 表单列表
+
+        retrieve_search_process_id,         // 流程列表
+        retrieve_search_form_id,             // 表单列表
+
+        retrieve_details,             // 子表数据[表单]
+        retrieve_changed,    // 变更记录
+        retrieve_definition, // 表单定义
+
+    }
+
+    /**
+     * 表单操作
+     */
+    public enum FORM_OPERATION {
+        create,
+        delete,             // 传入为body, 文档为param
+        update,
+        multi_create,               // 批量操作
+        delete_batch,               // 批量删除
+        multi_update,               // 批量更新
+        start,                      // 发起流程
+        batchSave,                  // 批量创建
+    }
+
+    /**
+     * 关联表单处理
+     *
+     * @param formType: "receipt" 跳转为表单【若是流程,则权限体系会失效,但也会显示审批节点】; formType: "process" 为流程
+     * @apiNote ppExt 接口更新, 传入jsonString或List都可以. 返回数据都是两层json解析, 组件 + _id格式
+     */
+    public List<Map> associationForm(String formUuid, String formInstanceId, String title, String subTitle, boolean isProcess) {
+        return associationForm(appType, formUuid, formInstanceId, title, subTitle, isProcess);
+    }
+
+    public static List<Map> associationForm(String appType, String formUuid, String formInstanceId, String title, String subTitle, boolean isProcess) {
+        String formType = isProcess ? "process" : "receipt";
+        return Arrays.asList(UtilMap.map("appType, formUuid, instanceId, title, subTitle, formType", appType, formUuid, formInstanceId, title, subTitle, formType));
+    }
+
+    /**
+     * 组件格式化取值 [取值] todo 明细表递归, 循环传递入参
+     */
+    public static Object getDataByCompId(Map formData, String compId_cur) {
+        if (compId_cur.contains("associationFormField_")) {
+            // 服务注册 #{_yida_all_data} 直接组件Id
+            if (!formData.containsKey(compId_cur)) {
+                // 接口返回数据, 关联组件ID, 不会返回组件id字段
+                compId_cur = compId_cur + "_id";
+            }
+            return JSON.parse(String.valueOf(formData.get(compId_cur)));
+        } else if (compId_cur.contains("employeeField_")) {
+            if (!formData.containsKey(compId_cur + "_id")) {
+                // 服务注册 #{_yida_all_data} 返回JsonString userid 数组
+                return JSON.parse(String.valueOf(formData.get(compId_cur)));
+            } else {
+                // 成员组件, 接口返回组件id返回为姓名, _id返回是userId
+                return formData.get(compId_cur + "_id");
+            }
+        }
+        return formData.get(compId_cur);
+    }
+
+    ////////////////////////// 老版本API //////////////////////////
+
+    /**
+     * 表单接口适用于流程: 查询和更新以及删除
+     */
+    private static String formatApiForm(String uri) {
+        return "/yida_vpc/form/" + uri + ".json";
+    }
+
+    private static String formatApiProcess(String uri) {
+        return "/yida_vpc/process/" + uri + ".json";
+    }
+
+    /**
+     * 接口地址: 表单
+     */
+    public static final String API_FORM_CREATE = formatApiForm("saveFormData");
+    public static final String API_FORM_DELETE = formatApiForm("deleteFormData");
+    public static final String API_FORM_UPDATE = formatApiForm("updateFormData");
+    public static final String API_FORM_DETAIL = formatApiForm("getFormDataById");
+    public static final String API_FORM_QUERY_ID = formatApiForm("searchFormDataIds");
+    public static final String API_FORM_QUERY_DATA = formatApiForm("searchFormDatas");
+
+    public static final String API_FORM_DEFINITION = "/yida_vpc/formDesign/getFormComponentDefinationList.json";
+
+    /**
+     * 接口地址: 流程
+     */
+    public static final String API_PROCESS_CREATE = formatApiProcess("startInstance");
+    public static final String API_PROCESS_DELETE = formatApiProcess("deleteInstance");
+    public static final String API_PROCESS_UPDATE = formatApiProcess("updateInstance");
+    public static final String API_PROCESS_DETAIL = formatApiProcess("getInstanceById");
+    public static final String API_PROCESS_BATCH_DETAIL = formatApiProcess("getInstancesByIds");
+    public static final String API_PROCESS_QUERY_ID = formatApiProcess("getInstanceIds");
+    public static final String API_PROCESS_QUERY_DATA = formatApiProcess("getInstances");
+
+    /**
+     * 其它接口
+     */
+    public static final String API_OPEN_URL = "/yida_vpc/file/getOpenUrl.json";
+}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


部分文件因为文件数量过多而无法显示