Przeglądaj źródła

running:榕智、心玮、谷元、绰琪、拾上

pruple_boy 2 lat temu
commit
73ab302dc5
100 zmienionych plików z 5569 dodań i 0 usunięć
  1. 37 0
      .gitignore
  2. 19 0
      mjava-chuoqi/pom.xml
  3. 77 0
      mjava-cloudpure/pom.xml
  4. 32 0
      mjava-cloudpure/src/main/java/com/malk/cloudpure/Boot.java
  5. 140 0
      mjava-cloudpure/src/main/java/com/malk/cloudpure/controller/TB2YDController.java
  6. 62 0
      mjava-cloudpure/src/main/java/com/malk/cloudpure/controller/TBCallBackController.java
  7. 16 0
      mjava-cloudpure/src/main/java/com/malk/cloudpure/service/TBService.java
  8. 56 0
      mjava-cloudpure/src/main/java/com/malk/cloudpure/service/impl/TBServiceImpl.java
  9. 45 0
      mjava-cloudpure/src/main/resources/application-dev.yml
  10. 37 0
      mjava-cloudpure/src/main/resources/application-prod.yml
  11. 5 0
      mjava-cloudpure/target/maven-archiver/pom.properties
  12. 0 0
      mjava-cloudpure/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
  13. 5 0
      mjava-cloudpure/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
  14. BIN
      mjava-cloudpure/target/mjava-cloudpure.jar.original
  15. 77 0
      mjava-dazhong/pom.xml
  16. 32 0
      mjava-dazhong/src/main/java/com/malk/dazhong/Boot.java
  17. 50 0
      mjava-dazhong/src/main/java/com/malk/dazhong/controller/YDController.java
  18. 38 0
      mjava-dazhong/src/main/resources/application-dev.yml
  19. 25 0
      mjava-dazhong/src/main/resources/application-prod.yml
  20. 55 0
      mjava-dazhong/src/test/resources/winsw.xml
  21. 5 0
      mjava-dazhong/target/maven-archiver/pom.properties
  22. 0 0
      mjava-dazhong/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
  23. 2 0
      mjava-dazhong/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
  24. BIN
      mjava-dazhong/target/mjava-dazhong.jar.original
  25. 77 0
      mjava-guyuan/pom.xml
  26. 32 0
      mjava-guyuan/src/main/java/com/malk/guyuan/Boot.java
  27. 166 0
      mjava-guyuan/src/main/java/com/malk/guyuan/controller/GuYuanController.java
  28. 57 0
      mjava-guyuan/src/main/resources/application-dev.yml
  29. 31 0
      mjava-guyuan/src/main/resources/application-prod.yml
  30. 35 0
      mjava-guyuan/src/test/resources/server.sh
  31. 5 0
      mjava-guyuan/target/maven-archiver/pom.properties
  32. 0 0
      mjava-guyuan/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
  33. 2 0
      mjava-guyuan/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
  34. BIN
      mjava-guyuan/target/mjava-guyuan.jar.original
  35. 77 0
      mjava-minjiaoyuan/pom.xml
  36. 32 0
      mjava-minjiaoyuan/src/main/java/com/malk/minjiaoyuan/Boot.java
  37. 77 0
      mjava-minjiaoyuan/src/main/java/com/malk/minjiaoyuan/controller/MJYController.java
  38. 58 0
      mjava-minjiaoyuan/src/main/resources/application-dev.yml
  39. 33 0
      mjava-minjiaoyuan/src/main/resources/application-prod.yml
  40. 35 0
      mjava-minjiaoyuan/src/test/resources/server.sh
  41. 77 0
      mjava-rongzhi/pom.xml
  42. 32 0
      mjava-rongzhi/src/main/java/com/malk/rongzhi/Boot.java
  43. 99 0
      mjava-rongzhi/src/main/java/com/malk/rongzhi/controller/RongZhiController.java
  44. 47 0
      mjava-rongzhi/src/main/resources/application-dev.yml
  45. 21 0
      mjava-rongzhi/src/main/resources/application-prod.yml
  46. 35 0
      mjava-rongzhi/src/test/resources/server.sh
  47. 0 0
      mjava-rongzhi/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
  48. 2 0
      mjava-rongzhi/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
  49. 77 0
      mjava-xinwei/pom.xml
  50. 32 0
      mjava-xinwei/src/main/java/com/malk/xinwei/Boot.java
  51. 55 0
      mjava-xinwei/src/main/java/com/malk/xinwei/controller/ChuoQiController.java
  52. 155 0
      mjava-xinwei/src/main/java/com/malk/xinwei/controller/LiChenController.java
  53. 128 0
      mjava-xinwei/src/main/java/com/malk/xinwei/controller/ShiShangController.java
  54. 143 0
      mjava-xinwei/src/main/java/com/malk/xinwei/controller/SuoDiSiController.java
  55. 113 0
      mjava-xinwei/src/main/java/com/malk/xinwei/controller/XinWeiController.java
  56. 53 0
      mjava-xinwei/src/main/resources/application-dev.yml
  57. 38 0
      mjava-xinwei/src/main/resources/application-prod.yml
  58. 35 0
      mjava-xinwei/src/test/resources/server.sh
  59. 5 0
      mjava-xinwei/target/maven-archiver/pom.properties
  60. 0 0
      mjava-xinwei/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
  61. 6 0
      mjava-xinwei/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
  62. BIN
      mjava-xinwei/target/mjava-xinwei.jar.original
  63. 84 0
      mjava/pom.xml
  64. 29 0
      mjava/src/main/java/com/malk/Boot.java
  65. 190 0
      mjava/src/main/java/com/malk/Filter/CatchException.java
  66. 54 0
      mjava/src/main/java/com/malk/Filter/ExceptionNotice.java
  67. 32 0
      mjava/src/main/java/com/malk/Filter/RequestFilter.java
  68. 41 0
      mjava/src/main/java/com/malk/Filter/RequestInterceptor.java
  69. 141 0
      mjava/src/main/java/com/malk/Util/UtilConvert.java
  70. 126 0
      mjava/src/main/java/com/malk/Util/UtilDateTime.java
  71. 135 0
      mjava/src/main/java/com/malk/Util/UtilEnv.java
  72. 227 0
      mjava/src/main/java/com/malk/Util/UtilExcel.java
  73. 25 0
      mjava/src/main/java/com/malk/Util/UtilFile.java
  74. 180 0
      mjava/src/main/java/com/malk/Util/UtilHttp.java
  75. 74 0
      mjava/src/main/java/com/malk/Util/UtilImport.java
  76. 30 0
      mjava/src/main/java/com/malk/Util/UtilList.java
  77. 85 0
      mjava/src/main/java/com/malk/Util/UtilMap.java
  78. 24 0
      mjava/src/main/java/com/malk/Util/UtilMath.java
  79. 61 0
      mjava/src/main/java/com/malk/Util/UtilServlet.java
  80. 41 0
      mjava/src/main/java/com/malk/Util/UtilString.java
  81. 29 0
      mjava/src/main/java/com/malk/Util/UtilToken.java
  82. 48 0
      mjava/src/main/java/com/malk/Util/UtilVendor.java
  83. 54 0
      mjava/src/main/java/com/malk/base/BaseDTO.java
  84. 29 0
      mjava/src/main/java/com/malk/base/BaseDao.java
  85. 97 0
      mjava/src/main/java/com/malk/base/BaseParam.java
  86. 9 0
      mjava/src/main/java/com/malk/base/BaseRepository.java
  87. 33 0
      mjava/src/main/java/com/malk/config/JpaConfiguration.java
  88. 52 0
      mjava/src/main/java/com/malk/config/WebConfiguration.java
  89. 9 0
      mjava/src/main/java/com/malk/config/YDConfiguration.java
  90. 41 0
      mjava/src/main/java/com/malk/config/mutilSource/DataSourceConfig.java
  91. 78 0
      mjava/src/main/java/com/malk/config/mutilSource/PrimaryConfig.java
  92. 74 0
      mjava/src/main/java/com/malk/config/mutilSource/SlaveConfig.java
  93. 139 0
      mjava/src/main/java/com/malk/config/swagger3/SwaggerConfiguration.java
  94. 39 0
      mjava/src/main/java/com/malk/config/swagger3/SwaggerProperties.java
  95. 207 0
      mjava/src/main/java/com/malk/controller/McAliworkController.java
  96. 47 0
      mjava/src/main/java/com/malk/controller/McLicenceController.java
  97. 158 0
      mjava/src/main/java/com/malk/controller/TB2YDController.java
  98. 61 0
      mjava/src/main/java/com/malk/controller/TBCallBackController.java
  99. 101 0
      mjava/src/main/java/com/malk/core/AsyncConfig.java
  100. 0 0
      mjava/src/main/java/com/malk/repository/dao/mutual/AyUserAttachmentRelRepository.java

+ 37 - 0
.gitignore

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

+ 19 - 0
mjava-chuoqi/pom.xml

@@ -0,0 +1,19 @@
+<?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-chuoqi</artifactId>
+
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+    </properties>
+
+</project>

+ 77 - 0
mjava-cloudpure/pom.xml

@@ -0,0 +1,77 @@
+<?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-cloudpure</artifactId>
+    <description>云璞公共服务环境</description>
+
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+    </properties>
+
+    <dependencies>
+        <!-- 阿里钉钉 & 宜搭依赖 -->
+        <dependency>
+            <groupId>com.taobao.top</groupId>
+            <artifactId>top-api-sdk-dev</artifactId>
+            <version>dingtalk-SNAPSHOT</version>
+            <scope>system</scope>
+            <systemPath>${pom.basedir}/lib/taobao-sdk-java-auto_1479188381469-20191205.jar</systemPath>
+        </dependency>
+        <dependency>
+            <groupId>com.taobao.top</groupId>
+            <artifactId>lippi-oapi-encrpt</artifactId>
+            <version>dingtalk-SNAPSHOT</version>
+            <scope>system</scope>
+            <systemPath>${pom.basedir}/lib/lippi-oapi-encrpt.jar</systemPath>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba.platform.shared</groupId>
+            <artifactId>xxpt.gateway.shared.client</artifactId>
+            <version>1.1.0</version>
+            <scope>system</scope>
+            <systemPath>${pom.basedir}/lib/xxpt.gateway.shared.client-1.1.0.jar</systemPath>
+        </dependency>
+
+        <!-- 核心模块-->
+        <dependency>
+            <groupId>com.malk</groupId>
+            <artifactId>mjava</artifactId>
+            <version>1.0-SNAPSHOT</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-cloudpure/src/main/java/com/malk/cloudpure/Boot.java

@@ -0,0 +1,32 @@
+package com.malk.cloudpure;
+
+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. 标识对表没有任何操作. 若不设置为 non, 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);
+    }
+}

+ 140 - 0
mjava-cloudpure/src/main/java/com/malk/cloudpure/controller/TB2YDController.java

@@ -0,0 +1,140 @@
+//package com.malk.cloudpure.com.malk.rongzhi.controller;
+//
+//import com.malk.Util.UtilServlet;
+//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.server.teambition.TBConf;
+//import com.malk.service.aliwork.YDClient_DD;
+//import com.malk.service.teambition.TBClient;
+//import lombok.extern.slf4j.Slf4j;
+//import org.springframework.beans.factory.annotation.Autowired;
+//import org.springframework.validation.annotation.Validated;
+//import org.springframework.web.bind.annotation.*;
+//
+//import java.util.ArrayList;
+//import java.util.List;
+//import java.util.Map;
+//import java.util.stream.Collectors;
+//
+///**
+// * TB-YD: tb2yd对接方案
+// */
+//@Slf4j
+//@RestController
+//@RequestMapping("/tb")
+//public class TB2YDController {
+//
+//    @Autowired
+//    private TBClient tbClient;
+//
+//    @Autowired
+//    private TBConf tbConf;
+//
+//    @Autowired
+//    private YDClient_DD ydClient_dd;
+//
+//    // 查询操作人在tb的userId
+//    private List<String> _convertUserIdWithSearch(String search) {
+//        List<Map> users = tbClient.userSearch(search);
+//        McException.assertAccessException(users.isEmpty(), search + ": 未匹配到TB用户, 请联系管理员添加");
+//        return users.stream().map(item -> String.valueOf(item.get("userId"))).collect(Collectors.toList());
+//    }
+//
+//    // 钉钉用户唯一键转同步用户ID
+//    private List<String> _convertUserIdWithSearch_bulk(Map<String, Object> data) {
+//        List<String> userIds = new ArrayList<>();
+//        for (String unique : (List<String>) data.get("usersUnique")) {
+//            userIds.addAll(_convertUserIdWithSearch(unique));
+//        }
+//        return userIds;
+//    }
+//
+//    /**
+//     * 搜索企业模板模板
+//     */
+//    @GetMapping("template")
+//    public McR getTemplateList(@RequestBody Map<String, String> data) {
+//        return McR.success(tbClient.templateSearch(data.get("id"), data.get("name")));
+//    }
+//
+//    /**
+//     * 搜索用户
+//     */
+//    @GetMapping("user")
+//    public McR searchUser(@RequestParam String search) {
+//        return McR.success(tbClient.userSearch(search));
+//    }
+//
+//    /**
+//     * 更新用户信息
+//     */
+//    @PutMapping("user")
+//    public McR updateUser(@RequestBody Map<String, ?> data) {
+//        McException.assertParamException_Null(UtilServlet.isNull(data, "members"));
+//        String operatorId = "";
+//        if (UtilServlet.isNotNull(data, "userName")) {
+//            operatorId = _convertUserIdWithSearch(String.valueOf(data.get("userName"))).get(0);
+//        }
+//        return McR.success(tbClient.updateUser(operatorId, (List<Map>) data.get("members")));
+//    }
+//
+//    /**
+//     * 创建空白项目, 通过模板创建项目
+//     */
+//    @PostMapping("project")
+//    public McR createProjectWithTemplate(@RequestBody Map<String, String> data) {
+//        McException.assertParamException_Null(UtilServlet.isNull(data, "projectName", "userName"));
+//        String operatorId = _convertUserIdWithSearch(String.valueOf(data.get("userName"))).get(0);
+//        if (UtilServlet.isNotNull(data, "templateId")) {
+//            return McR.success(tbClient.projectCreateWithTemplate(data.get("projectName"), data.get("templateId"), operatorId));
+//        }
+//        return McR.success(tbClient.projectCreate(data.get("projectName"), operatorId));
+//    }
+//
+//    @PutMapping("project/{id}")
+//    public McR updateProject(@PathVariable String id, @RequestBody Map<String, String> data) {
+//        McException.assertParamException_Null(UtilServlet.isNull(data, "userName"));
+//        String operatorId = _convertUserIdWithSearch(String.valueOf(data.get("userName"))).get(0);
+//        return McR.success(tbClient.projectUpdate(id, data, operatorId));
+//    }
+//
+//    /**
+//     * 创建项目成员, 修改项目成员的角色
+//     */
+//    @RequestMapping(value = "project/member", method = {RequestMethod.POST, RequestMethod.PUT})
+//    public McR createProjectMember(@RequestBody Map<String, Object> data) {
+//        McException.assertParamException_Null(UtilServlet.isNull(data, "userName"));
+//        List userIds = _convertUserIdWithSearch_bulk(data);
+//        if (UtilServlet.isNotNull(data, "roleIds")) {
+//            return McR.success(tbClient.updateProjectMember(userIds, (List<String>) data.get("roleIds"), String.valueOf(data.get("projectId"))));
+//        }
+//        McException.assertParamException_Null(UtilServlet.isNull(data, "userName"));
+//        String operatorId = _convertUserIdWithSearch(String.valueOf(data.get("userName"))).get(0);
+//        return McR.success(tbClient.createProjectMember(userIds, String.valueOf(data.get("projectId")), operatorId));
+//    }
+//
+//    /**
+//     * 获取项目角色列表
+//     */
+//    @GetMapping("{projectId}/role")
+//    McR queryProjectRoles(@PathVariable String projectId) {
+//        return McR.success(tbClient.queryProjectRoles(projectId));
+//    }
+//
+//    @PutMapping("task/{id}")
+//    McR updateTaskDueDate(@PathVariable String id, @RequestBody Map<String, String> data) {
+//        McException.assertParamException_Null(UtilServlet.isNull(data, "userName", "dueDate"));
+//        String operatorId = _convertUserIdWithSearch(String.valueOf(data.get("userName"))).get(0);
+//        return McR.success(tbClient.updateTeakDueDate(id, data.get("dueDate"), operatorId));
+//    }
+//
+//    /**
+//     * 新增表单数据, result返回实例Id
+//     */
+//    @PostMapping("form/create")
+//    McR createFormData(@RequestBody @Validated(YDParam.Create.class) YDParam param) {
+//        return McR.success(ydClient_dd.operateData(param, YDConf.TYPE.create));
+//    }
+//}

+ 62 - 0
mjava-cloudpure/src/main/java/com/malk/cloudpure/controller/TBCallBackController.java

@@ -0,0 +1,62 @@
+//package com.malk.cloudpure.com.malk.rongzhi.controller;
+//
+//import com.alibaba.fastjson.JSONObject;
+//import com.malk.cloudpure.service.TBService;
+//import com.malk.server.teambition.TBConf;
+//import lombok.extern.slf4j.Slf4j;
+//import org.springframework.beans.factory.annotation.Autowired;
+//import org.springframework.web.bind.annotation.PostMapping;
+//import org.springframework.web.bind.annotation.RequestBody;
+//import org.springframework.web.bind.annotation.RequestMapping;
+//import org.springframework.web.bind.annotation.RestController;
+//
+//import java.util.Arrays;
+//
+///**
+// * TB事件回调
+// * 回调可选择加密与不加密方式, tb发送成功为上游, 注册服务为下游, 支持ngrok外网穿透调用, 若应用发布后收不到回调, 新建一个带有时间的任务就会触发
+// * -
+// * 回调说明
+// * 1. 通过接口更操作的数据,也会与手动创建一样触发相同的回调, 除了项目更新接口调用实测不会触发回调, 手动修改正常回调
+// * 2. 项目创建会推送两次
+// * - 1. 在第二次推送多 { data: { project: { operatorId, url }} } 这两个字段内容
+// * - 2. 若是通过模板创建的项目,在两次项目更新回调中会回调一次 project.enable 回调, 其中任务只会回调创建, 不会回调更新
+// * 3. 任务创建, 会先回调创建事件, 接着立即回调任务更新事件 [若是通过模板创建, 任务只会回调创建, 不会回调更新]
+// * 4. 项目移入回收站,不会触发回调,删除后会触发项目与任务的 remove 事件; 若是将任务移入回收站, 会触发任务更新回调
+// */
+//@Slf4j
+//@RestController
+//@RequestMapping("/tb")
+//public class TBCallBackController {
+//
+//    @Autowired
+//    private TBService tbService;
+//
+//    @PostMapping("callback")
+//    public String callback(@RequestBody JSONObject eventJson) {
+//
+//        String success = "success";
+//        String eventName = eventJson.getString("event");
+//
+//        if (TBConf.EVENT_VERIFY_HOOK.equals(eventName)) {
+//            log.info("----- [TB]验证注册 -----");
+//            return success;
+//        }
+//
+//        if (Arrays.asList(TBConf.EVENT_TASK_CREATE, TBConf.EVENT_TASK_UPDATE).contains(eventName)) {
+//            log.debug("[TB]任务回调, {}, {}", eventName, eventJson);
+//            tbService.callBackTask(eventJson);
+//            return success;
+//        }
+//
+//        if (Arrays.asList(TBConf.EVENT_PROJECT_CREAT, TBConf.EVENT_PROJECT_UPDATE).contains(eventName)) {
+//            log.debug("[TB]项目回调, {}, {}", eventName, eventJson);
+//            tbService.callBackProject(eventJson);
+//            return success;
+//        }
+//
+//
+//        log.info("----- [TB]已注册, 未处理的其它回调 -----, {}, {}", eventName, eventJson);
+//        return success;
+//    }
+//}

+ 16 - 0
mjava-cloudpure/src/main/java/com/malk/cloudpure/service/TBService.java

@@ -0,0 +1,16 @@
+package com.malk.cloudpure.service;
+
+import com.alibaba.fastjson.JSONObject;
+
+public interface TBService {
+
+    /**
+     * 任务回调事件  [异步]
+     */
+    void callBackTask(JSONObject eventJson);
+
+    /**
+     * 项目回调事件  [异步]
+     */
+    void callBackProject(JSONObject eventJson);
+}

+ 56 - 0
mjava-cloudpure/src/main/java/com/malk/cloudpure/service/impl/TBServiceImpl.java

@@ -0,0 +1,56 @@
+//package com.malk.cloudpure.service.impl;
+//
+//import com.alibaba.fastjson.JSONObject;
+//import com.malk.cloudpure.service.TBService;
+//import com.malk.server.teambition.TBConf;
+//import com.malk.service.teambition.TBClient;
+//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;
+//
+//@Slf4j
+//@Service
+//public class TBServiceImpl implements TBService {
+//
+//    @Autowired
+//    private TBConf tbConf;
+//
+//    @Autowired
+//    private TBClient tbClient;
+//
+//    @Async
+//    @Override
+//    @SneakyThrows
+//    public void callBackTask(JSONObject eventJson) {
+//        String eventName = eventJson.getString("event");
+//        if (TBConf.EVENT_TASK_CREATE.equals(eventName)) {
+//            // TB任务创建
+//
+//
+//        }
+//
+//        if (TBConf.EVENT_TASK_UPDATE.equals(eventName)) {
+//            // TB任务更新
+//
+//        }
+//    }
+//
+//    @Async
+//    @Override
+//    @SneakyThrows
+//    public void callBackProject(JSONObject eventJson) {
+//        String eventName = eventJson.getString("event");
+//        if (TBConf.EVENT_PROJECT_CREAT.equals(eventName)) {
+//            // TB项目创建
+//
+//
+//        }
+//
+//        if (TBConf.EVENT_PROJECT_UPDATE.equals(eventName)) {
+//            // TB项目更新
+//
+//        }
+//    }
+//}

+ 45 - 0
mjava-cloudpure/src/main/resources/application-dev.yml

@@ -0,0 +1,45 @@
+# 环境配置
+server:
+  port: 9001
+
+# condition
+spel:
+  scheduling: false        # 定时任务是否执行
+  multiSource: false       # 是否多数据源配置
+
+spring:
+  # database
+  datasource:
+    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:
+    hibernate:
+      ddl-auto: none    # JPA对表没有任何操作
+    show-sql: true
+    database: MYSQL
+    database-platform: org.hibernate.dialect.MySQL57Dialect
+  flyway:
+    enabled: false      #  需要配置 jpa.hibernate.ddl-auto 为 none. 否则 flyway.enabled 配置会无效, 在没有数库连接情况下程序无法启动
+
+# dingtalk
+dingtalk:
+  agentId: 1999093926
+  appKey: dingylcuowclmme76je1
+  appSecret: u5_oBF5Kj491u3iefiaTnl8AO9vrSyE84x_jQxjCGyqV9JKnzaYBElkh_cBo177I
+  corpId: dingcc1b1ffad0d5ca1d
+  aesKey:
+  token:
+
+# teambition
+teambition:
+  AppID: 63589b8bb6803e162f9a57d8
+  AppSecret: 5mB3b73OFhSwo38xEVqahCLwQVhG1MW3
+  TenantId: 5ca44db8ca4fd40001b10559
+  OperatorId: 5e698cca21f5ad70dfba7d2b    # 公共账号, 需要有操作权限 [牧语]
+
+# aliwork
+aliwork:
+  appType: APP_GTK6SIE4MNVEEVPJLM7Z
+  systemToken: IA766O61FNR42YWEEEKTB5QAEEP827UYE3M9LK11

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

@@ -0,0 +1,37 @@
+# 环境配置
+server:
+  port: 8094
+
+spring:
+  # database
+  datasource:
+    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
+  flyway:
+    enabled: false      #  需要配置 jpa.hibernate.ddl-auto 为 none. 否则 flyway.enabled 配置会无效, 在没有数库连接情况下程序无法启动
+
+# dingtalk
+dingtalk:
+  agentId: 1999093926
+  appKey: dingylcuowclmme76je1
+  appSecret: u5_oBF5Kj491u3iefiaTnl8AO9vrSyE84x_jQxjCGyqV9JKnzaYBElkh_cBo177I
+  corpId: dingcc1b1ffad0d5ca1d
+  aesKey:
+  token:
+
+# teambition
+teambition:
+  AppID: 63589b8bb6803e162f9a57d8
+  AppSecret: 5mB3b73OFhSwo38xEVqahCLwQVhG1MW3
+  TenantId: 5ca44db8ca4fd40001b10559
+  OperatorId: 5e698cca21f5ad70dfba7d2b    # 公共账号, 需要有操作权限 [牧语]
+
+# aliwork
+aliwork:
+  appType: APP_GTK6SIE4MNVEEVPJLM7Z
+  systemToken: IA766O61FNR42YWEEEKTB5QAEEP827UYE3M9LK11

+ 5 - 0
mjava-cloudpure/target/maven-archiver/pom.properties

@@ -0,0 +1,5 @@
+#Generated by Maven
+#Mon Nov 21 10:35:26 CST 2022
+version=1.0-SNAPSHOT
+groupId=com.malk
+artifactId=mjava-cloudpure

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


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

@@ -0,0 +1,5 @@
+/Users/malk/server/java-mcli/mjava-cloudpure/src/main/java/com/malk/cloudpure/controller/TBCallBackController.java
+/Users/malk/server/java-mcli/mjava-cloudpure/src/main/java/com/malk/cloudpure/service/impl/TBServiceImpl.java
+/Users/malk/server/java-mcli/mjava-cloudpure/src/main/java/com/malk/cloudpure/service/TBService.java
+/Users/malk/server/java-mcli/mjava-cloudpure/src/main/java/com/malk/cloudpure/controller/TB2YDController.java
+/Users/malk/server/java-mcli/mjava-cloudpure/src/main/java/com/malk/cloudpure/Boot.java

BIN
mjava-cloudpure/target/mjava-cloudpure.jar.original


+ 77 - 0
mjava-dazhong/pom.xml

@@ -0,0 +1,77 @@
+<?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-dazhong</artifactId>
+    <description>大众供应商推送宜搭数据</description>
+
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+    </properties>
+
+    <dependencies>
+        <!-- 阿里钉钉 & 宜搭依赖 -->
+        <dependency>
+            <groupId>com.taobao.top</groupId>
+            <artifactId>top-api-sdk-dev</artifactId>
+            <version>dingtalk-SNAPSHOT</version>
+            <scope>system</scope>
+            <systemPath>${pom.basedir}/lib/taobao-sdk-java-auto_1479188381469-20191205.jar</systemPath>
+        </dependency>
+        <dependency>
+            <groupId>com.taobao.top</groupId>
+            <artifactId>lippi-oapi-encrpt</artifactId>
+            <version>dingtalk-SNAPSHOT</version>
+            <scope>system</scope>
+            <systemPath>${pom.basedir}/lib/lippi-oapi-encrpt.jar</systemPath>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba.platform.shared</groupId>
+            <artifactId>xxpt.gateway.shared.client</artifactId>
+            <version>1.1.0</version>
+            <scope>system</scope>
+            <systemPath>${pom.basedir}/lib/xxpt.gateway.shared.client-1.1.0.jar</systemPath>
+        </dependency>
+
+        <!-- 核心模块-->
+        <dependency>
+            <groupId>com.malk</groupId>
+            <artifactId>mjava</artifactId>
+            <version>1.0-SNAPSHOT</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-dazhong/src/main/java/com/malk/dazhong/Boot.java

@@ -0,0 +1,32 @@
+package com.malk.dazhong;
+
+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. 标识对表没有任何操作. 若不设置为 non, 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);
+    }
+}

+ 50 - 0
mjava-dazhong/src/main/java/com/malk/dazhong/controller/YDController.java

@@ -0,0 +1,50 @@
+package com.malk.dazhong.controller;
+
+import com.alibaba.fastjson.JSON;
+import com.malk.server.aliwork.YDConf;
+import com.malk.server.aliwork.YDParam;
+import com.malk.server.common.McR;
+import com.malk.service.aliwork.YDClient;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 指定运行环境 [参考TestController]
+ */
+@Validated
+@RestController
+@Slf4j
+@RequestMapping("/test")
+public class YDController {
+
+    @Autowired
+    private YDClient ydClient;
+
+    Integer index = 0;
+
+    @GetMapping("insert")
+    McR dzInsert() {
+
+        index++;
+
+        YDParam ydParam = new YDParam();
+        ydParam.setAppType("APP_BT0NLNNL1O1ZQD1JMJCQ");
+        ydParam.setSystemToken("16A6629115S3L0K6C9E7Y91UV9A23HV4J008LJ8");
+        ydParam.setFormUuid("FORM-LK766AC1L2Q34W8OBVVQ998IT10I26VNEK98L31");
+        Map data = new HashMap();
+        data.put("textField_qzpa61f", index);
+        data.put("textField_algkcgh", 65.0314);
+        data.put("textField_0aoq38c", 0.0146);
+        data.put("textField_6doxlyu", 0.0042);
+        ydParam.setFormDataJson(JSON.toJSONString(data));
+        ydClient.operateData(YDConf.API_FORM_CREATE, ydParam);
+        return McR.success();
+    }
+}

+ 38 - 0
mjava-dazhong/src/main/resources/application-dev.yml

@@ -0,0 +1,38 @@
+# 环境配置
+server:
+  port: 9001
+
+# condition
+spel:
+  scheduling: false        # 定时任务是否执行
+  multiSource: false       # 是否多数据源配置
+
+spring:
+  # database
+  datasource:
+    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
+  jpa:
+    hibernate:
+      ddl-auto: none    # JPA对表没有任何操作
+    show-sql: true
+    database: MYSQL
+    database-platform: org.hibernate.dialect.MySQL57Dialect
+  flyway:
+    enabled: false      #  需要配置 jpa.hibernate.ddl-auto 为 none. 否则 flyway.enabled 配置会无效, 在没有数库连接情况下程序无法启动
+
+# swagger3
+swagger:
+  enable: true
+
+# dingtalk
+dingtalk:
+  agentId: 1621831524
+  appKey: dingrgujdlc8ysgnfnyz
+  appSecret: LDCu5PLBoS58cUG0Q3NBpnN1DhcX6g3zyHDlVUSZ7CTHrGQPOSudTiYqjXg-ad7r
+  corpId: ding24d3021341c916b5ee0f45d8e4f7c288
+  aesKey: JtkDqwLDjiidhtb24U58h2WxJx6EWfSzoPiiXIQxJzJ
+  token: orDlKVufcdbz1sN8H9dFm61zpGFn7XRPishPk2CiZrpyN

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

@@ -0,0 +1,25 @@
+# 环境配置
+server:
+  port: 9001
+
+spring:
+  # database
+  datasource:
+    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
+  flyway:
+    enabled: false      #  需要配置 jpa.hibernate.ddl-auto 为 none. 否则 flyway.enabled 配置会无效, 在没有数库连接情况下程序无法启动
+
+# dingtalk
+dingtalk:
+  agentId: 1621831524
+  appKey: dingrgujdlc8ysgnfnyz
+  appSecret: LDCu5PLBoS58cUG0Q3NBpnN1DhcX6g3zyHDlVUSZ7CTHrGQPOSudTiYqjXg-ad7r
+  corpId: ding24d3021341c916b5ee0f45d8e4f7c288
+  aesKey: JtkDqwLDjiidhtb24U58h2WxJx6EWfSzoPiiXIQxJzJ
+  token: orDlKVufcdbz1sN8H9dFm61zpGFn7XRPishPk2CiZrpyN

+ 55 - 0
mjava-dazhong/src/test/resources/winsw.xml

@@ -0,0 +1,55 @@
+<!--
+  MIT License
+
+  Copyright (c) 2008-2020 Kohsuke Kawaguchi, Sun Microsystems, Inc., CloudBees,
+  Inc., Oleg Nenashev and other contributors
+
+  Permission is hereby granted, free of charge, to any person obtaining a copy
+  of this software and associated documentation files (the "Software"), to deal
+  in the Software without restriction, including without limitation the rights
+  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+  copies of the Software, and to permit persons to whom the Software is
+  furnished to do so, subject to the following conditions:
+
+  The above copyright notice and this permission notice shall be included in all
+  copies or substantial portions of the Software.
+
+  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+  SOFTWARE.
+-->
+
+<!--
+ This is an example of a minimal Windows Service Wrapper configuration, which includes only mandatory options.
+ 
+ This configuration file should be placed near the WinSW executable, the name should be the same.
+ E.g. for myapp.exe the configuration file name should be myapp.xml
+ 
+ You can find more information about the configuration options here: https://github.com/kohsuke/winsw/blob/master/doc/xmlConfigFile.md
+ Full example: https://github.com/kohsuke/winsw/blob/master/examples/sample-allOptions.xml
+-->
+
+<service>
+    <!-- 注册服务ID -->
+    <id>mjava-dz</id>
+    <!-- 启动服务名称 -->
+    <name>mjava-dz</name>
+    <!-- 对服务的描述 -->
+    <description>大众供应商测试demo, 验证本地服务器推送宜搭</description>
+    <!-- 「jdk需要安装」启动的可执行文件:若未配置环境变量executable需要执行绝对路径 -->
+    <!-- <executable>java</executable> -->
+    <executable>C:\Program Files\DingTalkServer\jdk1.8.0_221\bin\java</executable>
+    <!-- Xmx256m 代表堆内存最大值为256MB -jar后面的是项目名 -->
+    <arguments>-Xmx256m -Xms256m -jar mjava-dazhong.jar</arguments>
+    <!-- 服务的启动模式:默认Automatic -->
+    <startmode>Automatic</startmode>
+    <!-- 日志地址 -->
+    <logpath>%BASE%\ws</logpath>
+    <!-- 日志模式 -->
+    <logmode>rotate</logmode>
+</service>
+

+ 5 - 0
mjava-dazhong/target/maven-archiver/pom.properties

@@ -0,0 +1,5 @@
+#Generated by Maven
+#Thu Dec 22 16:24:26 CST 2022
+version=1.0-SNAPSHOT
+groupId=com.malk
+artifactId=mjava-dazhong

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


+ 2 - 0
mjava-dazhong/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst

@@ -0,0 +1,2 @@
+/Users/malk/server/java-mcli/mjava-dazhong/src/main/java/com/malk/dazhong/Boot.java
+/Users/malk/server/java-mcli/mjava-dazhong/src/main/java/com/malk/dazhong/controller/YDController.java

BIN
mjava-dazhong/target/mjava-dazhong.jar.original


+ 77 - 0
mjava-guyuan/pom.xml

@@ -0,0 +1,77 @@
+<?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-guyuan</artifactId>
+    <description>谷元发票功能开发: 发票验证数量,查询是否使用,校验税号</description>
+
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+    </properties>
+
+    <dependencies>
+        <!-- 阿里钉钉 & 宜搭依赖 -->
+        <dependency>
+            <groupId>com.taobao.top</groupId>
+            <artifactId>top-api-sdk-dev</artifactId>
+            <version>dingtalk-SNAPSHOT</version>
+            <scope>system</scope>
+            <systemPath>${pom.basedir}/lib/taobao-sdk-java-auto_1479188381469-20191205.jar</systemPath>
+        </dependency>
+        <dependency>
+            <groupId>com.taobao.top</groupId>
+            <artifactId>lippi-oapi-encrpt</artifactId>
+            <version>dingtalk-SNAPSHOT</version>
+            <scope>system</scope>
+            <systemPath>${pom.basedir}/lib/lippi-oapi-encrpt.jar</systemPath>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba.platform.shared</groupId>
+            <artifactId>xxpt.gateway.shared.client</artifactId>
+            <version>1.1.0</version>
+            <scope>system</scope>
+            <systemPath>${pom.basedir}/lib/xxpt.gateway.shared.client-1.1.0.jar</systemPath>
+        </dependency>
+
+        <!-- 核心模块-->
+        <dependency>
+            <groupId>com.malk</groupId>
+            <artifactId>mjava</artifactId>
+            <version>1.0-SNAPSHOT</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-guyuan/src/main/java/com/malk/guyuan/Boot.java

@@ -0,0 +1,32 @@
+package com.malk.guyuan;
+
+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. 标识对表没有任何操作. 若不设置为 non, 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);
+    }
+}

+ 166 - 0
mjava-guyuan/src/main/java/com/malk/guyuan/controller/GuYuanController.java

@@ -0,0 +1,166 @@
+package com.malk.guyuan.controller;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.alibaba.fastjson.JSON;
+import com.malk.Util.UtilServlet;
+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.server.dingtalk.DDR_New;
+import com.malk.service.aliwork.YDClient;
+import com.malk.service.aliwork.impl.YDClient_DD;
+import com.malk.service.aliyun.ALYInvoice;
+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.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 错误抛出与拦截详见CatchException
+ */
+@Slf4j
+@RestController
+@RequestMapping("/guyuan")
+public class GuYuanController {
+
+    @Autowired
+    private ALYInvoice invoice;
+
+    @Autowired
+    private YDClient_DD ydClient_dd;
+
+    @Autowired
+    private YDClient ydClient;
+
+    /**
+     * 阿里发票验真
+     */
+    @PostMapping("/invoice/check")
+    McR invoiceCheckPDF(@RequestBody Map<String, String> param) {
+        log.info("阿里发票验真, 文件, {}", param);
+        McException.assertParamException_Null(param, "url");
+        return McR.success(invoice.invoiceCheckPDF("4e2c048bfe1d4feea7354a66c7944fd1", param.get("url")));
+    }
+
+    /**
+     * 阿里发票验真: image
+     */
+    @PostMapping("invoice/ocr")
+    McR invoiceCheckImage(@RequestBody Map<String, String> param) {
+        log.info("阿里发票验真, 图片, {}", param);
+        return McR.success(invoice.invoiceCheckOCR("4e2c048bfe1d4feea7354a66c7944fd1", param.get("url")));
+    }
+
+    /**
+     * 宜搭临时免登地址: 钉钉平台
+     */
+    @PostMapping("openUrl")
+    McR openUrl(@RequestBody Map<String, String> param) {
+        McException.assertParamException_Null(param, "url");
+        return McR.success(ydClient_dd.convertTemporaryUrl(param.get("url")));
+    }
+
+    /**
+     * 宜搭临时免登地址: 宜搭版本
+     */
+    @PostMapping("yd/openUrl")
+    McR openUrl_YD(@RequestBody Map<String, String> param) {
+        McException.assertParamException_Null(param, "url");
+        YDParam ydParam = YDParam.builder()
+                .appType("APP_FKRK7Y94DPI1S9DV1605")
+                .systemToken("FN7666A1ZD0STZZ75W4CKD1GD07X3PUW2FBRKT")
+                .fileUrl(param.get("url"))
+                .build();
+        return McR.success(ydClient.queryData(YDConf.API_OPEN_URL, ydParam));
+    }
+
+    /**
+     * 全局查询子表单
+     */
+    @PostMapping("queryAll")
+    McR queryAll(@RequestBody YDParam ydParam) {
+        return McR.success(ydClient_dd.queryData(ydParam, YDConf.TYPE_QUERY.retrieve_list_all));
+    }
+
+    /**
+     * 全局查询(不匹配子表单)
+     */
+    @PostMapping("validate")
+    McR queryAll(HttpServletRequest request) {
+        Map<String, ?> param = UtilServlet.getParamMap(request);
+        log.info("全局查询(不匹配子表单), {}", param);
+        if (ObjectUtil.isNull(param.get("uniques"))) {
+            return McR.success();
+        }
+        McException.assertParamException_Null(param, "uniques", "formUuid", "compId");
+        // 容错 - 尾部分号的空格会被输入框忽略
+        String[] uniques = String.valueOf(param.get("uniques")).replace("; ", ";").split(";");
+        for (String val : uniques) {
+            // 查重校验: 单张发票唯一标识 + 审批已通过 / 审批中
+            List<Map> conditions = new ArrayList<>();
+            Map unique = new HashMap();
+            unique.put("key", param.get("compId"));
+            unique.put("value", val.split(": ")[1]);
+            unique.put("type", "TEXT");
+            unique.put("operator", "like");
+            unique.put("componentName", "TextField");
+            conditions.add(unique);
+
+            Map approve = new HashMap();
+            approve.put("key", "processApprovedResult");
+            approve.put("value", new String[]{"agree"});
+            approve.put("type", "ARRAY");
+            approve.put("operator", "in");
+            approve.put("componentName", "SelectField");
+            conditions.add(approve);
+            YDParam ydParam = YDParam.builder()
+                    .appType("APP_FKRK7Y94DPI1S9DV1605")
+                    .systemToken("FN7666A1ZD0STZZ75W4CKD1GD07X3PUW2FBRKT")
+                    .formUuid(String.valueOf(param.get("formUuid")))
+                    .searchCondition(JSON.toJSONString(conditions))
+                    .build();
+            DDR_New ddr_new = ydClient_dd.queryData(ydParam, YDConf.TYPE_QUERY.retrieve_list);
+            log.info("审批通过匹配结果, {}, {}", ddr_new.getTotalCount(), ddr_new.getData());
+            if (ddr_new.getTotalCount() > 0) {
+                return McR.errorAccess("发票已被使用, 请勿重复提交!");
+            }
+
+            conditions.remove(approve);
+            Map status = new HashMap();
+            status.put("key", "processInstanceStatus");
+            status.put("value", new String[]{"RUNNING"});
+            status.put("type", "ARRAY");
+            status.put("operator", "in");
+            status.put("componentName", "SelectField");
+            conditions.add(status);
+            ydParam.setSearchCondition(JSON.toJSONString(conditions));
+            ddr_new = ydClient_dd.queryData(ydParam, YDConf.TYPE_QUERY.retrieve_list);
+            log.info("审批通过匹配结果, {}, {}", ddr_new.getTotalCount(), ddr_new.getData());
+            if (ddr_new.getTotalCount() > 0) {
+                return McR.errorAccess("发票已在流程中, 请勿重复提交!");
+            }
+        }
+        return McR.success();
+    }
+
+    /**
+     * 服务状态返回
+     */
+    @PostMapping("validateTips")
+    McR validateTaxNo(HttpServletRequest request) {
+        Map data = UtilServlet.getParamMap(request);
+        if (!data.get("status").equals(1)) {
+            return McR.errorAccess("error");
+        }
+        return McR.success(data);
+    }
+}

+ 57 - 0
mjava-guyuan/src/main/resources/application-dev.yml

@@ -0,0 +1,57 @@
+# 环境配置
+server:
+  port: 9001
+  servlet:
+    context-path: /api
+
+# condition
+spel:
+  scheduling: false        # 定时任务是否执行
+  multiSource: false       # 是否多数据源配置
+
+spring:
+  # database
+  datasource:
+    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:
+    hibernate:
+      ddl-auto: none      # JPA对表没有任何操作
+    show-sql: true
+    database: MYSQL
+    database-platform: org.hibernate.dialect.MySQL57Dialect
+  flyway:
+    enabled: false        #  需要配置 jpa.hibernate.ddl-auto 为 none. 否则 flyway.enabled 配置会无效, 在没有数库连接情况下程序无法启动
+
+# swagger3
+swagger:
+  enable: true
+
+# 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: 2356105659
+  appKey: dinghov5fyfjlhzk1ej1
+  appSecret: QR_1_7D0UUYtfdtMlDlmnjNSkvU2Uo3gOyabo2h09rmvj5_x0uk0Lw7N9pCy7sf-
+  corpId: dinge281bf50c488ebfd35c2f4657eb6378f
+  aesKey:
+  token:
+  operator: ""   # OA管理员账号
+
+# aliwork
+aliwork:
+  appType: APP_FKRK7Y94DPI1S9DV1605
+  systemToken: FN7666A1ZD0STZZ75W4CKD1GD07X3PUW2FBRKT

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

@@ -0,0 +1,31 @@
+# 环境配置
+server:
+  port: 9004
+
+spring:
+  # database
+  datasource:
+    driver-class-name: com.mysql.cj.jdbc.Driver
+    username: root
+    password: cp-root@2022++
+    url: jdbc:mysql://47.97.181.40:3306/mjava?serverTimezone=Asia/Shanghai&useUnicode=yes&characterEncoding=UTF-8&useSSL=true
+  jpa:
+    database: MYSQL
+    database-platform: org.hibernate.dialect.MySQL57Dialect
+  flyway:
+    enabled: false      #  需要配置 jpa.hibernate.ddl-auto 为 none. 否则 flyway.enabled 配置会无效, 在没有数库连接情况下程序无法启动
+
+# dingtalk
+dingtalk:
+  agentId: 2356105659
+  appKey: dinghov5fyfjlhzk1ej1
+  appSecret: QR_1_7D0UUYtfdtMlDlmnjNSkvU2Uo3gOyabo2h09rmvj5_x0uk0Lw7N9pCy7sf-
+  corpId: dinge281bf50c488ebfd35c2f4657eb6378f
+  aesKey:
+  token:
+  operator: ""   # OA管理员账号
+
+# aliwork
+aliwork:
+  appType: APP_FKRK7Y94DPI1S9DV1605
+  systemToken: FN7666A1ZD0STZZ75W4CKD1GD07X3PUW2FBRKT

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

@@ -0,0 +1,35 @@
+#!/bin/bash
+appname='mjava-guyuan'
+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

+ 5 - 0
mjava-guyuan/target/maven-archiver/pom.properties

@@ -0,0 +1,5 @@
+#Generated by Maven
+#Mon Apr 17 17:19:53 CST 2023
+version=1.0-SNAPSHOT
+groupId=com.malk
+artifactId=mjava-guyuan

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


+ 2 - 0
mjava-guyuan/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst

@@ -0,0 +1,2 @@
+/Users/malk/server/java-mcli/mjava-guyuan/src/main/java/com/malk/guyuan/controller/GuYuanController.java
+/Users/malk/server/java-mcli/mjava-guyuan/src/main/java/com/malk/guyuan/Boot.java

BIN
mjava-guyuan/target/mjava-guyuan.jar.original


+ 77 - 0
mjava-minjiaoyuan/pom.xml

@@ -0,0 +1,77 @@
+<?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-minjiaoyuan</artifactId>
+    <description>闵教院宜搭对接服务</description>
+
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+    </properties>
+
+    <dependencies>
+        <!-- 阿里钉钉 & 宜搭依赖 -->
+        <dependency>
+            <groupId>com.taobao.top</groupId>
+            <artifactId>top-api-sdk-dev</artifactId>
+            <version>dingtalk-SNAPSHOT</version>
+            <scope>system</scope>
+            <systemPath>${pom.basedir}/lib/taobao-sdk-java-auto_1479188381469-20191205.jar</systemPath>
+        </dependency>
+        <dependency>
+            <groupId>com.taobao.top</groupId>
+            <artifactId>lippi-oapi-encrpt</artifactId>
+            <version>dingtalk-SNAPSHOT</version>
+            <scope>system</scope>
+            <systemPath>${pom.basedir}/lib/lippi-oapi-encrpt.jar</systemPath>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba.platform.shared</groupId>
+            <artifactId>xxpt.gateway.shared.client</artifactId>
+            <version>1.1.0</version>
+            <scope>system</scope>
+            <systemPath>${pom.basedir}/lib/xxpt.gateway.shared.client-1.1.0.jar</systemPath>
+        </dependency>
+
+        <!-- 核心模块-->
+        <dependency>
+            <groupId>com.malk</groupId>
+            <artifactId>mjava</artifactId>
+            <version>1.0-SNAPSHOT</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-minjiaoyuan/src/main/java/com/malk/minjiaoyuan/Boot.java

@@ -0,0 +1,32 @@
+package com.malk.minjiaoyuan;
+
+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. 标识对表没有任何操作. 若不设置为 non, 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);
+    }
+}

+ 77 - 0
mjava-minjiaoyuan/src/main/java/com/malk/minjiaoyuan/controller/MJYController.java

@@ -0,0 +1,77 @@
+package com.malk.minjiaoyuan.controller;
+
+import com.alibaba.fastjson.JSON;
+import com.malk.server.aliwork.YDConf;
+import com.malk.server.aliwork.YDParam;
+import com.malk.server.common.McR;
+import com.malk.service.aliwork.YDClient;
+import com.malk.service.aliwork.YDService;
+import com.malk.service.dingtalk.DDClient;
+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.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 错误抛出与拦截详见CatchException
+ */
+@Slf4j
+@RestController
+@RequestMapping("/minjiaoyuan")
+public class MJYController {
+
+    @Autowired
+    private DDClient ddClient;
+
+    @Autowired
+    private YDClient ydClient;
+
+    @Autowired
+    private YDService ydService;
+
+    public static final String APP_TYPE = "APP_GHMWTMHNZFEX24QOUC63";
+    public static final String SYSTEM_TOKEN = "4A9667B1NXM9XUSBDNR208ZMA2UQ20A99XEGLIC";
+
+    /**
+     * 同步名额数据
+     */
+    @PostMapping("sync/quota")
+    McR syncQuota() {
+
+        log.info("###### 同步名额数据 #####");
+        Map condition = new HashMap();
+        YDParam ydParam = YDParam.builder()
+                .appType(APP_TYPE)
+                .systemToken(SYSTEM_TOKEN)
+                .formUuid("FORM-IS866C91G3U9U0YPD9NJV4E1F2OR3I85FYEGL7")
+                .searchFieldJson(JSON.toJSONString(condition))
+                .currentPage(1)
+                .pageSize(100)
+                .build();
+        List<Map> list = new ArrayList();
+        ydService.queryLimitData(YDConf.API_FORM_QUERY_DATA, ydParam, YDService.INVOKE_TYPE.AWAIT_CONCURRENCE, list::addAll);
+
+        log.info("教师档案数据, {}", list.size());
+
+//        return McR.success(list.stream().map(item -> {
+//            Map record = new HashMap();
+//            record.put("name", ((Map) item.get("formData")).get("textField_lg62a86d"));
+//            record.put("code", ((Map) item.get("formData")).get("textField_lg62ksb8"));
+//            record.put("area", ((Map) item.get("formData")).get("textField_lg62a86e"));
+//            return record;
+//        }).collect(Collectors.toList()));
+        return McR.success();
+    }
+
+    @PostMapping("test")
+    McR test() {
+
+        return McR.success();
+    }
+}

+ 58 - 0
mjava-minjiaoyuan/src/main/resources/application-dev.yml

@@ -0,0 +1,58 @@
+# 环境配置
+server:
+  port: 9001
+  servlet:
+    context-path: /api
+
+# condition
+spel:
+  scheduling: false        # 定时任务是否执行
+  multiSource: false       # 是否多数据源配置
+
+spring:
+  # database
+  datasource:
+    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:
+    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
+
+# ekuaibao
+ekuaibao:
+  corpId: ID01jaBqslI9LV                  # 易快报的 corpId
+  platformApi: https://dd2.ekuaibao.com   # 易快报不同平台获取前缀的方式不同
+
+# dingtalk
+dingtalk:
+  agentId: 2549119888
+  appKey: dingbtomc2ifyftmqqc3
+  appSecret: jBWAtg9wNBBTu6Ef50sSKzLkVyOIlVoJ4gJTEgAZklUDOc6VZFSdonmkr7wkOyal
+  corpId: ding321c72787fffc78b35c2f4657eb6378f
+  aesKey:
+  token:
+  operator: "095358016629044412"   # 牧语[开头需要转一下字符串], OA管理员账号
+
+# aliwork
+aliwork:
+  appType: APP_GTK6SIE4MNVEEVPJLM7Z
+  systemToken: IA766O61FNR42YWEEEKTB5QAEEP827UYE3M9LK11
+
+

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

@@ -0,0 +1,33 @@
+# 环境配置
+server:
+  port: 9006
+
+spring:
+  # database
+  datasource:
+    driver-class-name: com.mysql.cj.jdbc.Driver
+    username: root
+    password: very_strong_password
+    url: jdbc:mysql://127.0.0.1:33061/mjava?serverTimezone=Asia/Shanghai&useUnicode=yes&characterEncoding=UTF-8&useSSL=true
+  jpa:
+    database: MYSQL
+    database-platform: org.hibernate.dialect.MySQL57Dialect
+
+# ekuaibao
+ekuaibao:
+  corpId: -yQbjbywbc640011                # 易快报的 corpId
+  platformApi: https://app.ekuaibao.com   # 易快报不同平台获取前缀的方式不同
+
+# dingtalk
+dingtalk:
+  agentId: 2546910043
+  appKey: ding9hj6uw741c0ql396
+  appSecret: 1qavoKpMOwtVDSJ8RZv2vCVYA7x1cm4PpXztHipdUnFm-74xQ3fzpyhGMA4Mqfd_
+  corpId: dingea1dc77298d50115ee0f45d8e4f7c288
+  aesKey:
+  token:
+
+# aliwork
+aliwork:
+  appType: APP_SZMBX0R7WC68M19JCJKG
+  systemToken: 2G766HA16IE97NYK6KTEL94YDW422EZNENRFLD3

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

@@ -0,0 +1,35 @@
+#!/bin/bash
+appname='mjava-rongzhi'
+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

+ 77 - 0
mjava-rongzhi/pom.xml

@@ -0,0 +1,77 @@
+<?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-rongzhi</artifactId>
+    <description>榕智钉钉考勤数据同步至易快报档案</description>
+
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+    </properties>
+
+    <dependencies>
+        <!-- 阿里钉钉 & 宜搭依赖 -->
+        <dependency>
+            <groupId>com.taobao.top</groupId>
+            <artifactId>top-api-sdk-dev</artifactId>
+            <version>dingtalk-SNAPSHOT</version>
+            <scope>system</scope>
+            <systemPath>${pom.basedir}/lib/taobao-sdk-java-auto_1479188381469-20191205.jar</systemPath>
+        </dependency>
+        <dependency>
+            <groupId>com.taobao.top</groupId>
+            <artifactId>lippi-oapi-encrpt</artifactId>
+            <version>dingtalk-SNAPSHOT</version>
+            <scope>system</scope>
+            <systemPath>${pom.basedir}/lib/lippi-oapi-encrpt.jar</systemPath>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba.platform.shared</groupId>
+            <artifactId>xxpt.gateway.shared.client</artifactId>
+            <version>1.1.0</version>
+            <scope>system</scope>
+            <systemPath>${pom.basedir}/lib/xxpt.gateway.shared.client-1.1.0.jar</systemPath>
+        </dependency>
+
+        <!-- 核心模块-->
+        <dependency>
+            <groupId>com.malk</groupId>
+            <artifactId>mjava</artifactId>
+            <version>1.0-SNAPSHOT</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-rongzhi/src/main/java/com/malk/rongzhi/Boot.java

@@ -0,0 +1,32 @@
+package com.malk.rongzhi;
+
+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. 标识对表没有任何操作. 若不设置为 non, 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);
+    }
+}

+ 99 - 0
mjava-rongzhi/src/main/java/com/malk/rongzhi/controller/RongZhiController.java

@@ -0,0 +1,99 @@
+package com.malk.rongzhi;
+
+import com.malk.Util.UtilHttp;
+import com.malk.Util.UtilMap;
+import com.malk.server.common.McException;
+import com.malk.server.common.McR;
+import com.malk.server.common.VenR;
+import com.malk.server.dingtalk.DDConf;
+import com.malk.service.dingtalk.DDClient_Contacts;
+import lombok.Data;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.codec.digest.DigestUtils;
+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 java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 错误抛出与拦截详见CatchException
+ */
+@Slf4j
+@RestController
+@RequestMapping("/rongzhi")
+public class RongZhiController {
+
+    // 请求类型枚举, 避用条件判定
+    private enum TYPE {
+        dingtalk("/dingding"),
+        ekuaibao("/ekuaibao");
+
+        @Getter
+        private String path;
+
+        TYPE(String path) {
+            this.path = path;
+        }
+    }
+
+    /// 避免内部类和嵌套类, 不能做序列化, 内部类添加static关键字
+    @Data
+    private static class RZR extends VenR {
+        private String err_code;
+        private String message;
+        private String data;
+
+        // 成功状态标记
+        private final static String SUC_CODE = "0";
+
+        /**
+         * 断言错误信息
+         */
+        @Override
+        public void assertSuccess() {
+            McException.assertException(!err_code.equals(SUC_CODE), err_code, message, "MC-RZ");
+        }
+    }
+
+    private static String nonce = "39350e21-d28c-423d-ae1c-97d9fdac6a60";
+
+    private String getAccessToken(TYPE type) {
+        String timestamp = String.valueOf(new Date().getTime());
+        String signature = DigestUtils.md5Hex("Max" + timestamp + nonce);
+        Map header = new HashMap();
+        header.put("timestamp", timestamp);
+        header.put("nonce", nonce);
+        header.put("signature", signature);
+        RZR rsp = (RZR) UtilHttp.doGet("https://one-api.max-digital.cn/v1/token" + type.path, header, null, RZR.class);
+        return rsp.data;
+    }
+
+
+    @Autowired
+    private DDClient_Contacts ddClientImpl_contacts;
+
+    /**
+     * 阿里发票验真
+     */
+    @PostMapping("/invoice/check")
+    McR invoiceCheckPDF(@RequestBody Map<String, String> param) {
+
+        ddClientImpl_contacts.listSubDepartmentId(UtilMap.map("access_token", getAccessToken(TYPE.dingtalk)), UtilMap.map("dept_id", DDConf.TOP_DEPARTMENT)).forEach(deptId -> {
+            
+
+            List<String> userIds = ddClientImpl_contacts.listDepartmentUserId(UtilMap.map("access_token", getAccessToken(TYPE.dingtalk)), UtilMap.map("dept_id", DDConf.TOP_DEPARTMENT));
+            log.info("xxxx, {}, {}", userIds.size(), userIds);
+        });
+
+
+        return McR.success(UtilMap.map("q, b, c", getAccessToken(TYPE.dingtalk), "xx", "xxxxx"));
+    }
+
+}

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

@@ -0,0 +1,47 @@
+# 环境配置
+server:
+  port: 9001
+  servlet:
+    context-path: /api
+
+# condition
+spel:
+  scheduling: false        # 定时任务是否执行
+  multiSource: false       # 是否多数据源配置
+
+spring:
+  # database
+  datasource:
+    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:
+    hibernate:
+      ddl-auto: none      # JPA对表没有任何操作
+    show-sql: true
+    database: MYSQL
+    database-platform: org.hibernate.dialect.MySQL57Dialect
+  flyway:
+    enabled: false        #  需要配置 jpa.hibernate.ddl-auto 为 none. 否则 flyway.enabled 配置会无效, 在没有数库连接情况下程序无法启动
+
+# swagger3
+swagger:
+  enable: true
+
+# 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
+
+# ekuaibao
+ekuaibao:
+  corpId: -yQbjbywbc640011                # 易快报的 corpId
+  platformApi: https://app.ekuaibao.com   # 易快报不同平台获取前缀的方式不同

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

@@ -0,0 +1,21 @@
+# 环境配置
+server:
+  port: 9004
+
+spring:
+  # database
+  datasource:
+    driver-class-name: com.mysql.cj.jdbc.Driver
+    username: root
+    password: cp-root@2022++
+    url: jdbc:mysql://47.97.181.40:3306/mjava?serverTimezone=Asia/Shanghai&useUnicode=yes&characterEncoding=UTF-8&useSSL=true
+  jpa:
+    database: MYSQL
+    database-platform: org.hibernate.dialect.MySQL57Dialect
+  flyway:
+    enabled: false      #  需要配置 jpa.hibernate.ddl-auto 为 none. 否则 flyway.enabled 配置会无效, 在没有数库连接情况下程序无法启动
+
+# ekuaibao
+ekuaibao:
+  corpId: -yQbjbywbc640011                # 易快报的 corpId
+  platformApi: https://app.ekuaibao.com   # 易快报不同平台获取前缀的方式不同

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

@@ -0,0 +1,35 @@
+#!/bin/bash
+appname='mjava-rongzhi'
+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

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


+ 2 - 0
mjava-rongzhi/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst

@@ -0,0 +1,2 @@
+/Users/malk/server/java-mcli/mjava-rongzhi/src/main/java/com/malk/rongzhi/controller/RongZhiController.java
+/Users/malk/server/java-mcli/mjava-rongzhi/src/main/java/com/malk/rongzhi/Boot.java

+ 77 - 0
mjava-xinwei/pom.xml

@@ -0,0 +1,77 @@
+<?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-xinwei</artifactId>
+    <description>心玮假期校验限制使用优先级连接器: 调休 - 年假 - 企业福利假</description>
+
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+    </properties>
+
+    <dependencies>
+        <!-- 阿里钉钉 & 宜搭依赖 -->
+        <dependency>
+            <groupId>com.taobao.top</groupId>
+            <artifactId>top-api-sdk-dev</artifactId>
+            <version>dingtalk-SNAPSHOT</version>
+            <scope>system</scope>
+            <systemPath>${pom.basedir}/lib/taobao-sdk-java-auto_1479188381469-20191205.jar</systemPath>
+        </dependency>
+        <dependency>
+            <groupId>com.taobao.top</groupId>
+            <artifactId>lippi-oapi-encrpt</artifactId>
+            <version>dingtalk-SNAPSHOT</version>
+            <scope>system</scope>
+            <systemPath>${pom.basedir}/lib/lippi-oapi-encrpt.jar</systemPath>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba.platform.shared</groupId>
+            <artifactId>xxpt.gateway.shared.client</artifactId>
+            <version>1.1.0</version>
+            <scope>system</scope>
+            <systemPath>${pom.basedir}/lib/xxpt.gateway.shared.client-1.1.0.jar</systemPath>
+        </dependency>
+
+        <!-- 核心模块-->
+        <dependency>
+            <groupId>com.malk</groupId>
+            <artifactId>mjava</artifactId>
+            <version>1.0-SNAPSHOT</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-xinwei/src/main/java/com/malk/xinwei/Boot.java

@@ -0,0 +1,32 @@
+package com.malk.xinwei;
+
+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. 标识对表没有任何操作. 若不设置为 non, 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);
+    }
+}

+ 55 - 0
mjava-xinwei/src/main/java/com/malk/xinwei/controller/ChuoQiController.java

@@ -0,0 +1,55 @@
+package com.malk.xinwei.controller;
+
+import com.alibaba.fastjson.JSON;
+import com.malk.Util.UtilServlet;
+import com.malk.server.common.McException;
+import com.malk.server.common.McR;
+import com.malk.service.dingtalk.DDClient;
+import com.malk.service.dingtalk.DDService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.EnableAsync;
+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 java.util.List;
+import java.util.Map;
+
+/**
+ * 错误抛出与拦截详见 CatchException
+ */
+@Slf4j
+@RestController
+@RequestMapping("/chuoqi")
+@EnableAsync
+public class ChuoQiController {
+
+    @Autowired
+    private DDClient ddClient;
+
+    @Autowired
+    private DDService ddService;
+
+    /**** 绰琪服务 ****/
+
+    @PostMapping("validate")
+    public McR validateLeave(@RequestBody Map<String, String> param) {
+        McException.assertParamException_Null(UtilServlet.isNull(param, "processInstanceId", "userId"));
+        Map instance = ddClient.queryProcessInstanceId(param.get("processInstanceId"));
+        List<Map> compList = (List<Map>) instance.get("form_component_values");
+        String type = String.valueOf(compList.stream().filter(item -> item.get("component_type").equals("DDHolidayField")).findAny().get().get("value"));
+        String dept = String.valueOf(instance.get("originator_dept_name"));
+        log.info("余额校验请求入参: {}, {}, {}", param, type, dept);
+        // 调休的小时数只能是4的倍数
+        if (type.contains("调休") && !dept.contains("河源")) {
+            float budget = Float.valueOf(String.valueOf(JSON.parseArray(type).get(2)));
+            if (budget % 4 != 0) {
+                log.info("撤销请假, {}", instance);
+                ddService.terminateRunningApprove_async(param.get("processInstanceId"), true, "调休必须以4小时为最小单位", "", param.get("userId"));
+            }
+        }
+        return McR.success();
+    }
+}

+ 155 - 0
mjava-xinwei/src/main/java/com/malk/xinwei/controller/LiChenController.java

@@ -0,0 +1,155 @@
+package com.malk.xinwei.controller;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.alibaba.fastjson.JSON;
+import com.malk.Util.UtilMap;
+import com.malk.server.aliwork.YDConf;
+import com.malk.server.aliwork.YDParam;
+import com.malk.server.common.McR;
+import com.malk.service.aliwork.YDClient;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 错误抛出与拦截详见 CatchException
+ */
+@Slf4j
+@RestController
+@RequestMapping("/chuoqi")
+@EnableAsync
+public class LiChenController {
+
+    /**** 力辰服务 ****/
+
+    @Autowired
+    private YDClient ydClient;
+
+    /**
+     * 查询实例详情: 力辰查询实例详情接口
+     */
+    @GetMapping("lc/form/{formInstId}")
+    McR getInstanceById(@PathVariable String formInstId) {
+
+        YDParam ydParam = YDParam.builder()
+                .appType("APP_BV3K6QNMINKYEDHQNI4N")
+                .systemToken("CH766981WHM8OEVBEAKCY7MYB9SV3ORPLIQELS5")
+                .formInstId(formInstId)
+                .build();
+        return McR.success(ydClient.operateData(YDConf.API_FORM_DETAIL, ydParam));
+    }
+
+    /**
+     * 写入往来单位
+     */
+    @SneakyThrows
+    @PostMapping("lc/form/wldx")
+    McR updateWangLaiDanWei(@RequestBody Map data) {
+
+        log.info("写入往来单位, {}", data);
+
+        List<Map> list = (List<Map>) data.get("data");
+        for (Map record : list) {
+            Map searchMap = UtilMap.map("textField_lgpz4fmf", record.get("FNumber"));
+            YDParam ydParam = YDParam.builder()
+                    .appType("APP_BV3K6QNMINKYEDHQNI4N")
+                    .systemToken("CH766981WHM8OEVBEAKCY7MYB9SV3ORPLIQELS5")
+                    .formUuid("FORM-A8666NA1404AITYX7V2NI9VHO2TK3TYOVYPGL0")
+                    .searchFieldJson(JSON.toJSONString(searchMap))
+                    .build();
+            Map rsp = ydClient.queryData(YDConf.API_FORM_QUERY_ID, ydParam);
+            if (ObjectUtil.isNotNull(rsp) && Integer.valueOf(String.valueOf((rsp.get("totalCount")))) == 0) {
+                ydParam.formDataJson = JSON.toJSONString(UtilMap.map("textField_lgpz6rn1, textField_lgpz4fmf", record.get("FName"), record.get("FNumber"))); // 往来名称:textField_lgpz6rn1; 往来编码:textField_lgpz4fmf
+                ydClient.operateDataByConcurrence(YDConf.API_FORM_CREATE, ydParam);
+            }
+            Thread.sleep(500);
+        }
+        return McR.success();
+    }
+
+    /**
+     * 写入付款申请
+     */
+    @SneakyThrows
+    @PostMapping("lc/form/fksq")
+    McR updateFuKuanShenQing(@RequestBody Map data) {
+
+        log.info("写入付款申请, {}", data);
+
+        List<Map> list = (List<Map>) data.get("data");
+        for (Map record : list) {
+            String billNo = String.valueOf(record.get("FBillNo"));
+            if (billNo.contains("FKSQ")) {
+                Map searchMap = UtilMap.map("textField_lgov75ms", billNo);
+                YDParam ydParam = YDParam.builder()
+                        .appType("APP_BV3K6QNMINKYEDHQNI4N")
+                        .systemToken("CH766981WHM8OEVBEAKCY7MYB9SV3ORPLIQELS5")
+                        .formUuid("FORM-4V966QC142J8WYDD9PO74DD2QOME3ZG0MIQELK1")
+                        .searchFieldJson(JSON.toJSONString(searchMap))
+                        .build();
+                Map rsp = ydClient.queryData(YDConf.API_FORM_QUERY_ID, ydParam);
+                if (ObjectUtil.isNotNull(rsp) && Integer.valueOf(String.valueOf((rsp.get("totalCount")))) == 0) {
+                    Map formData = UtilMap.map("textField_lgov75ms, radioField_lguqzv36", billNo, "是");
+                    formData.put("textField_lglwgnl7", record.get("FCREATORID"));
+                    formData.put("textField_lguqqubv", record.get("FCREATORNAME"));
+                    formData.put("", record.get("FPURCHASEORGID"));
+                    formData.put("dateField_leqinlqd", record.get("FDATE"));
+                    formData.put("", record.get("FBILLTYPEID"));
+                    formData.put("selectField_lgpzoio6", record.get("FCONTACTUNITTYPE"));
+                    formData.put("selectField_lexydxa5", record.get("FCONTACTUNIT"));
+                    formData.put("", record.get("FBUSINESSTYPE"));
+                    formData.put("textField_lewk1q3x", record.get("FPAYORGID"));
+                    formData.put("", record.get("F_BQB_Combo"));
+                    formData.put("radioField_leqinlq7", record.get("F_BQB_FKDQ"));
+                    List<Map> details = new ArrayList<>();
+                    List<Map> dataTable = (List<Map>) JSON.parse(String.valueOf(record.get("DataTable")));
+                    for (Map detail : dataTable) {
+                        Map rowTable = UtilMap.map("selectField_lewl78sf", detail.get("FSETTLETYPEID"));
+                        rowTable.put("textField_lfw71laq", detail.get("FEACHBANKACCOUNT"));
+                        rowTable.put("numberField_leqinlqa", detail.get("FAPPLYAMOUNTFOR"));
+                        rowTable.put("dateField_lewl78sh", detail.get("FENDDATE"));
+                        rowTable.put("dateField_lewl78sg", detail.get("FEXPECTPAYDATE"));
+                        rowTable.put("textField_lfw71lar", detail.get("FEACHCCOUNTNAME"));
+                        rowTable.put("textField_lfw71las", detail.get("FEACHBANKNAME"));
+                        rowTable.put("textField_leqinlq9", detail.get("FCOSTID"));
+                        details.add(rowTable);
+                    }
+                    formData.put("tableField_leqinlq8", details);
+                    ydParam.formDataJson = JSON.toJSONString(formData);
+                    ydParam.setProcessCode("TPROC--4V966QC142J8WYDD9PO74DD2QOME30H0MIQELL1");
+                    ydClient.operateDataByConcurrence(YDConf.API_PROCESS_CREATE, ydParam);
+                }
+                /**
+                 *   创建人编码:FCREATORID						宜搭:textField_lglwgnl7
+                 *   创建人姓名:FCREATORNAME					    宜搭:textField_lguqqubv
+                 * 	 采购组织:FPURCHASEORGID 				    宜搭:
+                 * 	 申请日期:FDATE							    宜搭:dateField_leqinlqd
+                 * 	 单据类型:FBILLTYPEID						宜搭:
+                 * 	 往来单位类型:FCONTACTUNITTYPE  		        宜搭:selectField_lgpzoio6
+                 * 	 往来单位:FCONTACTUNIT  				        宜搭:selectField_lexydxa5
+                 * 	 业务类型:FBUSINESSTYPE 					    宜搭:
+                 * 	 付款组织:FPAYORGID  					    宜搭:textField_lewk1q3x
+                 * 	 付款类型:F_BQB_Combo 					    宜搭:
+                 * 	 付款地区:F_BQB_FKDQ  (必填项)			    宜搭:radioField_leqinlq7
+                 *   付款申请单明细:DataTable
+                 * 	    结算方式:FSETTLETYPEID 					宜搭:selectField_lewl78sf
+                 * 	    对方银行账号:FEACHBANKACCOUNT 		    宜搭:textField_lfw71laq
+                 * 	    申请付款金额:FAPPLYAMOUNTFOR 			    宜搭:numberField_leqinlqa
+                 * 	    到期日:FENDDATE  						宜搭:dateField_lewl78sh
+                 * 	    期望付款日期:FEXPECTPAYDATE  			    宜搭:dateField_lewl78sg
+                 * 	    对方账户名称:FEACHCCOUNTNAME 	        	宜搭:textField_lfw71lar
+                 * 	    对方开户行:FEACHBANKNAME 				宜搭:textField_lfw71las
+                 * 	    费用项目:FCOSTID 						宜搭:textField_leqinlq9
+                 */
+                Thread.sleep(500);
+            }
+        }
+        return McR.success();
+    }
+}

+ 128 - 0
mjava-xinwei/src/main/java/com/malk/xinwei/controller/ShiShangController.java

@@ -0,0 +1,128 @@
+package com.malk.xinwei.controller;
+
+import com.alibaba.fastjson.JSON;
+import com.malk.Util.UtilServlet;
+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.aliyun.ALYInvoice;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.EnableAsync;
+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 java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * 错误抛出与拦截详见 CatchException
+ */
+@Slf4j
+@RestController
+@RequestMapping("/chuoqi")
+@EnableAsync
+public class ShiShangController {
+
+    @Autowired
+    private ALYInvoice invoice;
+
+    @Autowired
+    private YDClient ydClient;
+
+    /**** 拾上服务 ****/
+
+    /**
+     * 阿里发票验真
+     */
+    @PostMapping("invoice/check")
+    McR invoiceCheckPDF(@RequestBody Map<String, String> param) {
+        log.info("阿里发票验真, 文件, {}", param);
+        return McR.success(invoice.invoiceCheckPDF("3011b2ee820744679081f314d983b6bd", param.get("url")));
+    }
+
+    /**
+     * 阿里发票验真: 参数
+     */
+    @PostMapping("invoice/verify")
+    McR invoiceCheckParam(@RequestBody Map<String, String> param) {
+        log.info("阿里发票验真, 参数, {}", param);
+        String kprq = param.get("kprq").replace("年", "").replace("月", "").replace("日", "").replace("-", "").replace("/", "");
+        return McR.success(invoice.invoiceCheckCode("3011b2ee820744679081f314d983b6bd", param.get("fpdm"), param.get("fphm"), kprq, param.get("noTaxAmount"), param.get("checkCode")));
+    }
+
+    /**
+     * 阿里发票验真: image
+     */
+    @PostMapping("invoice/ocr")
+    McR invoiceCheckImage(@RequestBody Map<String, String> param) {
+        log.info("阿里发票验真, 图片, {}", param);
+        return McR.success(invoice.invoiceCheckOCR("3011b2ee820744679081f314d983b6bd", param.get("url")));
+    }
+
+    /**
+     * 退回重新提交明细处理
+     */
+    @PostMapping("invoice/return")
+    McR invoiceSendBack(@RequestBody Map param) {
+        log.info("退回重新提交明细处理, {}", param);
+        McException.assertParamException_Null(UtilServlet.isNull(param, "pre_ids", "pre_update", "cur_ids", "cur_update"));
+        YDParam ydParam = YDParam.builder()
+                .appType("APP_NRRPLFQ8GEEURWIFNYB0")
+                .systemToken("XJ866N71SJZ1OZ7WBUIL7C9SXLBO2QO4JDG5LDO")
+                .build();
+        List<String> pre_ids = (List<String>) param.get("pre_ids");
+        List<String> cur_ids = (List<String>) param.get("cur_ids");
+        // 释放状态实例Id
+        List free_ids = pre_ids.stream().filter(t -> !cur_ids.contains(t)).collect(Collectors.toList());
+        log.info("退回实例 {}, 占用实例, {}", free_ids, cur_ids);
+        free_ids.forEach(id -> {
+            ydParam.setFormInstId(String.valueOf(id));
+            ydParam.setUpdateFormDataJson(JSON.toJSONString(param.get("pre_update")));
+            ydClient.operateData(YDConf.API_FORM_UPDATE, ydParam);
+        });
+        // 占用选中实例Id
+        cur_ids.forEach(id -> {
+            ydParam.setFormInstId(String.valueOf(id));
+            ydParam.setUpdateFormDataJson(JSON.toJSONString(param.get("cur_update")));
+            ydClient.operateData(YDConf.API_FORM_UPDATE, ydParam);
+        });
+        return McR.success();
+    }
+
+    /**
+     * 宜搭临时免登地址: 宜搭版本
+     */
+    @PostMapping("yd/openUrl")
+    McR openUrl_YD(@RequestBody Map<String, String> param) {
+        McException.assertParamException_Null(param, "url");
+        YDParam ydParam = YDParam.builder()
+                .appType("APP_NRRPLFQ8GEEURWIFNYB0")
+                .systemToken("XJ866N71SJZ1OZ7WBUIL7C9SXLBO2QO4JDG5LDO")
+                .fileUrl(param.get("url"))
+                .build();
+        return McR.success(ydClient.queryData(YDConf.API_OPEN_URL, ydParam));
+    }
+
+    @PostMapping("/portal")
+    List<Map> portal() {
+
+        List<Map> list = new ArrayList<>();
+        Map item = new HashMap();
+        item.put("image", "https://gw.alicdn.com/tfs/TB1fcfbsY9YBuNjy0FgXXcxcXXa-1200-675.jpg");
+        item.put("title", "Arralis要做下一代毫米波雷达");
+        item.put("dateTime", "2018-11-01 12:45");
+        item.put("source", "'36Kr'");
+        item.put("source", "'https://www.baidu.com'");
+        list.add(item);
+
+        return list;
+    }
+}

+ 143 - 0
mjava-xinwei/src/main/java/com/malk/xinwei/controller/SuoDiSiController.java

@@ -0,0 +1,143 @@
+package com.malk.xinwei.controller;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.alibaba.fastjson.JSON;
+import com.malk.Util.UtilServlet;
+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 lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.EnableAsync;
+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.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * 错误抛出与拦截详见 CatchException
+ */
+@Slf4j
+@RestController
+@RequestMapping("/chuoqi")
+@EnableAsync
+public class SuoDiSiController {
+
+    /**** 索迪斯服务 ****/
+
+    @Autowired
+    private YDClient ydClient;
+
+    /**
+     * 动态审批人
+     */
+    @PostMapping("sds/test")
+    List<String> sdsTest(HttpServletRequest request) {
+        Map param = UtilServlet.getParamMap(request);
+        McException.assertParamException_Null(param, "finance");
+        Map condition = new HashMap();
+        condition.put("textField_lg52q035", param.get("finance"));
+        YDParam ydParam = YDParam.builder()
+                .appType("APP_Y75CEY3YR30YOLEWETJJ")
+                .systemToken("BY866R813JM8XVLGD7YELCPDH9IL29Z5BQQEL48")
+                .formUuid("FORM-8Y866XB1AXM9DFV3AW6D7AV2WWEK2F3OP25GLD")
+                .searchFieldJson(JSON.toJSONString(condition))
+                .build();
+        List<Map> list = (List<Map>) ydClient.queryData(YDConf.API_FORM_QUERY_DATA, ydParam).get("data");
+        Map formData = (Map) list.get(0).get("formData");
+        log.info("索迪斯动态审批人, {}, {}", param, formData);
+
+        return (List<String>) formData.get("employeeField_lg52q036_id");
+    }
+
+    /**
+     * 回写招聘子表
+     */
+    @PostMapping("sds/update")
+    Map sdsUpdate(HttpServletRequest request) {
+
+        // 查询招聘申请
+        Map param = UtilServlet.getParamMap(request);
+        McException.assertParamException_Null(param, "formInstId");
+        YDParam ydParam = YDParam.builder()
+                .appType("APP_Y75CEY3YR30YOLEWETJJ")
+                .systemToken("BY866R813JM8XVLGD7YELCPDH9IL29Z5BQQEL48")
+                .formInstId(String.valueOf(param.get("formInstId")))
+                .build();
+        Map rsp = (Map) ydClient.operateData(YDConf.API_FORM_DETAIL, ydParam);
+        Map formData = (Map) rsp.get("formData");
+        log.info("索迪斯回写子表, {}, {}", param, formData);
+
+        // 组装分发子表数据
+        Map detail = new HashMap();
+        detail.put("associationFormField_lf7pkag4", JSON.parse(String.valueOf(formData.get("associationFormField_lg54gbt9_id")))); // 职位组
+        detail.put("textField_lf7pkag5", formData.get("textField_lg54gbta")); // 新员工姓名
+        detail.put("dateField_lf7pkag6", formData.get("dateField_lg54gbtf")); // 入职日期
+
+        // todo: 成员, 附件, 关联表单
+        List<Map> association = JSON.parseArray(String.valueOf(JSON.parse(String.valueOf(formData.get("associationFormField_lg0gmbi8_id")))), Map.class);
+        String formInstId = String.valueOf(association.get(0).get("instanceId"));
+        ydParam.setFormInstId(formInstId);
+        Map rspSrc = (Map) ydClient.operateData(YDConf.API_FORM_DETAIL, ydParam);
+        Map formDataSrc = (Map) rspSrc.get("formData");
+        List<Map> list = new ArrayList<>();
+        if (ObjectUtil.isNotNull(formDataSrc.get("tableField_lf7pkag3"))) {
+            list = (List<Map>) formDataSrc.get("tableField_lf7pkag3");
+        }
+
+        // 更新流程数据
+        Map form = new HashMap();
+        list.add(detail);
+        form.put("tableField_lf7pkag3", list);
+        form.put("numberField_lfj9d501", 2);
+        ydParam.setUpdateFormDataJson(JSON.toJSONString(form));
+        ydClient.operateData(YDConf.API_FORM_UPDATE, ydParam);
+
+        log.info("更新, {}, {}", formInstId, form);
+
+        Map r = new HashMap();
+        r.put("success", true);
+        return r;
+    }
+
+    @Autowired
+    private YDService ydService;
+
+    /**
+     * GXG基础档案
+     */
+    @PostMapping("gxg/data-list")
+    McR gxgDataList(@RequestBody Map data) {
+        Map condition = new HashMap();
+        YDParam ydParam = YDParam.builder()
+                .appType("APP_YGFK3PFENGL4E7RMLZEV")
+                .systemToken("L5666S81WTM9LWRB9POYW974EWBA3ISV5WBGLDA")
+                .formUuid("FORM-4H666KB1FVP9UEXX8UQBT7GBB5CR20UV5WBGLGJ")
+                .searchFieldJson(JSON.toJSONString(condition))
+                .currentPage(1)
+                .pageSize(100)
+                .build();
+        List<Map> list = new ArrayList();
+        ydService.queryLimitData(YDConf.API_FORM_QUERY_DATA, ydParam, YDService.INVOKE_TYPE.AWAIT_CONCURRENCE, list::addAll);
+        log.info("GXG基础档案, {}, {}", data, list.size());
+        return McR.success(list.stream().map(item -> {
+            Map record = new HashMap();
+            record.put("name", ((Map) item.get("formData")).get("textField_lg62a86d"));
+            record.put("code", ((Map) item.get("formData")).get("textField_lg62ksb8"));
+            record.put("area", ((Map) item.get("formData")).get("textField_lg62a86e"));
+            return record;
+        }).collect(Collectors.toList()));
+    }
+}
+
+

+ 113 - 0
mjava-xinwei/src/main/java/com/malk/xinwei/controller/XinWeiController.java

@@ -0,0 +1,113 @@
+package com.malk.xinwei.controller;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.alibaba.fastjson.JSON;
+import com.malk.server.common.McR;
+import com.malk.server.dingtalk.DDConf;
+import com.malk.service.aliwork.YDClient;
+import com.malk.service.dingtalk.DDClient;
+import com.malk.service.dingtalk.DDClient_New;
+import com.malk.service.dingtalk.DDService;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.*;
+
+/**
+ * 错误抛出与拦截详见 CatchException
+ */
+@Slf4j
+@RestController
+@RequestMapping("/xinwei")
+@EnableAsync
+public class XinWeiController {
+
+    @Autowired
+    private DDClient ddClient;
+
+    @Autowired
+    private DDConf ddConf;
+
+    @Autowired
+    private DDService ddService;
+
+    /**** 心玮服务 ****/
+
+    @PostMapping("validate")
+    public McR validateLeave(@RequestBody Map<String, String> param) {
+        String proId = param.get("processInstanceId");
+        List<Map> compList = (List<Map>) (ddClient.queryProcessInstanceId(proId)).get("form_component_values");
+        String type = String.valueOf(compList.stream().filter(item -> item.get("component_type").equals("DDHolidayField")).findAny().get().get("value"));
+        log.info("余额校验请求入参: {}, {}", param, type);
+        // 1.11 取消调休对于的年假限制
+        if (type.contains("企业福利假")) {
+            Map<String, String> leaveType = new HashMap();
+            ddClient.queryVacationTypeList(ddConf.getOperator(), true).forEach(item -> {
+                if (Arrays.asList("调休", "年假").contains(String.valueOf(item.get("leave_name")))) {
+                    leaveType.put(String.valueOf(item.get("leave_name")), String.valueOf(item.get("leave_code")));
+                }
+            });
+            String message = "";
+            Map nianjia = null;
+            Map tiaoxiu = null;
+            float budget = Float.valueOf(String.valueOf(JSON.parseArray(type).get(2)));
+            // 优先级: 调休 > 年假 > 企业福利假
+            tiaoxiu = ddService.queryVacationQuota_balance(ddConf.getOperator(), leaveType.get("调休"), param.get("userId"), 0, 50);
+            if (ObjectUtil.isNotNull(tiaoxiu)) {
+                float balance = (float) tiaoxiu.get("balance");
+                if (budget * 8.0f <= balance) {
+                    message = "调休剩余" + balance + tiaoxiu.get("unit");
+                }
+            }
+            if (type.contains("企业福利假") && StringUtils.isBlank(message)) {
+                nianjia = ddService.queryVacationQuota_balance(ddConf.getOperator(), leaveType.get("年假"), param.get("userId"), 0, 50);
+                if (ObjectUtil.isNotNull(nianjia)) {
+                    float balance = (float) nianjia.get("balance");
+                    if (budget <= balance) {
+                        message = "年假剩余" + balance + nianjia.get("unit");
+                    }
+                }
+            }
+            log.info("查询假期规则列表, {}, {}, {}, {}", type, message, nianjia, tiaoxiu);
+            if (StringUtils.isNotBlank(message)) {
+                ddService.terminateRunningApprove_async(proId, true, message + ", 请优先使用", "", param.get("userId"));
+            }
+        }
+        return McR.success();
+    }
+
+
+    @Autowired
+    private YDClient ydClient;
+
+    @Autowired
+    private DDClient_New ddClient_new;
+
+    @GetMapping("sync")
+    public McR sync() {
+        // 宜搭的测试
+//        ArrayList list = new ArrayList();
+//
+//        YDParam ydParam = YDParam.builder().
+//                appType("APP_BT0NLNNL1O1ZQD1JMJCQ")
+//                .systemToken("16A6629115S3L0K6C9E7Y91UV9A23HV4J008LJ8")
+//                .formUuid("FORM-IS866QA1UIC7CJIA6U0IJ95126GR21AE7SFDLG")
+//                .build();
+//        Map rsp = ydClient.queryData(YDConf.API_FORM_QUERY_DATA, ydParam);
+//
+//        ddClient.queryProcessInstanceId("cNqDrkYPQNKXIZDjUvKoEA07901671162044");
+//        ddClient.cancelRunningApprove("0953580166290444112", "cNqDrkYPQNKXIZDjUvKoEA07901671162044");
+
+//        return McR.success(rsp.get("data"));
+
+//        ddClient_new.createTBTask("395639364223285514", "调休剩余9.9天, 请优先使用", "您的请假已被系统撤销", new Date().getTime() + 16000, Arrays.asList("395639364223285514"), null, null, false, 20, null);
+        ddClient_new.createTBTask("010160171416764428", "调休剩余5.6天, 请优先使用", "您的请假已被系统撤销", new Date().getTime() + 16000, Arrays.asList("010160171416764428"), null, null, false, 20, null);
+        ddClient_new.createTBTask("16282107913674491", "调休剩余2.0天, 请优先使用", "您的请假已被系统撤销", new Date().getTime() + 16000, Arrays.asList("16282107913674491"), null, null, false, 20, null);
+        ddClient_new.createTBTask("16296825472051479", "调休剩余4.6天, 请优先使用", "您的请假已被系统撤销", new Date().getTime() + 16000, Arrays.asList("16296825472051479"), null, null, false, 20, null);
+
+        return McR.success();
+    }
+}

+ 53 - 0
mjava-xinwei/src/main/resources/application-dev.yml

@@ -0,0 +1,53 @@
+# 环境配置
+server:
+  port: 9001
+  servlet:
+    context-path: /api
+
+# condition
+spel:
+  scheduling: false        # 定时任务是否执行
+  multiSource: false       # 是否多数据源配置
+
+spring:
+  # database
+  datasource:
+    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:
+    hibernate:
+      ddl-auto: none      # JPA对表没有任何操作
+    show-sql: true
+    database: MYSQL
+    database-platform: org.hibernate.dialect.MySQL57Dialect
+  flyway:
+    enabled: false        #  需要配置 jpa.hibernate.ddl-auto 为 none. 否则 flyway.enabled 配置会无效, 在没有数库连接情况下程序无法启动
+
+# swagger3
+swagger:
+  enable: true
+
+# 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: 1963716187
+  appKey: ding8qyulwwmad6j7k6c
+  appSecret: e_hRHuubw-Xi0OuPJOYdXSElVzOC0IPgMrHQTBuAM9BqW-DFnrcsSyBHi7Me3xSv
+  corpId: ding321c72787fffc78b35c2f4657eb6378f
+  aesKey:
+  token:
+  operator: "095358016629044412"   # 牧语[开头需要转一下字符串], OA管理员账号
+

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

@@ -0,0 +1,38 @@
+# 环境配置
+server:
+  port: 9003
+
+spring:
+  # database
+  datasource:
+    driver-class-name: com.mysql.cj.jdbc.Driver
+    username: root
+    password: cp-root@2022++
+    url: jdbc:mysql://47.97.181.40:3306/mjava?serverTimezone=Asia/Shanghai&useUnicode=yes&characterEncoding=UTF-8&useSSL=true
+  jpa:
+    database: MYSQL
+    database-platform: org.hibernate.dialect.MySQL57Dialect
+  flyway:
+    enabled: false      #  需要配置 jpa.hibernate.ddl-auto 为 none. 否则 flyway.enabled 配置会无效, 在没有数库连接情况下程序无法启动
+
+#### todolist::: 心玮端口 9002
+# dingtalk
+#dingtalk:
+#  agentId: 2243326753
+#  appKey: dingc4c0vmqkwocbaqvb
+#  appSecret: f47p_iEvKlQEXsRCCU0PLU3qsA9Gmrop1TLzrU96cHDlv0oAoh22jXQpyhjQOkK_
+#  corpId: ding24d3021341c916b5ee0f45d8e4f7c288
+#  aesKey:
+#  token:
+#  operator: "2941016436628629656"   # 云璞钉钉客服, OA管理员账号
+
+#### todolist::: 绰琪端口 9003
+# dingtalk
+dingtalk:
+  agentId: 2375407526
+  appKey: dingrpxzx0v4yuowk3ax
+  appSecret: 9D21ft6akRmnbm7TVPq6zMXgDACSbJkb4mlOjZ8Io4xpfcuHEh2v1agy0C9ol7j8
+  corpId: ding850e3b881d983dd5a1320dcb25e91351
+  aesKey:
+  token:
+  operator: "013228365924237898"   # 客户管理员, 张智坤

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

@@ -0,0 +1,35 @@
+#!/bin/bash
+appname='mjava-xinwei'
+if [ "$1" == "dev" ]; then
+  java -jar $appname.jar --spring.profiles.active=dev
+else
+  if [ "$1" == "start" ]; then
+    nohup java -jar $appname.jar &
+    echo "server prod is starting"
+  else
+    if [ "$1" == "test" ]; then
+      nohup java -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

+ 5 - 0
mjava-xinwei/target/maven-archiver/pom.properties

@@ -0,0 +1,5 @@
+#Generated by Maven
+#Tue Apr 18 14:45:04 CST 2023
+version=1.0-SNAPSHOT
+groupId=com.malk
+artifactId=mjava-xinwei

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


+ 6 - 0
mjava-xinwei/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst

@@ -0,0 +1,6 @@
+/Users/malk/server/java-mcli/mjava-xinwei/src/main/java/com/malk/xinwei/Boot.java
+/Users/malk/server/java-mcli/mjava-xinwei/src/main/java/com/malk/xinwei/controller/ShiShangController.java
+/Users/malk/server/java-mcli/mjava-xinwei/src/main/java/com/malk/xinwei/controller/ChuoQiController.java
+/Users/malk/server/java-mcli/mjava-xinwei/src/main/java/com/malk/xinwei/controller/SuoDiSiController.java
+/Users/malk/server/java-mcli/mjava-xinwei/src/main/java/com/malk/xinwei/controller/XinWeiController.java
+/Users/malk/server/java-mcli/mjava-xinwei/src/main/java/com/malk/xinwei/controller/LiChenController.java

BIN
mjava-xinwei/target/mjava-xinwei.jar.original


+ 84 - 0
mjava/pom.xml

@@ -0,0 +1,84 @@
+<?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</artifactId>
+    <description>mjava framework</description>
+
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+    </properties>
+
+    <dependencies>
+        <!-- 阿里钉钉 & 宜搭依赖 -->
+        <dependency>
+            <groupId>com.taobao.top</groupId>
+            <artifactId>top-api-sdk-dev</artifactId>
+            <version>dingtalk-SNAPSHOT</version>
+            <scope>system</scope>
+            <systemPath>${pom.basedir}/lib/taobao-sdk-java-auto_1479188381469-20191205.jar</systemPath>
+        </dependency>
+        <dependency>
+            <groupId>com.taobao.top</groupId>
+            <artifactId>lippi-oapi-encrpt</artifactId>
+            <version>dingtalk-SNAPSHOT</version>
+            <scope>system</scope>
+            <systemPath>${pom.basedir}/lib/lippi-oapi-encrpt.jar</systemPath>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba.platform.shared</groupId>
+            <artifactId>xxpt.gateway.shared.client</artifactId>
+            <version>1.1.0</version>
+            <scope>system</scope>
+            <systemPath>${pom.basedir}/lib/xxpt.gateway.shared.client-1.1.0.jar</systemPath>
+        </dependency>
+
+        <!-- jsp: tomcat-embed-jasper 需要在子项目内引用 -->
+        <dependency>
+            <groupId>org.apache.tomcat.embed</groupId>
+            <artifactId>tomcat-embed-jasper</artifactId>
+        </dependency>
+
+
+        <!-- MongoDB -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-mongodb</artifactId>
+        </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 再执行 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);
+    }
+}

+ 190 - 0
mjava/src/main/java/com/malk/Filter/CatchException.java

@@ -0,0 +1,190 @@
+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.beans.factory.annotation.Autowired;
+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 ExceptionCom(McException e) {
+        log.error(e.getMessage(), e);  // 记录错误日志
+        return McR.R(e.isSuccess(), e.getCode(), e.getMessage(), null, e.getSource());  // IGNORE_EXECUTE 为成功
+    }
+
+    /**
+     * @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));
+    }
+
+    /**
+     * @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 验证入参字段 @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 关于在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());
+    }
+
+    /**
+     * @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());
+    }
+
+    /**
+     * @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("请求体参数格式不合法");
+    }
+
+    /**
+     * @RequestParam 参数类型解析异常
+     */
+    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
+    public McR MethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e) {
+        log.error(e.getMessage(), e);  // 记录错误日志
+        return McR.errorParam("param参数 ->  " + e.getName() + "不合法: " + e.getMessage());
+    }
+
+    /**
+     * 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());
+    }
+
+    /**
+     * 系统错误抛出
+     * -
+     * 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());
+    }
+}

+ 54 - 0
mjava/src/main/java/com/malk/Filter/ExceptionNotice.java

@@ -0,0 +1,54 @@
+package com.malk.Filter;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.alibaba.fastjson.JSONObject;
+import com.malk.Util.UtilDateTime;
+import com.malk.server.common.McConf;
+import com.malk.server.common.McException;
+import com.malk.server.dingtalk.DDConf;
+import com.malk.service.dingtalk.DDClient;
+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);
+    }
+}

+ 141 - 0
mjava/src/main/java/com/malk/Util/UtilConvert.java

@@ -0,0 +1,141 @@
+package com.malk.Util;
+
+import lombok.extern.slf4j.Slf4j;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * 行转列工具类
+ */
+@Slf4j
+public class UtilConvert {
+
+    private static final String NULL_VALUE = "";
+    private static final String HEADER_NULL_VALUE = "";
+    private static Set<Object> headerSet;
+    private static Set<Object> firstColSet;
+
+    public static class ConvertData {
+        private Set<Object> headerSet;
+        private Set<Object> firstColSet;
+        private List<List<Object>> dataList;
+
+        public ConvertData(List<List<Object>> dataList, Set<Object> headerSet, Set<Object> firstColSet) {
+            this.headerSet = headerSet;
+            this.firstColSet = firstColSet;
+            this.dataList = dataList;
+        }
+
+        public Set<Object> getHeaderSet() {
+            return headerSet;
+        }
+
+        public void setHeaderSet(Set<Object> headerSet) {
+            this.headerSet = headerSet;
+        }
+
+        public Set<Object> getFirstColSet() {
+            return firstColSet;
+        }
+
+        public void setFirstColSet(Set<Object> firstColSet) {
+            this.firstColSet = firstColSet;
+        }
+
+        public List<List<Object>> getDataList() {
+            return dataList;
+        }
+
+        public void setDataList(List<List<Object>> dataList) {
+            this.dataList = dataList;
+        }
+    }
+
+    /**
+     * 行转列,返回ConvertData
+     *
+     * @param orignalList   原始list
+     * @param headerName    列表头字段名
+     * @param firstColName  首列字段名
+     * @param valueFiedName 值列的字段名
+     * @param needHeader    是否需要返回列表头
+     * @return ConvertData
+     */
+    public static synchronized ConvertData doConvertReturnObj(List orignalList, String headerName, String firstColName, String valueFiedName, boolean needHeader) throws Exception {
+        List<List<Object>> lists = doConvert(orignalList, headerName, firstColName, valueFiedName, needHeader);
+        return new ConvertData(lists, headerSet, firstColSet);
+    }
+
+    /**
+     * 行转列,返回转换后的list
+     *
+     * @param orignalList   原始list
+     * @param headerName    列表头字段名
+     * @param firstColName  首列字段名
+     * @param valueFiedName 值列的字段名
+     * @param needHeader    是否需要返回列表头
+     */
+    public static synchronized List<List<Object>> doConvert(List orignalList, String headerName, String firstColName, String valueFiedName, boolean needHeader) throws Exception {
+        headerSet = new LinkedHashSet<>();
+        firstColSet = new LinkedHashSet<>();
+        List<List<Object>> resultList = new ArrayList<>();
+
+        getHeaderFirstcolSet(orignalList, headerName, firstColName);
+        if (needHeader) {
+            List<Object> headerList = new ArrayList<>();
+            //填充进header
+            headerList.add(HEADER_NULL_VALUE);
+            headerList.addAll(headerSet);
+            resultList.add(headerList);
+        }
+        for (Object firstColNameItem : firstColSet) {
+            List<Object> colList = new ArrayList<>();
+            //名称
+            colList.add(firstColNameItem);
+            for (Object headerItem : headerSet) {
+                boolean flag = true;
+                for (Object orignalObjectItem : orignalList) {
+                    Field headerField = orignalObjectItem.getClass().getDeclaredField(headerName);
+                    headerField.setAccessible(true);
+                    Field firstColField = orignalObjectItem.getClass().getDeclaredField(firstColName);
+                    firstColField.setAccessible(true);
+                    Field valueField = orignalObjectItem.getClass().getDeclaredField(valueFiedName);
+                    valueField.setAccessible(true);
+                    if (headerItem.equals(headerField.get(orignalObjectItem))) {
+                        if (firstColNameItem.equals(firstColField.get(orignalObjectItem))) {
+                            colList.add(valueField.get(orignalObjectItem));
+                            flag = false;
+                            break;
+                        }
+                    }
+                }
+                if (flag) {
+                    colList.add(NULL_VALUE);
+                }
+            }
+            resultList.add(colList);
+        }
+        return resultList;
+    }
+
+    private static void getHeaderFirstcolSet(List orignalList, String headerName, String firstColName) {
+        try {
+            for (Object item : orignalList) {
+                Field headerField = item.getClass().getDeclaredField(headerName);
+                headerField.setAccessible(true);
+                Field firstColField = item.getClass().getDeclaredField(firstColName);
+                firstColField.setAccessible(true);
+                headerSet.add(headerField.get(item));
+                firstColSet.add(firstColField.get(item));
+            }
+        } catch (NoSuchFieldException e) {
+            log.error(e.getMessage(), e); // 记录错误日志
+        } catch (IllegalAccessException e) {
+            log.error(e.getMessage(), e); // 记录错误日志
+        }
+    }
+}

+ 126 - 0
mjava/src/main/java/com/malk/Util/UtilDateTime.java

@@ -0,0 +1,126 @@
+package com.malk.Util;
+
+import cn.hutool.core.util.ObjectUtil;
+import lombok.SneakyThrows;
+import org.apache.commons.lang3.StringUtils;
+
+import javax.annotation.Nullable;
+import java.text.SimpleDateFormat;
+import java.time.*;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.Temporal;
+import java.time.temporal.TemporalAdjusters;
+import java.util.Date;
+
+/**
+ * 时间格式 [Calendar 日历类, Date 是java 8前产物, LocalDateTime]
+ */
+public abstract class UtilDateTime {
+
+    public final static String DATE_PATTERN = "yyyy-MM-dd";
+    public final static String TIME_PATTERN = "HH:mm:ss";
+    public final static String DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss";
+
+    /// T表示分隔符,Z表示的是UTC "yyyy-MM-dd'T'HH:mm:ss.SSS Z"
+
+    //// convert ////
+
+    // 将java.util.Date 转换为java8 的java.time.LocalDateTime,默认时区为东8区
+    public static LocalDateTime convertToLocalDateTimeFromDate(Date date) {
+        return date.toInstant().atOffset(ZoneOffset.of("+8")).toLocalDateTime();
+    }
+
+    // 将java8 的 java.time.LocalDateTime 转换为 java.util.Date,默认时区为东8区
+    public static Date convertToDateFromLocalDateTime(LocalDateTime localDateTime) {
+        return Date.from(localDateTime.toInstant(ZoneOffset.of("+8")));
+    }
+
+    // 获取时间段内小时
+    public static float betweenHour(Temporal startInclusive, Temporal endExclusive) {
+        return Duration.between(startInclusive, endExclusive).toMillis() / 60f;
+    }
+
+    // 获取上月第一天0点
+    public static LocalDateTime firstDayOfLastMonth(LocalDateTime dateTime) {
+        int month = dateTime.getMonthValue() - 1;
+        int year = dateTime.getYear();
+        if (month == 0) {
+            month = 12;
+        }
+        return LocalDateTime.of(year, month, 1, 0, 0, 0);
+    }
+
+    // 计算时间差和指定如一月最后一天很方便, 如下个月第一天 .... [时间会保留传入时间]
+    public static LocalDateTime firstDayOfNextMonth(LocalDateTime dateTime) {
+        return dateTime.with(TemporalAdjusters.firstDayOfNextMonth());
+    }
+
+    // 获取当天0点时间
+    public static LocalDateTime minLocalDateTime(@Nullable LocalDateTime dateTime) {
+        return LocalDateTime.of(dateTime.toLocalDate(), LocalTime.MIN);
+    }
+
+    //// Date  ////
+
+    public static String formatDateTime(Date dateTime) {
+        if (ObjectUtil.isNull(dateTime)) return "";
+        return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS Z").format(dateTime);
+    }
+
+    public static String formatDate(Date dateTime) {
+        return new SimpleDateFormat(DATE_PATTERN).format(dateTime);
+    }
+
+    public static String formatTime(Date dateTime) {
+        return new SimpleDateFormat(TIME_PATTERN).format(dateTime);
+    }
+
+    @SneakyThrows
+    public static Date parseDateTime(String dateStr) {
+        if (StringUtils.isBlank(dateStr)) {
+            return new Date();
+        }
+        return new SimpleDateFormat(DATE_TIME_PATTERN).parse(dateStr);
+    }
+
+    @SneakyThrows
+    public static Date parseDate(String dateStr) {
+        return new SimpleDateFormat(DATE_PATTERN).parse(dateStr);
+    }
+
+    @SneakyThrows
+    public static Date parseTime(String dateStr) {
+        return new SimpleDateFormat(TIME_PATTERN).parse(dateStr);
+    }
+
+
+    //// LocalDateTime ////
+
+    public static String formatLocalDateTime(LocalDateTime dateTime) {
+        if (ObjectUtil.isNull(dateTime)) return "";
+        return DateTimeFormatter.ofPattern(DATE_TIME_PATTERN).format(dateTime);
+    }
+
+    public static String formatLocalDate(LocalDate dateTime) {
+        return DateTimeFormatter.ofPattern(DATE_PATTERN).format(dateTime);
+    }
+
+    public static String formatLocalTime(LocalTime dateTime) {
+        return DateTimeFormatter.ofPattern(TIME_PATTERN).format(dateTime);
+    }
+
+    public static LocalDateTime parseLocalDateTime(String dateStr) {
+        if (StringUtils.isBlank(dateStr)) {
+            return LocalDateTime.now();
+        }
+        return LocalDateTime.parse(dateStr, DateTimeFormatter.ofPattern(DATE_TIME_PATTERN));
+    }
+
+    public static LocalDate parseLocalDate(String dateStr) {
+        return LocalDate.parse(dateStr, DateTimeFormatter.ofPattern(DATE_PATTERN));
+    }
+
+    public static LocalTime parseLocalTime(String dateStr) {
+        return LocalTime.parse(dateStr, DateTimeFormatter.ofPattern(TIME_PATTERN));
+    }
+}

+ 135 - 0
mjava/src/main/java/com/malk/Util/UtilEnv.java

@@ -0,0 +1,135 @@
+package com.malk.Util;
+
+import org.springframework.aop.framework.AopContext;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.NoSuchBeanDefinitionException;
+import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Component;
+
+/**
+ * spring工具类 方便在非spring管理环境中获取bean
+ *
+ * @author Rangers
+ */
+@Component
+public final class UtilEnv implements BeanFactoryPostProcessor, ApplicationContextAware {
+
+    public final static String ENV_PROD = "prod";
+    public final static String ENV_TEST = "test";
+    public final static String ENV_DEV = "dev";
+
+    /**
+     * Spring应用上下文环境
+     */
+    private static ConfigurableListableBeanFactory beanFactory;
+
+    private static ApplicationContext applicationContext;
+
+    @Override
+    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
+        this.beanFactory = beanFactory;
+    }
+
+    @Override
+    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+        this.applicationContext = applicationContext;
+    }
+
+    /**
+     * 获取对象
+     *
+     * @param name
+     * @return Object 一个以所给名字注册的bean的实例
+     * @throws BeansException
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> T getBean(String name) throws BeansException {
+        return (T) beanFactory.getBean(name);
+    }
+
+    /**
+     * 获取类型为requiredType的对象
+     *
+     * @param clz
+     * @return
+     * @throws BeansException
+     */
+    public static <T> T getBean(Class<T> clz) throws BeansException {
+        T result = (T) beanFactory.getBean(clz);
+        return result;
+    }
+
+    /**
+     * 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true
+     *
+     * @param name
+     * @return boolean
+     */
+    public static boolean containsBean(String name) {
+        return beanFactory.containsBean(name);
+    }
+
+    /**
+     * 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException)
+     *
+     * @param name
+     * @return boolean
+     * @throws NoSuchBeanDefinitionException
+     */
+    public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException {
+        return beanFactory.isSingleton(name);
+    }
+
+    /**
+     * @param name
+     * @return Class 注册对象的类型
+     * @throws NoSuchBeanDefinitionException
+     */
+    public static Class<?> getType(String name) throws NoSuchBeanDefinitionException {
+        return beanFactory.getType(name);
+    }
+
+    /**
+     * 如果给定的bean名字在bean定义中有别名,则返回这些别名
+     *
+     * @param name
+     * @return
+     * @throws NoSuchBeanDefinitionException
+     */
+    public static String[] getAliases(String name) throws NoSuchBeanDefinitionException {
+        return beanFactory.getAliases(name);
+    }
+
+    /**
+     * 获取aop代理对象
+     *
+     * @param invoker
+     * @return
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> T getAopProxy(T invoker) {
+        return (T) AopContext.currentProxy();
+    }
+
+    /**
+     * 获取当前的环境配置,无配置返回null
+     *
+     * @return 当前的环境配置
+     */
+    public static String[] getActiveProfiles() {
+        return applicationContext.getEnvironment().getActiveProfiles();
+    }
+
+    /**
+     * 获取当前的环境配置,当有多个环境配置时,只获取第一个
+     *
+     * @return 当前的环境配置
+     */
+    public static String getActiveProfile() {
+        final String[] activeProfiles = getActiveProfiles();
+        return UtilList.isNotEmpty(activeProfiles) ? activeProfiles[0] : null;
+    }
+}

+ 227 - 0
mjava/src/main/java/com/malk/Util/UtilExcel.java

@@ -0,0 +1,227 @@
+package com.malk.Util;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.alibaba.excel.EasyExcel;
+import com.google.common.base.Strings;
+import com.malk.server.common.McException;
+import com.malk.server.common.McREnum;
+import lombok.Builder;
+import lombok.Data;
+import lombok.SneakyThrows;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.poi.hssf.usermodel.*;
+import org.apache.poi.ss.usermodel.BorderStyle;
+import org.springframework.core.io.ClassPathResource;
+
+import javax.annotation.Nullable;
+import javax.servlet.http.HttpServletResponse;
+import javax.validation.constraints.NotNull;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.math.BigDecimal;
+import java.net.URLEncoder;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * excel 导出工具 [poi & easy excel]
+ */
+@Builder
+@Data
+public class UtilExcel {
+
+    /**
+     * 设置响应流
+     */
+    @SneakyThrows
+    public static void setResponseHeader(HttpServletResponse response, @NotNull String fileName, String extension) {
+        // 后缀: poi, xlsx下预览无详情; EasyExcel 需要使用xlsx, 否则不能预览
+        if (StringUtils.isBlank(extension)) {
+            extension = ".xls";
+        }
+        String date = new SimpleDateFormat("yyyy-MM-dd HH_mm_ss").format(new Date());
+        fileName = fileName + "_" + date + extension;
+        // 设置导出流信息
+        response.setContentType("application/vnd.ms-excel;charset=UTF-8");
+        response.setCharacterEncoding("utf-8");
+        // 文件名兼容中文
+        fileName = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");
+        response.setHeader("Content-Disposition", "attachment;filename*=utf-8" + fileName);
+        // 想要让客户端可以访问到其他的首部信息,服务器不仅要在header里加入该首部,还要将它们在 Access-Control-Expose-Headers 里面列出来
+        response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
+    }
+
+    /////////////////// Poi ///////////////////
+
+    // 各个列的表头
+    private String[] heardList;
+    // 各个列的元素key值
+    private String[] heardKey;
+    // 需要填充的数据信息
+    private List<Map> data;
+    // 导出文件名称
+    private String fileName;
+
+    // 字体大小
+    @Builder.Default
+    private int fontSize = 12;
+    // 行高
+    @Builder.Default
+    private int rowHeight = 30;
+    // 列宽
+    @Builder.Default
+    private int columnWidth = 20;
+    // 工作表
+    @Builder.Default
+    private String sheetName = "sheet1";
+
+    /**
+     * 回调单元格样式
+     * -
+     * cellStyle.setFillForegroundColor(IndexedColors.RED.getIndex()); // 是设置前景色不是背景色
+     * cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+     */
+    @FunctionalInterface
+    public interface UpdateCellStyle {
+        void invoke(String value, HSSFCellStyle cellStyle);
+    }
+
+    public void exportExcelByPoi(HttpServletResponse response) {
+        exportExcelByPoi(response, null);
+    }
+
+    /**
+     * Poi 导出功能
+     * -
+     * - java中的强制类型转换只是针对单个对象的,想要偷懒将整个数组转换成另外一种类型的数组是不行的. toArray 运行中已经转为 Object, 需要指定初始化类型
+     * - 4.x打包引用会异常: HSSFCellStyle.BORDER_THIN 指向的是 CellStyle.BORDER_THIN. 另若引入 easyExcel, 无需再引入 poi [其依赖是低版本 poi]
+     */
+    public void exportExcelByPoi(HttpServletResponse response, UpdateCellStyle lambda) {
+        // 检查参数配置信息
+        checkConfig();
+        // 创建工作簿
+        HSSFWorkbook wb = new HSSFWorkbook();
+        // 创建工作表
+        HSSFSheet wbSheet = wb.createSheet(sheetName);
+        // 设置默认行宽
+        wbSheet.setDefaultColumnWidth(columnWidth);
+
+        // 设置表格样式
+        HSSFCellStyle styleHeader = createCellStyle(wb);
+        // 设置表头字体
+        HSSFFont fontHeader = wb.createFont();
+        fontHeader.setFontHeightInPoints((short) fontSize);
+        fontHeader.setBold(true);
+        styleHeader.setFont(fontHeader);
+
+        //设置列头元素
+        HSSFRow row = wbSheet.createRow(0);
+        HSSFCell cellHead = null;
+        for (int i = 0; i < heardList.length; i++) {
+            cellHead = row.createCell(i);
+            cellHead.setCellValue(heardList[i]);
+            cellHead.setCellStyle(styleHeader);
+        }
+
+        // 设置表格样式
+        HSSFCellStyle bodyStyle = createCellStyle(wb);
+        // 设置表格字体
+        HSSFFont fontBody = wb.createFont();
+        fontBody.setFontHeightInPoints((short) fontSize);
+        fontBody.setBold(false);
+        bodyStyle.setFont(fontBody);
+
+        //开始写入实体数据信息
+        int a = 1;
+        for (int i = 0; i < data.size(); i++) {
+            HSSFRow roww = wbSheet.createRow(a);
+            Map map = data.get(i);
+            HSSFCell cell = null;
+            for (int j = 0; j < heardKey.length; j++) {
+                cell = roww.createCell(j);
+                cell.setCellStyle(bodyStyle);
+                Object valueObject = map.get(heardKey[j]);
+                String value = null;
+                if (valueObject == null) {
+                    valueObject = "";
+                }
+                if (valueObject instanceof String) {
+                    //取出的数据是字符串直接赋值
+                    value = (String) map.get(heardKey[j]);
+                } else if (valueObject instanceof Integer) {
+                    //取出的数据是Integer
+                    value = String.valueOf(((Integer) (valueObject)).floatValue());
+                } else if (valueObject instanceof BigDecimal) {
+                    //取出的数据是BigDecimal
+                    value = String.valueOf(((BigDecimal) (valueObject)).floatValue());
+                } else {
+                    value = valueObject.toString();
+                }
+                cell.setCellValue(Strings.isNullOrEmpty(value) ? "" : value);
+                // 独立设置单元格样式
+                if (ObjectUtil.isNotNull(lambda)) {
+                    HSSFCellStyle newBodyStyle = createCellStyle(wb);
+                    newBodyStyle.setFont(fontBody);
+                    lambda.invoke(value, newBodyStyle);
+                    cell.setCellStyle(newBodyStyle);
+                }
+            }
+            a++;
+        }
+        // 导出文件
+        setResponseHeader(response, fileName, ".xls");
+        try {
+            // 取得输出流
+            OutputStream os = response.getOutputStream();
+            wb.write(os);
+            os.flush();
+            os.close();
+        } catch (IOException ex) {
+            throw new McException(McREnum.METHOD_EXECUTE);
+        }
+    }
+
+    // 不同的 CellStyle 需要独立创建
+    private HSSFCellStyle createCellStyle(HSSFWorkbook wb) {
+        HSSFCellStyle style = wb.createCellStyle();
+        style.setBorderBottom(BorderStyle.THIN);     //下边框
+        style.setBorderLeft(BorderStyle.THIN);       //左边框
+        style.setBorderTop(BorderStyle.THIN);        //上边框
+        style.setBorderRight(BorderStyle.THIN);      //右边框
+        return style;
+    }
+
+    // 检查数据配置问题
+    protected void checkConfig() {
+        if (heardKey == null || heardList.length == 0) {
+            McException.exceptionParam("列名数组不能为空或者为NULL");
+        }
+        if (fontSize < 0 || rowHeight < 0 || columnWidth < 0) {
+            McException.exceptionParam("字体、宽度或者高度不能为负值");
+        }
+        if (Strings.isNullOrEmpty(fileName)) {
+            McException.exceptionParam("导出文件名称不能为NULL");
+        }
+    }
+
+    /////////////////// EasyExcel ///////////////////
+
+    /**
+     * 导出功能 EasyExcel [假期余额导入模板 & 导入失败记录导出]
+     * -
+     * - ClassPathResource, 需要打包后才能访问到. 识别不是架包内内容
+     * - Mac下, 若报错 Times 字体找不到, 下载安装一下, 不影响功能使用
+     */
+    @SneakyThrows
+    public static void exportBalanceWithHolidayTemplate(HttpServletResponse response, List dataList, Class dtoClass, @Nullable String fileName) {
+        ClassPathResource classPathResource = new ClassPathResource("templates/HolidayTemp.xlsx");
+        InputStream inputStream = classPathResource.getInputStream();
+        UtilExcel.setResponseHeader(response, fileName, ".xlsx");
+        // 如果不用模板的方式导出的话,是doWrite
+        // EasyExcel.write(response.getOutputStream()).head(OFBalanceDTO.class).excelType(ExcelTypeEnum.XLSX).sheet().doWrite(list);
+        EasyExcel.write(response.getOutputStream(), dtoClass).withTemplate(inputStream).sheet().doFill(dataList);
+    }
+}

+ 25 - 0
mjava/src/main/java/com/malk/Util/UtilFile.java

@@ -0,0 +1,25 @@
+package com.malk.Util;
+
+import javax.annotation.Nullable;
+import java.io.File;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+public abstract class UtilFile {
+
+    /**
+     * 匹配路径: 自动追加年月日作为目录
+     * -
+     * 文件上传存储,必须是绝对路径. 日志的配置地址可以是相对路径
+     */
+    public static File mkdirIfNot(@Nullable String fileName, @Nullable String dirName) {
+        String date = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
+        String outputFile = dirName + "/" + date + "/" + fileName;
+        File file = new File(outputFile);
+        // 创建 mkdirIfNot
+        if (!file.getParentFile().exists()) {
+            file.getParentFile().mkdirs();
+        }
+        return file;
+    }
+}

+ 180 - 0
mjava/src/main/java/com/malk/Util/UtilHttp.java

@@ -0,0 +1,180 @@
+package com.malk.Util;
+
+import cn.hutool.core.util.CharsetUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.http.HttpRequest;
+import cn.hutool.http.HttpUtil;
+import com.alibaba.fastjson.JSON;
+import com.malk.server.common.VenR;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.Map;
+
+
+/**
+ * HttpUtil [取值详见CatchException]
+ * -
+ * - 后端方法说明
+ * 1. param 使用转码, 避免中文字符导致参数识别异常, 如中文小括号
+ * 2. body 注意 .form 是表单内容; .body 才是请求内容 [Restful请求]
+ * 3. form 方式, 支持文件上传, 参数包到 map. body 不允许为空, 导致异常
+ * 4. response 请求后若需要转为数据类型, 读取请求结果body为JSONString. 若直接获取格式字符串, 不能再转为对象或Map
+ * -
+ * -  前端请求格式
+ * 1. get: url上param, 后端取值@requestParam,也可用request.getParameterMap().get(“key”), 参数会被放入一个集合
+ * 2. post: body内json, 后端取值@requestBody, Map 或转为实体
+ * 3. form: body内格式为form, 和content-type有关系, 需要为form格式后端才能读取: 不能使用@RequestBody,参数会自动解析到实体; 若不是实体通过方法转Map
+ * 4. upload: body-formData, 一般用于文件上传, 追加数据流
+ */
+@Slf4j
+public abstract class UtilHttp {
+
+    /*** ------------ 创建POST请求 ------------ ***/
+
+    public static String doPost(String url, Map header, Map<String, Object> param, Object body, Map form, String usr, String pwd) {
+        log.debug("请求入参, url = {}, header = {}, param = {}, body = {}, form = {}", url, header, param, body, form);
+        String path = HttpUtil.urlWithForm(url, param, CharsetUtil.CHARSET_UTF_8, true);
+        HttpRequest request = HttpRequest.post(path).addHeaders(header).form(form); // 允许为空
+        if (ObjectUtil.isNotNull(body)) {
+            request.body(JSON.toJSONString(body));
+        }
+        if (StringUtils.isNotBlank(usr) && StringUtils.isNotBlank(pwd)) {
+            request.basicAuth(usr, pwd);
+        }
+        String rsp = request.execute().body();
+        log.debug("请求响应, {}", rsp);
+        return rsp;
+    }
+
+    public static String doPost(String url, Map header, Map<String, Object> param, Map body, Map form) {
+        return doPost(url, header, param, body, form, null, null);
+    }
+
+    public static String doPost(String url, Map header, Map<String, Object> param, Map body) {
+        return doPost(url, header, param, body, null, null, null);
+    }
+
+    public static String doUpload(String url, Map header, Map<String, Object> param, Map form) {
+        return doPost(url, header, param, null, form, null, null);
+    }
+
+    public static VenR doPost(String url, Map header, Map<String, Object> param, Map body, Map form, Class rClass) {
+        String rsp = doPost(url, header, param, body, form);
+        VenR r = (VenR) JSON.parseObject(rsp, rClass);
+        r.assertSuccess();
+        return r;
+    }
+
+    public static VenR doPost(String url, Map header, Map<String, Object> param, Map body, Class rClass) {
+        return doPost(url, header, param, body, null, rClass);
+    }
+
+    public static VenR doPost(String url, Map header, Map body, Class rClass) {
+        return doPost(url, header, null, body, null, rClass);
+    }
+
+    public static VenR doUpload(String url, Map header, Map<String, Object> param, Map form, Class rClass) {
+        return doPost(url, header, param, null, form, rClass);
+    }
+
+    public static VenR doUpload(String url, Map header, Map form, Class rClass) {
+        return doUpload(url, header, null, form, rClass);
+    }
+
+    /*** ------------ 创建GET请求 ------------ ***/
+
+    public static String doGet(String url, Map header, Map param) {
+        log.debug("请求入参, {}, {}, {}", url, header, param);
+        String path = HttpUtil.urlWithForm(url, param, CharsetUtil.CHARSET_UTF_8, true);
+        String rsp = HttpRequest.get(path).addHeaders(header).execute().body();
+        log.debug("请求响应, {}", rsp);
+        return rsp;
+    }
+
+    public static VenR doGet(String url, Map header, Map param, Class rClass) {
+        String rsp = doGet(url, header, param);
+        VenR r = (VenR) JSON.parseObject(rsp, rClass);
+        r.assertSuccess();
+        return r;
+    }
+
+    public static VenR doGet(String url, Map<String, Object> param, Class rClass) {
+        return doGet(url, null, param, rClass);
+    }
+
+    /*** ------------ 创建PUT请求 ------------ ***/
+
+    public static String doPut(String url, Map header, Map param, Map body) {
+        log.debug("请求入参, {}, {}, {}, {}", url, header, param, body);
+        String path = HttpUtil.urlWithForm(url, param, CharsetUtil.CHARSET_UTF_8, true);
+        HttpRequest request = HttpRequest.put(path).addHeaders(header);
+        if (ObjectUtil.isNotNull(body)) {
+            request.body(JSON.toJSONString(body));
+        }
+        String rsp = request.execute().body();
+        log.debug("请求响应, {}", rsp);
+        return rsp;
+    }
+
+    public static VenR doPut(String url, Map header, Map param, Map body, Class rClass) {
+        String rsp = doPut(url, header, param, body);
+        VenR r = (VenR) JSON.parseObject(rsp, rClass);
+        r.assertSuccess();
+        return r;
+    }
+
+    public static VenR doPut(String url, Map header, Map body, Class rClass) {
+        return doPut(url, header, null, body, rClass);
+    }
+
+    public static VenR doPut(String url, Map body, Class rClass) {
+        return doPut(url, null, null, body, rClass);
+    }
+
+    /*** ------------ 创建DELETE请求 ------------ ***/
+
+    public static String doDelete(String url, Map header) {
+        log.debug("请求入参, {}, {}, {}", url, header);
+        String rsp = HttpRequest.delete(url).addHeaders(header).execute().body();
+        log.debug("请求响应, {}", rsp);
+        return rsp;
+    }
+
+    public static VenR doDelete(String url, Map header, Class rClass) {
+        String rsp = doDelete(url, header);
+        VenR r = (VenR) JSON.parseObject(rsp, rClass);
+        r.assertSuccess();
+        return r;
+    }
+
+    /*** ------------ 创建PATCH请求 ------------ ***/
+
+    public static String doPatch(String url, Map header, Map param, Map body) {
+        log.debug("请求入参, {}, {}, {}, {}", url, header, param, body);
+        String path = HttpUtil.urlWithForm(url, param, CharsetUtil.CHARSET_UTF_8, true);
+        HttpRequest request = HttpRequest.patch(path).addHeaders(header);
+        if (ObjectUtil.isNotNull(body)) {
+            request.body(JSON.toJSONString(body));
+        }
+        String rsp = request.execute().body();
+        log.debug("请求响应, {}", rsp);
+        return rsp;
+    }
+
+    public static VenR doPatch(String url, Map header, Map param, Map body, Class rClass) {
+        String rsp = doPatch(url, header, param, body);
+        VenR r = (VenR) JSON.parseObject(rsp, rClass);
+        r.assertSuccess();
+        return r;
+    }
+
+    public static VenR doPatch(String url, Map header, Map body, Class rClass) {
+        return doPatch(url, header, null, body, rClass);
+    }
+
+    public static VenR doPatch(String url, Map body, Class rClass) {
+        return doPatch(url, null, null, body, rClass);
+    }
+
+}

+ 74 - 0
mjava/src/main/java/com/malk/Util/UtilImport.java

@@ -0,0 +1,74 @@
+package com.malk.Util;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.alibaba.excel.context.AnalysisContext;
+import com.alibaba.excel.event.AnalysisEventListener;
+import com.malk.base.BaseParam;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+import java.util.function.Consumer;
+
+/**
+ * easy excel 导入
+ */
+@Slf4j
+@Builder
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Component
+public class UtilImport extends AnalysisEventListener<BaseParam> {
+
+    @FunctionalInterface
+    public interface ExecuteLambda {
+        void row(BaseParam dto);
+    }
+
+    /**
+     * 表头信息读取
+     */
+    private Consumer headerLambda;
+
+    /**
+     * 执行每行调用
+     */
+    private ExecuteLambda executeLambda;
+
+    /**
+     * 导入结束回调
+     */
+    private Consumer completeLambda;
+
+    @Override
+    public void invoke(BaseParam balanceDTO, AnalysisContext analysisContext) {
+        // 2. 执行每行调用
+        log.info("执行每行调用: {}", balanceDTO);
+        if (ObjectUtil.isNotNull(executeLambda)) {
+            executeLambda.row(balanceDTO);
+        }
+    }
+
+    @Override
+    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
+        // 3. 导入结束回调
+        log.info("#### 导入结束回调 ####");
+        if (ObjectUtil.isNotNull(completeLambda)) {
+            headerLambda.accept(analysisContext);
+        }
+    }
+
+    @Override
+    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
+        // 1. 表头信息读取
+        log.info("表头信息读取: {}", headMap);
+        if (ObjectUtil.isNotNull(headerLambda)) {
+            headerLambda.accept(headMap);
+        }
+    }
+}

+ 30 - 0
mjava/src/main/java/com/malk/Util/UtilList.java

@@ -0,0 +1,30 @@
+package com.malk.Util;
+
+import cn.hutool.core.util.ObjectUtil;
+
+import java.util.List;
+
+public abstract class UtilList {
+
+    // 访问安全, 避免空指针访问访问属性异常
+    public static boolean isEmpty(List list) {
+        return ObjectUtil.isNull(list) || list.isEmpty();
+    }
+
+    public static boolean isNotEmpty(List list) {
+        return !isEmpty(list);
+    }
+
+    public static boolean isEmpty(String[] list) {
+        return ObjectUtil.isNull(list) || list.length == 0;
+    }
+
+    public static boolean isNotEmpty(String[] list) {
+        return !isEmpty(list);
+    }
+
+    public static Object getLast(List list) {
+        if (isEmpty(list)) return null;
+        return list.get(list.size() - 1);
+    }
+}

+ 85 - 0
mjava/src/main/java/com/malk/Util/UtilMap.java

@@ -0,0 +1,85 @@
+package com.malk.Util;
+
+import com.malk.server.common.McException;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+public class UtilMap {
+
+    // 工具方法
+    public static Consumer<Object> consumerWithIndex(BiConsumer<Object, Integer> consumer) {
+        class Obj {
+            int i;
+        }
+        Obj obj = new Obj();
+        return t -> {
+            int index = obj.i++;
+            consumer.accept(t, index);
+        };
+    }
+
+    // 快速创建map
+    public static Map<String, Object> map(String keys, Object... values) {
+        String[] props = keys.split(", ");
+        if (props.length != values.length) {
+            McException.assertParamException_Null(keys);
+        }
+        Map<String, Object> map = new HashMap<>();
+        Arrays.stream(values).forEach(consumerWithIndex((item, index) -> {
+            map.put(props[index], item);
+        }));
+        return map;
+    }
+
+    // 快速创建map
+    public static Map<String, String> map(String keys, String values) {
+        String[] props = keys.split(", ");
+        String[] contents = values.split(", ");
+        if (props.length != contents.length) {
+            McException.assertParamException_Null(keys);
+        }
+        Map<String, String> map = new HashMap<>();
+        Arrays.stream(contents).forEach(consumerWithIndex((item, index) -> {
+            map.put(props[index], String.valueOf(item));
+        }));
+        return map;
+    }
+
+    // 快速创建map
+    public static Map<String, Object> map(String skeys, String ckeys, Map map) {
+        String[] sprops = skeys.split(", ");
+        String[] cprops = ckeys.split(", ");
+        if (sprops.length != cprops.length) {
+            McException.assertParamException_Null(skeys);
+        }
+        Map data = new HashMap();
+        Arrays.stream(cprops).forEach(consumerWithIndex((item, index) -> {
+            data.put(sprops[index], map.get(item));
+        }));
+        return data;
+    }
+
+    // 快速创建map
+    public static Map<String, Object> map(String skeys, String ckeys, Map map, boolean isReserve) {
+        if (isReserve) {
+            map.putAll(map(skeys, ckeys, map));
+            return map;
+        }
+        return map(skeys, ckeys, map);
+    }
+
+    // distinctByKey: 从人员列表中过滤出一个子集(每个部门选一个人) employees.stream().filter(distinctByKey(Employee::getDeptCode)).collect(toList());
+    public static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
+        Map<Object, Boolean> seen = new ConcurrentHashMap<>();
+        return t -> Objects.isNull(seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE));
+    }
+
+}

+ 24 - 0
mjava/src/main/java/com/malk/Util/UtilMath.java

@@ -0,0 +1,24 @@
+package com.malk.Util;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+
+public abstract class UtilMath {
+
+    // 不要使用浮点数进行大小比较, 比较使用 BigDecimal
+    public final static BigDecimal formatFloatRound(float value) {
+        BigDecimal bd = new BigDecimal(value);
+        return bd.setScale(2, RoundingMode.HALF_UP);
+    }
+
+    // 用来对超过16位有效位的数据进行精确的运算
+    public final static BigDecimal formatDoubleRound(double value) {
+        BigDecimal bd = new BigDecimal(value);
+        return bd.setScale(2, RoundingMode.HALF_UP);
+    }
+
+    // Math.ceil 需要有浮点数, 否则转 int 类型忽略掉, 向上取整就会是当前值
+    public final static int pagesOfTotalAndSize(int total, int size) {
+        return (int) Math.ceil(total * 1.0f / size);
+    }
+}

+ 61 - 0
mjava/src/main/java/com/malk/Util/UtilServlet.java

@@ -0,0 +1,61 @@
+package com.malk.Util;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+@Slf4j
+public abstract class UtilServlet {
+
+    /**
+     * 获取 formData 参数转 map
+     */
+    public static Map<String, Object> getParamMap(HttpServletRequest req) {
+        Map map = new HashMap();
+        Map<String, String[]> paramMap = req.getParameterMap();
+        if (paramMap != null && !paramMap.isEmpty()) {
+            Set<String> keySet = paramMap.keySet();
+            Iterator var4 = keySet.iterator();
+            while (true) {
+                while (var4.hasNext()) {
+                    String key = (String) var4.next();
+                    String[] values = paramMap.get(key);
+                    if (values != null && values.length != 0) {
+                        if (values.length == 1) {
+                            map.put(key, values[0]);
+                        } else {
+                            map.put(key, values);
+                        }
+                    } else {
+                        map.put(key, "");
+                    }
+                }
+                return (Map) map;
+            }
+        } else {
+            return (Map) map;
+        }
+    }
+
+    /**
+     * 判断map取值是否为空
+     */
+    public static String isNull(Map param, String... keys) {
+        for (String key : keys) {
+            if (!param.containsKey(key) || param.get(key) == null || String.valueOf(param.get(key)).trim().equals("")
+                    || String.valueOf(param.get(key)).trim().equals("null")) {
+                return key;
+            }
+        }
+        return null;
+    }
+
+    public static boolean isNotNull(Map param, String... keys) {
+        return StringUtils.isBlank(isNull(param, keys));
+    }
+}

+ 41 - 0
mjava/src/main/java/com/malk/Util/UtilString.java

@@ -0,0 +1,41 @@
+package com.malk.Util;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+@Slf4j
+public abstract class UtilString {
+
+    // 字符串拆分为集合按照英文逗号
+    public static List stringSplitToList(String str) {
+        String reg = ",";
+        if (str.contains(", ")) reg = ", ";
+        return Arrays.asList(str.split(reg));
+    }
+
+    // json序列化已经将空字符串过滤, 若转换还有null字符串, 可能是key为null或SerializerFeature未指定到类型, 如Date
+    public static boolean isNotBlankCompatNull(String str) {
+        return StringUtils.isNotBlank(str) && !str.equals("null");
+    }
+
+    public static boolean isBlankCompatNull(String str) {
+        return !isNotBlankCompatNull(str);
+    }
+
+    // 字符串判断为空返回空字符串而不是null
+    public static String stringFormatNull(Map<String, String> data, String key) {
+        String str = data.get(key);
+        if (StringUtils.isBlank(str)) return "";
+        if (isBlankCompatNull(str)) return "";
+        return str;
+    }
+
+    // 格式化保留两位小数
+    public static String stringFormatFixed(Object value) {
+        return String.format("%.2f", value);
+    }
+}

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

@@ -0,0 +1,29 @@
+package com.malk.Util;
+
+import cn.hutool.cache.CacheUtil;
+import cn.hutool.cache.impl.TimedCache;
+
+/**
+ * token过期处理
+ */
+public abstract class UtilToken {
+
+    private static final TimedCache<String, String> TIMED_CACHE = CacheUtil.newTimedCache(0);
+
+    public static void put(String key, String value, Long timeout) {
+        if (timeout > 5000) timeout -= 5000; // 避免极端情况, 冗余5s容错处理
+        /** 设置消逝时间 */
+        TIMED_CACHE.put(key, value, timeout);
+    }
+
+    public static String get(String key) {
+        // 不刷新消逝时间
+        return TIMED_CACHE.get(key, false);
+    }
+
+    public static String getWithRefresh(String key) {
+        // 重新刷新消逝时间
+        return TIMED_CACHE.get(key);
+
+    }
+}

+ 48 - 0
mjava/src/main/java/com/malk/Util/UtilVendor.java

@@ -0,0 +1,48 @@
+package com.malk.Util;
+
+import com.itextpdf.text.pdf.BaseFont;
+import com.malk.server.common.FilePath;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.xhtmlrenderer.pdf.ITextFontResolver;
+import org.xhtmlrenderer.pdf.ITextRenderer;
+
+import java.io.FileOutputStream;
+import java.io.OutputStream;
+
+/**
+ * 引用了配置文件, 访问对象必须通过 @Autowired 进行注入, 否则识别到为空 [配置文件说明参考Setting]
+ */
+@Slf4j
+@Component
+public class UtilVendor {
+
+    @Autowired
+    private FilePath filePath;
+
+    /**
+     * 网页转存为PDF文件: http://www.lrfun.com/html/technology/java/2019/0509/137.html
+     */
+    public void urlToPdf(String url, String outputFileName) {
+        try {
+            String outputFile = filePath.getPath().getFile() + "/" + outputFileName;
+            java.io.File targetFile = new java.io.File(outputFile);
+            // 创建父级文件路径
+            if (!targetFile.getParentFile().exists()) {
+                targetFile.getParentFile().mkdirs();
+            }
+            log.info("PDF 存储路径, {}", outputFile);
+            OutputStream os = new FileOutputStream(outputFile);
+            ITextRenderer renderer = new ITextRenderer();
+            renderer.setDocument(url);
+            ITextFontResolver fontResolver = renderer.getFontResolver();
+            fontResolver.addFont(filePath.getSource().getFonts(), BaseFont.IDENTITY_H, BaseFont.EMBEDDED); //Linux
+            renderer.layout();
+            renderer.createPDF(os);
+            os.close();
+        } catch (Exception e) {
+            log.error(e.getMessage(), e); // 记录错误日志
+        }
+    }
+}

+ 54 - 0
mjava/src/main/java/com/malk/base/BaseDTO.java

@@ -0,0 +1,54 @@
+package com.malk.base;
+
+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
+ */
+@MappedSuperclass
+@Data
+@NoArgsConstructor
+@EntityListeners(AuditingEntityListener.class)
+public abstract class BaseDTO extends BaseParam {
+
+    // 若是实体若不直接在 com.malk 下, 可声明继承id, 避免编辑器提示 [不加也不影响编译以及运行]
+    @ExcelIgnore
+    @Id
+    @JsonIgnore
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private 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;
+}

+ 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语法]
+ * - 索引参数: 索引值从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<BaseDTO, Long> {
+
+}

+ 97 - 0
mjava/src/main/java/com/malk/base/BaseParam.java

@@ -0,0 +1,97 @@
+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 : 注在属性上,提供 get 方法
+ * @EqualsAndHashCode : 注在类上,提供对应的 equals 和 hashCode 方法
+ * @Log4j/@Slf4j : 注在类上,提供对应的 Logger 对象,变量名为 log
+ * @Builder:为类生成相对略微复杂的构建器API。来初始化实例对象::类名.属性(值).属性(值).build() 9. @Singular:在使用@Singular注释注释一个集合字段(使用@Builder注释类),lombok会将该构建器节点视为一个集合,并生成两个adder方法而不是setter方法::点一次集合增加一个元素
+ * @Builder.Default:在类中id和insertTime上都添加注解@Builder.Default,当在使用这个实体对象时,就不需要在为这两个字段进行初始化值
+ */
+@Data
+@NoArgsConstructor
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public abstract class BaseParam {
+
+    /**
+     * 对象拷贝: 若是复制一个对象, 建议使用 cloneParam 避免性能问题
+     */
+    @Deprecated
+    @SneakyThrows
+    public BaseParam copyParam() {
+        BaseParam comVo = this.getClass().newInstance();
+        BeanUtils.copyProperties(this, comVo);
+        return comVo;
+    }
+
+    /**
+     * todo: 4.11 继承Serializable后执行深拷贝, 不能类型转换, 不继承Serializable则胡返回空
+     * 对象拷贝: 复制一个新的对象, 避免条件被修改, 尤其并发下分页混乱情况
+     */
+    public BaseParam cloneParam() {
+        return ObjectUtil.clone(this);
+    }
+
+    /**
+     * 对象属性合并: jda之save接口会以传入数据为准,若传入为空或不传入,更新会置空。目前解决办法两种,通过注解实现JPQL/SQL,或者查询出数据,将未传入字段属性拷贝后再更新【性能消耗】
+     */
+    public void mergeParam(BaseParam 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;
+    }
+}

+ 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<BaseDTO, Long>, QuerydslPredicateExecutor<BaseDTO> {
+
+}
+

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

@@ -0,0 +1,33 @@
+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.malk"})
+ * 8. 查询: 使用native,查询列不匹配实体属性,会报错The column name xxx is not valid,返回Map可解决 (Map是Jpa的TupleBackedMa, 通过try取值)
+ * 9. 单数据源切换: 在dao与entity均添加mutual作为公共模块, 单数据源下服务于JpaConfiguration, 若是多数据源与PrimaryConfig一起作为主数据源配置
+ */
+@ConditionalOnProperty(name = "spel.multiSource", havingValue = "false")
+@Configuration
+@EnableJpaRepositories(basePackages = {"com.malk.repository.dao.mutual"})
+@EntityScan(basePackages = "com.malk.repository.entity.mutual")
+public class JpaConfiguration {
+
+    @Bean
+    PersistenceExceptionTranslationPostProcessor persistenceExceptionTranslationPostProcessor() {
+        return new PersistenceExceptionTranslationPostProcessor();
+    }
+}

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

@@ -0,0 +1,52 @@
+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("/**")
+                .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:39991/dev/assets/logo/logo-text.png [自定义拦截器添加路径排除: excludePathPatterns]
+     * 当在SpringBoot项目内添加网页资源时,在windows服务器,需要C:\Windows\System32下添加tomcat-native-1.2.14-win32-bin.zip内x64下两个文件, 重启项目
+     */
+    @Override
+    public void addResourceHandlers(ResourceHandlerRegistry registry) {
+        registry.addResourceHandler("/mjs/**").addResourceLocations("classpath:/static/mjs/");
+        registry.addResourceHandler("/assets/**").addResourceLocations("classpath:/assets/");
+        registry.addResourceHandler("/templates/**").addResourceLocations("classpath:/templates/");
+    }
+}

+ 9 - 0
mjava/src/main/java/com/malk/config/YDConfiguration.java

@@ -0,0 +1,9 @@
+package com.malk.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.ImportResource;
+
+@Configuration
+@ImportResource(locations = {"classpath:alibaba/aecpgateway.xml"})
+public class YDConfiguration {
+}

+ 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();
+    }
+}

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

@@ -0,0 +1,78 @@
+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 注解 [包含公共部分]
+ */
+@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.mutual"}  // dao层配置主数据&公共所在目录
+)
+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())
+                // 设置实体类所在目录: 包含主数据源与公共
+                .packages("com.malk.repository.entity.primary", "com.malk.repository.entity.mutual")
+                // 持久化单元名称,当存在多个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()));
+    }
+}
+

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

@@ -0,0 +1,74 @@
+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;
+
+/**
+ * 从数据源配置
+ */
+@ConditionalOnProperty(name = "spel.multiSource", havingValue = "true")
+@Configuration
+@EnableTransactionManagement
+@EnableJpaRepositories(
+        entityManagerFactoryRef = "entityManagerFactorySlave",
+        transactionManagerRef = "transactionManagerSlave",
+        basePackages = {"com.malk.repository.dao.slave"}
+)
+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())
+                // 设置实体类所在目录
+                .packages("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()));
+    }
+}
+

+ 139 - 0
mjava/src/main/java/com/malk/config/swagger3/SwaggerConfiguration.java

@@ -0,0 +1,139 @@
+//package com.malk.config.swagger3;
+//
+//import io.swagger.annotations.ApiOperation;
+//import io.swagger.models.auth.In;
+//import org.apache.commons.lang3.reflect.FieldUtils;
+//import org.springframework.boot.SpringBootVersion;
+//import org.springframework.context.annotation.Bean;
+//import org.springframework.context.annotation.Configuration;
+//import org.springframework.context.annotation.Profile;
+//import org.springframework.util.ReflectionUtils;
+//import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
+//import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+//import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+//import springfox.documentation.RequestHandler;
+//import springfox.documentation.builders.ApiInfoBuilder;
+//import springfox.documentation.builders.PathSelectors;
+//import springfox.documentation.builders.RequestHandlerSelectors;
+//import springfox.documentation.oas.annotations.EnableOpenApi;
+//import springfox.documentation.service.*;
+//import springfox.documentation.spi.DocumentationType;
+//import springfox.documentation.spi.service.contexts.SecurityContext;
+//import springfox.documentation.spring.web.plugins.Docket;
+//
+//import java.lang.reflect.Field;
+//import java.util.*;
+//import java.util.function.Predicate;
+//
+///**
+// * swagger3 加载 UI, 添加全局 path
+// * -
+// * 路径: http://localhost:9001/dev/swagger-ui/index.html#/
+// */
+//@Profile({"dev", "local"})
+//@Configuration
+//@EnableOpenApi
+//public class SwaggerConfiguration implements WebMvcConfigurer {
+//
+//    private final SwaggerProperties swaggerProperties;
+//
+//    public SwaggerConfiguration(SwaggerProperties swaggerProperties) {
+//        this.swaggerProperties = swaggerProperties;
+//    }
+//
+//    /// api bean ///
+//    private Docket _restApi(Predicate<RequestHandler> apis, Predicate<String> paths) {
+//        return new Docket(DocumentationType.OAS_30)
+//                .enable(swaggerProperties.getEnable())      // 定义是否开启swagger,false为关闭,可以通过变量控制
+//                .apiInfo(apiInfo())                         // 将api的元信息设置为包含在json ResourceListing响应中。
+//                .host(swaggerProperties.getTryHost())       // 接口调试地址
+//                .select()                                   // 选择哪些接口作为swagger的doc发布
+//                .apis(apis)
+//                .paths(PathSelectors.regex("(?!/ApiError.*).*"))
+//                .paths(paths)
+//                .build()
+//                .protocols(newHashSet("https", "http")) // 支持的通讯协议集合
+//                .securitySchemes(securitySchemes())          // 授权信息设置,必要的header token等认证信息
+//                .securityContexts(securityContexts());       // 授权信息全局应用
+//    }
+//
+//    /**
+//     * 文档说明
+//     */
+//    @Bean
+//    public Docket createRestApi() {
+//        return _restApi(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class), PathSelectors.any())
+//                .groupName("api");
+//    }
+//
+//    /**
+//     * 加载全部
+//     */
+//    @Bean
+//    public Docket createRestDev() {
+//        return _restApi(RequestHandlerSelectors.basePackage("com.malk"), PathSelectors.any())
+//                .groupName("dev");
+//    }
+//
+//    /**
+//     * API 页面头部信息
+//     */
+//    private ApiInfo apiInfo() {
+//        return new ApiInfoBuilder()
+//                .title(swaggerProperties.getApplicationName() + " Api Doc")
+//                .description(swaggerProperties.getApplicationDescription())
+//                .contact(new Contact("malk", "https://mc.100ali.com/", "pruple_boy@163.com"))
+//                .version("Application Version: " + swaggerProperties.getApplicationVersion() + ", Spring Boot Version: " + SpringBootVersion.getVersion())
+//                .build();
+//    }
+//
+//    /**
+//     * 设置授权信息
+//     */
+//    private List<SecurityScheme> securitySchemes() {
+//        ApiKey apiKey = new ApiKey("BASE_TOKEN", "token", In.HEADER.toValue());
+//        return Collections.singletonList(apiKey);
+//    }
+//
+//    /**
+//     * 授权信息全局应用
+//     */
+//    private List<SecurityContext> securityContexts() {
+//        return Collections.singletonList(
+//                SecurityContext.builder()
+//                        .securityReferences(Collections.singletonList(new SecurityReference("BASE_TOKEN", new AuthorizationScope[]{new AuthorizationScope("global", "")})))
+//                        .build()
+//        );
+//    }
+//
+//    @SafeVarargs
+//    private final <T> Set<T> newHashSet(T... ts) {
+//        if (ts.length > 0) {
+//            return new LinkedHashSet<>(Arrays.asList(ts));
+//        }
+//        return null;
+//    }
+//
+//    /**
+//     * 通用拦截器排除swagger设置,所有拦截器都会自动加swagger相关的资源排除信息
+//     */
+//    @SuppressWarnings("unchecked")
+//    @Override
+//    public void addInterceptors(InterceptorRegistry registry) {
+//        try {
+//            Field registrationsField = FieldUtils.getField(InterceptorRegistry.class, "registrations", true);
+//            List<InterceptorRegistration> registrations = (List<InterceptorRegistration>) ReflectionUtils.getField(registrationsField, registry);
+//            if (registrations != null) {
+//                for (InterceptorRegistration interceptorRegistration : registrations) {
+//                    interceptorRegistration
+//                            .excludePathPatterns("/swagger**/**")
+//                            .excludePathPatterns("/webjars/**")
+//                            .excludePathPatterns("/v3/**")
+//                            .excludePathPatterns("/doc.html");
+//                }
+//            }
+//        } catch (Exception e) {
+//            e.printStackTrace();
+//        }
+//    }
+//}

+ 39 - 0
mjava/src/main/java/com/malk/config/swagger3/SwaggerProperties.java

@@ -0,0 +1,39 @@
+//package com.malk.config.swagger3;
+//
+//import lombok.Data;
+//import org.springframework.boot.context.properties.ConfigurationProperties;
+//import org.springframework.stereotype.Component;
+//
+///**
+// * swagger3 参数读取
+// */
+//@Component
+//@Data
+//@ConfigurationProperties("swagger")
+//public class SwaggerProperties {
+//
+//    /**
+//     * 是否开启swagger,生产环境配置关闭
+//     */
+//    private Boolean enable;
+//
+//    /**
+//     * 项目应用名
+//     */
+//    private String applicationName;
+//
+//    /**
+//     * 项目版本信息
+//     */
+//    private String applicationVersion;
+//
+//    /**
+//     * 项目描述信息
+//     */
+//    private String applicationDescription;
+//
+//    /**
+//     * 接口调试地址
+//     */
+//    private String tryHost;
+//}

+ 207 - 0
mjava/src/main/java/com/malk/controller/McAliworkController.java

@@ -0,0 +1,207 @@
+package com.malk.controller;
+
+import com.malk.server.aliwork.YDConf;
+import com.malk.server.aliwork.YDParam;
+import com.malk.server.common.McR;
+import com.malk.service.aliwork.YDClient;
+import com.malk.service.aliwork.YDService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.validation.annotation.Validated;
+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;
+
+/**
+ * 错误抛出与拦截详见CatchException
+ */
+
+@Api(tags = "宜搭", value = "YD", description = "宜搭相关接口")
+@RestController
+@RequestMapping("/aliwork")
+public class McAliworkController {
+
+    @Autowired
+    private YDService service;
+
+    @Autowired
+    private YDClient client;
+
+    ////////////////////////////////////////
+    // 原子接口
+    ////////////////////////////////////////
+    
+    @ApiOperation(tags = {"mc"}, value = "表单数据查询", notes = "匹配条件, 查询表单, 返回formData列表")
+    @PostMapping("form/queryDataList")
+    McR queryFormDataMatchCondition(@RequestBody @Validated(YDParam.Retrieve_Condition.class) YDParam param) {
+        return McR.success(client.queryData(YDConf.API_FORM_QUERY_DATA, param));
+    }
+
+    /**
+     * 2.匹配条件, 查询流程, 返回data列表
+     */
+    @ApiOperation(value = "登1录", notes = "用户登录", httpMethod = "POST")
+    @PostMapping("process/queryDataList")
+    McR queryProcessDataMatchCondition(@RequestBody @Validated(YDParam.Retrieve_Condition.class) YDParam param) {
+        return McR.success(client.queryData(YDConf.API_PROCESS_QUERY_DATA, param));
+    }
+
+    /**
+     * 3.匹配条件, 查询表单, 返回formInstId列表
+     */
+    @PostMapping("form/queryIdList")
+    McR queryFormInstIdMatchCondition(@RequestBody @Validated(YDParam.Retrieve_Condition.class) YDParam param) {
+        return McR.success(client.queryData(YDConf.API_FORM_QUERY_ID, param));
+    }
+
+    /**
+     * 4.匹配条件, 查询流程, 返回processInstanceId列表
+     */
+    @PostMapping("process/queryIdList")
+    McR queryProcessInstanceIdMatchCondition(@RequestBody @Validated(YDParam.Retrieve_Condition.class) YDParam param) {
+        return McR.success(client.queryData(YDConf.API_FORM_QUERY_ID, param));
+    }
+
+    /**
+     * 5.匹配formInstId, 返回表单实例详情
+     */
+    @PostMapping("form/queryDetail")
+    McR queryFormDetail(@RequestBody @Validated(YDParam.Retrieve_FormInstId.class) YDParam param) {
+        return McR.success(client.operateData(YDConf.API_FORM_DETAIL, param));
+    }
+
+    /**
+     * 6.匹配processInstanceId, 返回流程实例详情
+     */
+    @PostMapping("process/queryDetail")
+    McR queryProcessDetail(@RequestBody @Validated(YDParam.Retrieve_ProcessInstanceId.class) YDParam param) {
+        return McR.success(client.operateData(YDConf.API_PROCESS_DETAIL, param));
+    }
+
+    /**
+     * 7.匹配processInstanceIds, 批量返回流程实例详情列表
+     */
+    @PostMapping("process/queryBatchDetail")
+    McR queryProcessBatchDetail(@RequestBody @Validated(YDParam.Retrieve_ProcessInstanceIds.class) YDParam param) {
+        return McR.success(client.queryData(YDConf.API_PROCESS_BATCH_DETAIL, param));
+    }
+
+    /**
+     * 8.新增表单数据, result返回实例Id
+     */
+    @PostMapping("form/create")
+    McR createFormData(@RequestBody @Validated(YDParam.Create.class) YDParam param) {
+        return McR.success(client.operateData(YDConf.API_FORM_CREATE, param));
+    }
+
+    /**
+     * 9.新增流程数据, result返回实例Id
+     */
+    @PostMapping("proces/create")
+    McR createProcessData(@RequestBody @Validated(YDParam.Create_Process.class) YDParam param) {
+        return McR.success(client.operateData(YDConf.API_PROCESS_CREATE, param));
+    }
+
+    /**
+     * 10.删除表单, 返回result为空, 若失败抛出异常于过滤器处理
+     */
+    @PostMapping("form/delete")
+    McR deleteFormData(@RequestBody @Validated(YDParam.Delete.class) YDParam param) {
+        return McR.success(client.operateData(YDConf.API_FORM_DELETE, param));
+    }
+
+    /**
+     * 11.删除流程, 返回result为空, 若失败抛出异常于过滤器处理
+     */
+    @PostMapping("process/delete")
+    McR deleteProcessData(@RequestBody @Validated(YDParam.Delete_ProcessInstanceId.class) YDParam param) {
+        return McR.success(client.operateData(YDConf.API_PROCESS_DELETE, param));
+    }
+
+    /**
+     * 12.更新表单, 返回result为空, 若失败抛出异常于过滤器处理
+     */
+    @PostMapping("form/upate")
+    McR updateFormData(@RequestBody @Validated(YDParam.Update.class) YDParam param) {
+        return McR.success(client.operateData(YDConf.API_FORM_UPDATE, param));
+    }
+
+    /**
+     * 13.更新表单, 返回result为空, 若失败抛出异常于过滤器处理
+     */
+    @PostMapping("process/upate")
+    McR updateProcessData(@RequestBody @Validated(YDParam.Update_ProcessInstanceId.class) YDParam param) {
+        return McR.success(client.operateData(YDConf.API_FORM_UPDATE, param));
+    }
+
+
+    ////////////////////////////////////////
+    // 服务接口
+    ////////////////////////////////////////
+
+    /**
+     * a. 并发查询全量数据
+     */
+    @PostMapping("server/queryAllByConcurrence")
+    McR queryAllDataByConcurrence(@RequestBody @Validated(YDParam.Retrieve_Condition.class) YDParam param) {
+        return McR.success(service.queryAllDataByConcurrence(param));
+    }
+
+    /**
+     * b. 同步查询全量数据
+     */
+    @PostMapping("server/queryAll")
+    McR queryAllData(@RequestBody @Validated(YDParam.Retrieve_Condition.class) YDParam param) {
+        return McR.success(service.queryAllData(param));
+    }
+
+    /**
+     * c. 查询条件实现: 批量删除, 一个分页
+     */
+    @PostMapping("server/batchDelete")
+    McR batchDelete(@RequestBody @Validated(YDParam.Retrieve_Condition.class) YDParam param) {
+        return McR.success(service.operatePageData(param));
+    }
+
+    /**
+     * d. 查询条件实现: 批量更新, 一个分页
+     */
+    @PostMapping("server/batchUpdate")
+    McR batchUpdate(@RequestBody @Validated({YDParam.Retrieve_Condition_Update.class}) YDParam param) {
+        return McR.success(service.operatePageData(param));
+    }
+
+    /**
+     * e. 查询条件实现: 批量删除, 一个切片
+     */
+    @PostMapping("server/limitDelete")
+    McR limitDelete(@RequestBody @Validated({YDParam.Retrieve_Condition.class}) YDParam param) {
+        return McR.success(service.operateLimitData(param));
+    }
+
+    /**
+     * f. 查询条件实现: 批量更新, 一个切片
+     */
+    @PostMapping("server/limitUpdate")
+    McR limitUpdate(@RequestBody @Validated(YDParam.Retrieve_Condition_Update.class) YDParam param) {
+        return McR.success(service.operateLimitData(param));
+    }
+
+    /**
+     * g. 查询条件实现: 全量删除
+     */
+    @PostMapping("server/deleteAll")
+    McR deleteAll(@RequestBody @Validated({YDParam.Retrieve_Condition.class}) YDParam param) {
+        return McR.success(service.operateAllData(param));
+    }
+
+    /**
+     * h. 查询条件实现: 全量更新
+     */
+    @PostMapping("server/updateALl")
+    McR updateAll(@RequestBody @Validated(YDParam.Retrieve_Condition_Update.class) YDParam param) {
+        return McR.success(service.operateAllData(param));
+    }
+}

+ 47 - 0
mjava/src/main/java/com/malk/controller/McLicenceController.java

@@ -0,0 +1,47 @@
+package com.malk.controller;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.malk.repository.dao.mutual.McAuthorizationDao;
+import com.malk.repository.entity.mutual.McAuthorizationDTO;
+import com.malk.server.common.McR;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 错误抛出与拦截详见 CatchException
+ * -
+ * 关于入参为JSONString类型: body内的json需要传入字符串, 若为对象会解析报错. 若是formData, 传入对象会自动转为字符串, 不会解析错误, 无需转为JSONString
+ * 关于Body(json)和FormData的说明:首先若是接收为实体类,添加@RequestBody可实例化JSON格式,不加则实例化是FormData格式。无实体前者是Map,后者需要通过request转map
+ */
+@Api(tags = "mc", value = "mcli", description = "mjava 相关接口")
+@Slf4j
+@RestController
+@RequestMapping("/licence")
+public class McLicenceController {
+
+    @Autowired
+    McAuthorizationDao useAuthDao;
+
+    @ApiOperation(tags = {"mc", "宜搭"}, value = "申请宜搭接口授权", notes = "申请通过后, 才可使用 mjava 相关接口能力")
+    @PostMapping("auth/approve")
+    McR approve(@RequestBody @Validated McAuthorizationDTO useAuthDo) {
+        McAuthorizationDTO recordDo = useAuthDao.findByAppType(useAuthDo.getAppType());
+        // jda之save接口会会以传入数据为准,[忽略空字段避免被覆盖]
+        if (ObjectUtil.isNotNull(recordDo)) {
+            recordDo.mergeParam(useAuthDo);
+        }
+        useAuthDao.save(useAuthDo);
+        return McR.success();
+    }
+
+    @ApiOperation(tags = {"mc", "宜搭"}, value = "查询宜搭接口授权", notes = "查询 appType, 应用是否取得调用权限")
+    @PostMapping("auth/detail")
+    McR detail(@RequestParam String appType) {
+        log.info("查询授权, {}", appType);
+        return McR.success(useAuthDao.findByAppType(appType));
+    }
+}

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

@@ -0,0 +1,158 @@
+package com.malk.controller;
+
+import com.malk.Util.UtilServlet;
+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.server.teambition.TBConf;
+import com.malk.service.aliwork.YDClient_DD;
+import com.malk.service.teambition.TBClient;
+import com.malk.service.teambition.TBService;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * TB-YD: tb2yd对接方案
+ */
+@Slf4j
+@RestController
+@RequestMapping("/tb")
+public class TB2YDController {
+
+    @Autowired
+    private TBClient tbClient;
+
+    @Autowired
+    private TBConf tbConf;
+
+    @Autowired
+    private YDConf ydConf;
+
+    @Autowired
+    private YDClient_DD ydClient_dd;
+
+    @Autowired
+    private TBService tbService;
+
+    /**
+     * 搜索企业模板模板
+     */
+    @PostMapping("template")
+    public McR getTemplateList(@RequestBody Map<String, String> data) {
+        return McR.success(tbClient.templateSearch(data.get("id"), data.get("name")));
+    }
+
+    /**
+     * 搜索用户
+     */
+    @GetMapping("user")
+    public McR searchUser(@RequestParam String search) {
+        return McR.success(tbClient.userSearch(search));
+    }
+
+    /**
+     * 更新用户信息
+     */
+    @PutMapping("user")
+    public McR updateUser(@RequestBody Map<String, ?> data) {
+        McException.assertParamException_Null(UtilServlet.isNull(data, "members"));
+        String operatorId = "";
+        if (UtilServlet.isNotNull(data, "userName")) {
+            operatorId = tbService.convertUserIdWithSearch(String.valueOf(data.get("userName"))).get(0);
+        }
+        return McR.success(tbClient.updateUser(operatorId, (List<Map>) data.get("members")));
+    }
+
+    /**
+     * 创建空白项目, 通过模板创建项目
+     */
+    @SneakyThrows
+    @PostMapping("project")
+    public McR createProjectWithTemplate(@RequestBody Map<String, String> data) {
+        McException.assertParamException_Null(UtilServlet.isNull(data, "projectName", "userName"));
+        String operatorId = tbService.convertUserIdWithSearch(String.valueOf(data.get("userName"))).get(0);
+        Map projectInfo = null;
+        if (UtilServlet.isNotNull(data, "templateId")) {
+            projectInfo = tbClient.projectCreateWithTemplate(data.get("projectName"), data.get("templateId"), operatorId);
+        } else {
+            projectInfo = tbClient.projectCreate(data.get("projectName"), operatorId);
+        }
+        // 更新宜搭 & tb项目信息
+        tbService.updateProject_YD_TB(projectInfo, data.get("userName"));
+        return McR.success(projectInfo);
+    }
+
+    /**
+     * 更新项目
+     */
+    @PutMapping("project/{id}")
+    public McR updateProject(@PathVariable String id, @RequestBody Map<String, String> data) {
+        McException.assertParamException_Null(UtilServlet.isNull(data, "userName"));
+        String operatorId = tbService.convertUserIdWithSearch(String.valueOf(data.get("userName"))).get(0);
+        return McR.success(tbClient.projectUpdate(id, data, operatorId));
+    }
+
+    /**
+     * 创建项目成员, 修改项目成员的角色
+     */
+    @RequestMapping(value = "project/member", method = {RequestMethod.POST, RequestMethod.PUT})
+    public McR createProjectMember(@RequestBody Map<String, Object> data) {
+        McException.assertParamException_Null(UtilServlet.isNull(data, "userName"));
+        List userIds = tbService.convertUserIdWithSearch_bulk(data);
+        if (UtilServlet.isNotNull(data, "roleIds")) {
+            return McR.success(tbClient.updateProjectMember(userIds, (List<String>) data.get("roleIds"), String.valueOf(data.get("projectId"))));
+        }
+        McException.assertParamException_Null(UtilServlet.isNull(data, "userName"));
+        String operatorId = tbService.convertUserIdWithSearch(String.valueOf(data.get("userName"))).get(0);
+        return McR.success(tbClient.createProjectMember(userIds, String.valueOf(data.get("projectId")), operatorId));
+    }
+
+    /**
+     * 获取项目角色列表
+     */
+    @GetMapping("{projectId}/role")
+    McR queryProjectRoles(@PathVariable String projectId) {
+        return McR.success(tbClient.queryProjectRoles(projectId));
+    }
+
+    /**
+     * 更新任务截止时间
+     */
+    @PutMapping("task/{id}")
+    McR updateTaskDueDate(@PathVariable String id, @RequestBody Map<String, String> data) {
+        McException.assertParamException_Null(UtilServlet.isNull(data, "userName", "dueDate"));
+        String operatorId = tbService.convertUserIdWithSearch(String.valueOf(data.get("userName"))).get(0);
+        return McR.success(tbClient.updateTeakDueDate(id, data.get("dueDate"), operatorId));
+    }
+
+    /**
+     * 新增表单数据
+     */
+    @PostMapping("form")
+    McR createFormData(@RequestBody @Validated(YDParam.Create.class) YDParam param) {
+        return McR.success(ydClient_dd.operateData(param, YDConf.TYPE_OPERATION.create));
+    }
+
+    /**
+     * 更新表单数据
+     */
+    @PutMapping("form")
+    McR updateFormData(@RequestBody @Validated(YDParam.Update.class) YDParam param) {
+        return McR.success(ydClient_dd.operateData(param, YDConf.TYPE_OPERATION.update));
+    }
+
+    /**
+     * 表单数据列表
+     */
+    @GetMapping("form")
+    McR queryFormData(@RequestBody @Validated(YDParam.Retrieve_Condition.class) YDParam param) {
+        return McR.success(ydClient_dd.queryData(param, YDConf.TYPE_QUERY.retrieve_list));
+    }
+}

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

@@ -0,0 +1,61 @@
+package com.malk.controller;
+
+import com.alibaba.fastjson.JSONObject;
+import com.malk.server.teambition.TBConf;
+import com.malk.service.teambition.TBService;
+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 java.util.Arrays;
+
+/**
+ * TB事件回调
+ * 回调可选择加密与不加密方式, tb发送成功为上游, 注册服务为下游, 支持ngrok外网穿透调用, 若应用发布后收不到回调, 新建一个带有时间的任务就会触发
+ * -
+ * 回调说明
+ * 1. 通过接口更操作的数据,也会与手动创建一样触发相同的回调, 除了项目更新接口调用实测不会触发回调, 手动修改正常回调
+ * 2. 项目创建会推送两次
+ * - 1. 在第二次推送多 { data: { project: { operatorId, url }} } 这两个字段内容
+ * - 2. 若是通过模板创建的项目,在两次项目更新回调中会回调一次 project.enable 回调, 其中任务只会回调创建, 不会回调更新
+ * 3. 任务创建, 会先回调创建事件, 接着立即回调任务更新事件 [若是通过模板创建, 任务只会回调创建, 不会回调更新]
+ * 4. 项目移入回收站,不会触发回调,删除后会触发项目与任务的 remove 事件; 若是将任务移入回收站, 会触发任务更新回调
+ */
+@Slf4j
+@RestController
+@RequestMapping("/tb")
+public class TBCallBackController {
+
+    @Autowired
+    private TBService tbService;
+
+    @PostMapping("callback")
+    public String callback(@RequestBody JSONObject eventJson) {
+
+        String success = "success";
+        String eventName = eventJson.getString("event");
+
+        if (TBConf.EVENT_VERIFY_HOOK.equals(eventName)) {
+            log.info("----- [TB]验证注册 -----");
+            return success;
+        }
+
+        if (Arrays.asList(TBConf.EVENT_TASK_CREATE, TBConf.EVENT_TASK_UPDATE).contains(eventName)) {
+            log.info("[TB]任务回调, {}, {}", eventName, eventJson);
+            tbService.callBackTask(eventJson);
+            return success;
+        }
+
+        if (Arrays.asList(TBConf.EVENT_PROJECT_CREAT, TBConf.EVENT_PROJECT_UPDATE).contains(eventName)) {
+            log.info("[TB]项目回调, {}, {}", eventName, eventJson);
+            tbService.callBackProject(eventJson);
+            return success;
+        }
+
+        log.info("----- [TB]已注册, 未处理的其它回调 -----, {}, {}", eventName, eventJson);
+        return success;
+    }
+}

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

@@ -0,0 +1,101 @@
+package com.malk.core;
+
+import com.malk.Filter.ExceptionNotice;
+import lombok.extern.slf4j.Slf4j;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
+import org.springframework.beans.factory.annotation.Autowired;
+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();
+            }
+        };
+    }
+}

+ 0 - 0
mjava/src/main/java/com/malk/repository/dao/mutual/AyUserAttachmentRelRepository.java


Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików