Browse Source

dlp 生产

pruple_boy 1 year ago
parent
commit
ece9426b50
67 changed files with 1822 additions and 99 deletions
  1. 60 0
      mjava-aipocloud/pom.xml
  2. 32 0
      mjava-aipocloud/src/main/java/com/malk/aipocloud/Boot.java
  3. 69 0
      mjava-aipocloud/src/main/java/com/malk/aipocloud/controller/ABController.java
  4. 22 0
      mjava-aipocloud/src/main/java/com/malk/aipocloud/controller/DDController.java
  5. 60 0
      mjava-aipocloud/src/main/java/com/malk/aipocloud/delegate/DDDelegate.java
  6. 26 0
      mjava-aipocloud/src/main/java/com/malk/aipocloud/repository/dao/ABEventDao.java
  7. 17 0
      mjava-aipocloud/src/main/java/com/malk/aipocloud/repository/dao/ABUserDao.java
  8. 151 0
      mjava-aipocloud/src/main/java/com/malk/aipocloud/repository/entity/AbEventPo.java
  9. 40 0
      mjava-aipocloud/src/main/java/com/malk/aipocloud/repository/entity/AbUserPo.java
  10. 52 0
      mjava-aipocloud/src/main/java/com/malk/aipocloud/schedule/ABScheduleTask.java
  11. 21 0
      mjava-aipocloud/src/main/java/com/malk/aipocloud/service/ABClient.java
  12. 255 0
      mjava-aipocloud/src/main/java/com/malk/aipocloud/service/impl/ABImplClient.java
  13. 48 0
      mjava-aipocloud/src/main/resources/application-dev.yml
  14. 35 0
      mjava-aipocloud/src/main/resources/application-prod.yml
  15. 34 0
      mjava-aipocloud/src/main/resources/application-test.yml
  16. 100 0
      mjava-aipocloud/src/main/resources/static/json/query.json
  17. 35 0
      mjava-aipocloud/src/test/resources/server.sh
  18. 0 1
      mjava-cloudpure/src/main/java/com/malk/cloudpure/controller/DDController.java
  19. 3 3
      mjava-cloudpure/src/main/java/com/malk/cloudpure/delegate/DDDelegate.java
  20. 2 2
      mjava-cloudpure/src/main/java/com/malk/cloudpure/service/impl/CPImplClient.java
  21. 1 1
      mjava-guyuan/src/main/java/com/malk/guyuan/filter/CatchException_YXY.java
  22. 2 0
      mjava-hake/src/main/java/com/malk/hake/service/impl/HKImplClient.java
  23. 38 0
      mjava-hake/src/main/resources/application-test.yml
  24. 3 1
      mjava-hangshi/src/main/java/com/malk/hangshi/controller/HSController.java
  25. 54 0
      mjava-hongfeng/pom.xml
  26. 32 0
      mjava-hongfeng/src/main/java/com/malk/hongfeng/Boot.java
  27. 159 0
      mjava-hongfeng/src/main/java/com/malk/hongfeng/controller/HFController.java
  28. 65 0
      mjava-hongfeng/src/main/resources/application-dev.yml
  29. 38 0
      mjava-hongfeng/src/main/resources/application-prod.yml
  30. 39 0
      mjava-hongfeng/src/test/resources/server.sh
  31. 56 0
      mjava-hongfeng/src/test/resources/winsw.xml
  32. 3 3
      mjava-laidi/src/main/java/com/malk/laidi/delegate/DDDelegate.java
  33. 33 0
      mjava-luyi/src/main/java/com/malk/luyi/controller/LYController.java
  34. 1 1
      mjava-mcli/src/main/java/com/malk/mcli/test/JSPTestController.java
  35. 35 9
      mjava-taisen/src/main/java/com/malk/taisen/controller/TSController.java
  36. 1 1
      mjava-yangu/src/main/java/com/malk/yangu/filter/CatchException_YXY.java
  37. 1 1
      mjava-zhuogao/src/main/resources/static/web/js/npm.ant-design-vue.f9e3cf7518d9033fa5d0.0.1.0.js
  38. 1 1
      mjava-zhuogao/src/main/resources/static/web/js/npm.quasar.71cbd0363a3dc849e7db.0.1.0.js
  39. 9 0
      mjava/src/main/java/com/malk/base/BasePo.java
  40. 1 1
      mjava/src/main/java/com/malk/config/WebConfiguration.java
  41. 3 11
      mjava/src/main/java/com/malk/controller/DDCallbackController.java
  42. 1 1
      mjava/src/main/java/com/malk/core/AsyncConfig.java
  43. 4 2
      mjava/src/main/java/com/malk/delegate/DDEvent_Delegate.java
  44. 14 0
      mjava/src/main/java/com/malk/delegate/McDelegate.java
  45. 3 3
      mjava/src/main/java/com/malk/delegate/impl/DDImplEvent_Delegate.java
  46. 21 0
      mjava/src/main/java/com/malk/delegate/impl/McImplDelegate.java
  47. 1 1
      mjava/src/main/java/com/malk/Filter/CatchException.java
  48. 1 1
      mjava/src/main/java/com/malk/Filter/ExceptionNotice.java
  49. 1 1
      mjava/src/main/java/com/malk/Filter/RequestFilter.java
  50. 1 1
      mjava/src/main/java/com/malk/Filter/RequestInterceptor.java
  51. 4 0
      mjava/src/main/java/com/malk/server/aliwork/YDConf.java
  52. 1 1
      mjava/src/main/java/com/malk/server/dingtalk/DDFormComponentDto.java
  53. 5 0
      mjava/src/main/java/com/malk/server/dingtalk/DDR.java
  54. 4 1
      mjava/src/main/java/com/malk/service/aliwork/YDService.java
  55. 28 9
      mjava/src/main/java/com/malk/service/aliwork/impl/YDServiceImpl.java
  56. 7 1
      mjava/src/main/java/com/malk/service/dingtalk/DDClient_Contacts.java
  57. 16 0
      mjava/src/main/java/com/malk/service/dingtalk/DDClient_Notice.java
  58. 1 1
      mjava/src/main/java/com/malk/service/dingtalk/DDClient_Log.java
  59. 0 1
      mjava/src/main/java/com/malk/service/dingtalk/DDService.java
  60. 19 0
      mjava/src/main/java/com/malk/service/dingtalk/impl/DDImplClient_Contacts.java
  61. 4 4
      mjava/src/main/java/com/malk/service/dingtalk/impl/DDImplClient_Event.java
  62. 37 0
      mjava/src/main/java/com/malk/service/dingtalk/impl/DDImplClient_Notice.java
  63. 2 2
      mjava/src/main/java/com/malk/service/dingtalk/impl/DDImplClient_WorkDaily.java
  64. 1 0
      mjava/src/main/java/com/malk/utils/UtilHttp.java
  65. 7 1
      mjava/src/main/java/com/malk/utils/UtilMap.java
  66. 0 30
      mjava/target/classes/META-INF/spring-configuration-metadata.json
  67. 2 2
      pom.xml

+ 60 - 0
mjava-aipocloud/pom.xml

@@ -0,0 +1,60 @@
+<?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-aipocloud</artifactId>
+    <description>云上艾柏, 天空卫士接入钉钉审批</description>
+
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+    </properties>
+
+    <dependencies>
+        <!-- 核心模块-->
+        <dependency>
+            <groupId>com.malk</groupId>
+            <artifactId>mjava</artifactId>
+            <version>${mjava.version}</version>
+        </dependency>
+        <!-- sqlserver 依赖-->
+        <dependency>
+            <groupId>com.microsoft.sqlserver</groupId>
+            <artifactId>mssql-jdbc</artifactId>
+            <scope>runtime</scope>
+        </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-aipocloud/src/main/java/com/malk/aipocloud/Boot.java

@@ -0,0 +1,32 @@
+package com.malk.aipocloud;
+
+import com.querydsl.jpa.impl.JPAQueryFactory;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Bean;
+import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
+
+import javax.persistence.EntityManager;
+
+/**
+ * corp项目: 扫描公共模块
+ * -
+ * 若是无需数据库模块, 配置无效地址也可启动, 引入mjava不支持直接 @SpringBootApplication(exclude = DataSourceAutoConfiguration.class) 配置
+ * 需要配置 jpa.hibernate.ddl-auto 为 none. 标识对表没有任何操作. 若不设置为 none, flyway.enabled 配置会无效, 在没有数库连接情况下程序无法启动
+ */
+@EnableJpaAuditing
+@SpringBootApplication(scanBasePackages = {"com.malk"})
+public class Boot {
+
+    public static void main(String... args) {
+        SpringApplication.run(Boot.class, args);
+    }
+
+    /**
+     * 让Spring管理JPAQueryFactory [不使用Qualifier详见mjava-Boot]
+     */
+    @Bean
+    public JPAQueryFactory jpaQueryFactory(EntityManager entityManager) {
+        return new JPAQueryFactory(entityManager);
+    }
+}

+ 69 - 0
mjava-aipocloud/src/main/java/com/malk/aipocloud/controller/ABController.java

@@ -0,0 +1,69 @@
+package com.malk.aipocloud.controller;
+
+/**
+ * 错误抛出与拦截详见 CatchException
+ */
+
+import com.malk.aipocloud.repository.dao.ABEventDao;
+import com.malk.aipocloud.service.ABClient;
+import com.malk.delegate.McDelegate;
+import com.malk.server.common.McR;
+import com.malk.utils.UtilDateTime;
+import lombok.Synchronized;
+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;
+
+@Slf4j
+@RestController
+@RequestMapping
+public class ABController {
+
+    @Autowired
+    private ABClient abClient;
+
+    @Autowired
+    private ABEventDao abEventDao;
+
+    /**
+     * 同步审批单
+     */
+    @PostMapping("process/sync")
+    McR syncProcess() {
+
+        abClient.syncProcess(UtilDateTime.parseLocalDateTime("2023-11-29 00:00:00"), UtilDateTime.parseLocalDateTime("2023-11-29 23:59:59"));
+//        LocalDateTime now = LocalDateTime.now().minusHours(8);
+//        abClient.syncProcess(now.minusMinutes(5), now);
+        return McR.success();
+    }
+
+    /**
+     * 通讯录同步
+     */
+    @Synchronized
+    @PostMapping("contact/sync")
+    McR syncContact() {
+
+        log.info("手动触发, 通讯录同步");
+        abClient.syncContact();
+        return McR.success();
+    }
+
+
+    @Autowired
+    private McDelegate mcDelegate;
+
+    @PostMapping("test")
+    McR test() {
+
+        log.info("11111");
+
+        mcDelegate.setTimeout(() -> {
+
+            log.info("22222");
+        }, 5000);
+        return McR.success();
+    }
+}

+ 22 - 0
mjava-aipocloud/src/main/java/com/malk/aipocloud/controller/DDController.java

@@ -0,0 +1,22 @@
+package com.malk.aipocloud.controller;
+
+import com.malk.controller.DDCallbackController;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 钉钉事件回调 3_1
+ * -
+ * [子项目直接继承即可有调用, 无需实现]
+ * -
+ * 注解 @RequestMapping 路径不能重复 [主子项目属同一个项目];
+ * 获取项目回调请求地址, https://mc.cloudpure.cn/frp/xxx/dd/callback [调试代理: frp + nginx]
+ */
+@Slf4j
+@RestController
+@RequestMapping("/dd")
+public class DDController extends DDCallbackController {
+
+
+}

+ 60 - 0
mjava-aipocloud/src/main/java/com/malk/aipocloud/delegate/DDDelegate.java

@@ -0,0 +1,60 @@
+package com.malk.aipocloud.delegate;
+
+import com.malk.aipocloud.service.ABClient;
+import com.malk.delegate.DDEvent;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Primary;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+
+/**
+ * OA审批事件 [主项目也有实现, 添加 @Primary 优先注入主项目实现]
+ * -
+ * 取消方案: 撤销和拒绝流程不继续执行连接器, 因此不使用连接器与轮询审批记录方案 [低效而且占用较高钉钉api调次数];
+ * 优化方案: 通过事件订阅实现实时同步所有审批状态, 定时查询钉钉回调失败记录 [配置钉钉事件Delegate, 添加定时任务]
+ */
+@Slf4j
+@Service
+@Primary
+public class DDDelegate implements DDEvent {
+
+    // 审批任务回调执行业务逻辑
+    @Async
+    @Override
+    public void executeEvent_Task_Finish(String processInstanceId, String processCode, boolean isAgree, String remark) {
+        log.info("executeEvent_Task_Finish");
+    }
+
+    @Async
+    @Override
+    public void executeEvent_Task_Start(String processInstanceId, String processCode) {
+        log.info("executeEvent_Task_Start");
+    }
+
+    @Async
+    @Override
+    public void executeEvent_Task_Redirect(String processInstanceId, String processCode) {
+        log.info("executeEvent_Task_Redirect");
+    }
+
+    @Async
+    @Override
+    public void executeEvent_Instance_Start(String processInstanceId, String processCode) {
+        log.info("executeEvent_Instance_Start");
+    }
+
+    @Autowired
+    private ABClient abClient;
+
+    // 审批实例回调执行业务逻辑
+    @Async
+    @Override
+    public void executeEvent_Instance_Finish(String processInstanceId, String processCode, boolean isAgree, boolean isTerminate, String staffId) {
+        log.info("executeEvent_Instance_Finish");
+        String approveResult = isAgree ? "agree" : "refuse";
+        if (isTerminate) approveResult = "terminated";
+        log.info("审批实例回调执行业务逻辑, {}", approveResult);
+        abClient.callbackApprove(processCode, processInstanceId, approveResult, staffId);
+    }
+}

+ 26 - 0
mjava-aipocloud/src/main/java/com/malk/aipocloud/repository/dao/ABEventDao.java

@@ -0,0 +1,26 @@
+package com.malk.aipocloud.repository.dao;
+
+import com.malk.aipocloud.repository.entity.AbEventPo;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+
+import javax.transaction.Transactional;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 钉钉花名册同步
+ */
+@Transactional
+public interface ABEventDao extends JpaRepository<AbEventPo, Long> {
+
+    AbEventPo findByInstanceId(String instanceId);
+
+    @Query("SELECT m from AbEventPo m WHERE (m.detectDateTime >= ?1 AND m.detectDateTime <= ?2) AND m.channelType = ?3 AND m.logonName = ?4 and (m.state = 'process' or (m.state = 'agree' and m.expire > ?5 ))")
+    List<AbEventPo> queryList(Date sTime, Date eTime, int channel, String name, Date expire);
+
+    @Modifying
+    @Query("update AbEventPo m set m.state = ?2, m.sync = ?3 where m.instanceId = ?1")
+    void updateState(String instanceId, String state, String sync);
+}

+ 17 - 0
mjava-aipocloud/src/main/java/com/malk/aipocloud/repository/dao/ABUserDao.java

@@ -0,0 +1,17 @@
+package com.malk.aipocloud.repository.dao;
+
+import com.malk.aipocloud.repository.entity.AbUserPo;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import javax.transaction.Transactional;
+
+/**
+ * 钉钉花名册同步
+ */
+@Transactional
+public interface ABUserDao extends JpaRepository<AbUserPo, Long> {
+
+    AbUserPo findByEmail(String email);
+
+    AbUserPo findByUserId(String userId);
+}

File diff suppressed because it is too large
+ 151 - 0
mjava-aipocloud/src/main/java/com/malk/aipocloud/repository/entity/AbEventPo.java


+ 40 - 0
mjava-aipocloud/src/main/java/com/malk/aipocloud/repository/entity/AbUserPo.java

@@ -0,0 +1,40 @@
+package com.malk.aipocloud.repository.entity;
+
+import com.malk.base.BasePo;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.persistence.Entity;
+import javax.persistence.Table;
+
+@Entity
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+@Table(name = "aipocloud_user")
+public class AbUserPo extends BasePo {
+
+    /**
+     * 员工Id
+     */
+    private String userId;
+    
+    /**
+     * 员工姓名
+     */
+    private String name;
+
+    /**
+     * email
+     */
+    private String email;
+
+
+    /**
+     * 部门名称
+     */
+    private String dept;
+}

+ 52 - 0
mjava-aipocloud/src/main/java/com/malk/aipocloud/schedule/ABScheduleTask.java

@@ -0,0 +1,52 @@
+package com.malk.aipocloud.schedule;
+
+import com.malk.aipocloud.service.ABClient;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.scheduling.annotation.Scheduled;
+
+import java.time.LocalDateTime;
+
+/**
+ * @EnableScheduling 开启定时任务 [配置参考McScheduleTask]
+ */
+@Slf4j
+@Configuration
+@EnableScheduling
+@ConditionalOnProperty(name = {"spel.scheduling"})
+public class ABScheduleTask {
+
+    @Autowired
+    private ABClient abClient;
+
+    /**
+     * 同步审批单: 查询5min, 避免拒绝\撤销重复推送问题
+     */
+    @Scheduled(cron = "0 1/5 * * * ?")
+    public void timer_1() {
+        try {
+            // todo utc时间和gmt+8时间,是utc加上8小时就是gmt时间
+            LocalDateTime now = LocalDateTime.now().minusHours(8);
+            abClient.syncProcess(now.minusMinutes(5), now);
+        } catch (Exception e) {
+            // 记录错误信息
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * 同步通讯录: 每小时3分执行, 错开审批单同步
+     */
+    @Scheduled(cron = "0 3 * * * ?")
+    public void timer_2() {
+        try {
+            abClient.syncContact();
+        } catch (Exception e) {
+            // 记录错误信息
+            e.printStackTrace();
+        }
+    }
+}

+ 21 - 0
mjava-aipocloud/src/main/java/com/malk/aipocloud/service/ABClient.java

@@ -0,0 +1,21 @@
+package com.malk.aipocloud.service;
+
+import java.time.LocalDateTime;
+
+public interface ABClient {
+
+    /**
+     * 通讯录同步
+     */
+    void syncContact();
+
+    /**
+     * 同步审批单 [prd 审批去重逻辑:用户 + 通道 + md5 + 有效期(审批通过且在生效时间内)] - 查询5min, 避免拒绝\撤销重复推送问题
+     */
+    void syncProcess(LocalDateTime sTime, LocalDateTime eTime);
+
+    /**
+     * 审批回调
+     */
+    void callbackApprove(String processCode, String processInstanceId, String state, String staffId);
+}

+ 255 - 0
mjava-aipocloud/src/main/java/com/malk/aipocloud/service/impl/ABImplClient.java

@@ -0,0 +1,255 @@
+package com.malk.aipocloud.service.impl;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.alibaba.fastjson.JSON;
+import com.malk.aipocloud.repository.dao.ABEventDao;
+import com.malk.aipocloud.repository.dao.ABUserDao;
+import com.malk.aipocloud.repository.entity.AbEventPo;
+import com.malk.aipocloud.repository.entity.AbUserPo;
+import com.malk.aipocloud.service.ABClient;
+import com.malk.server.dingtalk.DDConf;
+import com.malk.server.dingtalk.DDFormComponentDto;
+import com.malk.service.dingtalk.DDClient;
+import com.malk.service.dingtalk.DDClient_Contacts;
+import com.malk.service.dingtalk.DDClient_Notice;
+import com.malk.service.dingtalk.DDClient_Workflow;
+import com.malk.utils.*;
+import lombok.SneakyThrows;
+import lombok.Synchronized;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+@Slf4j
+@Service
+public class ABImplClient implements ABClient {
+
+    @Autowired
+    private DDClient ddClient;
+
+    @Autowired
+    private DDClient_Contacts ddClient_contacts;
+
+    @Autowired
+    private ABUserDao abUserDao;
+
+    /**
+     * 通讯录同步
+     */
+    @Override
+    @Synchronized
+    public void syncContact() {
+
+        List<Long> deptList = ddClient_contacts.getDepartmentId_all(ddClient.getAccessToken(), true, DDConf.TOP_DEPARTMENT);
+        for (long deptId : deptList) {
+            List<String> userIds = ddClient_contacts.listDepartmentUserId(ddClient.getAccessToken(), deptId);
+            if (userIds.size() == 0) {
+                continue;
+            }
+            String deptName = String.valueOf(ddClient_contacts.getDepartmentInfo(ddClient.getAccessToken(), deptId).get("name"));
+            for (String userId : userIds) {
+                Map userInfo = ddClient_contacts.getUserInfoById(ddClient.getAccessToken(), userId);
+                log.info("同步人员, {}", userInfo);
+                if (StringUtils.isBlank(UtilMap.getString(userInfo, "email"))) {
+                    continue;
+                }
+                AbUserPo userPo = AbUserPo.builder()
+                        .userId(userId)
+                        .name(String.valueOf(userInfo.get("name")))
+                        .dept(deptName)
+                        .email(String.valueOf(userInfo.get("email")))
+                        .build();
+                userPo.upsert(abUserDao.findByUserId(userId)); // 匹配更新
+                abUserDao.save(userPo);
+            }
+        }
+    }
+
+    @Autowired
+    private DDClient_Workflow ddClient_workflow;
+
+    @Autowired
+    private ABEventDao abEventDao;
+
+    @Autowired
+    private DDConf ddConf;
+
+    // DLP
+    private static final String CODE = "PROC-FA464E73-56A4-43DF-A9DD-CCE11114C129";
+    //private static final String CODE = "PROC-B501FF97-E6FC-41D5-8704-D988D4878F45"; // poc
+    private static final String USER = "dingTalk";
+    private static final int DLP_DAYS = 1;
+    private static final int DLP_NUM = 10;
+
+    private String _getUrl(String path) {
+        return "https://10.14.2.5:8443" + path; // 内网
+//        return "https://ucss.aipocloud.com:8443" + path; // 公网
+    }
+
+    // 认证逻辑: token作为后续接口密码
+    private String _getToken() {
+
+        Map header = UtilMap.map("content-type, user-agent", "application/json, QKAct-External-Client");
+        Map body = UtilMap.map("client-id", USER);
+        String rsp = UtilHttp.doRequest(UtilHttp.METHOD.POST, _getUrl("/qkact/v0/checkin"),
+                header, null, body, null, "dingtalk_dlp", "SkyP@ssw0rd1!");
+        Map data = (Map) JSON.parse(rsp);
+        return UtilMap.getString(data, "access-token");
+    }
+
+    /**
+     * 同步审批单 [prd 审批去重逻辑:用户 + 通道 + md5 + 有效期(审批通过且在生效时间内)] - 查询5min, 避免拒绝\撤销重复推送问题
+     */
+    @Override
+    @Synchronized
+    public void syncProcess(LocalDateTime sTime, LocalDateTime eTime) {
+
+        String parttern = "yyyy-MM-dd'T'HH:mm:ss";
+        String pwd = _getToken();
+        Map query = (Map) UtilFile.readJsonObjectFromResource("static/json/query.json");  // 事件查询条件: 查询5min, 避免拒绝\撤销重复推送问题
+        query = (Map) JSON.parse(JSON.toJSONString(query).replace("xxxx-xx-xxSxx:xx:xx", UtilDateTime.formatLocal(sTime, parttern)).replace("xxxx-xx-xxExx:xx:xx", UtilDateTime.formatLocal(eTime, parttern)));
+
+        Map header = UtilMap.map("content-type, user-agent", "application/json, QKAct-External-Client");
+        String rsp = UtilHttp.doRequest(UtilHttp.METHOD.POST, _getUrl("/sps/v1/proxy/dlp-endpoint/_search"), header, null, query, null, USER, pwd);
+        Map data = (Map) JSON.parse(rsp);
+        List<Map> list = (List<Map>) UtilMap.getList(UtilMap.getMap(data, "hits"), "hits");
+        for (Map record : list) {
+
+            record = UtilMap.getMap(record, "_source");
+            List<Map> attachments = (List<Map>) record.get("attachments");
+            if (UtilList.isEmpty(attachments)) {
+                continue;
+            }
+            // prd 审批去重逻辑:用户 + 通道 + md5 + 有效期(审批通过且在生效时间内)
+            Map<String, String> source = UtilMap.getMap(record, "source");
+            int channel = UtilMap.getInt(record, "channelType");
+            Date detect = UtilDateTime.parse(String.valueOf(record.get("detectDateTime")), "yyyy-MM-dd'T'HH:mm:ss.SSS");
+            Date expire = new Date(detect.getTime() + DLP_DAYS * 24 * 60 * 60 * 1000L);
+            List<AbEventPo> eventPoList = abEventDao.queryList(UtilDateTime.convertToDateFromLocalDateTime(sTime), UtilDateTime.convertToDateFromLocalDateTime(eTime), channel, source.get("logonName"), expire);
+            if (eventPoList.stream().filter(item -> attachments.get(0).get("fileMd5").equals(item.getAttachmentsDisplay().get(0).get("fileMd5"))).findAny().isPresent()) {
+                log.info("记录已存在, {}", record);
+                continue;
+            }
+            // 邮箱错误通知管理员
+            String userId = "";
+            AbUserPo userPo = abUserDao.findByEmail(source.get("mail"));
+            if (ObjectUtil.isNull(userPo)) {
+                Map message = UtilMap.map("msgtype", "text");
+                message.put("text", UtilMap.map("content", "「" + source.get("mail") + "」: 未匹配到客户,需要维护花名册邮箱信息。"));
+                ddClient_notice.sendNotification(ddClient.getAccessToken(), Arrays.asList(ddConf.getOperator()), null, false, message);
+                continue;
+            }
+            userId = userPo.getUserId();
+            //userId = ddConf.getOperator(); // test
+            List<Map<String, String>> policies = UtilMap.getList(record, "matchedPolicies");
+            List<String> tPolicies = policies.stream().map(item -> item.get("name")).collect(Collectors.toList());
+            List<Map<String, String>> rules = UtilMap.getList(record, "extendIncidentRules");
+            List<String> tRules = rules.stream().map(item -> item.get("name")).collect(Collectors.toList());
+            List<Map<String, String>> destinations = UtilMap.getList(record, "destinations");
+            List<String> tDestinations = destinations.stream().map(item -> item.get("displayName")).collect(Collectors.toList());
+
+            AbEventPo eventPo = AbEventPo.builder()
+                    .serialId(UtilMap.getInt(record, "serialId"))
+                    .userId(userId)
+                    .userName(userPo.getName())
+                    .logonName(source.get("logonName"))
+                    .department(source.get("department"))
+                    .fqdn(source.get("fqdn"))
+                    .ipAddress(source.get("ipAddress"))
+                    .email(source.get("mail"))
+                    .detectDateTime(detect)
+                    .expire(expire)
+                    .channelType(channel)
+                    .channelTypeDisplay(AbEventPo.convertChannelTypeDisplay(channel))
+                    .policiesName(String.join(", ", tPolicies))
+                    .classificationLevelNames(String.join(", ", UtilMap.getList(record, "classificationLevelNames")))
+                    .rulesName(String.join(", ", tRules))
+                    .tagNames(String.join(", ", UtilMap.getList(record, "tagNames")))
+                    .displayName(String.join(", ", tDestinations))
+                    .attachments(JSON.toJSONString(attachments))
+                    .attachmentsDisplay(attachments)
+                    .build();
+            // 组件数据格式化: prd 发送通道钉钉取值发送的目标名称
+            Map ruleForm = UtilMap.map("logonName, department, fqdn, detectDateTime, displayName, tagNames",
+                    "用户姓名, 用户所在部门, 计算机名称, DLP事件产生时间, 发送通道, 文件标签名称");
+            Map ruleDetail = UtilMap.map("attachmentsDisplay", UtilMap.map("filename, fileSize, fileMd5", "附件名称, 附件大小, 附件MD5"));
+            ruleForm.put("attachmentsDisplay", "附件信息");
+
+            // 推送钉钉审批
+            Map formData = (Map<String, ?>) JSON.parse(JSON.toJSONString(eventPo));
+            detect = new Date(detect.getTime() + 8 * 60 * 60 * 1000L); // prd utc转gmt显示
+            formData.putAll(UtilMap.map("detectDateTime, logonName", UtilDateTime.formatDateTime(detect), JSON.toJSONString(Arrays.asList(userId)))); // 默认值
+            List<Map> formValues = DDFormComponentDto.formatComponentValues(formData, ruleForm, ruleDetail);
+            Map extInfo = UtilMap.map("dept_id", DDConf.TOP_DEPARTMENT);
+            String processInstanceId = ddClient_workflow.doProcessInstances(ddClient.getAccessToken(), userId, CODE, formValues, extInfo);
+            eventPo.setInstanceId(processInstanceId);
+            eventPo.setState("process");
+            abEventDao.save(eventPo);
+            log.info("推送钉钉审批, {}", eventPo);
+        }
+    }
+
+    @Autowired
+    private DDClient_Notice ddClient_notice;
+
+    /**
+     * 审批回调
+     */
+    @SneakyThrows
+    @Override
+    public void callbackApprove(String processCode, String processInstanceId, String state, String staffId) {
+        // 回调结果
+        if (CODE.equals(processCode)) {
+
+            if (!"agree".equals(state)) {
+                abEventDao.updateState(processInstanceId, state, null);
+                return;
+            }
+            // prd: 审批去重逻辑:用户 + 通道 + md5 +有效期(审批通过且在生效时间内)
+            AbEventPo abEventPo = abEventDao.findByInstanceId(processInstanceId);
+            List<Map> attachments = abEventPo.getAttachmentsDisplay();
+            if (attachments.isEmpty()) {
+                return;
+            }
+            // 匹配审批人信息
+            Map userInfo = ddClient_contacts.getUserInfoById(ddClient.getAccessToken(), staffId);
+            Map data = UtilMap.map("approver, submitter_fqdn, approved_time, forensic", userInfo.get("name"), abEventPo.getFqdn(), new Date().getTime() / 1000L, 1);
+            data.putAll(UtilMap.map("submitter_name, submitter_ip, submitter_email", abEventPo.getLogonName(), abEventPo.getIpAddress().split(",")[0], abEventPo.getEmail()));
+            Map attachment = attachments.get(0);
+            data.putAll(UtilMap.map("file_size, file_md5, file_name", attachment.get("fileSize"), attachment.get("fileMd5"), attachment.get("filename")));
+            // prd channel 号 是100-106 的对应成22 通道进行回传
+            int channel = abEventPo.getChannelType();
+            if (channel >= 100 && channel <= 106) {
+                channel = 22;
+            }
+            data.put("channel", Arrays.asList(channel));
+            // 匹配单据必填项
+            //List<Map<String, String>> formComponentValues = UtilMap.getList(instance, "formComponentValues");
+            //String expired = formComponentValues.stream().filter(item -> "过期时间".equals(item.get("name"))).findAny().get().get("value");
+            //data.put("expired_time", UtilDateTime.parse(expired, "yyyy-MM-dd HH:mm").getTime());
+            //data.put("max_num", formComponentValues.stream().filter(item -> "可发送次数".equals(item.get("name"))).findAny().get().get("value"));
+            data.put("expired_time", abEventPo.getExpire().getTime() / 1000L);
+            data.put("max_num", DLP_NUM);
+
+            Map body = UtilMap.map("data", Arrays.asList(data));
+            Map header = UtilMap.map("content-type, user-agent", "application/json, QKAct-External-Client");
+            String rsp = UtilHttp.doRequest(UtilHttp.METHOD.POST, _getUrl("/qkact/v0/dlp/incident/approval"),
+                    header, null, body, null, USER, _getToken());
+            abEventDao.updateState(processInstanceId, state, rsp);
+            // 延迟5分钟通知
+            Thread.sleep(5 * 60 * 1000);
+            Map message = UtilMap.map("msgtype", "text");
+            String tips = "「" + abEventPo.getUserName() + "」,您的文件:「" + attachment.get("filename") + "」通过「" + abEventPo.getDisplayName() + "」外发的审批已通过,截止「" + UtilDateTime.formatDateTime(abEventPo.getExpire()) + "」前有效,请重新发起文件外发。";
+            message.put("text", UtilMap.map("content", tips));
+            ddClient_notice.sendNotification(ddClient.getAccessToken(), Arrays.asList(abEventPo.getUserId()), null, false, message);
+        }
+    }
+}

+ 48 - 0
mjava-aipocloud/src/main/resources/application-dev.yml

@@ -0,0 +1,48 @@
+# 环境配置
+server:
+  port: 9001
+  servlet:
+    context-path: /api/aipocloud
+
+# condition
+spel:
+  scheduling: false        # 定时任务是否执行
+  multiSource: false       # 是否多数据源配置
+
+spring:
+  # database
+  datasource:
+    driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
+    url: jdbc:sqlserver://159.27.208.34:1433;SelectMethod=cursor;DatabaseName=dingtalk
+    username: DingTalk
+    password: Dingding2023@#
+    # JPA
+    jpa:
+      database: sql_server
+      properties:
+        hibernate:
+          default_schema: dbo
+
+# 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: 2704247534
+  appKey: dingqmijjypfepl0tsuq
+  appSecret: dixOqjK4Zw8PajvrtY1mbKxs4DIJJJmq6WvqdSDTCStBWAPyTeobgQFxZ1VhH-Z3
+  corpId: ding321c72787fffc78b35c2f4657eb6378f
+  aesKey: vBtjZT6yIJXPywnOqmMHYUyPBpglTostOMpdQIMrSHk
+  token: ngnPYIwW5RfZwxnb1OjQJMb6U62NYKbvxGtcVaYe1hRaaKM1j8qG
+  operator: "095358016629044412"   # OA管理员账号
+
+

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

@@ -0,0 +1,35 @@
+# 环境配置
+server:
+  port: 9021
+  servlet:
+    context-path: /api/aipocloud
+
+# condition
+spel:
+  scheduling: true         # 定时任务是否执行
+  multiSource: false       # 是否多数据源配置
+
+spring:
+  # database
+  datasource:
+    hikari:
+      connection-init-sql: SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci           # SqlServer, Oracle 无需设置类型
+    driver-class-name: com.mysql.cj.jdbc.Driver
+    username: root
+    password: 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
+    hibernate:
+      ddl-auto: none
+
+# dingtalk
+dingtalk:
+  agentId: 2791266100
+  appKey: ding0dmmeh4ufremtbkp
+  appSecret: RHE6gWY-0PBUSFKNF6j__v1II9WD5h0u2TZDK3QwKKczivXid2br_SMWK-EKSDLT
+  corpId: dingbc79c496d04b812f35c2f4657eb6378f
+  aesKey: vBtjZT6yIJXPywnOqmMHYUyPBpglTostOMpdQIMrSHk
+  token: ngnPYIwW5RfZwxnb1OjQJMb6U62NYKbvxGtcVaYe1hRaaKM1j8qG
+  operator: "0220663466860353"   # OA管理员账号

+ 34 - 0
mjava-aipocloud/src/main/resources/application-test.yml

@@ -0,0 +1,34 @@
+# 环境配置
+server:
+  port: 9091
+  servlet:
+    context-path: /api/aipocloud
+
+# condition
+spel:
+  scheduling: true         # 定时任务是否执行
+  multiSource: false       # 是否多数据源配置
+
+spring:
+  # database
+  datasource:
+    driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
+    url: jdbc:sqlserver://159.27.208.34:1433;SelectMethod=cursor;DatabaseName=dingtalk
+    username: DingTalk
+    password: Dingding2023@#
+    # JPA
+    jpa:
+      database: sql_server
+      properties:
+        hibernate:
+          default_schema: dbo
+
+# dingtalk
+dingtalk:
+  agentId: 2791266100
+  appKey: ding0dmmeh4ufremtbkp
+  appSecret: RHE6gWY-0PBUSFKNF6j__v1II9WD5h0u2TZDK3QwKKczivXid2br_SMWK-EKSDLT
+  corpId: dingbc79c496d04b812f35c2f4657eb6378f
+  aesKey: vBtjZT6yIJXPywnOqmMHYUyPBpglTostOMpdQIMrSHk
+  token: ngnPYIwW5RfZwxnb1OjQJMb6U62NYKbvxGtcVaYe1hRaaKM1j8qG
+  operator: "0220663466860353"   # OA管理员账号

+ 100 - 0
mjava-aipocloud/src/main/resources/static/json/query.json

@@ -0,0 +1,100 @@
+{
+  "from": 0,
+  "size": 1000,
+  "query": {
+    "bool": {
+      "must": [
+        {
+          "range": {
+            "detectDateTime": {
+              "from": "xxxx-xx-xxSxx:xx:xx.000+0000",
+              "to": "xxxx-xx-xxExx:xx:xx.000+0000",
+              "include_lower": true,
+              "include_upper": true,
+              "boost": 1
+            }
+          }
+        },
+        {
+          "terms": {
+            "actionType": [
+              13
+            ],
+            "boost": 1
+          }
+        },
+        {
+          "terms": {
+            "isVisible": [
+              true
+            ],
+            "boost": 1
+          }
+        },
+        {
+          "bool": {
+            "should": [
+              {
+                "terms": {
+                  "isOrdinary": [
+                    true
+                  ],
+                  "boost": 1
+                }
+              },
+              {
+                "bool": {
+                  "must_not": [
+                    {
+                      "exists": {
+                        "field": "isOrdinary",
+                        "boost": 1
+                      }
+                    }
+                  ],
+                  "adjust_pure_negative": true,
+                  "boost": 1
+                }
+              }
+            ],
+            "adjust_pure_negative": true,
+            "boost": 1
+          }
+        }
+      ],
+      "adjust_pure_negative": true,
+      "boost": 1
+    }
+  },
+  "_source": {
+    "includes": [
+      "serialId",
+      "source.logonName",
+      "source.department",
+      "source.fqdn",
+      "detectDateTime",
+      "channelType",
+      "source.mail",
+      "source.ipAddress",
+      "matchedPolicies.name",
+      "classificationLevelNames",
+      "extendIncidentRules.name",
+      "tagNames",
+      "destinations.displayName",
+      "attachments.filename",
+      "attachments.fileSize",
+      "attachments.fileMd5"
+    ],
+    "excludes": [
+      "matchedPolicies.matchedRules.matchedConditions"
+    ]
+  },
+  "sort": [
+    {
+      "detectDateTime": {
+        "order": "desc"
+      }
+    }
+  ],
+  "track_total_hits": 2147483647
+}

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

@@ -0,0 +1,35 @@
+#!/bin/bash
+appname='mjava-aipocloud'
+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 - 1
mjava-cloudpure/src/main/java/com/malk/cloudpure/controller/DDController.java

@@ -18,5 +18,4 @@ import org.springframework.web.bind.annotation.RestController;
 @RequestMapping("/cp/dd")
 public class DDController extends DDCallbackController {
 
-
 }

+ 3 - 3
mjava-cloudpure/src/main/java/com/malk/cloudpure/delegate/DDDelegate.java

@@ -1,6 +1,6 @@
 package com.malk.cloudpure.delegate;
 
-import com.malk.delegate.DDEvent_Delegate;
+import com.malk.delegate.DDEvent;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.context.annotation.Primary;
 import org.springframework.scheduling.annotation.Async;
@@ -15,7 +15,7 @@ import org.springframework.stereotype.Service;
 @Slf4j
 @Service
 @Primary
-public class DDDelegate implements DDEvent_Delegate {
+public class DDDelegate implements DDEvent {
 
     // 审批任务回调执行业务逻辑
     @Async
@@ -46,7 +46,7 @@ public class DDDelegate implements DDEvent_Delegate {
     // 审批实例回调执行业务逻辑
     @Async
     @Override
-    public void executeEvent_Instance_Finish(String processInstanceId, String processCode, boolean isAgree, boolean isTerminate) {
+    public void executeEvent_Instance_Finish(String processInstanceId, String processCode, boolean isAgree, boolean isTerminate, String staffId) {
         log.info("executeEvent_Instance_Finish");
         String approveResult = isAgree ? "agree" : "refuse";
         if (isTerminate) approveResult = "terminated";

+ 2 - 2
mjava-cloudpure/src/main/java/com/malk/cloudpure/service/impl/CPImplClient.java

@@ -12,7 +12,7 @@ import com.malk.service.aliwork.YDService;
 import com.malk.service.dingtalk.DDClient;
 import com.malk.service.dingtalk.DDClient_Contacts;
 import com.malk.service.dingtalk.DDClient_Extension;
-import com.malk.service.dingtalk.DDClient_Log;
+import com.malk.service.dingtalk.DDClient_Report;
 import com.malk.service.xbongbong.XBBClient;
 import com.malk.utils.UtilDateTime;
 import com.malk.utils.UtilMap;
@@ -72,7 +72,7 @@ public class CPImplClient implements CPClient {
     }
 
     @Autowired
-    private DDClient_Log ddClient_log;
+    private DDClient_Report ddClient_log;
 
     @Autowired
     private DDClient ddClient;

+ 1 - 1
mjava-guyuan/src/main/java/com/malk/guyuan/filter/CatchException_YXY.java

@@ -1,6 +1,6 @@
 package com.malk.guyuan.filter;
 
-import com.malk.Filter.CatchException;
+import com.malk.filter.CatchException;
 import com.malk.server.common.McR;
 import com.tencentcloudapi.common.exception.TencentCloudSDKException;
 import lombok.extern.slf4j.Slf4j;

+ 2 - 0
mjava-hake/src/main/java/com/malk/hake/service/impl/HKImplClient.java

@@ -166,6 +166,7 @@ public class HKImplClient implements HKClient {
                 if (userInfo.containsKey("org_email")) {
                     data.put("email", userInfo.get("org_email"));
                 }
+                data.put("employeeStartDate", UtilDateTime.formatDate(new Date(UtilMap.getLong(userInfo, "hired_date"))));
                 data.put("employeeDept", deptName);
                 data.put("dtDepts", dpetCascade);
                 data.put("dtPersonGroup", "");
@@ -180,6 +181,7 @@ public class HKImplClient implements HKClient {
         for (Map<String, String> map : maps) {
             // prd 钉钉离职数据获取不到工号, 通过99999 monitor判断, 若存在则更新部门, 负责忽略
             Map data = UtilMap.map("employeeNumber, employeeName, dtPersonId, mobilePhone, employeeDept", "99999", map.get("name"), map.get("userId"), map.get("mobile"), "离职");
+            data.put("employeeFinishDate", map.get("leaveTime").split("T")[0]);
             _syncContact(data, map.get("userId"));
             log.info("离职人员, {}", JSON.toJSONString(UtilMap.map("employees", Arrays.asList(data))));
         }

+ 38 - 0
mjava-hake/src/main/resources/application-test.yml

@@ -0,0 +1,38 @@
+# 环境配置
+server:
+  port: 9019
+  servlet:
+    context-path: /api/hake
+
+# condition
+spel:
+  scheduling: false        # 定时任务是否执行
+  multiSource: false       # 是否多数据源配置
+
+spring:
+  # database
+  datasource:
+    hikari:
+      connection-init-sql: SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci           # SqlServer, Oracle 无需设置类型
+    driver-class-name: com.mysql.cj.jdbc.Driver
+    username: root
+    password: mu123
+    url: jdbc:mysql://127.0.0.1:3306/mjava?serverTimezone=Asia/Shanghai&useUnicode=yes&characterEncoding=UTF-8&useSSL=true
+  jpa:
+    database: MYSQL
+    database-platform: org.hibernate.dialect.MySQL57Dialect
+
+# dingtalk
+dingtalk:
+  agentId: 2757249888
+  appKey: ding3zk0vhfifznseopg
+  appSecret: Eso_p9HrVrJClEqcwbYuuOeDbS5Lb0e8Qq_HWtJm4_GYR38E-O5UEvakWpxXAvqq
+  corpId: dinge61fe69900ea236b35c2f4657eb6378f
+  aesKey:
+  token:
+  operator: ""   # OA管理员账号
+
+# aliwork
+aliwork:
+  appType: "APP_QWUVLI1R6XYUXWAOPF6O"
+  systemToken: "SE766NA1XW0F9PG19UX926VYF9RS31DVAYMNLJ27"

+ 3 - 1
mjava-hangshi/src/main/java/com/malk/hangshi/controller/HSController.java

@@ -66,10 +66,12 @@ public class HSController {
 
         Map param = UtilServlet.getParamMap(request);
         log.info("自愿上报安全管理, {}", param);
+        McException.assertParamException_Null(param, "corpName");
+        String type = "总公司".equals(param.get("corpName")) ? "总公司安全管理部" : "子公司安全部人员";
 
         List<Map> list = (List<Map>) ydClient.queryData(YDParam.builder()
                 .formUuid("FORM-3C866TC1RA29KJXD7XF4N4ZXW9932Z69W6CFLR")
-                .searchFieldJson(JSON.toJSONString(UtilMap.map("textField_lfw8yizv, selectField_lfw82zh0", "子公司安全部人员", param.get("corpName"))))
+                .searchFieldJson(JSON.toJSONString(UtilMap.map("textField_lfw8yizv, selectField_lfw82zh0", type, param.get("corpName"))))
                 .build(), YDConf.FORM_QUERY.retrieve_search_form).getData();
         List<String> userIds = list.stream().map(item -> String.valueOf(((Map) item.get("formData")).get("textField_lfw8yizu"))).collect(Collectors.toList());
         return userIds;

+ 54 - 0
mjava-hongfeng/pom.xml

@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>java-mcli</artifactId>
+        <groupId>com.malk</groupId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>mjava-hongfeng</artifactId>
+    <description>虹口\虹峰人力资源</description>
+
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+    </properties>
+
+    <dependencies>
+        <!-- 核心模块-->
+        <dependency>
+            <groupId>com.malk</groupId>
+            <artifactId>mjava</artifactId>
+            <version>${mjava.version}</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <version>2.1.1.RELEASE</version>
+                <configuration>
+                    <includeSystemScope>true</includeSystemScope>
+                    <!-- 如果没有该配置,devtools不会生效: 打包时关闭 -->
+                    <fork>false</fork>
+                    <!-- 避免中文乱码 -->
+                    <jvmArguments>-Dfile.encoding=UTF-8</jvmArguments>
+                </configuration>
+                <!-- 允许生成可运行jar -->
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+        <finalName>${project.artifactId}</finalName>
+    </build>
+</project>

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

@@ -0,0 +1,32 @@
+package com.malk.hongfeng;
+
+import com.querydsl.jpa.impl.JPAQueryFactory;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Bean;
+import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
+
+import javax.persistence.EntityManager;
+
+/**
+ * corp项目: 扫描公共模块
+ * -
+ * 若是无需数据库模块, 配置无效地址也可启动, 引入mjava不支持直接 @SpringBootApplication(exclude = DataSourceAutoConfiguration.class) 配置
+ * 需要配置 jpa.hibernate.ddl-auto 为 none. 标识对表没有任何操作. 若不设置为 none, flyway.enabled 配置会无效, 在没有数库连接情况下程序无法启动
+ */
+@EnableJpaAuditing
+@SpringBootApplication(scanBasePackages = {"com.malk"})
+public class Boot {
+
+    public static void main(String... args) {
+        SpringApplication.run(Boot.class, args);
+    }
+
+    /**
+     * 让Spring管理JPAQueryFactory [不使用Qualifier详见mjava-Boot]
+     */
+    @Bean
+    public JPAQueryFactory jpaQueryFactory(EntityManager entityManager) {
+        return new JPAQueryFactory(entityManager);
+    }
+}

+ 159 - 0
mjava-hongfeng/src/main/java/com/malk/hongfeng/controller/HFController.java

@@ -0,0 +1,159 @@
+package com.malk.hongfeng.controller;
+
+/**
+ * 错误抛出与拦截详见 CatchException
+ */
+
+import cn.hutool.core.util.ObjectUtil;
+import com.alibaba.fastjson.JSON;
+import com.malk.server.aliwork.YDConf;
+import com.malk.server.aliwork.YDParam;
+import com.malk.server.common.McException;
+import com.malk.server.common.McR;
+import com.malk.server.dingtalk.DDConf;
+import com.malk.service.aliwork.YDClient;
+import com.malk.service.aliwork.YDService;
+import com.malk.service.dingtalk.DDClient;
+import com.malk.service.dingtalk.DDClient_Contacts;
+import com.malk.utils.UtilMap;
+import com.malk.utils.UtilServlet;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+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 javax.servlet.http.HttpServletRequest;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+@Slf4j
+@RestController
+@RequestMapping
+public class HFController {
+
+    @Autowired
+    private YDClient ydClient;
+
+    @Autowired
+    private DDClient ddClient;
+
+    @Autowired
+    private DDClient_Contacts ddClient_contacts;
+
+    @PostMapping("user")
+    McR user(HttpServletRequest request) {
+        Map data = UtilServlet.getParamMap(request);
+        log.info("部门信息, {}", data);
+
+        long deptId = _getDptId(UtilMap.getString(data, "dept"), UtilMap.getString(data, "type"), null);        // 兼容用户创建
+        Map extInfo = UtilMap.map("email", "hf@dingtalk.com");
+        try {
+            Map result = ddClient_contacts.createUser_dingTalk(ddClient.getAccessToken(), UtilMap.getString(data, "cardNo"), "dingtalk123", UtilMap.getString(data, "name"), Arrays.asList(deptId), extInfo);
+            // 更新宜搭用户ID
+            String instId = UtilMap.getString(data, "instId");
+            ydClient.operateData(YDParam.builder()
+                    .formInstanceId(instId)
+                    .updateFormDataJson(JSON.toJSONString(UtilMap.map("employeeField_lozj5ga2, textField_lozi7dtc", Arrays.asList(result.get("userid")), deptId)))
+                    .build(), YDConf.FORM_OPERATION.update);
+        } catch (McException e) {
+            log.error(e.getMessage(), e);  // 记录错误日志
+        }
+        return McR.success();
+    }
+
+    @PostMapping("dept")
+    McR dept(HttpServletRequest request) {
+        Map data = UtilServlet.getParamMap(request);
+        log.info("部门信息, {}", data);
+
+        _getDptId(UtilMap.getString(data, "dept"), UtilMap.getString(data, "type"), UtilMap.getString(data, "instId"));
+        return McR.success();
+    }
+
+    // 获取部门ID, 不存在创建
+    long _getDptId(String dept, String belong, String instId) {
+
+        // 查询是否存在单位
+        List<Map> dataList = (List<Map>) ydClient.queryData(YDParam.builder()
+                .formUuid("FORM-F8666NB1TJPAM8J46DE07DC7BWUU2S8DM1RHL2")
+                .searchFieldJson(JSON.toJSONString(UtilMap.map("textField_lhr1qpgw", dept)))
+                .build(), YDConf.FORM_QUERY.retrieve_search_form).getData();
+        if (dataList.size() > 0) {
+            Map formData = null;
+            if (dataList.size() == 1) {
+                formData = (Map) dataList.get(0).get("formData");
+            } else {
+                // ppExt: 宜搭单行输入框, 会命中模糊查询
+                formData = dataList.stream().filter(item -> dept.equals(item.get("textField_lhr1qpgw"))).findAny().get();
+            }
+            // 导入触发, 数据可立即查询到. 此时部门还未更新, 过滤
+            if (StringUtils.isNotBlank(UtilMap.getString(formData, "textField_lozi7dtc"))) {
+                return UtilMap.getLong(formData, "textField_lozi7dtc");
+            }
+        }
+        // 不存在创建新部门
+        long deptId = DDConf.TOP_DEPARTMENT;
+        if ("虹口".equals(belong)) {
+            deptId = 906069409L;
+        }
+        if ("虹峰".equals(belong)) {
+            deptId = 906484067L;
+        }
+        Map result = ddClient_contacts.createDepartment(ddClient.getAccessToken(), dept, deptId, UtilMap.map("create_dept_group", false));
+        long dept_id = UtilMap.getLong(result, "dept_id");
+        // 更新宜搭部门ID
+        if (StringUtils.isNotBlank(instId)) {
+            ydClient.operateData(YDParam.builder()
+                    .formInstanceId(instId)
+                    .updateFormDataJson(JSON.toJSONString(UtilMap.map("textField_lozi7dtc", dept_id)))
+                    .build(), YDConf.FORM_OPERATION.update);
+        } else {
+            // 兼容用户创建
+            ydClient.operateData(YDParam.builder()
+                    .formUuid("FORM-F8666NB1TJPAM8J46DE07DC7BWUU2S8DM1RHL2")
+                    .formDataJson(JSON.toJSONString(UtilMap.map("textField_lozi7dtc, textField_lhr1qpgw, selectField_lhyev50s", dept_id, dept, belong)))
+                    .build(), YDConf.FORM_OPERATION.create);
+        }
+        return dept_id;
+    }
+
+    @Autowired
+    private YDService ydService;
+
+    @PostMapping("test")
+    McR test() {
+
+        List<Map> dataList = ydService.queryFormData_all(YDParam.builder()
+                .formUuid("FORM-CP7660811C7A9ZSIE41VABIL55Z72X2NMYPHLD1")
+                .dynamicOrder(JSON.toJSONString(UtilMap.map("textField_lhr135cr", "+")))
+                .build());
+        log.info("同步数量, {}", dataList.size());
+        dataList.forEach(item -> {
+
+            log.info("修改人员, {}", item.get("textField_lhr135cl"));
+            List<Map> details = (List<Map>) item.get("tableField_lnwm6abn");
+            details = details.stream().filter(detail -> {
+                if (ObjectUtil.isNotNull(detail.get("selectField_logsa5ru"))) {
+                    detail.put("textField_logsa5rb", item.get("selectField_lozq62h7"));
+                    return true;
+                }
+                return false;
+            }).collect(Collectors.toList());
+            Map data = UtilMap.map("tableField_lnwm6abn", details);
+            if (StringUtils.isBlank(String.valueOf(item.get("selectField_loxqxuv7")))) {
+                data.put("selectField_loxqxuv7", item.get("textField_lhr135dc"));
+            }
+            ydClient.operateData(YDParam.builder()
+                    .formInstanceId(item.get("instanceId").toString())
+                    .updateFormDataJson(JSON.toJSONString(data))
+                    .useLatestVersion(true)
+                    .build(), YDConf.FORM_OPERATION.update);
+        });
+
+        return McR.success();
+    }
+}

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

@@ -0,0 +1,65 @@
+# 环境配置
+server:
+  port: 9001
+  servlet:
+    context-path: /api/hongfeng
+
+# condition
+spel:
+  scheduling: false        # 定时任务是否执行
+  multiSource: false       # 是否多数据源配置
+
+spring:
+  # database
+  datasource:
+    hikari:
+      connection-init-sql: SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci           # SqlServer, Oracle 无需设置类型
+    driver-class-name: com.mysql.cj.jdbc.Driver
+    username: root
+    password: mu123
+    url: jdbc:mysql://127.0.0.1:3306/mjava?serverTimezone=Asia/Shanghai&useUnicode=yes&characterEncoding=UTF-8&useSSL=true
+    # 主库
+    primary:
+      username: root
+      password: mu123
+      jdbc-url: jdbc:mysql://127.0.0.1:3306/mjava?serverTimezone=Asia/Shanghai&useUnicode=yes&characterEncoding=UTF-8&useSSL=true
+    # 从库
+    slave:
+      username: root
+      password: mu123
+      jdbc-url: jdbc:mysql://127.0.0.1:3306/mjava_slave?serverTimezone=Asia/Shanghai&useUnicode=yes&characterEncoding=UTF-8&useSSL=true
+  jpa:
+    hibernate:
+      ddl-auto: none      # JPA对表没有任何操作
+    show-sql: true
+    database: MYSQL
+    database-platform: org.hibernate.dialect.MySQL57Dialect
+
+# filepath
+file:
+  path:
+    file: /Users/malk/server/_Tool/var/mjava/tmp/file/
+    image: /Users/malk/server/_Tool/var/mjava/tmp/image/
+    tmp: /Users/malk/server/_Tool/var/mjava/tmp/
+  source:
+    fonts: /Users/malk/server/_Tool/fonts/simsun.ttc
+logging:
+  file:
+    path: /Users/malk/server/_Tool/var/mjava/log
+
+# dingtalk
+dingtalk:
+  agentId:
+  appKey: dingwucwzoklmsoeilhs
+  appSecret: jtRFRosFr0DCN8XWRnco_zFVETBMqEs7tTMaJcznTJJYoh0VcLdHgxbyABVW-DNK
+  corpId: ding9fccaae9cab903dbf5bf40eda33b7ba0
+  aesKey:
+  token:
+  operator: ""   # OA管理员账号
+
+# aliwork
+aliwork:
+  appType: APP_DEBW3QRX4VB52DKXD5IA
+  systemToken: TL866181KMTAV5TEDV37X6VDTH692GIJMYPHL0
+
+

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

@@ -0,0 +1,38 @@
+# 环境配置
+server:
+  port: 9020
+  servlet:
+    context-path: /api/hongfeng
+
+# condition
+spel:
+  scheduling: true         # 定时任务是否执行
+  multiSource: false       # 是否多数据源配置
+
+spring:
+  # database
+  datasource:
+    hikari:
+      connection-init-sql: SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci           # SqlServer, Oracle 无需设置类型
+    driver-class-name: com.mysql.cj.jdbc.Driver
+    username: root
+    password: mu123
+    url: jdbc:mysql://127.0.0.1:3306/mjava?serverTimezone=Asia/Shanghai&useUnicode=yes&characterEncoding=UTF-8&useSSL=true
+  jpa:
+    database: MYSQL
+    database-platform: org.hibernate.dialect.MySQL57Dialect
+
+# dingtalk
+dingtalk:
+  agentId: 2591532974
+  appKey: dingwucwzoklmsoeilhs
+  appSecret: jtRFRosFr0DCN8XWRnco_zFVETBMqEs7tTMaJcznTJJYoh0VcLdHgxbyABVW-DNK
+  corpId: ding9fccaae9cab903dbf5bf40eda33b7ba0
+  aesKey:
+  token:
+  operator: ""   # OA管理员账号
+
+# aliwork
+aliwork:
+  appType: APP_DEBW3QRX4VB52DKXD5IA
+  systemToken: TL866181KMTAV5TEDV37X6VDTH692GIJMYPHL0

+ 39 - 0
mjava-hongfeng/src/test/resources/server.sh

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

+ 56 - 0
mjava-hongfeng/src/test/resources/winsw.xml

@@ -0,0 +1,56 @@
+<!--
+  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</id>
+    <!-- 启动服务名称 -->
+    <name>mjava</name>
+    <!-- 对服务的描述 -->
+    <description>标准化后端接口, 自用脚手架封装</description>
+    <!-- 「jdk需要安装」启动的可执行文件:若未配置环境变量executable需要执行绝对路径 -->
+    <!-- <executable>java</executable> -->
+    <executable>C:\Program Files\DingTalkServer\jdk1.8.0_221\bin\java</executable>
+    <!-- Xmx256m 代表堆内存最大值为256MB -jar后面的是项目名 [-Dfile.encoding=UTF-8 Windows会乱码, 但Mac上VSCode正常] -->
+    <arguments>-Xms256m -Xmx256m -jar mjava.jar</arguments>
+    <!-- <arguments>-Xms256m -Xmx256m -Dfile.encoding=UTF-8 -jar mjava.jar</arguments> -->
+    <!-- 服务的启动模式:默认Automatic -->
+    <startmode>Automatic</startmode>
+    <!-- 日志地址 -->
+    <logpath>%BASE%\ws</logpath>
+    <!-- 日志模式 -->
+    <logmode>rotate</logmode>
+</service>
+

+ 3 - 3
mjava-laidi/src/main/java/com/malk/laidi/delegate/DDDelegate.java

@@ -1,6 +1,6 @@
 package com.malk.laidi.delegate;
 
-import com.malk.delegate.DDEvent_Delegate;
+import com.malk.delegate.DDEvent;
 import com.malk.laidi.service.LDClient;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -17,7 +17,7 @@ import org.springframework.stereotype.Service;
 @Slf4j
 @Service
 @Primary
-public class DDDelegate implements DDEvent_Delegate {
+public class DDDelegate implements DDEvent {
 
     // 审批任务回调执行业务逻辑
     @Async
@@ -50,7 +50,7 @@ public class DDDelegate implements DDEvent_Delegate {
     // 审批实例回调执行业务逻辑
     @Async
     @Override
-    public void executeEvent_Instance_Finish(String processInstanceId, String processCode, boolean isAgree, boolean isTerminate) {
+    public void executeEvent_Instance_Finish(String processInstanceId, String processCode, boolean isAgree, boolean isTerminate, String staffId) {
         log.info("executeEvent_Instance_Finish");
         if ("PROC-BEC29A2E-D8CA-4B66-B35A-67AC1C1EBB36".endsWith(processCode) && isAgree) {
             ldClient.syncCC(processInstanceId);

+ 33 - 0
mjava-luyi/src/main/java/com/malk/luyi/controller/LYController.java

@@ -12,6 +12,8 @@ import com.malk.server.common.McR;
 import com.malk.server.dingtalk.DDR_New;
 import com.malk.service.aliwork.YDClient;
 import com.malk.utils.UtilMap;
+import com.malk.utils.UtilServlet;
+import lombok.SneakyThrows;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.PostMapping;
@@ -19,6 +21,7 @@ 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.List;
 import java.util.Map;
@@ -110,6 +113,36 @@ public class LYController {
         return McR.success(images);
     }
 
+    /**
+     * 客户关联渠道
+     */
+    @PostMapping("relation")
+    @SneakyThrows
+    McR relation(HttpServletRequest request) {
+
+        Map data = UtilServlet.getParamMap(request);
+        log.info("细胞查询, {}", data);
+
+        if ("否".equals(data.get("type"))) {
+            return McR.success();
+        }
+        Thread.sleep(3000); // todo: 公共延迟方法
+        List<Map> dataList = (List<Map>) ydClient.queryData(YDParam.builder()
+                .formUuid("FORM-6YA66WB1BATD2ZPWA2Z2S813SFFC3EGRBHYLLH")
+                .searchFieldJson(JSON.toJSONString(UtilMap.map("textField_lozlyqpb", data.get("code"))))
+                .build(), YDConf.FORM_QUERY.retrieve_search_form).getData();
+        if (dataList.size() == 1) {
+            Map formData = (Map) dataList.get(0).get("formData");
+            String title = String.valueOf(formData.get("textField_llyhc8ca"));
+            List<Map> associations = YDConf.associationForm("APP_I45JU7MQMRRVZ472GS1U", "FORM-6YA66WB1BATD2ZPWA2Z2S813SFFC3EGRBHYLLH", String.valueOf(dataList.get(0).get("formInstanceId")), title, title, false);
+            ydClient.operateData(YDParam.builder()
+                    .formInstanceId(String.valueOf(data.get("instanceId")))
+                    .updateFormDataJson(JSON.toJSONString(UtilMap.map("textField_llkbx3ob, associationFormField_lm4hoevr, textField_lm4hoevs", formData.get("serialNumberField_llyhc8ck"), associations, title)))
+                    .build(), YDConf.FORM_OPERATION.update);
+        }
+        return McR.success();
+    }
+
     @PostMapping("test")
     McR test() {
 

+ 1 - 1
mjava-mcli/src/main/java/com/malk/mcli/test/JSPTestController.java

@@ -24,7 +24,7 @@ import java.util.Map;
  * 4. jsp文件有三种语法, JSP表达式 / EL表达式 / JSTL标签库; 正常书写 html, 数据获取通过 ModelAndView 传递进来 [c标签最实用]
  * 5. 在controller内返回 ModelAndView, 渲染为jsp文件名称, 参数传递可用 request.setAttribute / modelAndView.addObject
  * 6. 可选: 在启动配置内, environment - working directory 下填写 $MODULE_WORKING_DIR$
- * 7. 进阶: 实现jsp效果后, 后续可通过 mvc 解耦视图和控制器, view 可使用模板依赖进行统一管理与继承, com.zhuogao.zhuogao.controller 可通过 servlet 统一接管
+ * 7. 进阶: 实现jsp效果后, 后续可通过 mvc 解耦视图和控制器, view 可使用模板依赖进行统一管理与继承, com.zhuogao.zhuogao.com.malk.hongfeng.controller 可通过 servlet 统一接管
  **/
 @Slf4j
 @Profile({"dev", "test"})

+ 35 - 9
mjava-taisen/src/main/java/com/malk/taisen/controller/TSController.java

@@ -7,12 +7,14 @@ package com.malk.taisen.controller;
 import com.alibaba.fastjson.JSON;
 import com.malk.server.aliwork.YDConf;
 import com.malk.server.aliwork.YDParam;
+import com.malk.server.common.McException;
 import com.malk.server.common.McR;
 import com.malk.server.dingtalk.DDR_New;
 import com.malk.service.aliwork.YDClient;
 import com.malk.service.aliwork.YDService;
 import com.malk.utils.UtilDateTime;
 import com.malk.utils.UtilMap;
+import com.malk.utils.UtilServlet;
 import com.malk.utils.UtilString;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -21,6 +23,7 @@ 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.Collections;
 import java.util.Date;
 import java.util.List;
@@ -38,11 +41,35 @@ public class TSController {
     @Autowired
     private YDConf ydConf;
 
+    @Autowired
+    private YDService ydService;
+
     /**
      * 保存提交版本
      */
     @PostMapping("backend")
-    McR backend(@RequestBody Map<String, String> data) {
+    McR backend(HttpServletRequest request) {
+
+        Map data = UtilServlet.getParamMap(request);
+        log.info("保存提交版本, {}", data);
+        McException.assertParamException_Null(data, "instanceId, type");
+        if ("手动同步".equals(data.get("type"))) {
+            return McR.success();
+        }
+        // type 自动备份: 原报销单提交后同步, 手动同步: 版本备份报价单提交后更新
+        String instanceId = String.valueOf(data.get("instanceId"));
+        String updateInstanceId = String.valueOf(data.get("updateInstanceId"));
+        Map update = UtilMap.map("radioField_lp9wi7lb, textField_lowp416t", data.get("type"), instanceId);
+        ydService.mirrorFormData(instanceId, "FORM-L8966281S0WF1JA4AWGDM4QC94TS2E3US8WOLO5", "TPROC--L896628148XFT9KDB3IHI5M14A8W2V7US8WOL1", update, updateInstanceId);
+
+        return McR.success();
+    }
+
+    /**
+     * 恢复报销明细
+     */
+    @PostMapping("recover")
+    McR recover(@RequestBody Map<String, String> data) {
 
         String cId = data.get("cId");
         String sId = data.get("sId");
@@ -150,24 +177,26 @@ public class TSController {
     @PostMapping("test15")
     McR test15() {
 
-        String fid = "ab4863d2-7e60-463e-8141-6ccd0a272282";
+        String fid = "6f565a2f-a605-4f01-93c2-db177e2118c5";
 
         Map formData = ydClient.queryData(YDParam.builder()
                 .formInstanceId(fid)
                 .build(), YDConf.FORM_QUERY.retrieve_id).getFormData();
         List<Map> details = (List<Map>) formData.get("tableField_l6lmxo5r");
 
+
         details.forEach(item -> {
 
-            if (UtilMap.getFloat(item, "numberField_l6napwwi") == 298f) {
-                item.put("numberField_l6napwwi", 130);
+            if (UtilMap.getFloat(item, "numberField_l6napwwi") == 146.66f || UtilMap.getFloat(item, "numberField_l6napwwi") == 235f || UtilMap.getFloat(item, "numberField_l6napwwi") == 162.66f) {
+                item.put("numberField_l6napwwi", 0);
             }
             item.put("associationFormField_l6vk2yxg", JSON.parse(String.valueOf(item.get("associationFormField_l6vk2yxg_id"))));
             item.put("associationFormField_l6napwww", JSON.parse(String.valueOf(item.get("associationFormField_l6napwww_id"))));
 
             log.info("xxxx, {}", item.get("numberField_l6napwwi"));
         });
-        Map updateForm = UtilMap.map("tableField_l6lmxo5r, numberField_l6lmxo6h, textField_l6bynqte", details, 874.66, "捌佰柒拾肆元陆角陆分");
+        details = details.stream().filter(item -> UtilMap.getFloat(item, "numberField_l6napwwi") > 0f).collect(Collectors.toList());
+        Map updateForm = UtilMap.map("tableField_l6lmxo5r, numberField_l6lmxo6h, textField_l6bynqte", details, 1788, "壹仟柒佰捌拾捌元整");
 
         ydClient.operateData(YDParam.builder()
                 .formInstanceId(fid)
@@ -176,9 +205,6 @@ public class TSController {
         return McR.success(updateForm);
     }
 
-    @Autowired
-    private YDService ydService;
-
     // 变更记录
     @PostMapping("changed")
     McR changed() {
@@ -277,7 +303,7 @@ public class TSController {
 
         // 重新审批: selectField_lo6wzjxo, 流程结束:selectField_lo6wzjxp
 //        Object rsp = ydService.mirrorFormData(id, fd, pd, UtilMap.map("selectField_lo6wzjxo, employeeField_lo6wzjxm, employeeField_lo6wzjxn", "是", Arrays.asList("112743683235841523"), Arrays.asList("112743683235841523")));
-        Object rsp = ydService.mirrorFormData(id, fd, pd, UtilMap.map("selectField_lo6wzjxo, radioField_l8k1azrt", "是", "是"));
+        Object rsp = ydService.mirrorFormData(id, fd, pd, UtilMap.map("selectField_lo6wzjxo, radioField_l8k1azrt", "是", "是"), null);
         return McR.success(rsp);
     }
 

+ 1 - 1
mjava-yangu/src/main/java/com/malk/yangu/filter/CatchException_YXY.java

@@ -1,6 +1,6 @@
 package com.malk.yangu.filter;
 
-import com.malk.Filter.CatchException;
+import com.malk.filter.CatchException;
 import com.malk.server.common.McR;
 import com.tencentcloudapi.common.exception.TencentCloudSDKException;
 import lombok.extern.slf4j.Slf4j;

File diff suppressed because it is too large
+ 1 - 1
mjava-zhuogao/src/main/resources/static/web/js/npm.ant-design-vue.f9e3cf7518d9033fa5d0.0.1.0.js


File diff suppressed because it is too large
+ 1 - 1
mjava-zhuogao/src/main/resources/static/web/js/npm.quasar.71cbd0363a3dc849e7db.0.1.0.js


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

@@ -1,5 +1,6 @@
 package com.malk.base;
 
+import cn.hutool.core.util.ObjectUtil;
 import com.alibaba.excel.annotation.ExcelIgnore;
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.fasterxml.jackson.annotation.JsonIgnore;
@@ -52,4 +53,12 @@ public abstract class BasePo extends BaseDto {
     @Temporal(TemporalType.TIMESTAMP)
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     private Date updateTime;
+
+    public void upsert(BasePo po_old) {
+
+        if (ObjectUtil.isNotNull(po_old)) {
+            this.id = po_old.id;
+            this.setCreateTime(po_old.getCreateTime());
+        }
+    }
 }

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

@@ -1,6 +1,6 @@
 package com.malk.config;
 
-import com.malk.Filter.RequestInterceptor;
+import com.malk.filter.RequestInterceptor;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.context.annotation.Configuration;

+ 3 - 11
mjava/src/main/java/com/malk/controller/DDCallbackController.java

@@ -6,8 +6,6 @@ import com.malk.server.common.McR;
 import com.malk.server.dingtalk.DDConf;
 import com.malk.server.dingtalk.crypto.DingCallbackCrypto;
 import com.malk.service.dingtalk.DDClient_Event;
-import com.malk.utils.UtilHttp;
-import com.malk.utils.UtilMap;
 import lombok.SneakyThrows;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -48,7 +46,6 @@ public class DDCallbackController {
                                               @RequestParam(value = "nonce", required = false) String nonce,
                                               @RequestBody(required = false) JSONObject json) {
 
-
         DingCallbackCrypto callbackCrypto = new DingCallbackCrypto(ddConf.getToken(), ddConf.getAesKey(), ddConf.getAppKey());
         final String decryptMsg = callbackCrypto.getDecryptMsg(signature, timestamp, nonce, json.getString("encrypt"));
         JSONObject eventJson = JSON.parseObject(decryptMsg);
@@ -56,21 +53,16 @@ public class DDCallbackController {
         String eventType = eventJson.getString("EventType");
         if (DDConf.CALLBACK_CHECK.equals(eventType)) {
             log.info("----- [DD]验证注册 -----");
-//            return success;
+            return success;
         }
         // [回调任务执行逻辑: 异步] 钉钉超时3s未返回会被记录为失败, 可通过失败接口获取记录
         if (Arrays.asList(DDConf.BPMS_INSTANCE_CHANGE, DDConf.BPMS_TASK_CHANGE).contains(eventType)) {
             log.info("[DD]审批回调, {}", eventJson);
             ddClient_event.callBackEvent_Workflow(eventJson);
-//            return success;
+            return success;
         }
-        log.info("xxx, {}", JSON.toJSONString(success));
         log.info("----- [DD]已注册, 未处理的其它回调 -----, {}", eventJson);
-//        return success;
-
-        String rsp = UtilHttp.doPost("http://192.168.1.131:36497/api/Values/callback", null, UtilMap.map("signature, timestamp, nonce", signature, timestamp, nonce), json);
-        log.info("xxx, {}", rsp);
-        return (Map<String, String>) JSON.parse(rsp);
+        return success;
     }
 
     @PostMapping("robot")

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

@@ -1,6 +1,6 @@
 package com.malk.core;
 
-//import com.mcli.Filter.ExceptionNotice;
+//import com.mcli.filter.ExceptionNotice;
 
 import lombok.extern.slf4j.Slf4j;
 import org.slf4j.Logger;

+ 4 - 2
mjava/src/main/java/com/malk/delegate/DDEvent_Delegate.java

@@ -10,11 +10,13 @@ import org.springframework.scheduling.annotation.Async;
  * 子项目实现接口 [静态代理], 添加对应 processCode 单据业务逻辑
  * OA审批, 撤销和拒绝流程不继续执行连接器, 通过事件订阅实现实时同步
  */
-public interface DDEvent_Delegate {
+public interface DDEvent {
 
 
     // todo, 回调做try, 失败记录做存储, 提供查询接口
 
+    // todo, 回调参数统一, 宜搭查询接口统一
+
     // 审批任务回调执行业务逻辑
     @Async
     void executeEvent_Task_Finish(String processInstanceId, String processCode, boolean isAgree, String remark);
@@ -27,7 +29,7 @@ public interface DDEvent_Delegate {
 
     // 审批实例回调执行业务逻辑
     @Async
-    void executeEvent_Instance_Finish(String processInstanceId, String processCode, boolean isAgree, boolean isTerminate);
+    void executeEvent_Instance_Finish(String processInstanceId, String processCode, boolean isAgree, boolean isTerminate, String staffId);
 
     @Async
     void executeEvent_Instance_Start(String processInstanceId, String processCode);

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

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

+ 3 - 3
mjava/src/main/java/com/malk/delegate/impl/DDImplEvent_Delegate.java

@@ -1,6 +1,6 @@
 package com.malk.delegate.impl;
 
-import com.malk.delegate.DDEvent_Delegate;
+import com.malk.delegate.DDEvent;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
@@ -13,7 +13,7 @@ import org.springframework.stereotype.Service;
  */
 @Slf4j
 @Service
-public class DDImplEvent_Delegate implements DDEvent_Delegate {
+public class DDImplEvent implements DDEvent {
 
     // 审批任务回调执行业务逻辑
     @Async
@@ -43,7 +43,7 @@ public class DDImplEvent_Delegate implements DDEvent_Delegate {
     // 审批实例回调执行业务逻辑
     @Async
     @Override
-    public void executeEvent_Instance_Finish(String processInstanceId, String processCode, boolean isAgree, boolean isTerminate) {
+    public void executeEvent_Instance_Finish(String processInstanceId, String processCode, boolean isAgree, boolean isTerminate, String staffId) {
         log.info("executeEvent_Instance_Finish: 未被代理");
     }
 }

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

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

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

@@ -1,4 +1,4 @@
-package com.malk.Filter;
+package com.malk.filter;
 
 import com.alibaba.fastjson.JSONException;
 import com.malk.server.common.McException;

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

@@ -1,4 +1,4 @@
-//package com.mcli.Filter;
+//package com.mcli.filter;
 //
 //import cn.hutool.core.util.ObjectUtil;
 //import com.alibaba.fastjson.JSONObject;

+ 1 - 1
mjava/src/main/java/com/malk/Filter/RequestFilter.java

@@ -1,4 +1,4 @@
-package com.malk.Filter;
+package com.malk.filter;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;

+ 1 - 1
mjava/src/main/java/com/malk/Filter/RequestInterceptor.java

@@ -1,4 +1,4 @@
-package com.malk.Filter;
+package com.malk.filter;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;

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

@@ -85,6 +85,10 @@ public class YDConf {
      * @apiNote ppExt 接口更新, 传入jsonString或List都可以. 返回数据都是两层json解析, 组件 + _id格式
      */
     public List<Map> associationForm(String formUuid, String formInstanceId, String title, String subTitle, boolean isProcess) {
+        return associationForm(appType, formUuid, formInstanceId, title, subTitle, isProcess);
+    }
+
+    public static List<Map> associationForm(String appType, String formUuid, String formInstanceId, String title, String subTitle, boolean isProcess) {
         String formType = isProcess ? "process" : "receipt";
         return Arrays.asList(UtilMap.map("appType, formUuid, instanceId, title, subTitle, formType", appType, formUuid, formInstanceId, title, subTitle, formType));
     }

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

@@ -35,7 +35,7 @@ public class DDFormComponentDto {
     private String value;
 
     /**
-     * // todo: 钉钉回调, 数字组件不能传null或空, 文本组件null过滤为空; 计算字段胡自动计算, 不用传值
+     * todo: 钉钉回调, 数字组件不能传null或空, 文本组件null过滤为空; 计算字段胡自动计算, 不用传值, 日期仅支持为年月日或年月日时分两种格式, 人员组件JSON.toJSONString(Arrays.asList(userId))))
      * 快速格式化, 若value非直接取值需预处理好
      *
      * @param formData   表单数据源, 若Value类型为list, 则触发ruleDetail

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

@@ -54,6 +54,11 @@ public class DDR<T> extends VenR {
 
     private String corpid;
 
+    /**
+     * 发送工作通知
+     */
+    private String task_id; // 可调用获取工作通知消息的发送结果查询结果
+
     /**
      * 考勤打卡数据
      */

+ 4 - 1
mjava/src/main/java/com/malk/service/aliwork/YDService.java

@@ -41,6 +41,9 @@ public interface YDService {
      */
     List<Map> queryAllFormData(YDParam YDParam);
 
+    // todo: 宜搭大数据量处理会有异常, 可结合排序, 或失败记录进行处理 (避免更新后, 数据重新查询排序导致未全量同步)
+    List<Map> queryFormData_all(YDParam YDParam);
+
     /**
      * 查询宜搭数据
      */
@@ -62,7 +65,7 @@ public interface YDService {
     /**
      * 全表复制 [两张表组件完全一致]
      */
-    Object mirrorFormData(String instanceId, String formUuid, String processCode, Map updateData);
+    Object mirrorFormData(String instanceId, String formUuid, String processCode, Map updateData, String updateInstanceId);
 
     /**
      * upsert方法 [todo: 优化 批量的数据兼容]

+ 28 - 9
mjava/src/main/java/com/malk/service/aliwork/impl/YDServiceImpl.java

@@ -9,6 +9,7 @@ import com.malk.server.dingtalk.DDR_New;
 import com.malk.service.aliwork.YDClient;
 import com.malk.service.aliwork.YDService;
 import com.malk.utils.UtilMap;
+import com.malk.utils.UtilString;
 import lombok.SneakyThrows;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.collections4.map.HashedMap;
@@ -103,6 +104,16 @@ public class YDServiceImpl implements YDService {
         return dataList;
     }
 
+    @Override
+    public List<Map> queryFormData_all(YDParam YDParam) {
+        List<Map> dataList = queryAllFormData(YDParam);
+        return dataList.stream().map(item -> {
+            Map formData = (Map) item.get("formData");
+            formData.put("instanceId", item.get("formInstanceId"));
+            return formData;
+        }).collect(Collectors.toList());
+    }
+
     /**
      * 查询宜搭数据
      */
@@ -113,7 +124,11 @@ public class YDServiceImpl implements YDService {
                         .formUuid(formUuid)
                         .searchFieldJson(JSON.toJSONString(conditions)).build(),
                 YDConf.FORM_QUERY.retrieve_search_form).getData();
-        return dataList.stream().map(item -> (Map) item.get("formData")).collect(Collectors.toList());
+        return dataList.stream().map(item -> {
+            Map formData = (Map) item.get("formData");
+            formData.put("instanceId", item.get("formInstanceId"));
+            return formData;
+        }).collect(Collectors.toList());
     }
 
     /**
@@ -174,7 +189,7 @@ public class YDServiceImpl implements YDService {
      * 全表复制 [两张表组件完全一致]
      */
     @Override
-    public Object mirrorFormData(String instanceId, String formUuid, String processCode, Map updateData) {
+    public Object mirrorFormData(String instanceId, String formUuid, String processCode, Map updateData, String updateInstanceId) {
 
         YDConf.FORM_OPERATION type = StringUtils.isNotBlank(processCode) ? YDConf.FORM_OPERATION.start : YDConf.FORM_OPERATION.create;
         Map<String, ?> formData = ydClient.queryData(YDParam.builder()
@@ -221,15 +236,19 @@ public class YDServiceImpl implements YDService {
                 dataForm.put(compId, table);
             }
         }
-
         UtilMap.putAll(dataForm, updateData);
-        log.info("全表镜像, {}", JSON.toJSONString(dataForm));
+        if (UtilString.isBlankCompatNull(updateInstanceId)) {
+            return ydClient.operateData(YDParam.builder()
+                    .formUuid(formUuid)
+                    .processCode(processCode)
+                    .userId(String.valueOf(UtilMap.getList(formData, "employeeField_l843wfsm").get(0)))
+                    .formDataJson(JSON.toJSONString(dataForm))
+                    .build(), type);
+        }
         return ydClient.operateData(YDParam.builder()
-                .formUuid(formUuid)
-                .processCode(processCode)
-                .userId(String.valueOf(UtilMap.getList(formData, "employeeField_l843wfsm").get(0)))
-                .formDataJson(JSON.toJSONString(dataForm))
-                .build(), type);
+                .formInstanceId(updateInstanceId)
+                .updateFormDataJson(JSON.toJSONString(dataForm))
+                .build(), YDConf.FORM_OPERATION.update);
     }
 
     /**

+ 7 - 1
mjava/src/main/java/com/malk/service/dingtalk/DDClient_Contacts.java

@@ -41,7 +41,8 @@ public interface DDClient_Contacts {
     /**
      * 查询用户详情
      * -
-     * [如入职时间 手机号等, 需要再在花名册添加员工可见, 接口才会返回]
+     * [入职时间 需要再在花名册添加员工可见, 接口才会返回]
+     * [手机号\邮箱等, 只有应用开通通讯录邮箱等个人信息权限,才会返回该字段: 如个人邮箱 fieldEmail]
      * ppExt: 企业邮箱org_email, 若无则字段不返回; 手机号telephone无值, 但mobile会返回
      */
     Map getUserInfoById(String access_token, String userId);
@@ -74,6 +75,11 @@ public interface DDClient_Contacts {
      */
     Map createUser(String access_token, String name, String mobile, List<String> dept_id_list, Map body_ext);
 
+    /**
+     * 创建钉钉自建企业账号 [邮箱为必填]
+     */
+    Map createUser_dingTalk(String access_token, String login_id, String init_password, String name, List<Long> dept_id_list, Map body_ext);
+
     /**
      * 创建部门
      */

+ 16 - 0
mjava/src/main/java/com/malk/service/dingtalk/DDClient_Notice.java

@@ -0,0 +1,16 @@
+package com.malk.service.dingtalk;
+
+import java.util.List;
+import java.util.Map;
+
+public interface DDClient_Notice {
+
+    /**
+     * 发送工作通知
+     *
+     * @param userid_list  接收者的userid列表,最大用户列表长度100
+     * @param dept_id_list 接收者的部门id列表,最大列表长度20。接收者是部门ID时,包括子部门下的所有用户
+     * @param to_all_user  是否发送给企业全部用户。当设置为false时必须指定userid_list或dept_id_list其中一个参数的值
+     */
+    String sendNotification(String access_token, List<String> userid_list, List<String> dept_id_list, boolean to_all_user, Map msg);
+}

+ 1 - 1
mjava/src/main/java/com/malk/service/dingtalk/DDClient_Log.java

@@ -3,7 +3,7 @@ package com.malk.service.dingtalk;
 import java.util.List;
 import java.util.Map;
 
-public interface DDClient_Log {
+public interface DDClient_Report {
 
     /**
      * 获取用户发出的日志列表

+ 0 - 1
mjava/src/main/java/com/malk/service/dingtalk/DDService.java

@@ -30,7 +30,6 @@ public interface DDService {
      */
     Map uploadFileFormUrlForOverflow(String accessToken, String userId, String filePath);
 
-
     /**
      * 上传钉盘文件
      *

+ 19 - 0
mjava/src/main/java/com/malk/service/dingtalk/impl/DDImplClient_Contacts.java

@@ -193,6 +193,22 @@ public class DDImplClient_Contacts implements DDClient_Contacts {
         return (Map) DDR.doPost("https://oapi.dingtalk.com/topapi/v2/user/create", null, param, body).getResult();
     }
 
+    /**
+     * 创建钉钉自建企业账号
+     *
+     * @apiNote https://open.dingtalk.com/document/orgapp/create-dingtalk-user-created-dedicated-account
+     */
+    @Override
+    public Map createUser_dingTalk(String access_token, String login_id, String init_password, String name, List<Long> dept_id_list, Map body_ext) {
+        Map param = UtilMap.map("access_token", access_token);
+        String deptIds = String.join(",", dept_id_list.stream().map(dept -> String.valueOf(dept)).collect(Collectors.toList()));
+        Map body = UtilMap.map("exclusive_account, exclusive_account_type, login_id, init_password, name, dept_id_list", true, "dingtalk", login_id, init_password, name, deptIds);
+        if (ObjectUtil.isNotNull(body_ext)) {
+            body.putAll(body_ext);
+        }
+        return (Map) DDR.doPost("https://oapi.dingtalk.com/topapi/v2/user/create", null, param, body).getResult();
+    }
+
     /**
      * 创建部门
      *
@@ -200,6 +216,9 @@ public class DDImplClient_Contacts implements DDClient_Contacts {
      */
     @Override
     public Map createDepartment(String access_token, String name, long parent_id, Map body_ext) {
+
+        // 长度限制为1~64个字符,不允许包含字符"-"","以及","
+        name = name.replaceAll("-", "").replaceAll(",", "");
         Map param = UtilMap.map("access_token", access_token);
         Map body = UtilMap.map("name, parent_id", name, parent_id);
         if (ObjectUtil.isNotNull(body_ext)) {

+ 4 - 4
mjava/src/main/java/com/malk/service/dingtalk/impl/DDImplClient_Event.java

@@ -2,7 +2,7 @@ package com.malk.service.dingtalk.impl;
 
 import cn.hutool.core.util.ObjectUtil;
 import com.alibaba.fastjson.JSONObject;
-import com.malk.delegate.DDEvent_Delegate;
+import com.malk.delegate.DDEvent;
 import com.malk.server.dingtalk.DDConf;
 import com.malk.server.dingtalk.DDR;
 import com.malk.service.dingtalk.DDClient_Event;
@@ -26,7 +26,7 @@ public class DDImplClient_Event implements DDClient_Event {
 
     // 子项目实现接口 [静态代理]
     @Autowired
-    private DDEvent_Delegate event_delegate;
+    private DDEvent event_delegate;
 
     /**
      * 钉钉审批回调事件
@@ -73,13 +73,13 @@ public class DDImplClient_Event implements DDClient_Event {
             boolean isTerminate = "terminate".equals(type);
             if (isTerminate) {
                 log.info("[终止]审批实例回调, {}", eventJson);
-                event_delegate.executeEvent_Instance_Finish(processInstanceId, processCode, isAgree, isTerminate);
+                event_delegate.executeEvent_Instance_Finish(processInstanceId, processCode, isAgree, isTerminate, eventJson.getString("staffId"));
                 return;
             }
             // 实例回调: 审批实例开始、结束 -> finish:审批正常结束(同意或拒绝), terminate:审批终止(发起人撤销审批单)
             if ("finish".equals(type)) {
                 log.info("[结束]审批实例回调, {}", eventJson);
-                event_delegate.executeEvent_Instance_Finish(processInstanceId, processCode, isAgree, isTerminate);
+                event_delegate.executeEvent_Instance_Finish(processInstanceId, processCode, isAgree, isTerminate, eventJson.getString("staffId"));
             }
             return;
         }

+ 37 - 0
mjava/src/main/java/com/malk/service/dingtalk/impl/DDImplClient_Notice.java

@@ -0,0 +1,37 @@
+package com.malk.service.dingtalk.impl;
+
+import com.malk.server.dingtalk.DDConf;
+import com.malk.server.dingtalk.DDR;
+import com.malk.service.dingtalk.DDClient_Notice;
+import com.malk.utils.UtilList;
+import com.malk.utils.UtilMap;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.Map;
+
+@Service
+@Slf4j
+public class DDImplClient_Notice implements DDClient_Notice {
+
+    @Autowired
+    private DDConf ddConf;
+
+    /**
+     * 发送工作通知
+     */
+    @Override
+    public String sendNotification(String access_token, List<String> userid_list, List<String> dept_id_list, boolean to_all_user, Map msg) {
+        Map body = UtilMap.map("agent_id, to_all_user, msg", ddConf.getAgentId(), to_all_user, msg);
+        if (UtilList.isNotEmpty(userid_list)) {
+            body.put("userid_list", String.join(",", userid_list));
+        }
+        if (UtilList.isNotEmpty(dept_id_list)) {
+            body.put("dept_id_list", String.join(",", dept_id_list));
+        }
+        DDR ddr = DDR.doPost("https://oapi.dingtalk.com/topapi/message/corpconversation/asyncsend_v2", null, DDConf.initTokenParams(access_token), body);
+        return ddr.getTask_id();
+    }
+}

+ 2 - 2
mjava/src/main/java/com/malk/service/dingtalk/impl/DDImplClient_WorkDaily.java

@@ -3,7 +3,7 @@ package com.malk.service.dingtalk.impl;
 import cn.hutool.core.util.ObjectUtil;
 import com.malk.server.dingtalk.DDConf;
 import com.malk.server.dingtalk.DDR;
-import com.malk.service.dingtalk.DDClient_Log;
+import com.malk.service.dingtalk.DDClient_Report;
 import com.malk.utils.UtilMap;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
@@ -13,7 +13,7 @@ import java.util.Map;
 
 @Service
 @Slf4j
-public class DDImplClient_WorkDaily implements DDClient_Log {
+public class DDImplClient_Report implements DDClient_Report {
 
     /**
      * 获取用户发出的日志列表

+ 1 - 0
mjava/src/main/java/com/malk/utils/UtilHttp.java

@@ -46,6 +46,7 @@ public abstract class UtilHttp {
 
     /******** 创建请求 ********/
 
+    // todo: 认证格式 - Authorization:Basic base64(“admin:密码”)
     public static String doRequest(METHOD method, 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);

+ 7 - 1
mjava/src/main/java/com/malk/utils/UtilMap.java

@@ -159,10 +159,16 @@ public abstract class UtilMap {
      */
     public static String getString(Map data, String key) {
         if (data.containsKey(key)) {
+            // 不能判空字符串
             if (ObjectUtil.isNull(data.get(key))) {
                 return "";
             }
-            return String.valueOf(data.get(key));
+            String value = String.valueOf(data.get(key));
+            // 屏蔽空指针转换
+            if (value.equals("null")) {
+                return "";
+            }
+            return value;
         }
         return "";
     }

+ 0 - 30
mjava/target/classes/META-INF/spring-configuration-metadata.json

@@ -40,11 +40,6 @@
       "type": "com.malk.server.common.FilePath$Path",
       "sourceType": "com.malk.server.common.FilePath$Path"
     },
-    {
-      "name": "file.path",
-      "type": "com.malk.server.common.FilePath$Path",
-      "sourceType": "com.malk.server.common.FilePath$Path"
-    },
     {
       "name": "file.source",
       "type": "com.malk.server.common.FilePath$Source",
@@ -60,11 +55,6 @@
       "type": "com.malk.server.common.FilePath$Source",
       "sourceType": "com.malk.server.common.FilePath$Source"
     },
-    {
-      "name": "file.source",
-      "type": "com.malk.server.common.FilePath$Source",
-      "sourceType": "com.malk.server.common.FilePath$Source"
-    },
     {
       "name": "fxiaoke",
       "type": "com.malk.server.fxiaoke.FXKConf",
@@ -214,16 +204,6 @@
       "type": "java.lang.String",
       "sourceType": "com.malk.server.common.FilePath$Path"
     },
-    {
-      "name": "file.path.file",
-      "type": "java.lang.String",
-      "sourceType": "com.malk.server.common.FilePath$Path"
-    },
-    {
-      "name": "file.path.image",
-      "type": "java.lang.String",
-      "sourceType": "com.malk.server.common.FilePath$Path"
-    },
     {
       "name": "file.path.image",
       "type": "java.lang.String",
@@ -244,16 +224,6 @@
       "type": "java.lang.String",
       "sourceType": "com.malk.server.common.FilePath$Path"
     },
-    {
-      "name": "file.path.tmp",
-      "type": "java.lang.String",
-      "sourceType": "com.malk.server.common.FilePath$Path"
-    },
-    {
-      "name": "file.source.fonts",
-      "type": "java.lang.String",
-      "sourceType": "com.malk.server.common.FilePath$Source"
-    },
     {
       "name": "file.source.fonts",
       "type": "java.lang.String",

+ 2 - 2
pom.xml

@@ -26,8 +26,8 @@
         <module>mjava-luyi</module>
         <module>mjava-taisen</module>
         <module>mjava-hake</module>
-        <module>majva-hongfengrenli</module>
-        <module>mjava_hongfeng</module>
+        <module>mjava-hongfeng</module>
+        <module>mjava-aipocloud</module>
     </modules>
     <packaging>pom</packaging>