pruple_boy 1 год назад
Родитель
Сommit
e9ba128ad2
40 измененных файлов с 1044 добавлено и 394 удалено
  1. 3 5
      mjava-fengkaili/src/main/java/com/malk/fengkaili/controller/FKLController.java
  2. 1 1
      mjava-fengkaili/src/main/java/com/malk/fengkaili/repository/dao/FKLDdContactDao.java
  3. 2 1
      mjava-fengkaili/src/main/java/com/malk/fengkaili/service/FKLService.java
  4. 14 5
      mjava-fengkaili/src/main/java/com/malk/fengkaili/service/impl/FKLImplService.java
  5. 3 3
      mjava-gewu/src/main/java/com/malk/gewu/controller/GWController.java
  6. 3 0
      mjava-gewu/src/main/java/com/malk/gewu/service/GWService.java
  7. 6 0
      mjava-gewu/src/main/java/com/malk/gewu/service/impl/GWImplService.java
  8. 17 165
      mjava-hake/src/main/java/com/malk/hake/controller/HKController.java
  9. 45 0
      mjava-hake/src/main/java/com/malk/hake/schedule/HKScheduleTask.java
  10. 26 0
      mjava-hake/src/main/java/com/malk/hake/service/HKClient.java
  11. 219 0
      mjava-hake/src/main/java/com/malk/hake/service/impl/HKImplClient.java
  12. 1 1
      mjava-hake/src/main/resources/application-prod.yml
  13. 9 1
      mjava-hake/src/main/resources/static/json/form.json
  14. 18 0
      mjava-laidi/src/main/java/com/malk/laidi/controller/DDController.java
  15. 9 136
      mjava-laidi/src/main/java/com/malk/laidi/controller/LDController.java
  16. 59 0
      mjava-laidi/src/main/java/com/malk/laidi/delegate/DDDelegate.java
  17. 23 0
      mjava-laidi/src/main/java/com/malk/laidi/service/LDClient.java
  18. 156 0
      mjava-laidi/src/main/java/com/malk/laidi/service/impl/LDImplClient.java
  19. 16 8
      mjava-laidi/src/main/resources/application-dev.yml
  20. 8 8
      mjava-laidi/src/main/resources/application-prod.yml
  21. 67 0
      mjava-laidi/src/test/resources/sample/CPTimer.cs
  22. 1 3
      mjava-laidi/src/test/resources/sample/CP_Utils.cs
  23. 13 12
      mjava-laidi/src/test/resources/sample/附件同步钉盘.cs
  24. 17 8
      mjava-laidi/src/test/resources/sample/附件同步钉盘.js
  25. 103 14
      mjava-taisen/src/main/java/com/malk/taisen/controller/TSController.java
  26. 8 3
      mjava-taisen/src/main/resources/application-dev.yml
  27. 8 8
      mjava-taisen/src/main/resources/application-prod.yml
  28. 35 0
      mjava-taisen/src/test/resources/server.sh
  29. 12 3
      mjava/src/main/java/com/malk/controller/DDCallbackController.java
  30. 3 2
      mjava/src/main/java/com/malk/server/aliwork/YDConf.java
  31. 3 1
      mjava/src/main/java/com/malk/server/dingtalk/crypto/DingCallbackCrypto.java
  32. 6 1
      mjava/src/main/java/com/malk/service/aliwork/YDService.java
  33. 64 1
      mjava/src/main/java/com/malk/service/aliwork/impl/YDServiceImpl.java
  34. 1 1
      mjava/src/main/java/com/malk/service/dingtalk/DDClient_Storage.java
  35. 1 1
      mjava/src/main/java/com/malk/service/dingtalk/DDClient_Workflow.java
  36. 13 1
      mjava/src/main/java/com/malk/service/dingtalk/impl/DDImplClient_Contacts.java
  37. 11 0
      mjava/src/main/java/com/malk/service/dingtalk/impl/DDImplClient_Storage.java
  38. 1 1
      mjava/src/main/java/com/malk/service/h3yun/CYClient.java
  39. 30 0
      mjava/target/classes/META-INF/spring-configuration-metadata.json
  40. 9 0
      pom.xml

+ 3 - 5
mjava-fengkaili/src/main/java/com/malk/fengkaili/controller/FKLController.java

@@ -22,10 +22,7 @@ import org.springframework.web.bind.annotation.RestController;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 import java.util.stream.Collectors;
 
 @Slf4j
@@ -54,7 +51,8 @@ public class FKLController {
 
         List<Long> dpetIds = (List<Long>) data.get("deptId");
         // 基于用户分页
-        Page page = fklService.queryUserInfos(UtilMap.getInt(data, "page"), UtilMap.getInt(data, "size"), UtilMap.getString(data, "name"), dpetIds);
+        Date sDate = UtilDateTime.parseDate(UtilMap.getString(data, "startTime"));
+        Page page = fklService.queryUserInfos(UtilMap.getInt(data, "page"), UtilMap.getInt(data, "size"), UtilMap.getString(data, "name"), dpetIds, sDate);
         McException.assertAccessException(page.getTotalElements() == 0, "查询用户为空!");
 
         List<FKLDdContactPo> userInfos = page.getContent();

+ 1 - 1
mjava-fengkaili/src/main/java/com/malk/fengkaili/repository/dao/FKLDdContactDao.java

@@ -15,7 +15,7 @@ import java.util.Date;
 @Transactional
 public interface FKLDdContactDao extends JpaRepository<FKLDdContactPo, Long>, JpaSpecificationExecutor<FKLDdContactPo> {
 
-    boolean existsByUserId(String userId);
+    FKLDdContactPo findByUserId(String userId);
 
     @Modifying
     @Query("update FKLDdContactPo set leaveDate = ?2 where userId = ?1")

+ 2 - 1
mjava-fengkaili/src/main/java/com/malk/fengkaili/service/FKLService.java

@@ -3,6 +3,7 @@ package com.malk.fengkaili.service;
 import com.malk.fengkaili.repository.entity.FKLDdContactPo;
 import org.springframework.data.domain.Page;
 
+import java.util.Date;
 import java.util.List;
 import java.util.Map;
 
@@ -16,7 +17,7 @@ public interface FKLService {
     /**
      * 查询用户列表
      */
-    Page<FKLDdContactPo> queryUserInfos(int page, int size, String name, List<Long> deptIds);
+    Page<FKLDdContactPo> queryUserInfos(int page, int size, String name, List<Long> deptIds, Date sDate);
 
     /**
      * 考勤数据统计

+ 14 - 5
mjava-fengkaili/src/main/java/com/malk/fengkaili/service/impl/FKLImplService.java

@@ -64,12 +64,12 @@ public class FKLImplService implements FKLService {
                 String deptName = ddService.getUserDepartmentHierarchyJoin(ddClient.getAccessToken(), userIds.get(0), "-");
                 for (String userId : userIds) {
                     // 牧语
-                    if ("0953580166811961653".equals(userId) || fklDdContactDao.existsByUserId(userId)) {
+                    if ("0953580166811961653".equals(userId)) {
                         continue;
                     }
                     Map userinfo = ddClient_contacts.getUserInfoById(ddClient.getAccessToken(), userId);
                     // 员工信息表, 落库
-                    fklDdContactDao.save(FKLDdContactPo.builder()
+                    FKLDdContactPo contactPo = FKLDdContactPo.builder()
                             .userId(userId)
                             .name(UtilMap.getString(userinfo, "name"))
                             .jobNumber(UtilMap.getString(userinfo, "job_number"))
@@ -78,8 +78,15 @@ public class FKLImplService implements FKLService {
                             .mobile(UtilMap.getString(userinfo, "mobile"))
                             .hiredDate(userinfo.containsKey("hired_date") ? new Date(UtilMap.getLong(userinfo, "hired_date")) : null)
                             .remark(UtilMap.getString(userinfo, "remark")) // 无需打卡 标记
-                            .build());
-                    log.info("同步#入职人员, {}", userinfo);
+                            .build();
+                    FKLDdContactPo po = fklDdContactDao.findByUserId(userId);
+                    // 员工更新, 组织架构调整
+                    if (ObjectUtil.isNotNull(po)) {
+                        contactPo.id = po.id;
+                        contactPo.setCreateTime(po.getCreateTime());
+                    }
+                    fklDdContactDao.save(contactPo);
+                    log.info("同步人员, {}", contactPo);
                 }
             }
         });
@@ -96,7 +103,7 @@ public class FKLImplService implements FKLService {
      * 查询用户列表
      */
     @Override
-    public Page<FKLDdContactPo> queryUserInfos(int page, int size, String name, List<Long> deptIds) {
+    public Page<FKLDdContactPo> queryUserInfos(int page, int size, String name, List<Long> deptIds, Date sDate) {
 
         // 分页 & 排序
         Sort sort = Sort.by(Sort.Direction.ASC, "deptName");
@@ -111,6 +118,8 @@ public class FKLImplService implements FKLService {
             if (UtilList.isNotEmpty(deptIds)) {
                 predicateList.add(criteriaBuilder.in(root.get("deptId")).value(deptIds));
             }
+            // 2月前离职人员过滤 [or语法]
+            predicateList.add(criteriaBuilder.or(criteriaBuilder.isNull(root.get("leaveDate")), criteriaBuilder.greaterThan(root.get("leaveDate"), sDate)));
             return criteriaBuilder.and(predicateList.toArray(new javax.persistence.criteria.Predicate[predicateList.size()]));
         };
         // 无数据时返回空列表

+ 3 - 3
mjava-gewu/src/main/java/com/malk/gewu/controller/GWController.java

@@ -10,7 +10,6 @@ import com.malk.server.aliwork.YDConf;
 import com.malk.server.aliwork.YDParam;
 import com.malk.server.common.McR;
 import com.malk.service.aliwork.YDClient;
-import com.malk.utils.UtilFile;
 import com.malk.utils.UtilMap;
 import com.malk.utils.UtilServlet;
 import lombok.extern.slf4j.Slf4j;
@@ -82,9 +81,10 @@ public class GWController {
 //        return McR.success(JSON.parse(json));
 
 //        return McR.success(UtilFile.readJsonObjectFromResource("templates/personnel"));
-        return McR.success(UtilFile.readJsonObjectFromResource("static/json/personnel.json"));
+//        return McR.success(UtilFile.readJsonObjectFromResource("static/json/personnel.json"));
 
-//        return McR.success(ddClient_personnel.getPersonnelMeta(ddClient.getAccessToken(), ddConf.getAgentId()));
+        gwService.test();
+        return McR.success();
     }
 
 

+ 3 - 0
mjava-gewu/src/main/java/com/malk/gewu/service/GWService.java

@@ -6,4 +6,7 @@ public interface GWService {
      * 同步花名册信息
      */
     void syncRoster();
+
+    // test
+    void test();
 }

+ 6 - 0
mjava-gewu/src/main/java/com/malk/gewu/service/impl/GWImplService.java

@@ -99,4 +99,10 @@ public class GWImplService implements GWService {
             });
         });
     }
+
+    /// test
+    @Override
+    public void test() {
+        ddClient_personnel.getPersonnelMeta(ddClient.getAccessToken(), ddConf.getAgentId());
+    }
 }

+ 17 - 165
mjava-hake/src/main/java/com/malk/hake/controller/HKController.java

@@ -1,39 +1,30 @@
 package com.malk.hake.controller;
 
-/**
- * 错误抛出与拦截详见 CatchException
- */
-
 import com.alibaba.fastjson.JSON;
-import com.malk.server.aliwork.YDConf;
-import com.malk.server.aliwork.YDParam;
+import com.malk.hake.service.HKClient;
 import com.malk.server.common.McException;
 import com.malk.server.common.McR;
 import com.malk.server.common.McREnum;
-import com.malk.server.dingtalk.DDConf;
-import com.malk.service.aliwork.YDClient;
-import com.malk.service.dingtalk.DDClient;
-import com.malk.service.dingtalk.DDClient_Contacts;
-import com.malk.service.dingtalk.DDService;
-import com.malk.utils.*;
+import com.malk.utils.UtilServlet;
 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.web.bind.annotation.*;
 
 import javax.servlet.http.HttpServletRequest;
-import java.time.LocalDateTime;
-import java.util.*;
+import java.util.Map;
 
+/**
+ * 错误抛出与拦截详见 CatchException
+ */
 @Slf4j
 @RestController
 @RequestMapping
 public class HKController {
 
     @Autowired
-    private YDClient ydClient;
+    private HKClient hkClient;
 
     /**
      * 通用流程发起
@@ -42,77 +33,13 @@ public class HKController {
     @PostMapping("process/start")
     McR startProcess(@RequestBody Map data, HttpServletRequest request, @RequestParam String code) {
 
-        log.info("流程发起, {}, {}", code, data);
         Map header = UtilServlet.getHeaders(request);
+        log.info("流程发起, {}, {}, {}", code, data, header);
         McException.assertException(!"dinge61fe69900ea236b35c2f4657eb6378f".equals(header.get("authorization")), McREnum.NOT_AUTHORIZED);
-        Map meta = (Map) UtilFile.readJsonObjectFromResource("static/json/form.json"); // 本地匹配了宜搭组件ID
-        McException.assertAccessException(!meta.containsKey(code), "单据编码未匹配!");
-        // 组织单据数据
-        Map<String, ?> component = (Map) ((Map) meta.get(code)).get("compIds");
-        Map formData = UtilMap.empty();
-        for (String key : component.keySet()) {
-            if (key.startsWith("employeeField_")) {
-                formData.put(key, Arrays.asList(data.get(component.get(key))));
-            } else if (key.startsWith("tableField_")) {
-                List<Map> rows = (List<Map>) data.get("rows");
-                List<Map> details = new ArrayList<>();
-                Map<String, String> compIds = (Map) component.get(key);
-                for (Map row : rows) {
-                    Map detail = UtilMap.empty();
-                    for (String prop : compIds.keySet()) {
-                        detail.put(prop, row.get(compIds.get(prop)));
-                    }
-                    details.add(detail);
-                }
-                formData.put(key, details);
-            } else {
-                formData.put(key, data.get(component.get(key)));
-            }
-        }
-        // 发起人匹配规则
-        String userId = _getUserId(code, meta, data);
-        Object InstanceId = ydClient.operateData(YDParam.builder()
-                .formUuid(String.valueOf(((Map) meta.get(code)).get("formUuid")))
-                .processCode(String.valueOf(((Map) meta.get(code)).get("processCode")))
-                .userId(userId)
-                .formDataJson(JSON.toJSONString(formData))
-                .build(), YDConf.FORM_OPERATION.start);
-        // 回传钉钉单据号
-        Thread.sleep(800);
-        Map form = ydClient.queryData(YDParam.builder()
-                .formInstanceId(String.valueOf(InstanceId))
-                .build(), YDConf.FORM_QUERY.retrieve_id).getFormData();
-        return McR.success(UtilMap.map("ddProcessId, dtNumber", InstanceId, form.get(meta.get(code + "_RC"))));
+        Map result = hkClient.startProcess(code, data);
+        return McR.success(result);
     }
 
-    /// 发起人处理
-    String _getUserId(String code, Map meta, Map data) {
-        String userId = String.valueOf(data.get(String.valueOf(((Map) meta.get(code)).get("creator"))));
-        // 报价单
-        if ("BJ".equals(code)) {
-            if (Arrays.asList("样品", "大货").contains(data.get("quoteType"))) {
-                userId = String.valueOf(data.get("dtOurReferencePersonId"));
-            } else {
-                userId = String.valueOf(data.get("dtSellerPersonId"));
-            }
-        }
-        if (UtilString.isBlankCompatNull(userId)) {
-            userId = YDConf.PUB_ACCOUNT;
-        }
-        return userId;
-    }
-
-    @Autowired
-    private DDClient ddClient;
-
-    @Autowired
-    private DDClient_Contacts ddClient_contacts;
-
-    @Autowired
-    private DDService ddService;
-
-    private static final String AUTH = "bbb76928-a9db-42fa-ac83-cc9a2ed75162";
-
     /**
      * 通用审批校验 [区分类型, 宜搭提示不同报错信息]
      */
@@ -133,22 +60,8 @@ public class HKController {
     McR callbackProcess(HttpServletRequest request) {
 
         Map data = UtilServlet.getParamMap(request);
-
-        // 拒绝审批意见必填校验
-        McException.assertParamException_Null(data, "Result");
-        if ("0".equals(data.get("Result"))) {
-            McException.assertParamException_Null(data, "Remark");
-        }
-        // 回调monitor审批信息
-        data.put("Url", "https://pxi03f.aliwork.com/APP_QWUVLI1R6XYUXWAOPF6O/processDetail?procInsId=" + data.get("DdProcessId"));
-        data.put("ApprovalDate", UtilDateTime.formatLocalDateTime(LocalDateTime.now()));
-        Map UserInfo = ddClient_contacts.getUserInfoById(ddClient.getAccessToken(), String.valueOf(data.get("Approver")));
-        data.put("Approver", String.valueOf(UserInfo.get("name")));
         log.info("审批回调, {}", JSON.toJSONString(data));
-        // prd: 通过 http status 判定, 200 即为成功
-        String rsp = UtilHttp.doPost("http://116.228.113.106:10001/api/public/Approval", UtilMap.map("Authorization", AUTH), UtilMap.map("Code", data.get("Code")), data);
-        Map result = (Map) JSON.parse(rsp);
-        McException.assertException(result.containsKey("errorMsg"), String.valueOf(result.get("status")), String.valueOf(result.get("errorMsg")), "Monitor");
+        hkClient.callbackProcess(data);
         return McR.success();
     }
 
@@ -159,77 +72,16 @@ public class HKController {
     @PostMapping("contact/sync")
     McR syncContact() {
 
-//        List<Long> deptList = Arrays.asList(475336040L);
-        // prd: 非全量同步: 总部\哈克\负责人
-        List<Long> deptList = UtilList.asList(DDConf.TOP_DEPARTMENT);
-        deptList.addAll(ddClient_contacts.getDepartmentId_all(ddClient.getAccessToken(), true, 129113277L)); // 总部
-        deptList.addAll(ddClient_contacts.getDepartmentId_all(ddClient.getAccessToken(), true, 128984162L)); // 哈克
-        for (long deptId : deptList) {
-            List<String> userIds = ddClient_contacts.listDepartmentUserId(ddClient.getAccessToken(), deptId);
-            if (userIds.size() == 0) {
-                continue;
-            }
-            // prd: 取第3层作为部门, 为空取值总经办
-            String dpetCascade = ddService.getUserDepartmentHierarchyJoin(ddClient.getAccessToken(), userIds.get(0), "-");
-            String deptName = "总经办";
-            String[] depts = dpetCascade.split("-");
-            if (depts.length >= 3) {
-                deptName = depts[2];
-            }
-            for (String userId : userIds) {
-                Map userInfo = ddClient_contacts.getUserInfoById(ddClient.getAccessToken(), userId);
-                Map data = UtilMap.map("employeeNumber, employeeName, dtPersonId, lastName", userInfo.get("job_number"), userInfo.get("name"), userInfo.get("userid"), userInfo.get("title"));
-                // prd: 手机号花名册未展示, 因此添加判空, 避免覆盖Monitor
-                if (StringUtils.isNotBlank(String.valueOf(userInfo.get("mobile")))) {
-                    data.put("mobilePhone", userInfo.get("mobile"));
-                }
-                if (userInfo.containsKey("org_email")) {
-                    data.put("email", userInfo.get("org_email"));
-                }
-                data.put("employeeDept", deptName);
-                data.put("dtDepts", dpetCascade);
-                data.put("dtPersonGroup", "");
-                log.info("同步人员, {}", JSON.toJSONString(UtilMap.map("employees", Arrays.asList(data))));
-                // prd: 通过 http status 判定, 200 即为成功
-                String rsp = UtilHttp.doPost("http://116.228.113.106:10001/api/public/EmployeeImport", UtilMap.map("Authorization", AUTH), null, UtilMap.map("employees", Arrays.asList(data)));
-                Map result = (Map) JSON.parse(rsp);
-                if (UtilList.isNotEmpty(UtilMap.getList(result, "fail"))) {
-                    result = (Map) UtilMap.getList(result, "fail").get(0); // 单条同步, 记录错误人员
-                }
-                if (result.containsKey("msg")) {
-                    ydClient.operateData(YDParam.builder()
-                            .formUuid("FORM-W2A66Z91912F39B2AHY0VD4CRK9F3C4JB74OLN")
-                            .formDataJson(JSON.toJSONString(UtilMap.map("dateField_lo47byyd, employeeField_lo47byyj, textareaField_lo47byyo, textareaField_lo47sanj", new Date().getTime(), userId, result, data)))
-                            .build(), YDConf.FORM_OPERATION.create);
-                }
-                //McException.assertException(result.containsKey("msg"), String.valueOf(result.get("status")), String.valueOf(result.get("msg")), "Monitor");
-            }
-        }
+        log.info("手动触发, 通讯录同步");
+        hkClient.syncContact();
         return McR.success();
     }
 
-    @PostMapping("test11")
-    McR test11() {
+    @PostMapping("monitor/sync")
+    McR pay() {
 
-        ddClient_contacts.getUserInfoById(ddClient.getAccessToken(), "131022580326061279");
-        ddClient_contacts.getDepartmentDetail_all(ddClient.getAccessToken(), true);
+        log.info("手动触发, 拉取Monitor同步");
+        hkClient.syncMonitor("FKSQ");
         return McR.success();
     }
-
-    //////
-
-    // dslink或连接器内, 会转换json为map, 需要条件两层
-    @PostMapping("test")
-    Map test(@RequestBody Map data) {
-
-        log.info("xxx, {}", data);
-        return UtilMap.map("json", JSON.toJSONString(JSON.toJSONString(data)));
-    }
-
-    @PostMapping("test2")
-    Map test2(@RequestBody Map<String, ?> data) {
-
-        log.info("xxx, {}", data);
-        return UtilMap.map("data", JSON.parse(String.valueOf(data.get("json"))));
-    }
 }

+ 45 - 0
mjava-hake/src/main/java/com/malk/hake/schedule/HKScheduleTask.java

@@ -0,0 +1,45 @@
+package com.malk.hake.schedule;
+
+import com.malk.hake.service.HKClient;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.scheduling.annotation.Scheduled;
+
+/**
+ * @EnableScheduling 开启定时任务 [配置参考McScheduleTask]
+ */
+@Slf4j
+@Configuration
+@EnableScheduling
+@ConditionalOnProperty(name = {"spel.scheduling"})
+public class HKScheduleTask {
+
+    @Autowired
+    private HKClient hkClient;
+
+    /**
+     * 同步通讯录: 早上6:00  中午11:30    下午16:00    晚上19:30
+     */
+    @Scheduled(cron = "0 0 6,16 * * ? ")
+    public void timer_1() {
+        try {
+            hkClient.syncContact();
+        } catch (Exception e) {
+            // 记录错误信息
+            e.printStackTrace();
+        }
+    }
+
+    @Scheduled(cron = "0 30 11,19 * * ? ")
+    public void timer_2() {
+        try {
+            hkClient.syncContact();
+        } catch (Exception e) {
+            // 记录错误信息
+            e.printStackTrace();
+        }
+    }
+}

+ 26 - 0
mjava-hake/src/main/java/com/malk/hake/service/HKClient.java

@@ -0,0 +1,26 @@
+package com.malk.hake.service;
+
+import java.util.Map;
+
+public interface HKClient {
+
+    /**
+     * 通用流程发起
+     */
+    Map<String, Object> startProcess(String code, Map data);
+
+    /**
+     * 通用审批回调
+     */
+    void callbackProcess(Map data);
+
+    /**
+     * 通讯录同步
+     */
+    void syncContact();
+
+    /**
+     * 拉取数据发起
+     */
+    void syncMonitor(String code);
+}

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

@@ -0,0 +1,219 @@
+package com.malk.hake.service.impl;
+
+import com.alibaba.fastjson.JSON;
+import com.malk.hake.service.HKClient;
+import com.malk.server.aliwork.YDConf;
+import com.malk.server.aliwork.YDParam;
+import com.malk.server.common.McException;
+import com.malk.server.dingtalk.DDConf;
+import com.malk.service.aliwork.YDClient;
+import com.malk.service.dingtalk.DDClient;
+import com.malk.service.dingtalk.DDClient_Contacts;
+import com.malk.service.dingtalk.DDService;
+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.*;
+
+@Service
+@Slf4j
+public class HKImplClient implements HKClient {
+
+    @Autowired
+    private YDClient ydClient;
+
+    // monitor接口ip
+    String _getEnvApi(String path) {
+        if (UtilEnv.getActiveProfile().equals(UtilEnv.ENV_PROD)) {
+            return "http://172.16.20.13:10002/api/public" + path;
+        }
+        return "http://116.228.113.106:10001/api/public" + path;
+    }
+
+    /**
+     * 通用流程发起
+     */
+    @SneakyThrows
+    @Override
+    public Map<String, Object> startProcess(String code, Map data) {
+        Map meta = (Map) UtilFile.readJsonObjectFromResource("static/json/form.json"); // 本地匹配了宜搭组件ID
+        McException.assertAccessException(!meta.containsKey(code), "单据编码未匹配!");
+        // 组织单据数据
+        Map<String, ?> component = (Map) ((Map) meta.get(code)).get("compIds");
+        Map formData = UtilMap.empty();
+        for (String key : component.keySet()) {
+            if (key.startsWith("employeeField_")) {
+                formData.put(key, Arrays.asList(data.get(component.get(key))));
+            } else if (key.startsWith("tableField_")) {
+                List<Map> rows = (List<Map>) data.get("rows");
+                List<Map> details = new ArrayList<>();
+                Map<String, String> compIds = (Map) component.get(key);
+                for (Map row : rows) {
+                    Map detail = UtilMap.empty();
+                    for (String prop : compIds.keySet()) {
+                        detail.put(prop, row.get(compIds.get(prop)));
+                    }
+                    details.add(detail);
+                }
+                formData.put(key, details);
+            } else {
+                formData.put(key, data.get(component.get(key)));
+            }
+        }
+        // 发起人匹配规则
+        String userId = _getUserId(code, meta, data);
+        Object InstanceId = ydClient.operateData(YDParam.builder()
+                .formUuid(String.valueOf(((Map) meta.get(code)).get("formUuid")))
+                .processCode(String.valueOf(((Map) meta.get(code)).get("processCode")))
+                .userId(userId)
+                .formDataJson(JSON.toJSONString(formData))
+                .build(), YDConf.FORM_OPERATION.start);
+        // 回传钉钉单据号
+        Thread.sleep(800);
+        Map form = ydClient.queryData(YDParam.builder()
+                .formInstanceId(String.valueOf(InstanceId))
+                .build(), YDConf.FORM_QUERY.retrieve_id).getFormData();
+        return UtilMap.map("ddProcessId, dtNumber", InstanceId, form.get(meta.get(code + "_RC")));
+    }
+
+    /// 发起人处理
+    String _getUserId(String code, Map meta, Map data) {
+        String userId = String.valueOf(data.get(String.valueOf(((Map) meta.get(code)).get("creator"))));
+        // 报价单
+        if ("BJ".equals(code)) {
+            if (Arrays.asList("样品", "大货").contains(data.get("quoteType"))) {
+                userId = String.valueOf(data.get("dtOurReferencePersonId"));
+            } else {
+                userId = String.valueOf(data.get("dtSellerPersonId"));
+            }
+        }
+        if (UtilString.isBlankCompatNull(userId)) {
+            userId = YDConf.PUB_ACCOUNT;
+        }
+        return userId;
+    }
+
+    @Autowired
+    private DDClient ddClient;
+
+    @Autowired
+    private DDClient_Contacts ddClient_contacts;
+
+    @Autowired
+    private DDService ddService;
+
+    private static final String AUTH = "bbb76928-a9db-42fa-ac83-cc9a2ed75162";
+
+    /**
+     * 通用审批回调 [区分类型, 宜搭提示不同报错信息]
+     */
+    @Override
+    public void callbackProcess(Map data) {
+
+        // 拒绝审批意见必填校验
+        McException.assertParamException_Null(data, "Result");
+        if ("0".equals(data.get("Result"))) {
+            McException.assertParamException_Null(data, "Remark");
+        }
+        // 回调monitor审批信息
+        data.put("Url", "https://pxi03f.aliwork.com/APP_QWUVLI1R6XYUXWAOPF6O/processDetail?procInsId=" + data.get("DdProcessId"));
+        data.put("ApprovalDate", UtilDateTime.formatLocalDateTime(LocalDateTime.now()));
+        Map UserInfo = ddClient_contacts.getUserInfoById(ddClient.getAccessToken(), String.valueOf(data.get("Approver")));
+        data.put("Approver", String.valueOf(UserInfo.get("name")));
+        log.info("审批回调, {}", JSON.toJSONString(data));
+        // prd: 通过 http status 判定, 200 即为成功
+        String rsp = UtilHttp.doPost(_getEnvApi("/Approval"), UtilMap.map("Authorization", AUTH), UtilMap.map("Code", data.get("Code")), data);
+        Map result = (Map) JSON.parse(rsp);
+        McException.assertException(result.containsKey("errorMsg"), String.valueOf(result.get("status")), String.valueOf(result.get("errorMsg")), "Monitor");
+    }
+
+    /**
+     * 通讯录同步
+     */
+    @Override
+    @Synchronized
+    public void syncContact() {
+
+        // prd: 非全量同步: 总部\哈克\负责人
+        List<Long> deptList = UtilList.asList(DDConf.TOP_DEPARTMENT);
+        deptList.addAll(ddClient_contacts.getDepartmentId_all(ddClient.getAccessToken(), true, 129113277L)); // 总部
+        deptList.addAll(ddClient_contacts.getDepartmentId_all(ddClient.getAccessToken(), true, 128984162L)); // 哈克
+        for (long deptId : deptList) {
+            List<String> userIds = ddClient_contacts.listDepartmentUserId(ddClient.getAccessToken(), deptId);
+            if (userIds.size() == 0) {
+                continue;
+            }
+            // prd: 取第3层作为部门, 为空取值总经办
+            String dpetCascade = ddService.getUserDepartmentHierarchyJoin(ddClient.getAccessToken(), userIds.get(0), "-");
+            String deptName = "总经办";
+            String[] depts = dpetCascade.split("-");
+            if (depts.length >= 3) {
+                deptName = depts[2];
+            }
+            for (String userId : userIds) {
+                Map userInfo = ddClient_contacts.getUserInfoById(ddClient.getAccessToken(), userId);
+                Map data = UtilMap.map("employeeNumber, employeeName, dtPersonId, lastName", userInfo.get("job_number"), userInfo.get("name"), userInfo.get("userid"), userInfo.get("title"));
+                // prd: 手机号花名册未展示, 因此添加判空, 避免覆盖Monitor
+                if (StringUtils.isNotBlank(String.valueOf(userInfo.get("mobile")))) {
+                    data.put("mobilePhone", userInfo.get("mobile"));
+                }
+                if (userInfo.containsKey("org_email")) {
+                    data.put("email", userInfo.get("org_email"));
+                }
+                data.put("employeeDept", deptName);
+                data.put("dtDepts", dpetCascade);
+                data.put("dtPersonGroup", "");
+                log.info("同步人员, {}", JSON.toJSONString(UtilMap.map("employees", Arrays.asList(data))));
+                _syncContact(data, userId);
+            }
+        }
+
+        // 离职人员同步
+        Date sDate = UtilDateTime.convertToDateFromLocalDateTime(LocalDateTime.now().minusDays(1));
+        List<Map<String, String>> maps = ddClient_contacts.getLeaveEmployeeRecords(ddClient.getAccessToken(), sDate, null);
+        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"), "离职");
+            _syncContact(data, map.get("userId"));
+            log.info("离职人员, {}", JSON.toJSONString(UtilMap.map("employees", Arrays.asList(data))));
+        }
+    }
+
+    /// 员工信息写入monitor
+    void _syncContact(Map data, String userId) {
+
+        // prd: 通过 http status 判定, 200 即为成功
+        String rsp = UtilHttp.doPost(_getEnvApi("/EmployeeImport"), UtilMap.map("Authorization", AUTH), null, UtilMap.map("employees", Arrays.asList(data)));
+        Map result = (Map) JSON.parse(rsp);
+        if (UtilList.isNotEmpty(UtilMap.getList(result, "fail"))) {
+            result = (Map) UtilMap.getList(result, "fail").get(0); // 单条同步, 记录错误人员
+        }
+        //McException.assertException(result.containsKey("msg"), String.valueOf(result.get("status")), String.valueOf(result.get("msg")), "Monitor");
+        if (result.containsKey("msg")) {
+            ydClient.operateData(YDParam.builder()
+                    .formUuid("FORM-W2A66Z91912F39B2AHY0VD4CRK9F3C4JB74OLN")
+                    .formDataJson(JSON.toJSONString(UtilMap.map("dateField_lo47byyd, employeeField_lo47byyj, textareaField_lo47byyo, textareaField_lo47sanj", new Date().getTime(), userId, result, data)))
+                    .build(), YDConf.FORM_OPERATION.create);
+        }
+    }
+
+    /**
+     * 拉取数据发起
+     */
+    @Override
+    public void syncMonitor(String code) {
+
+        String rsp = UtilHttp.doPost(_getEnvApi("/Payment"), UtilMap.map("Authorization", AUTH), null, UtilMap.empty());
+        Map result = (Map) JSON.parse(rsp);
+        // prd: 通过 http status 判定, 200 即为成功
+        for (Map row : (List<Map>) UtilMap.getList(result, "rows")) {
+            startProcess(code, row);
+        }
+    }
+}

+ 1 - 1
mjava-hake/src/main/resources/application-prod.yml

@@ -1,6 +1,6 @@
 # 环境配置
 server:
-  port: 9019
+  port: 9011
   servlet:
     context-path: /api/hake
 

+ 9 - 1
mjava-hake/src/main/resources/static/json/form.json

@@ -43,6 +43,7 @@
       "textField_lnmyiqai": "deliveryAddress",
       "textField_lnmyiqaf": "seller",
       "textField_lnmyiqak": "dtPersonId",
+      "employeeField_lo5g6p79": "dtPersonId",
       "textField_lnmyiqan": "dtPersonGroup",
       "numberField_lnmyrzwx": "reason",
       "tableField_lnmyiqau": {
@@ -65,10 +66,11 @@
     "processCode": "TPROC--JD8668C1FYZEYMQ6BY5HA43UFP9T2JVBYYMNL8",
     "creator": "dtPersonId",
     "compIds": {
-      "textField_lnmyiqad": "supplierNumber",
+      "textField_lnmyiqad": "orderNumber",
       "textField_lnmyiqae": "supplierName",
       "textField_lnmyiqag": "term",
       "textField_lnmyiqak": "dtPersonId",
+      "employeeField_lo5ggrvr": "dtPersonId",
       "textField_lnmyiqan": "dtPersonGroup"
     }
   },
@@ -78,10 +80,12 @@
     "processCode": "TPROC--FDA66N81Q50FAELE9UEQDC4R2OR435FP1ZMNLA",
     "creator": "dtPersonId",
     "compIds": {
+      "textField_lo5h6ojh": "orderNumber",
       "textField_lnmyiqad": "supplierNumber",
       "textField_lnmyiqae": "supplierName",
       "textField_lnmyiqag": "seller",
       "textField_lnmyiqak": "dtPersonId",
+      "employeeField_lo5fmt0u": "dtPersonId",
       "textField_lnmyiqan": "dtPersonGroup",
       "tableField_lnmyiqau": {
         "textField_lnmyiqaw": "partNumber",
@@ -102,10 +106,12 @@
     "processCode": "TPROC--0K666DC1000FWTLICOT0GAHRLA363YTU5ZMNLA",
     "creator": "dtPersonId",
     "compIds": {
+      "textField_lo5i25ar": "orderNumber",
       "textField_lnmyiqad": "supplierNumber",
       "textField_lnmyiqae": "supplierName",
       "textField_lnmyiqag": "seller",
       "textField_lnmyiqak": "dtPersonId",
+      "employeeField_lo5gpzw4": "dtPersonId",
       "textField_lnmyiqan": "dtPersonGroup",
       "tableField_lnmyiqau": {
         "textField_lnmyiqaw": "partNumber",
@@ -230,6 +236,7 @@
       "textField_lnmyiqag": "customerName",
       "textField_lnmyiqai": "deliveryAddress",
       "textField_lnmyiqak": "dtPersonId",
+      "employeeField_lo5h89wz": "dtPersonId",
       "textField_lnmyiqan": "dtPersonGroup",
       "tableField_lnmyiqau": {
         "textField_lnmyiqav": "partNumber",
@@ -254,6 +261,7 @@
       "textField_lnmyiqaf": "customerName",
       "textField_lnmyiqag": "seller",
       "textField_lnmyiqai": "dtPersonId",
+      "employeeField_lo5hdfsd": "dtPersonId",
       "textField_lnmyiqak": "dtPersonGroup",
       "numberField_lnn0i86l": "reason",
       "tableField_lnmyiqau": {

+ 18 - 0
mjava-laidi/src/main/java/com/malk/laidi/controller/DDController.java

@@ -0,0 +1,18 @@
+package com.malk.laidi.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;
+
+/**
+ * 钉钉事件回调
+ * -
+ * 子项目直接继承即可有调用, 无需实现, 获取项目回调请求地址 [.]
+ */
+@Slf4j
+@RestController
+@RequestMapping("/dd")
+public class DDController extends DDCallbackController {
+
+}

+ 9 - 136
mjava-laidi/src/main/java/com/malk/laidi/controller/LDController.java

@@ -1,21 +1,14 @@
 package com.malk.laidi.controller;
 
 import com.alibaba.fastjson.JSON;
+import com.malk.laidi.service.LDClient;
 import com.malk.server.common.McR;
-import com.malk.server.dingtalk.DDConf;
-import com.malk.service.dingtalk.*;
-import com.malk.service.h3yun.CYClient;
 import com.malk.utils.UtilMap;
-import com.malk.utils.UtilString;
 import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.collections4.map.HashedMap;
-import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 
 import javax.servlet.http.HttpServletRequest;
-import java.util.ArrayList;
-import java.util.List;
 import java.util.Map;
 
 /**
@@ -26,157 +19,37 @@ import java.util.Map;
 @RequestMapping()
 public class LDController {
 
-    @Autowired
-    private DDClient ddClient;
-
-    @Autowired
-    private DDClient_Workflow ddClient_workflow;
 
     @Autowired
-    private CYClient cyClient;
-
-    /// 匹配氚云用户ID [ ppExt 临时方案为, H_User系统表, 字段DingTalkAccount「返回格式为 userId.corpId」, 通过氚云定时任务储存与钉钉对照关系 ]
-    String _getCYUserIdByDingTalkUserId(String dUserId) {
-        Map extInfo = UtilMap.map("matcherJson", JSON.toJSONString(UtilMap.map("Type, Name, Operator, Value", "Item", "F0000003", 2, dUserId)));
-        List<Map> userList = cyClient.queryData(ddClient.getAccessToken(), "D1489118960387db0274aa18b9168cde213c3d3", extInfo);
-        return String.valueOf(userList.get(0).get("F0000002"));
-    }
+    private LDClient ldClient;
 
     /**
      * 同步OA出差单到氚云
      */
     @PostMapping("sync-cc")
     McR syncCC(@RequestBody Map data) {
-
-        String processInstanceId = UtilMap.getString(data, "processInstanceId");
 //        String processInstanceId = "KGkpcf8qRUe26Y4Vh7ICxQ08031691566909"; //
 //        String processInstanceId = "ckDghK5NSHuLYBVammK4Cg08031694687962";
-
-        Map processData = ddClient_workflow.getProcessInstanceId(ddClient.getAccessToken(), processInstanceId);
-        List<Map> formComponentValues = (List<Map>) processData.get("formComponentValues");
-
-        // OA组件name, 匹配氚云组件ID
-        String schemaCode = "D148911abbb84666b4b481783235d0fcfc39d8d";
-        Map<String, String> compsId_main = UtilMap.map("出差事由, 出差天数, 出差备注, 同行人", "F0000002, F0000010, F0000011, F0000015");
-        Map<String, String> compsId_itinerary = UtilMap.map("交通工具, 单程往返, 出发城市, 目的城市, 开始时间, 结束时间, 时长", "F0000003, F0000004, F0000005, F0000006, F0000007, F0000008, F0000009");
-        compsId_main.put("行程明细", "D148911F875802263af84ddaa01b9e6bd679eb74"); // 子表组件
-
-        String dUserId = String.valueOf(processData.get("originatorUserId"));
-        String cUserId = _getCYUserIdByDingTalkUserId(dUserId);
-//        String cUserId = "fe68dfe1-27f7-4533-8a55-081590e4462a"; // 吴作江
-        Map formData = UtilMap.map("F0000013", cUserId);
-
-        for (String name : compsId_main.keySet()) {
-            String compId = compsId_main.get(name);
-            // 判定是否子表 [氚云]
-            if (compId.startsWith("D")) {
-                List<Map> details = new ArrayList<>();
-                // ppExt: 出差申请单为明细组件, 存在多条情况
-                String schedule = (String) formComponentValues.stream().filter(item -> "itinerary".equals(item.get("bizAlias"))).findAny().get().get("value");
-                List<Map> itineraryList = ((List<Map>) JSON.parse(schedule));
-                // 循环明细数据
-                for (Map itinerary : itineraryList) {
-                    List<Map> rowValue = (List<Map>) itinerary.get("rowValue");
-                    Map rowData = new HashedMap();
-                    // 循环子表组件
-                    for (String subName : compsId_itinerary.keySet()) {
-                        rowData.put(compsId_itinerary.get(subName), rowValue.stream().filter(item -> subName.equals(item.get("label"))).findAny().get().get("value"));
-                    }
-                    details.add(rowData);
-                }
-                formData.put(compId, details);
-                continue;
-            }
-            Map formComp = formComponentValues.stream().filter(item -> name.equals(item.get("name"))).findAny().get();
-            Object value = formComp.get("value");
-            // 同行人, 数据处理
-            if ("InnerContactField".equals(formComp.get("componentType")) && formComp.containsKey("value")) {
-                List<Map> empInfos = (List<Map>) JSON.parse(String.valueOf(formComp.get("extValue")));
-                List<String> emplsId = new ArrayList<>();
-                for (Map empInfo : empInfos) {
-                    emplsId.add(_getCYUserIdByDingTalkUserId(String.valueOf(empInfo.get("emplId"))));
-                }
-                value = emplsId; // 成员多选
-            }
-            formData.put(compId, value);
-        }
-
-        Map extInfo = UtilMap.map("opUserId, bizObjectJson, isDraft", cUserId, JSON.toJSONString(formData), true);
-        Map rsp = cyClient.operateData(ddClient.getAccessToken(), schemaCode, extInfo);
-        cyClient.operateData(ddClient.getAccessToken(), schemaCode, UtilMap.map("bizObjectId, opUserId", rsp.get("bizObjectId"), cUserId));
-
+        ldClient.syncCC(String.valueOf(data.get("processInstanceId")));
         return McR.success();
     }
 
-
-    @Autowired
-    private DDClient_Storage ddClient_storage;
-
-    @Autowired
-    private DDConf ddConf;
-
-    @Autowired
-    private DDClient_Contacts ddClient_contacts;
-
-    @Autowired
-    private DDService ddService;
-
     /**
-     * 同步氚云附件到钉盘
+     * 同步氚云附件到钉盘 [异步]
      */
     @PostMapping("h3yun-http")
     McR http(@RequestBody Map<String, String> data, @RequestParam String code) {
-
         log.info("氚云http请求, code = {}, body = {}", code, JSON.toJSONString(data));
-
-        if (UtilString.isNotBlankCompatNull(data.get("attachments")) && StringUtils.isNotBlank(data.get("projectName"))) {
-
-            String spaceId = "2034568562"; // 钉盘空间
-            String parentId = "120089274640"; // 项目管理文件夹
-            String proName = data.get("projectName");
-
-            // 获取用户unionId
-            String unionId = String.valueOf(ddClient_contacts.getUserInfoById(ddClient.getAccessToken(), ddConf.getOperator()).get("unionid"));
-            // 获取项目文件夹 [RETURN_DENTRY_IF_EXISTS: 文件夹名称冲突策略, 返回已存在文件夹, 避免查询文件夹列表]UtilMap.map("conflictStrategy", "RETURN_DENTRY_IF_EXISTS")
-            Map dentry = ddClient_storage.addFolders(ddClient.getAccessToken(), unionId, spaceId, parentId, proName, UtilMap.map("conflictStrategy", "RETURN_DENTRY_IF_EXISTS"));
-
-            // 获取氚云附件ID, 项目名称
-            List<Map> attachments = (List<Map>) JSON.parse(data.get("attachments"));
-            for (Map attachment : attachments) {
-                String urlFile = cyClient.getTemporaryUrls(ddClient.getAccessToken(), String.valueOf(attachment.get("ObjectId")));
-                // 上传文件
-                ddService.uploadFileFormUrlForDingDrive(ddClient.getAccessToken(), unionId, spaceId, String.valueOf(dentry.get("id")), urlFile, String.valueOf(attachment.get("FileName")));
-            }
-        }
+        ldClient.syncDP(String.valueOf(data.get("attachments")), String.valueOf(data.get("projectName")), String.valueOf(data.get("typeName")));
         return McR.success(UtilMap.map("status", "是"));
     }
 
-    @PostMapping("sync-dp")
-    McR syncDP(@RequestBody Map data) {
-
-
-        // 获取氚云附件ID, 项目名称
-
-        String spaceId = "2034568562";
-        String proName = "项目AA-2";
-
-        // 获取用户unionId
-        String unionId = String.valueOf(ddClient_contacts.getUserInfoById(ddClient.getAccessToken(), ddConf.getOperator()).get("unionid"));
-        // 获取项目文件夹 [RETURN_DENTRY_IF_EXISTS: 文件夹名称冲突策略, 返回已存在文件夹, 避免查询文件夹列表]UtilMap.map("conflictStrategy", "RETURN_DENTRY_IF_EXISTS")
-        Map dentry = ddClient_storage.addFolders(ddClient.getAccessToken(), unionId, spaceId, "0", proName, UtilMap.map("conflictStrategy", "RETURN_DENTRY_IF_EXISTS"));
-        // 上传文件
-        ddService.uploadFileFormUrlForDingDrive(ddClient.getAccessToken(), ddConf.getOperator(), spaceId, String.valueOf(dentry.get("id")), "https://mc.cloudpure.cn/portal/cloudpure/尾巴.jpg", "尾巴");
-        return McR.success();
-    }
-
+    /**
+     * 获取钉盘&文件夹信息, spaceId, parentId [可手动创建]
+     */
     @PostMapping("test")
     McR test(HttpServletRequest request) {
-
-        // 手动创建钉盘文件夹后, 获取spaceId
-        String unionId = String.valueOf(ddClient_contacts.getUserInfoById(ddClient.getAccessToken(), ddConf.getOperator()).get("unionid"));
-        ddClient_storage.getDentries(ddClient.getAccessToken(), unionId, "2034568562", "0", UtilMap.map("conflictStrategy", "RETURN_DENTRY_IF_EXISTS"));
-
-//        ddClient_storage.getSpaces(ddClient.getAccessToken(), unionId);
+        ldClient.test();
         return McR.success();
     }
 }

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

@@ -0,0 +1,59 @@
+package com.malk.laidi.delegate;
+
+import com.malk.delegate.DDEvent_Delegate;
+import com.malk.laidi.service.LDClient;
+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_Delegate {
+
+    // 审批任务回调执行业务逻辑
+    @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 LDClient ldClient;
+
+    // 审批实例回调执行业务逻辑
+    @Async
+    @Override
+    public void executeEvent_Instance_Finish(String processInstanceId, String processCode, boolean isAgree, boolean isTerminate) {
+        log.info("executeEvent_Instance_Finish");
+        if ("PROC-BEC29A2E-D8CA-4B66-B35A-67AC1C1EBB36".endsWith(processCode) && isAgree) {
+            ldClient.syncCC(processInstanceId);
+        }
+    }
+}

+ 23 - 0
mjava-laidi/src/main/java/com/malk/laidi/service/LDClient.java

@@ -0,0 +1,23 @@
+package com.malk.laidi.service;
+
+import org.springframework.scheduling.annotation.Async;
+
+public interface LDClient {
+
+
+    /**
+     * 同步OA出差单到氚云
+     */
+    void syncCC(String processInstanceId);
+
+    /**
+     * 同步氚云附件到钉盘 [异步]
+     */
+    @Async
+    void syncDP(String sAttachments, String sProjectName, String typeName);
+
+    /**
+     * 获取钉盘&文件夹信息, spaceId, parentId [可手动创建]
+     */
+    void test();
+}

+ 156 - 0
mjava-laidi/src/main/java/com/malk/laidi/service/impl/LDImplClient.java

@@ -0,0 +1,156 @@
+package com.malk.laidi.service.impl;
+
+import com.alibaba.fastjson.JSON;
+import com.malk.laidi.service.LDClient;
+import com.malk.server.dingtalk.DDConf;
+import com.malk.service.dingtalk.*;
+import com.malk.service.h3yun.CYClient;
+import com.malk.utils.UtilMap;
+import com.malk.utils.UtilString;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.map.HashedMap;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+@Service
+@Slf4j
+public class LDImplClient implements LDClient {
+
+    @Autowired
+    private DDClient ddClient;
+
+    @Autowired
+    private DDClient_Workflow ddClient_workflow;
+
+    @Autowired
+    private CYClient cyClient;
+
+    /**
+     * 同步OA出差单到氚云
+     */
+    @Override
+    public void syncCC(String processInstanceId) {
+
+        Map processData = ddClient_workflow.getProcessInstanceId(ddClient.getAccessToken(), processInstanceId);
+        List<Map> formComponentValues = (List<Map>) processData.get("formComponentValues");
+
+        // OA组件name, 匹配氚云组件ID
+        String schemaCode = "D148911abbb84666b4b481783235d0fcfc39d8d";
+        Map<String, String> compsId_main = UtilMap.map("出差事由, 出差天数, 出差备注, 同行人", "F0000002, F0000010, F0000011, F0000015");
+        Map<String, String> compsId_itinerary = UtilMap.map("交通工具, 单程往返, 出发城市, 目的城市, 开始时间, 结束时间, 时长", "F0000003, F0000004, F0000005, F0000006, F0000007, F0000008, F0000009");
+        compsId_main.put("行程明细", "D148911F875802263af84ddaa01b9e6bd679eb74"); // 子表组件
+
+        String dUserId = String.valueOf(processData.get("originatorUserId"));
+        String cUserId = _getCYUserIdByDingTalkUserId(dUserId);
+        Map formData = UtilMap.map("F0000013", cUserId);
+
+        for (String name : compsId_main.keySet()) {
+            String compId = compsId_main.get(name);
+            // 判定是否子表 [氚云]
+            if (compId.startsWith("D")) {
+                List<Map> details = new ArrayList<>();
+                // ppExt: 出差申请单为明细组件, 存在多条情况
+                String schedule = (String) formComponentValues.stream().filter(item -> "itinerary".equals(item.get("bizAlias"))).findAny().get().get("value");
+                List<Map> itineraryList = ((List<Map>) JSON.parse(schedule));
+                // 循环明细数据
+                for (Map itinerary : itineraryList) {
+                    List<Map> rowValue = (List<Map>) itinerary.get("rowValue");
+                    Map rowData = new HashedMap();
+                    // 循环子表组件
+                    for (String subName : compsId_itinerary.keySet()) {
+                        rowData.put(compsId_itinerary.get(subName), rowValue.stream().filter(item -> subName.equals(item.get("label"))).findAny().get().get("value"));
+                    }
+                    details.add(rowData);
+                }
+                formData.put(compId, details);
+                continue;
+            }
+            Map formComp = formComponentValues.stream().filter(item -> name.equals(item.get("name"))).findAny().get();
+            Object value = formComp.get("value");
+            // 同行人, 数据处理
+            if ("InnerContactField".equals(formComp.get("componentType")) && formComp.containsKey("value")) {
+                List<Map> empInfos = (List<Map>) JSON.parse(String.valueOf(formComp.get("extValue")));
+                List<String> emplsId = new ArrayList<>();
+                for (Map empInfo : empInfos) {
+                    emplsId.add(_getCYUserIdByDingTalkUserId(String.valueOf(empInfo.get("emplId"))));
+                }
+                value = emplsId; // 成员多选
+            }
+            formData.put(compId, value);
+        }
+
+        Map extInfo = UtilMap.map("opUserId, bizObjectJson, isDraft", cUserId, JSON.toJSONString(formData), true);
+        Map rsp = cyClient.operateData(ddClient.getAccessToken(), schemaCode, extInfo);
+        cyClient.operateData(ddClient.getAccessToken(), schemaCode, UtilMap.map("bizObjectId, opUserId", rsp.get("bizObjectId"), cUserId));
+    }
+
+    /// 匹配氚云用户ID [ ppExt 临时方案为, H_User系统表, 字段DingTalkAccount「返回格式为 userId.corpId」, 通过氚云定时任务储存与钉钉对照关系 ]
+    String _getCYUserIdByDingTalkUserId(String dUserId) {
+        Map extInfo = UtilMap.map("matcherJson", JSON.toJSONString(UtilMap.map("Type, Name, Operator, Value", "Item", "F0000003", 2, dUserId)));
+        List<Map> userList = cyClient.queryData(ddClient.getAccessToken(), "D1489118960387db0274aa18b9168cde213c3d3", extInfo);
+        return String.valueOf(userList.get(0).get("F0000002"));
+    }
+
+
+    @Autowired
+    private DDClient_Storage ddClient_storage;
+
+    @Autowired
+    private DDConf ddConf;
+
+    @Autowired
+    private DDClient_Contacts ddClient_contacts;
+
+    @Autowired
+    private DDService ddService;
+
+    /**
+     * 同步氚云附件到钉盘 [异步]
+     */
+    @Async
+    @Override
+    public void syncDP(String sAttachments, String sProjectName, String typeName) {
+
+        if (UtilString.isBlankCompatNull(sAttachments) || StringUtils.isBlank(sProjectName) || StringUtils.isBlank(typeName)) {
+            return;
+        }
+        // prd: 文件目录:钉盘空间 / 氚云项目附件 + 项目名称/ 日期(年月日)/ 附件名称
+        String spaceId = "2034568562"; // 钉盘空间
+        String parentId = "120089274640"; // 氚云项目附件
+
+        // 获取用户unionId
+        String unionId = String.valueOf(ddClient_contacts.getUserInfoById(ddClient.getAccessToken(), ddConf.getOperator()).get("unionid"));
+        // 获取项目文件夹 [RETURN_DENTRY_IF_EXISTS: 文件夹名称冲突策略, 返回已存在文件夹, 避免查询文件夹列表]UtilMap.map("conflictStrategy", "RETURN_DENTRY_IF_EXISTS")
+        Map dentry = ddClient_storage.addFolders(ddClient.getAccessToken(), unionId, spaceId, parentId, sProjectName, UtilMap.map("conflictStrategy", "RETURN_DENTRY_IF_EXISTS"));
+        dentry = ddClient_storage.addFolders(ddClient.getAccessToken(), unionId, spaceId, String.valueOf(dentry.get("id")), typeName, UtilMap.map("conflictStrategy", "RETURN_DENTRY_IF_EXISTS"));
+
+        // 获取氚云附件ID, 项目名称
+        List<Map> attachments = (List<Map>) JSON.parse(sAttachments);
+        for (Map attachment : attachments) {
+            String urlFile = cyClient.getTemporaryUrls(ddClient.getAccessToken(), String.valueOf(attachment.get("ObjectId")));
+            // 上传文件
+            ddService.uploadFileFormUrlForDingDrive(ddClient.getAccessToken(), unionId, spaceId, String.valueOf(dentry.get("id")), urlFile, String.valueOf(attachment.get("FileName")));
+        }
+
+    }
+
+    /**
+     * 获取钉盘&文件夹信息, spaceId, parentId [可手动创建]
+     */
+    @Override
+    public void test() {
+        String unionId = String.valueOf(ddClient_contacts.getUserInfoById(ddClient.getAccessToken(), ddConf.getOperator()).get("unionid"));
+
+//        ddClient_storage.getSpaces(ddClient.getAccessToken(), unionId);
+//        ddClient_storage.getDentries(ddClient.getAccessToken(), unionId, "2034568562", "0", UtilMap.map("conflictStrategy", "RETURN_DENTRY_IF_EXISTS"));
+
+//        Map dentry = ddClient_storage.addFolders(ddClient.getAccessToken(), unionId, "2034568562", "0", "\\\\xxt|///est.", UtilMap.map("conflictStrategy", "RETURN_DENTRY_IF_EXISTS"));
+
+    }
+}

+ 16 - 8
mjava-laidi/src/main/resources/application-dev.yml

@@ -36,14 +36,22 @@ logging:
 
 # dingtalk
 dingtalk:
-  agentId: 2741708038
-  appKey: ding0zl1or1vcgtirtob
-  appSecret: p_oYq2uaI4SZ-AatzNIb5N0HgothHqQCyJjlqtoy3UdrRNYL8Dak07sRdSRK4I_W
-  corpId: ding9ecc537cefafaf1c35c2f4657eb6378f
-  aesKey:
-  token:
-  robotCode:
-  operator: "102938654423116841"   # 孙中保 [开头0, 需要转一下字符串]
+  #  agentId: 2741708038
+  #  appKey: ding0zl1or1vcgtirtob
+  #  appSecret: p_oYq2uaI4SZ-AatzNIb5N0HgothHqQCyJjlqtoy3UdrRNYL8Dak07sRdSRK4I_W
+  #  corpId: ding9ecc537cefafaf1c35c2f4657eb6378f
+  #  aesKey: gQPE639PBHN7PNIIpKu3smnaISf5oqIfLMIKHyDFwSf
+  #  token: yLQuiiYIBvrzN3tmOduDn
+  #  robotCode:
+  #  operator: "102938654423116841"   # 孙中保 [开头0, 需要转一下字符串]
+
+  # 凯悦测试
+  agentId: 2219693533
+  appKey: dingvxsxgk6wpbcxwgkt
+  appSecret: d4XTXE_LHv5fXA5LbtzpWhL8Uo1AXhbmytK3qApF7ZoiiJJi4tXveI5egMc5feeI
+  corpId: ding3d180832e53201ee35c2f4657eb6378f
+  aesKey: hJOeo2jbLpAQgb92hMeptnASVWWAlCSlxVlnR6XdomS
+  token: nzqkgaWvJ8gNzLEg+
 
 # h3yun
 h3yun:

+ 8 - 8
mjava-laidi/src/main/resources/application-prod.yml

@@ -2,7 +2,7 @@
 server:
   port: 9018
   servlet:
-    context-path: /api/kuaikeli
+    context-path: /api/laidi
 
 # condition
 spel:
@@ -24,14 +24,14 @@ spring:
 
 # dingtalk
 dingtalk:
-  agentId: 2737389469
-  appKey: dingbdo9neuaibv5yjzb
-  appSecret: Gqy7vUkifTDQMCy5kQH8NaNVu1JepPr0Ci66XX5PiNts_mo6kNJEzYPumf3WcnmF
-  corpId: ding7071108579851504f5bf40eda33b7ba0
-  aesKey:
-  token:
+  agentId: 2741708038
+  appKey: ding0zl1or1vcgtirtob
+  appSecret: p_oYq2uaI4SZ-AatzNIb5N0HgothHqQCyJjlqtoy3UdrRNYL8Dak07sRdSRK4I_W
+  corpId: ding9ecc537cefafaf1c35c2f4657eb6378f
+  aesKey: gQPE639PBHN7PNIIpKu3smnaISf5oqIfLMIKHyDFwSf
+  token: yLQuiiYIBvrzN3tmOduDn
   robotCode:
-  operator: "8c69e45f31f3102938654423116841"   # 孙中保 [开头0, 需要转一下字符串]
+  operator: "102938654423116841"   # 孙中保 [开头0, 需要转一下字符串]
 
 # h3yun
 h3yun:

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

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

+ 1 - 3
mjava-laidi/src/test/resources/sample/CP_Utils.cs

@@ -7,7 +7,7 @@ using H3;
 public class CP_Utils
 {
 
-    /********** 莱蒂 **********/
+    /********** 莱蒂业务代码 **********/
 
     /// 附件同步到钉盘
     public static string syncDingDriveForProject(H3.IEngine engine, string projectName, string attachments) {
@@ -21,8 +21,6 @@ public class CP_Utils
         return data["status"] + string.Empty;
     }
 
-    /********** 工具 **********/
-
     /**
      * 氚云HTTP
      * 1. code 公用请求,code区分业务逻辑

+ 13 - 12
mjava-laidi/src/test/resources/sample/附件同步钉盘.cs

@@ -4,9 +4,9 @@ using System.Collections.Generic;
 using System.Text;
 using H3;
 
-public class D00086761755ee1ac3a4f30ab5aea330c1c47ff: H3.SmartForm.SmartFormController
+public class D000867Sscjmr10zg51xaa22ib6infyy2: H3.SmartForm.SmartFormController
 {
-    public D00086761755ee1ac3a4f30ab5aea330c1c47ff(H3.SmartForm.SmartFormRequest request): base(request)
+    public D000867Sscjmr10zg51xaa22ib6infyy2(H3.SmartForm.SmartFormRequest request): base(request)
     {
     }
 
@@ -22,20 +22,21 @@ public class D00086761755ee1ac3a4f30ab5aea330c1c47ff: H3.SmartForm.SmartFormCont
         {
             // 附件控件 [注意:前端直接获取的附件ID,无文件相关信息。若需要如文件名称,需要单独再查询SQL]
             System.Data.DataTable attachments = this.Engine.Query.QueryTable(string.Format("select * from H_bizobjectfile where ObjectId in ({0})", this.Request["attachmentIds"]), null);
-            // 请求处理 [项目名称,附件]
+            // 请求处理 [项目名称,附件] - 异步
             string status = CP_Utils.syncDingDriveForProject(this.Engine, this.Request["projectName"] + string.Empty, this.Serialize(attachments));
-            // 更新同步状态
-            this.Engine.Query.QueryTable(string.Format("update i_D00086761755ee1ac3a4f30ab5aea330c1c47ff set F0000003 = '{0}' where ObjectId = '{1}'", status, this.Request.BizObjectId), null);
         }
         // 审批节点:后端通过获取即可得到附件对象,返回值与H_bizobjectfile 结构一致
-        if(actionName == "Submit" && this.Request.ActivityCode == "Activity3")
+        if(actionName == "Submit" && this.Request.ActivityCode == "Activity3")  // 流程修改1:审批节点标识
         {
-            // 附件控件 [注意:新增的时候获取不了ID,包含发起审批节点]
-            H3.DataModel.BizObjectFileHeader[] files = (H3.DataModel.BizObjectFileHeader[]) this.Request.BizObject["F0000001"];
-            // 请求处理 [项目名称,附件]
-            string status = CP_Utils.syncDingDriveForProject(this.Engine, this.Request.BizObject["F0000002"] + string.Empty, this.Serialize(files));
-            // 更新同步状态
-            this.Engine.Query.QueryTable(string.Format("update i_D00086761755ee1ac3a4f30ab5aea330c1c47ff set F0000003 = '{0}' where ObjectId = '{1}'", status, this.Request.BizObjectId), null);
+            string[] compIds = { "F0000001", "F0000003" }; // 流程修改2:附件组件id
+            foreach(string compId in compIds)
+            {
+                H3.DataModel.BizObjectFileHeader[] files = (H3.DataModel.BizObjectFileHeader[]) this.Request.BizObject[compId];
+                if(files != null) {
+                    // 请求处理 [项目名称,附件] - 异步
+                    CP_Utils.syncDingDriveForProject(this.Engine, this.Request.BizObject["F0000002"] + string.Empty, this.Serialize(files));  // 流程修改3:项目名称组件id
+                }
+            }
         }
         base.OnSubmit(actionName, postValue, response);
     }

+ 17 - 8
mjava-laidi/src/test/resources/sample/附件同步钉盘.js

@@ -19,13 +19,16 @@ $.extend($.JForm, {
     // 加载事件
     OnLoad: function () {
 
-        this.F0000001.BindChange("F0000001", () => {
-            console.log(this.F0000001.GetValue())
+        //获取params{ SeasonObjectiveID: objId} 中的SeasonObjectiveID值
+        var listData = $.IGetParams("SeasonObjectiveID")
+
+        //判断加载时是否存在数据,如果不存在则不执行弹窗事件
+        if (listData + "" != "") {
+            //控件赋值
+            this.F0000001.SetValue(listData);
+            console.log(listData)
+        }
 
-            // 表单提交:由于附件控件新增的时候获取不了ID,因此通过前端传递传递。注意1:非Submit的情况下,不能通过 this.Request.BizObject["组件ID"] 取值。注意2:前端 AfterSubmit 提交后事件 调用服务不执行
-            // const attachmentIds = this.F0000001.GetValue().AttachmentIds.split(";").map(item => `'${item}'`);
-            // $.SmartForm.PostForm("sync", { "attachmentIds": attachmentIds.join(","), "projectName": this.F0000002.GetValue() })
-        })
     },
 
     // 按钮事件
@@ -40,8 +43,14 @@ $.extend($.JForm, {
     // 提交前事件
     BeforeSubmit: function (action, postValue) {
         // 表单提交:由于附件控件新增的时候获取不了ID,因此通过前端传递传递。注意1:非Submit的情况下,不能通过 this.Request.BizObject["组件ID"] 取值。注意2:前端 AfterSubmit 提交后事件 调用服务不执行
-        // const attachmentIds = this.F0000001.GetValue().AttachmentIds.split(";").map(item => `'${item}'`);
-        // $.SmartForm.PostForm("sync", { "attachmentIds": attachmentIds.join(","), "projectName": this.F0000002.GetValue() })
+        // const compIds = ["F0000001", "F0000003"] // 表单修改1:附件组件id
+        // compIds.forEach(compId => {
+        //     const attachmentIds = this.F0000001.GetValue().AttachmentIds.split(";").map(item => `'${item}'`);
+        //     $.SmartForm.PostForm("sync", {
+        //         "attachmentIds": attachmentIds.join(","),
+        //         "projectName": this.F0000002.GetValue()  // 表单修改2:项目名称id
+        //     })
+        // })
     },
 
     // 提交后事件

+ 103 - 14
mjava-taisen/src/main/java/com/malk/taisen/controller/TSController.java

@@ -8,13 +8,16 @@ import com.alibaba.fastjson.JSON;
 import com.malk.server.aliwork.YDConf;
 import com.malk.server.aliwork.YDParam;
 import com.malk.server.common.McR;
+import com.malk.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.UtilString;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
@@ -35,6 +38,36 @@ public class TSController {
     @Autowired
     private YDConf ydConf;
 
+    /**
+     * 保存提交版本
+     */
+    @PostMapping("backend")
+    McR backend(@RequestBody Map<String, String> data) {
+
+        String cId = data.get("cId");
+        String sId = data.get("sId");
+
+        Map formData = ydClient.queryData(YDParam.builder()
+                        .formInstanceId(cId)
+                        .build(),
+                YDConf.FORM_QUERY.retrieve_id).getFormData();
+        List<Map> details = ((List<Map>) formData.get("tableField_l6lmxo5r")).stream().map(item -> {
+            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"))));
+            return item;
+        }).collect(Collectors.toList());
+
+        Map updateForm = UtilMap.map("tableField_l6lmxo5r", details);
+        ydClient.operateData(YDParam.builder()
+                .formInstanceId(sId)
+                .updateFormDataJson(JSON.toJSONString(updateForm)).build(), YDConf.FORM_OPERATION.update);
+
+        return McR.success(formData);
+    }
+
+
+    ///// 数据修复
+
     @PostMapping("test11")
     McR test11() {
 
@@ -50,8 +83,12 @@ public class TSController {
 //        String sId = "5005d16d-a392-43a6-bdcb-c1ae93d30deb";
 
         // 陈豹
-        String tId = "640cff6e-89fb-4d50-9c45-aa2fcf8b06d5";
-        String sId = "13dfaba7-7f27-4f51-bcb6-5c8d71b758c1";
+//        String tId = "640cff6e-89fb-4d50-9c45-aa2fcf8b06d5";
+//        String sId = "13dfaba7-7f27-4f51-bcb6-5c8d71b758c1";
+
+        //
+        String tId = "7cd24af9-2ab2-4c70-b723-4db5b75089e4";
+        String sId = "06aec1b9-7023-49aa-b607-cacafae6f9f6";
 
         Map formData = ydClient.queryData(YDParam.builder()
                         .formInstanceId(tId)
@@ -59,6 +96,7 @@ public class TSController {
                 YDConf.FORM_QUERY.retrieve_id).getFormData();
         List<Map> details = ((List<Map>) formData.get("tableField_l6lmxo5r")).stream().map(item -> {
             item.put("associationFormField_l6vk2yxg", JSON.parse(item.get("associationFormField_l6vk2yxg_id").toString()));
+            item.put("associationFormField_l6napwww", JSON.parse(String.valueOf(item.get("associationFormField_l6napwww_id"))));
             // 关联申请
 //            List<Map> dataList = (List<Map>) ydClient.queryData(YDParam.builder()
 //                            .formUuid("FORM-78766VC1KTX2HZQ6E6IR3CYXWUFU3RO8RGN6L6")
@@ -81,7 +119,7 @@ public class TSController {
     @PostMapping("test13")
     McR test13() {
 
-        String fid = "0fa54443-8253-4f70-b1b7-18850e20a683";
+        String fid = "6f565a2f-a605-4f01-93c2-db177e2118c5";
 
         Map formData = ydClient.queryData(YDParam.builder()
                 .formInstanceId(fid)
@@ -90,20 +128,50 @@ public class TSController {
 
         details.forEach(item -> {
 
-//            if (UtilString.isBlankCompatNull(String.valueOf(item.get("numberField_l6lmxo61")))) {
-//                item.put("numberField_l6lmxo61", 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"))));
+            if (UtilString.isBlankCompatNull(String.valueOf(item.get("numberField_l6lmxo61")))) {
+                item.put("numberField_l6lmxo61", 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_l6lmxo61"));
         });
 
         Map updateForm = UtilMap.map("tableField_l6lmxo5r", details);
 
-//        ydClient.operateData(YDParam.builder()
-//                .formInstanceId(fid)
-//                .updateFormDataJson(JSON.toJSONString(updateForm)).build(), YDConf.FORM_OPERATION.update);
+        ydClient.operateData(YDParam.builder()
+                .formInstanceId(fid)
+                .updateFormDataJson(JSON.toJSONString(updateForm)).build(), YDConf.FORM_OPERATION.update);
+
+        return McR.success(updateForm);
+    }
+
+    // 修改报销金额
+    @PostMapping("test15")
+    McR test15() {
+
+        String fid = "ab4863d2-7e60-463e-8141-6ccd0a272282";
+
+        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);
+            }
+            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, "捌佰柒拾肆元陆角陆分");
+
+        ydClient.operateData(YDParam.builder()
+                .formInstanceId(fid)
+                .updateFormDataJson(JSON.toJSONString(updateForm)).build(), YDConf.FORM_OPERATION.update);
 
         return McR.success(updateForm);
     }
@@ -197,9 +265,30 @@ public class TSController {
 
 
     @PostMapping("test")
-    McR test() {
+    McR test(String id, @RequestBody Map data) {
 
-        log.info("xxxx, {}");
-        return McR.success();
+
+        id = "27791e35-0ade-4f58-91ac-0eb62f8db9b6";
+//        String fd = "FORM-56666571H80F5YH6B53L8AMCU02I2HLC4KLNL5"; // 测试
+//        String pd = "TPROC--56666571H80F5YH6B53L8AMCU02I29QC4KLNL6";
+
+        String fd = "FORM-UX866Q61LRK2LACIB8A6M6T1ZOLM2VY5NYB6LZ"; // 正式
+        String pd = "TPROC--UX866Q61LRK2LACIB8A6M6T1ZOLM2YY5NYB6L01";
+
+        // 重新审批: 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", "是", "是"));
+        return McR.success(rsp);
+    }
+
+
+    @PostMapping("exist")
+    DDR_New exist(@RequestBody Map data) {
+
+        DDR_New ddr_new = ydClient.queryData(YDParam.builder()
+                .formUuid(String.valueOf(data.get("formUuid")))
+                .searchFieldJson(JSON.toJSONString(data.get("conditions")))
+                .build(), YDConf.FORM_QUERY.retrieve_search_form);
+        return ddr_new;
     }
 }

+ 8 - 3
mjava-taisen/src/main/resources/application-dev.yml

@@ -42,15 +42,14 @@ dingtalk:
   corpId: dinge61fe69900ea236b35c2f4657eb6378f
   aesKey:
   token:
-  operator: "01021447385039757257"   # OA管理员账号
+  operator: ""   # OA管理员账号
 
-# aliwork
+## aliwork
 aliwork:
   appType: "APP_N9NPHVTQLPBPO8MR6WFG"
   systemToken: "UM6660D1PGF2O34KAVVKG8XZ756E3O06MZX5LW"
 
 
-
 # 广舜临时修改
 # dingtalk
 #dingtalk:
@@ -67,3 +66,9 @@ aliwork:
 #  appType: "APP_DKH67ZJCOYTS28FQKR2P"
 #  systemToken: "UIA663C1XGSCK4YCF7HBSDK01NMJ3THCHOHKL7O2"
 
+
+# 东方新华临时服务
+# aliwork
+#aliwork:
+#  appType: "APP_LNBUWW7ZBXRUWE29PCOV"
+#  systemToken: "WC866ZA1PI1B2L1JDNL334F44KI03GREIV1ILZ13"

+ 8 - 8
mjava-taisen/src/main/resources/application-prod.yml

@@ -1,6 +1,6 @@
 # 环境配置
 server:
-  port: 9018
+  port: 9014
   servlet:
     context-path: /api/taisen
 
@@ -24,15 +24,15 @@ spring:
 
 # dingtalk
 dingtalk:
-  agentId: 2660236361
-  appKey: dinguuieqv4lkvp3vkaf
-  appSecret: N5JjPU9RDk77pTze5vRWmiWLDjPKeYJV3sQrmYgN_SC57nOALmj570rVB0SGGcQQ
-  corpId: dingec9ee223c2b3a671
+  agentId: 1782962089
+  appKey: dingfnrpklgoaoynrbsw
+  appSecret: 6FxZudtXjdYeHNQN7-Os_A-zXGF7slS7UX9d3alu9uIsK56w4gXiZN0yXPp67FDh
+  corpId: dinge61fe69900ea236b35c2f4657eb6378f
   aesKey:
   token:
   operator: ""   # OA管理员账号
 
-# aliwork
+## aliwork
 aliwork:
-  appType: APP_FX4PR3OWW4WCFOHI2ZSR
-  systemToken: 2G766HA1RDHC1C2CCRY62544NL8L21T5786KL27
+  appType: "APP_N9NPHVTQLPBPO8MR6WFG"
+  systemToken: "UM6660D1PGF2O34KAVVKG8XZ756E3O06MZX5LW"

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

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

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

@@ -6,6 +6,8 @@ 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;
@@ -45,6 +47,8 @@ public class DDCallbackController {
                                               @RequestParam(value = "timestamp", required = false) String timestamp,
                                               @RequestParam(value = "nonce", required = false) String nonce,
                                               @RequestBody(required = false) JSONObject json) {
+
+
         DingCallbackCrypto callbackCrypto = new DingCallbackCrypto(ddConf.getToken(), ddConf.getAesKey(), ddConf.getAppKey());
         final String decryptMsg = callbackCrypto.getDecryptMsg(signature, timestamp, nonce, json.getString("encrypt"));
         JSONObject eventJson = JSON.parseObject(decryptMsg);
@@ -52,16 +56,21 @@ 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;
+//        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);
     }
 
     @PostMapping("robot")

+ 3 - 2
mjava/src/main/java/com/malk/server/aliwork/YDConf.java

@@ -59,7 +59,8 @@ public class YDConf {
         retrieve_search_form_id,             // 表单列表
 
         retrieve_details,             // 子表数据[表单]
-        retrieve_changed    // 变更记录
+        retrieve_changed,    // 变更记录
+        retrieve_definition, // 表单定义
 
     }
 
@@ -89,7 +90,7 @@ public class YDConf {
     }
 
     /**
-     * 组件格式化取值 [取值] todo 明细表递归
+     * 组件格式化取值 [取值] todo 明细表递归, 循环传递入参
      */
     public static Object getDataByCompId(Map formData, String compId_cur) {
         if (compId_cur.contains("associationFormField_")) {

+ 3 - 1
mjava/src/main/java/com/malk/server/dingtalk/crypto/DingCallbackCrypto.java

@@ -1,5 +1,6 @@
 package com.malk.server.dingtalk.crypto;
 
+import com.google.common.io.BaseEncoding;
 import org.apache.commons.codec.binary.Base64;
 
 import javax.crypto.Cipher;
@@ -56,7 +57,8 @@ public class DingCallbackCrypto {
         }
         this.token = token;
         this.corpId = corpId;
-        aesKey = Base64.decodeBase64(encodingAesKey + "=");
+        // ppExt: 23.10.26 钉钉新方式以Steam接入, HTTP形式commonsc-codec在升级之后,其内部做了一个validateCharacter校验. 使用 guava 替代
+        aesKey = BaseEncoding.base64().decode(encodingAesKey + "=");
     }
 
     public Map<String, String> getEncryptedMap(String plaintext) throws DingTalkEncryptException {

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

@@ -47,7 +47,7 @@ public interface YDService {
     List queryDataList_FormData(String formUuid, Map conditions);
 
     /**
-     * 全表复制 [服务注册传递参数都是 string 格式]
+     * 字段复制 [服务注册传递参数都是 string 格式]
      * -
      * fixme: 服务注册
      * 1. 在提交校验,因为数据还未执行, 除 #{_yida_all_data} 外,绝大部分字段都是为空,且若是子表字段数据:会拆为为单一字段,数据为该字段下明细数据数组
@@ -59,6 +59,11 @@ public interface YDService {
      */
     Object copyFormData(Map data);
 
+    /**
+     * 全表复制 [两张表组件完全一致]
+     */
+    Object mirrorFormData(String instanceId, String formUuid, String processCode, Map updateData);
+
     /**
      * upsert方法 [todo: 优化 批量的数据兼容]
      *

+ 64 - 1
mjava/src/main/java/com/malk/service/aliwork/impl/YDServiceImpl.java

@@ -12,6 +12,7 @@ import com.malk.utils.UtilMap;
 import lombok.SneakyThrows;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.collections4.map.HashedMap;
+import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
@@ -116,7 +117,7 @@ public class YDServiceImpl implements YDService {
     }
 
     /**
-     * 全表复制
+     * 字段复制
      */
     @Override
     public Object copyFormData(Map data) {
@@ -169,6 +170,68 @@ public class YDServiceImpl implements YDService {
                 .build(), type);
     }
 
+    /**
+     * 全表复制 [两张表组件完全一致]
+     */
+    @Override
+    public Object mirrorFormData(String instanceId, String formUuid, String processCode, Map updateData) {
+
+        YDConf.FORM_OPERATION type = StringUtils.isNotBlank(processCode) ? YDConf.FORM_OPERATION.start : YDConf.FORM_OPERATION.create;
+        Map<String, ?> formData = ydClient.queryData(YDParam.builder()
+                        .formInstanceId(instanceId)
+                        .build(),
+                YDConf.FORM_QUERY.retrieve_id).getFormData();
+
+        Map dataForm = UtilMap.empty();
+        for (String compId : formData.keySet()) {
+            // ppExt: 组件会返回带有_id/_value字段, 关联表单 & 成员组件赋值为原字段 [关联表单仅仅会返回带Id字段, 成员组件不带Id返回姓名过滤]
+            if (compId.endsWith("_value") || (compId.startsWith("employeeField_") && !compId.endsWith("_id"))) {
+                continue;
+            }
+            if (compId.endsWith("_id")) {
+                if (compId.startsWith("employeeField_") || compId.startsWith("associationFormField_")) {
+                    compId = compId.replace("_id", "");
+                } else {
+                    continue;
+                }
+            }
+            dataForm.put(compId, YDConf.getDataByCompId(formData, compId));
+            if (compId.startsWith("tableField_")) {
+
+                List<Map<String, ?>> details = (List<Map<String, ?>>) formData.get(compId);
+                List<Map> table = new ArrayList<>();
+                for (Map detail : details) {
+                    Map row = UtilMap.empty();
+                    for (String subId : details.get(0).keySet()) {
+                        // ppExt: 组件会返回带有_id/_value字段, 关联表单 & 成员组件赋值为原字段 [关联表单仅仅会返回带Id字段, 成员组件不带Id返回姓名过滤]
+                        if (subId.endsWith("_value") || (subId.startsWith("employeeField_") && !subId.endsWith("_id"))) {
+                            continue;
+                        }
+                        if (subId.endsWith("_id")) {
+                            if (subId.startsWith("employeeField_") || subId.startsWith("associationFormField_")) {
+                                subId = subId.replace("_id", "");
+                            } else {
+                                continue;
+                            }
+                        }
+                        row.put(subId, YDConf.getDataByCompId(detail, subId));
+                    }
+                    table.add(row);
+                }
+                dataForm.put(compId, table);
+            }
+        }
+
+        UtilMap.putAll(dataForm, updateData);
+        log.info("全表镜像, {}", JSON.toJSONString(dataForm));
+        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);
+    }
+
     /**
      * upsert方法
      */

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

@@ -33,7 +33,7 @@ public interface DDClient_Storage {
     List<Map> getDentries(String accessToken, String unionId, String spaceId, String parentId, Map extInfo);
 
     /**
-     * 添加文件夹
+     * 添加文件夹 [特殊字符已处理]
      *
      * @param name   文件夹的名称,命名有以下要求:头尾不能包含空格,否则会自动去除; 不能包含特殊字符,包括:制表符、*、"、<、>、|; 不能以"."结尾
      * @param option ppExt: 文件夹名称冲突策略, 返回已存在文件夹, 避免查询文件夹列表  UtilMap.map("conflictStrategy", "RETURN_DENTRY_IF_EXISTS")

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

@@ -9,7 +9,7 @@ import java.util.Map;
 public interface DDClient_Workflow {
 
     /**
-     * 发起审批实例 [旧版本有字段报错提示]
+     * 发起审批实例 [旧版本有字段报错提示] ppExt: 子组件内的数字输入框只要是开了编辑权限的,审批以后都会计入修改记录 [但手动发起不编辑不会记录]
      *
      * @param body_ext 包含必填字段, 不能为空, 统一扩展合并到请求参数内
      * @implNote 若approvers已传值时(即直接指定审批人列表, 最大列表长度:20。),则deptId不需填写。若approvers未传值时(即不直接指定审批人列表),则deptId需必填,若为根部门ID需填1。

+ 13 - 1
mjava/src/main/java/com/malk/service/dingtalk/impl/DDImplClient_Contacts.java

@@ -9,6 +9,7 @@ import com.malk.utils.UtilDateTime;
 import com.malk.utils.UtilList;
 import com.malk.utils.UtilMap;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
 import org.springframework.stereotype.Service;
 
 import java.util.ArrayList;
@@ -141,6 +142,11 @@ public class DDImplClient_Contacts implements DDClient_Contacts {
      */
     @Override
     public List<Map<String, String>> getLeaveEmployeeRecords(String access_token, Date startTime, Map extInfo) {
+        return _getLeaveEmployeeRecords(access_token, startTime, extInfo, new ArrayList<>());
+    }
+
+    /// 递归所有离职人员
+    public List<Map<String, String>> _getLeaveEmployeeRecords(String access_token, Date startTime, Map extInfo, List dataList) {
         String start = UtilDateTime.format(startTime, "yyyy-MM-dd'T'HH:mm:ss") + "Z"; // 字符格式
         Map body = UtilMap.map("startTime", start);
         if (ObjectUtil.isNotNull(extInfo)) {
@@ -153,7 +159,13 @@ public class DDImplClient_Contacts implements DDClient_Contacts {
         if (!body.containsKey("maxResults")) {
             body.put("maxResults", 50);
         }
-        return DDR_New.doGet("https://api.dingtalk.com/v1.0/contact/empLeaveRecords", DDConf.initTokenHeader(access_token), body).getRecords();
+        DDR_New ddr_new = DDR_New.doGet("https://api.dingtalk.com/v1.0/contact/empLeaveRecords", DDConf.initTokenHeader(access_token), body);
+        dataList.addAll(ddr_new.getRecords());
+        if (StringUtils.isNotBlank(ddr_new.getNextToken())) {
+            extInfo = UtilMap.putNotNull(extInfo, "nextToken", ddr_new.getNextToken());
+            _getLeaveEmployeeRecords(access_token, startTime, extInfo, dataList);
+        }
+        return dataList;
     }
 
     /**

+ 11 - 0
mjava/src/main/java/com/malk/service/dingtalk/impl/DDImplClient_Storage.java

@@ -102,6 +102,17 @@ public class DDImplClient_Storage implements DDClient_Storage {
     @Override
     public Map addFolders(String accessToken, String unionId, String spaceId, String parentId, String name, Map option) {
 
+        // ppExt: / \ * < > | "  [参考: 常用Unicode字符对照表]
+        name = name.replaceAll("\\u002F", "");
+        name = name.replaceAll("\\u005C", "");
+        name = name.replaceAll("\\u002A", "");
+        name = name.replaceAll("\\u003C", "");
+        name = name.replaceAll("\\u003E", "");
+        name = name.replaceAll("\\u007C", "");
+        name = name.replaceAll("\\u0022", "");
+        if (name.endsWith(".")) {
+            name = name.substring(0, name.length() - 1);
+        }
         Map param = UtilMap.map("unionId", unionId);
         Map body = UtilMap.map("name", name);
         if (ObjectUtil.isNotNull(option)) {

+ 1 - 1
mjava/src/main/java/com/malk/service/h3yun/CYClient.java

@@ -53,7 +53,7 @@ public interface CYClient {
      * 获取用户数据
      * -
      * ppExt 氚云组织架构只有全量查询接口, 且部门和用户ID, 有单独ID. 更甚接口未返回钉钉userId与手机号登信息
-     * 1. todo: 后续考虑新建氚云webservice进行写入
+     * 1. todo: 后续考虑新建氚云webservice进行查询
      * 2. 临时方案为, H_User系统表, 字段DingTalkAccount「返回格式为 userId.corpId」, 通过氚云定时任务储存与钉钉对照关系
      *
      * @param isRecursive 是否递归获取子级部门下的用户。默认值为false

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

@@ -40,6 +40,11 @@
       "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",
@@ -55,6 +60,11 @@
       "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",
@@ -204,6 +214,16 @@
       "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",
@@ -224,6 +244,16 @@
       "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",

+ 9 - 0
pom.xml

@@ -26,6 +26,8 @@
         <module>mjava-luyi</module>
         <module>mjava-taisen</module>
         <module>mjava-hake</module>
+        <module>majva-hongfengrenli</module>
+        <module>mjava_hongfeng</module>
     </modules>
     <packaging>pom</packaging>
 
@@ -47,6 +49,7 @@
         <validation-api.version>2.0.1.Final</validation-api.version>
         <fastjson.version>1.2.83</fastjson.version>
         <commons-lang3.version>3.10</commons-lang3.version>
+        <guava.version>30.1.1-jre</guava.version>
         <hutool-all.version>5.6.0</hutool-all.version>
         <spring-boot-starter-data-jpa.version>2.1.3.RELEASE</spring-boot-starter-data-jpa.version>
         <querydsl-apt.version>4.2.1</querydsl-apt.version>
@@ -125,6 +128,12 @@
                 <artifactId>commons-lang3</artifactId>
                 <version>${commons-lang3.version}</version>
             </dependency>
+            <!-- ppExt: 23.10.26 钉钉新方式以Steam接入, HTTP形式commonsc-codec在升级之后,其内部做了一个validateCharacter校验. 使用 guava 替代-->
+            <dependency>
+                <groupId>com.google.guava</groupId>
+                <artifactId>guava</artifactId>
+                <version>${guava.version}</version>
+            </dependency>
             <!-- 国产工具集 -->
             <dependency>
                 <groupId>cn.hutool</groupId>