pruple_boy 1 year ago
parent
commit
6bdaa61b2b
46 changed files with 3607 additions and 164 deletions
  1. 1 1
      mjava-aiwei/pom.xml
  2. 19 7
      mjava-aiwei/src/main/java/com/malk/aiwei/controller/TBController.java
  3. 121 0
      mjava-aiwei/src/main/java/com/malk/aiwei/controller/TBxYDController.java
  4. 0 13
      mjava-aiwei/src/main/java/com/malk/aiwei/controller/YDController.java
  5. 112 0
      mjava-aiwei/src/main/java/com/malk/aiwei/delegate/TBDelegate.java
  6. 23 0
      mjava-aiwei/src/main/java/com/malk/aiwei/server/AWServer.java
  7. 21 0
      mjava-aiwei/src/main/java/com/malk/aiwei/service/AWClint.java
  8. 124 0
      mjava-aiwei/src/main/java/com/malk/aiwei/service/impl/AWImplClient.java
  9. 34 13
      mjava-aiwei/src/main/resources/application-dev.yml
  10. 25 19
      mjava-aiwei/src/main/resources/application-prod.yml
  11. 24 25
      mjava-aiwei/src/main/resources/application-test.yml
  12. 35 0
      mjava-aiwei/src/test/resources/server.sh
  13. 3 0
      mjava-gewu/src/main/java/com/malk/gewu/service/impl/GWImplService.java
  14. 1 1
      mjava-hake/src/main/resources/static/json/form.json
  15. 2 0
      mjava-hangshi/src/main/java/com/malk/hangshi/controller/HSController.java
  16. 54 0
      mjava-lemeng/pom.xml
  17. 32 0
      mjava-lemeng/src/main/java/com/malk/lemeng/Boot.java
  18. 61 0
      mjava-lemeng/src/main/java/com/malk/lemeng/controller/LMController.java
  19. 35 0
      mjava-lemeng/src/main/java/com/malk/lemeng/schedule/LMScheduleTask.java
  20. 22 0
      mjava-lemeng/src/main/java/com/malk/lemeng/service/LMService.java
  21. 186 0
      mjava-lemeng/src/main/java/com/malk/lemeng/service/impl/LMImplService.java
  22. 65 0
      mjava-lemeng/src/main/resources/application-dev.yml
  23. 38 0
      mjava-lemeng/src/main/resources/application-prod.yml
  24. 855 0
      mjava-lemeng/src/main/resources/static/json/personnel
  25. 855 0
      mjava-lemeng/src/main/resources/static/json/personnel.json
  26. 39 0
      mjava-lemeng/src/test/resource/server.sh
  27. 1 1
      mjava-shangfeng/src/main/resources/application-dev.yml
  28. 34 5
      mjava-taisen/src/main/java/com/malk/taisen/controller/TSController.java
  29. 61 0
      mjava/src/main/java/com/malk/controller/TBCallBackController.java
  30. 3 0
      mjava/src/main/java/com/malk/delegate/McDelegate.java
  31. 23 0
      mjava/src/main/java/com/malk/delegate/TBEvent.java
  32. 29 0
      mjava/src/main/java/com/malk/delegate/impl/TBImplEvent.java
  33. 12 0
      mjava/src/main/java/com/malk/server/aliwork/YDConf.java
  34. 90 0
      mjava/src/main/java/com/malk/server/teambition/TBConf.java
  35. 31 0
      mjava/src/main/java/com/malk/server/teambition/TBR.java
  36. 1 1
      mjava/src/main/java/com/malk/service/aliwork/YDService.java
  37. 133 0
      mjava/src/main/java/com/malk/service/teambition/TBClient.java
  38. 22 0
      mjava/src/main/java/com/malk/service/teambition/TBService.java
  39. 205 0
      mjava/src/main/java/com/malk/service/teambition/impl/TBClientImpl.java
  40. 102 0
      mjava/src/main/java/com/malk/service/teambition/impl/TBServiceImpl.java
  41. 17 0
      mjava/src/main/java/com/malk/utils/UtilMap.java
  42. 7 0
      mjava/src/main/java/com/malk/utils/UtilString.java
  43. 2 2
      mjava/src/main/resources/application-dev.yml
  44. 30 60
      mjava/target/classes/META-INF/spring-configuration-metadata.json
  45. 2 2
      mjava/target/classes/application-dev.yml
  46. 15 14
      pom.xml

+ 1 - 1
mjava-aiwei/pom.xml

@@ -9,7 +9,7 @@
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
-    <artifactId>mjava-aipocloud</artifactId>
+    <artifactId>mjava-aiwei</artifactId>
     <description>艾为电子, TBx宜搭</description>
 
     <properties>

+ 19 - 7
mjava-aiwei/src/main/java/com/malk/aiwei/controller/TBController.java

@@ -1,17 +1,29 @@
 package com.malk.aiwei.controller;
 
-/**
- * 错误抛出与拦截详见 CatchException
- */
-
+import com.malk.controller.TBCallBackController;
+import com.malk.utils.UtilEnv;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
+/**
+ * TB事件回调 3_1
+ * -
+ * [子项目直接继承即可有调用, 无需实现]
+ * -
+ * 注解 @RequestMapping 路径不能重复 [主子项目属同一个项目];
+ * 获取项目回调请求地址, https://mc.cloudpure.cn/frp/xxx/tb/callback [调试代理: frp + nginx]
+ */
 @Slf4j
 @RestController
-@RequestMapping
-public class TBController {
+@RequestMapping("/tb")
+public class TBController extends TBCallBackController {
 
-    
+    // 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;
+    }
 }

+ 121 - 0
mjava-aiwei/src/main/java/com/malk/aiwei/controller/TBxYDController.java

@@ -0,0 +1,121 @@
+package com.malk.aiwei.controller;
+
+/**
+ * 错误抛出与拦截详见 CatchException
+ */
+
+import com.alibaba.fastjson.JSON;
+import com.malk.aiwei.service.AWClint;
+import com.malk.server.aliwork.YDConf;
+import com.malk.server.aliwork.YDParam;
+import com.malk.server.common.McR;
+import com.malk.server.dingtalk.DDConf;
+import com.malk.server.teambition.TBConf;
+import com.malk.service.aliwork.YDClient;
+import com.malk.service.teambition.TBClient;
+import com.malk.service.teambition.TBService;
+import com.malk.utils.UtilMap;
+import com.malk.utils.UtilServlet;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+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.List;
+import java.util.Map;
+
+@Slf4j
+@RestController
+@RequestMapping
+public class TBxYDController {
+
+    /**
+     * 交付物审批回调
+     */
+    @PostMapping("approved")
+    McR approve(HttpServletRequest request) {
+        
+        Map<String, ?> data = UtilServlet.getParamMap(request);
+        log.info("交付物审批回调, {}", data);
+//        String workStatusId = "通过".equals(data.get("approve")) ? "65966fb2f33db90815434eb1" : "65966fb2f33db90815434eab";
+        String workStatusId = "通过".equals(data.get("approve")) ? "65967ad70e25b16b2e91e56b" : "659679ec3bf0fb46b4b68c9f";
+        tbClient.updateTaskFlowStatus(String.valueOf(data.get("taskId")), String.valueOf(data.get("creatorId")), workStatusId, String.valueOf(data.get("approve")));
+        return McR.success();
+    }
+
+    /**
+     * 检查项check回调
+     */
+    @PostMapping("checked")
+    McR check(HttpServletRequest request) {
+
+        Map<String, ?> data = UtilServlet.getParamMap(request);
+        log.info("检查项check回调, {}", data);
+        String result = UtilMap.getString(data, "approve");
+        Map body = TBConf.assembleCustomFieldName("checklist状态", result); // 撤销, 终止, 已检查
+        tbClient.updateTaskCustomField(String.valueOf(data.get("taskId")), String.valueOf(data.get("creatorId")), body);
+        return McR.success();
+    }
+
+
+    @Autowired
+    private TBClient tbClient;
+
+    @Autowired
+    private TBService tbService;
+
+    @Autowired
+    private TBConf tbConf;
+
+    @Autowired
+    private DDConf ddConf;
+
+    @Autowired
+    private YDClient ydClient;
+
+
+    @GetMapping("t/task")
+    McR tb(String taskId) {
+
+        log.info("taskId, {}", taskId);
+        List<Map> taskList = tbClient.queryTaskDetail(taskId, "", "");
+        return McR.success(taskList);
+    }
+
+    @GetMapping("y/project")
+    McR yd(String taskId) {
+
+        List<Map> dataList = (List<Map>) ydClient.queryData(YDParam.builder()
+                .formUuid("FORM-3C7396A12ADB48A8833EBD90089C93833R21")
+                .build(), YDConf.FORM_QUERY.retrieve_search_form).getData();
+
+        return McR.success(dataList);
+    }
+
+    @Autowired
+    private AWClint awClint;
+
+    @GetMapping("t/approve")
+    McR tApprove() {
+        List<Map> docs = (List<Map>) JSON.parse("[{\"downloadUrl\":\"https://alidocs.dingtalk.com/i/team/o5WXMMM6PLYkJXwO/docs/5WXMN1dEajMJ6mwO?utm_source=tb&utm_medium=tb_task_attach\",\"name\":\"部署信息.docx\"}]");
+        awClint.approve("C0001", "65964fe5ae6f3b884c04e332", docs, "659df9e94aa556a15b8cc7ee", "65682c174655a82b4fa04dfe", "0249EDD1-754E-44C8-87F0-255B0E32021F");
+        return McR.success();
+    }
+
+    @GetMapping("t/checklist")
+    McR tChecklist() {
+        awClint.check("C0001", "65964fe5ae6f3b884c04e332", null, null, null);
+        return McR.success();
+    }
+
+    @GetMapping("test")
+    McR test() {
+
+        tbClient.idMapQuery(tbConf.getOperatorId(), "dingTalk-user", ddConf.getCorpId());
+        return McR.success();
+    }
+
+}

+ 0 - 13
mjava-aiwei/src/main/java/com/malk/aiwei/controller/YDController.java

@@ -1,13 +0,0 @@
-package com.malk.aiwei.controller;
-
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
-
-@Slf4j
-@RestController
-@RequestMapping
-public class YDController {
-    
-
-}

+ 112 - 0
mjava-aiwei/src/main/java/com/malk/aiwei/delegate/TBDelegate.java

@@ -0,0 +1,112 @@
+package com.malk.aiwei.delegate;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.malk.aiwei.service.AWClint;
+import com.malk.delegate.TBEvent;
+import com.malk.server.dingtalk.DDConf;
+import com.malk.server.teambition.TBConf;
+import com.malk.service.teambition.TBClient;
+import com.malk.utils.UtilMap;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Primary;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * OA审批事件 [主项目也有实现, 添加 @Primary 优先注入主项目实现]
+ * -
+ * 取消方案: 撤销和拒绝流程不继续执行连接器, 因此不使用连接器与轮询审批记录方案 [低效而且占用较高钉钉api调次数];
+ * 优化方案: 通过事件订阅实现实时同步所有审批状态, 定时查询钉钉回调失败记录 [配置钉钉事件Delegate, 添加定时任务]
+ */
+@Slf4j
+@Service
+@Primary
+public class TBDelegate implements TBEvent {
+
+
+    @Autowired
+    private TBClient tbClient;
+
+    @Autowired
+    private AWClint awClint;
+
+    @Autowired
+    private DDConf ddConf;
+
+    @Async
+    @Override
+    @SneakyThrows
+    public void callBackTask(JSONObject eventJson) {
+        String eventName = eventJson.getString("event");
+        log.info("callBackTask, {}, {}", eventName, eventJson);
+        // todo 查询工作流接口
+        if ("v3.task.taskflowstatus.update".equals(eventName)) {
+
+            JSONObject data = eventJson.getJSONObject("data");
+            List<String> workDones = Arrays.asList("659679ec3bf0fb46b4b68ca2", "65964fe63bf0fb46b4b6264d", "65964fe63bf0fb46b4b6264f"); // "65937802de54cc9e2351121c"; // 已完成, 触发checklist
+            String approve = "65967ad70e25b16b2e91e568"; // "65937802de54cc9e23511219"; // 提交评审, 触发交付物审批
+            List<String> allState = new ArrayList<>();
+            allState.addAll(workDones);
+            allState.add(approve);
+            if (!allState.contains(data.getString("tfsId"))) {
+                return;
+            }
+            List<Map> rTask = tbClient.queryTaskDetail(data.getString("taskId"), null, null);
+            List<Map> customfields = UtilMap.getList(rTask.get(0), "customfields");
+            String taskCode = "658d21e84aa556a15b887402"; // "659a67b83207833e1e2f744e"; // 任务编码字段ID
+            String tCode = TBConf.getTaskFieldValue_First(customfields, taskCode);
+            // 过滤编码为空任务
+            if (StringUtils.isBlank(tCode)) {
+                return;
+            }
+            // 匹配发起人, 新版本id映射表解析
+            Map<String, String> extra = (Map) tbClient.idMapQuery(data.getString("creatorId"), "dingTalk-user", ddConf.getCorpId()).get(0).get("extra");
+            String userId = extra.get("userId");
+            if (workDones.contains(data.getString("tfsId"))) {
+                String result = awClint.check(tCode, data.getString("projectId"), data.getString("taskId"), data.getString("creatorId"), userId);
+                if (StringUtils.isBlank(result)) {
+                    return;
+                }
+                // 回写审批记录链接, 自定义字段 [共用错误原因字段]
+                Map body = TBConf.assembleCustomFieldName("checklist状态", "检查中");
+                tbClient.updateTaskCustomField(data.getString("taskId"), data.getString("creatorId"), body);
+                Map body2 = TBConf.assembleCustomFieldName("自检要素", result);
+                tbClient.updateTaskCustomField(data.getString("taskId"), data.getString("creatorId"), body2);
+            }
+            if (approve.equals(data.getString("tfsId"))) {
+                String deliverable = "65694e28b2fdeecdb23b5a2f"; // "659655758c54dca4b61c274e"; // 交付物任务字段ID
+                List<Map> attachments = TBConf.getTaskFieldValue(customfields, deliverable).stream().map(item -> {
+                    Map link = (Map) JSON.parse(String.valueOf(item.get("metaString")));
+                    // ppExt: 知识库绑定没有文件后缀, 宜搭识别目前仅能点击下载跳转预览, 添加统一docx后缀, 文档会自行区分
+                    return UtilMap.map("downloadUrl, name", link.get("url"), link.get("title") + ".docx");
+                }).collect(Collectors.toList());
+
+                String result = awClint.approve(tCode, data.getString("projectId"), attachments, data.getString("taskId"), data.getString("creatorId"), userId);
+                if (StringUtils.isBlank(result)) {
+                    return;
+                }
+                Map body2 = TBConf.assembleCustomFieldName("交付件描述", result);
+                tbClient.updateTaskCustomField(data.getString("taskId"), data.getString("creatorId"), body2);
+            }
+        }
+    }
+
+    @Async
+    @Override
+    @SneakyThrows
+    public void callBackProject(JSONObject eventJson) {
+        String eventName = eventJson.getString("event");
+        log.info("callBackProject, {}", eventJson);
+    }
+
+}

+ 23 - 0
mjava-aiwei/src/main/java/com/malk/aiwei/server/AWServer.java

@@ -0,0 +1,23 @@
+package com.malk.aiwei.server;
+
+import com.alibaba.fastjson.JSON;
+import com.malk.utils.UtilHttp;
+import com.malk.utils.UtilMap;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class AWServer {
+
+
+    private static final String appKey = "AW394034j";
+    private static final String secret = "RJbH7RMH3vllt4KDri303Mlw@df3434k";
+
+    public static String getToken() {
+
+        Map body = UtilMap.map("appKey, secret", appKey, secret);
+        String rsp = UtilHttp.doPost("https://apigateway.awinic.com/token/getToken", null, body, new HashMap());
+        Map result = (Map) JSON.parse(rsp);
+        return UtilMap.getString(result, "token");
+    }
+}

+ 21 - 0
mjava-aiwei/src/main/java/com/malk/aiwei/service/AWClint.java

@@ -0,0 +1,21 @@
+package com.malk.aiwei.service;
+
+import java.util.List;
+import java.util.Map;
+
+public interface AWClint {
+
+    /**
+     * 交付物审批
+     * -
+     * ppExt
+     * 1. 宜搭附件传递 downloadUrl 和 name 即可实现在线预览
+     * 2. 知识库绑定没有文件后缀, 宜搭识别目前仅能点击下载跳转预览, 添加统一docx后缀, 文档会自行区分
+     */
+    String approve(String tCode, String pCode, List<Map> docs, String taskId, String operatorId, String userId);
+
+    /**
+     * 检查项check
+     */
+    String check(String tCode, String pCode, String taskId, String operatorId, String userId);
+}

+ 124 - 0
mjava-aiwei/src/main/java/com/malk/aiwei/service/impl/AWImplClient.java

@@ -0,0 +1,124 @@
+package com.malk.aiwei.service.impl;
+
+import com.alibaba.fastjson.JSON;
+import com.malk.aiwei.service.AWClint;
+import com.malk.server.aliwork.YDConf;
+import com.malk.server.aliwork.YDParam;
+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.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+@Service
+@Slf4j
+public class AWImplClient implements AWClint {
+
+
+    @Autowired
+    private YDClient ydClient;
+
+    @Autowired
+    private YDService ydService;
+
+    /**
+     * 交付物审批 [ppExt: 宜搭附件传递 downloadUrl 和 name 即可实现在线预览]
+     */
+    @Override
+    public String approve(String tCode, String pCode, List<Map> docs, String taskId, String operatorId, String userId) {
+
+        log.info("交付物审批, {}", JSON.toJSONString(UtilMap.map("tCode, pCode, docs, taskId, operatorId, userId", tCode, pCode, docs, taskId, operatorId, userId)));
+        // 工作流tb配置了流转逻辑, 冗余稳定性
+        DDR_New ddr_new = ydClient.queryData(YDParam.builder()
+                .formUuid("FORM-812FD46AF391449A8F206EDB3221B38840UQ")
+                .searchFieldJson(JSON.toJSONString(UtilMap.map("selectField_lqxuswzd, selectField_lqxuswze", tCode, pCode)))
+                .instanceStatus("RUNNING")
+                .build(), YDConf.FORM_QUERY.retrieve_search_process);
+        if (ddr_new.getTotalCount() > 0) {
+            return "";
+        }
+        // 查询任务表与项目表
+        List<Map> tList = ydService.queryDataList_FormData("FORM-A25299893F614A6EAA672514D3A76BB0QDBF", UtilMap.map("textField_lqxu8st8", tCode));
+        if (tList.size() == 0) {
+            return "未配置交付物审批.";
+        }
+        List<Map> pList = ydService.queryDataList_FormData("FORM-141E21DF183846028E21727CE43CD1C75CLZ", UtilMap.map("textField_lqxtykce", pCode));
+        if (pList.size() == 0) {
+            return "未匹配到项目主数据";
+        }
+        List<Map> roles = (List<Map>) pList.get(0).get("tableField_lqxtykcf");
+        Map formData = UtilMap.map("selectField_lqxuswzd, selectField_lqxuswze, textField_lqxuc9m4", tCode, pCode, UtilMap.getString(pList.get(0), "textField_lqxtykcd"));
+        formData.put("attachmentField_lqxtebtq", docs);
+        // 匹配任务编码与项目角色
+        List<Map<String, String>> compIds = Arrays.asList(UtilMap.map("tsRole, prRole, prEmp", "selectField_lqxu76th, selectField_lqxu76th, employeeField_lqxtebtw"),
+                UtilMap.map("tsRole, prRole, prEmp", "selectField_lqxu76tj, selectField_lqxuw78q, employeeField_lqxtebtx"), // 任务表角色, 交付物评审表: 角色, 审批人
+                UtilMap.map("tsRole, prRole, prEmp", "selectField_lqxu76tk, selectField_lqxuw78r, employeeField_lqxtebty"));
+        for (Map compId : compIds) {
+
+            Optional optional = roles.stream().filter(item -> UtilString.equal(tList.get(0).get(compId.get("tsRole")), item.get("selectField_lqxu6bgf"))).findAny();
+            if (optional.isPresent()) {
+                Map detail = (Map) optional.get();
+                formData.put(compId.get("prRole"), detail.get("selectField_lqxu6bgf"));
+                formData.put(compId.get("prEmp"), detail.get("employeeField_lqxtykch_id"));
+            }
+        }
+        // 发起评审
+        UtilMap.putAll(formData, UtilMap.map("textField_lr3dlwsa, textField_lr3er4qb", taskId, operatorId));
+        String instanceId = (String) ydClient.operateData(YDParam.builder()
+                .formUuid("FORM-812FD46AF391449A8F206EDB3221B38840UQ")
+                .processCode("TPROC--RJC66SC1NEFHXJ0H770K0CF4WN1K21HQ706RL5")
+                .formDataJson(JSON.toJSONString(formData))
+                .userId(userId)
+                .build(), YDConf.FORM_OPERATION.start);
+//        return "记录: " + "https://kabom7.aliwork.com/APP_H7WUJTKB448F9IBDC6C4/processDetail?procInsId=" + instanceId;
+        return "记录: " + "https://yida.awinic.com/APP_R5EBUF2FPN3Y8DRF93M4/processDetail?procInsId=" + instanceId;
+    }
+
+    /**
+     * 检查项check
+     */
+    @Override
+    public String check(String tCode, String pCode, String taskId, String operatorId, String userId) {
+
+        log.info("交付物审批, {}", JSON.toJSONString(UtilMap.map("tCode, pCode, taskId, operatorId, userId", tCode, pCode, taskId, operatorId, userId)));
+        // 是否存在chek记录过滤
+        List<Map> cList = ydService.queryDataList_FormData("FORM-7A50EA1991CE4494949FF7E1D4592FFBOVUS", UtilMap.map("selectField_lqxuswzd, selectField_lqxuswze", tCode, pCode));
+        if (cList.size() > 0) {
+            return "";
+        }
+        // 查询任务编码检查项与任务
+        List<Map> tList = ydService.queryFormData_all(YDParam.builder()
+                .formUuid("FORM-1A5D4D7FBF88409B956EBE51F9342A6BKOLP")
+                .searchFieldJson(JSON.toJSONString(UtilMap.map("textField_lqxu8st8", tCode)))
+                .build());
+        if (tList.size() == 0) {
+            return "未配置检查项.";
+        }
+        List<Map> pList = ydService.queryDataList_FormData("FORM-141E21DF183846028E21727CE43CD1C75CLZ", UtilMap.map("textField_lqxtykce", pCode));
+        if (pList.size() == 0) {
+            return "未匹配到项目主数据.";
+        }
+        Map formData = UtilMap.map("selectField_lqxuswzd, selectField_lqxuswze, textField_lqxuc9m4", tCode, pCode, UtilMap.getString(pList.get(0), "textField_lqxtykcd"));
+        // 填充检查项目, 填充子表
+        List<Map> checkList = tList.stream().map(item -> UtilMap.map("textField_lqxxgj4u", item.get("textField_lqxxgj4u"))).collect(Collectors.toList());
+        formData.put("tableField_lqxxgj4s", checkList);
+        // 发起评审
+        UtilMap.putAll(formData, UtilMap.map("textField_lr3dlwsa, textField_lr3er4qb", taskId, operatorId));
+        String instanceId = (String) ydClient.operateData(YDParam.builder()
+                .formUuid("FORM-7A50EA1991CE4494949FF7E1D4592FFBOVUS")
+                .processCode("TPROC--AKA66FB1XHFHN82QEFIQ591SNJ5W3AMBJ06RL9")
+                .formDataJson(JSON.toJSONString(formData))
+                .userId(userId)
+                .build(), YDConf.FORM_OPERATION.start);
+        return "记录: " + "https://yida.awinic.com/APP_R5EBUF2FPN3Y8DRF93M4/processDetail?procInsId=" + instanceId;
+    }
+}

+ 34 - 13
mjava-aiwei/src/main/resources/application-dev.yml

@@ -2,12 +2,7 @@
 server:
   port: 9001
   servlet:
-    context-path: /api/aipocloud
-  ssl:
-    key-store: classpath:ssl/cp.100ali.com.jks
-    key-store-password: n91xkery
-    keyStoreType: JKS      # 证书类型为jks, 非Tomcat下pfx [密码\路径不对程序启动异常]
-    http: 9000             # 同时启用http服务端口, 原默认端口只能https访问, 此处http端口仅支持http访问
+    context-path: /api/aiwei
 
 # condition
 spel:
@@ -52,14 +47,40 @@ logging:
   file:
     path: /Users/malk/server/_Tool/var/mjava/log
 
+## dingtalk
+#dingtalk:
+#  agentId: 2691784047
+#  appKey: dinghbynhnd2dbgypmsa
+#  appSecret: Kl5Xw8x0TlEIlvcJuUkYZD18UTTShJmfdKrAIpY8oX-Q_tazyUKA28nQh7dG5-mq
+#  corpId: ding321c72787fffc78b35c2f4657eb6378f
+#  aesKey:
+#  token:
+#  operator: ""   # OA管理员账号
+#
+## aliwork
+#aliwork:
+#  appType: "APP_H7WUJTKB448F9IBDC6C4"
+#  systemToken: "DHA66081DN6GRFNC6GTRW5NIJS082ZF0UN9PLLF"
+
+# teambition
+teambition:
+  AppID: 65956b5dd0ac095d62d0e592
+  AppSecret: gjQUoqKa1PHjTiyQFFuachfqKPyNeacA
+  TenantId: 6034c885e71842e1e5bb5218        # 管理后台 - 企业xx - 企业ID
+  OperatorId: 5e698cca57e7c1003626f3ff      # 公共账号, 需要有操作权限 [牧语]
+
 # dingtalk
 dingtalk:
-  agentId: 2704247534
-  appKey: dingqmijjypfepl0tsuq
-  appSecret: dixOqjK4Zw8PajvrtY1mbKxs4DIJJJmq6WvqdSDTCStBWAPyTeobgQFxZ1VhH-Z3
-  corpId: ding321c72787fffc78b35c2f4657eb6378f
-  aesKey: vBtjZT6yIJXPywnOqmMHYUyPBpglTostOMpdQIMrSHk
-  token: ngnPYIwW5RfZwxnb1OjQJMb6U62NYKbvxGtcVaYe1hRaaKM1j8qG
-  operator: "095358016629044412"   # OA管理员账号
+  agentId: 2848797049
+  appKey: dingbqy1qugrihao43dl
+  appSecret: UUaTKTWgLdduHvMSl0ipm19f_PDarHLHqnpz4vFZXjkkmFNmfWuwoPF1evjIRwvd
+  corpId: ding5fcad818b0d9f62c35c2f4657eb6378f
+  aesKey:
+  token:
+  operator: ""   # OA管理员账号
 
+# aliwork
+aliwork:
+  appType: "APP_R5EBUF2FPN3Y8DRF93M4"
+  systemToken: "ON566NC1VNIHPANP9TNVHB3TBIWS3E0TUZ5RLF3"
 

+ 25 - 19
mjava-aiwei/src/main/resources/application-prod.yml

@@ -1,17 +1,12 @@
 # 环境配置
 server:
-  port: 9021
+  port: 9001
   servlet:
-    context-path: /api/aipocloud
-  ssl:
-    key-store: classpath:ssl/cp.100ali.com.jks
-    key-store-password: n91xkery
-    keyStoreType: JKS      # 证书类型为jks, 非Tomcat下pfx [密码\路径不对程序启动异常]
-    http: 9022             # 同时启用http服务端口, 原默认端口只能https访问, 此处http端口仅支持http访问
+    context-path: /api/aiwei
 
 # condition
 spel:
-  scheduling: true         # 定时任务是否执行
+  scheduling: false        # 定时任务是否执行
   multiSource: false       # 是否多数据源配置
 
 spring:
@@ -21,20 +16,31 @@ spring:
       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
+    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
-    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管理员账号
+  agentId: 2848797049
+  appKey: dingbqy1qugrihao43dl
+  appSecret: UUaTKTWgLdduHvMSl0ipm19f_PDarHLHqnpz4vFZXjkkmFNmfWuwoPF1evjIRwvd
+  corpId: ding5fcad818b0d9f62c35c2f4657eb6378f
+  aesKey:
+  token:
+  operator: "0249EDD1-754E-44C8-87F0-255B0E32021F"   # OA管理员账号
+
+# aliwork
+aliwork:
+  appType: "APP_R5EBUF2FPN3Y8DRF93M4"
+  systemToken: "ON566NC1VNIHPANP9TNVHB3TBIWS3E0TUZ5RLF3"
+
+# teambition
+teambition:
+  AppID: 659cf4922fefb4a7ec89137b
+  AppSecret: ploT7pyTcEVz91i5IIBZ8Pw7LbrOWPYD
+  TenantId: 655f1512ad7db5a6a70cf7b1                # 管理后台 - 企业xx - 企业ID
+  OperatorId: 65682c174655a82b4fa04dfe              # 公共账号, 需要有操作权限 [x]
+  ApiHost: https://tb.awinic.com:443/gateway        # 私有部署

+ 24 - 25
mjava-aiwei/src/main/resources/application-test.yml

@@ -1,39 +1,38 @@
 # 环境配置
 server:
-  port: 9091
+  port: 9023
   servlet:
-    context-path: /api/aipocloud
-  ssl:
-    key-store: classpath:ssl/server.jks
-    key-store-password: jJFTKCDX5mP9uvFb
-    keyStoreType: JKS      # 证书类型为jks, 非Tomcat下pfx [密码\路径不对程序启动异常]
-    http: 9090             # 同时启用http服务端口, 原默认端口只能https访问, 此处http端口仅支持http访问
+    context-path: /api/aiwei
 
 # condition
 spel:
-  scheduling: true         # 定时任务是否执行
+  scheduling: false        # 定时任务是否执行
   multiSource: false       # 是否多数据源配置
 
 spring:
   # database
   datasource:
-    driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
-    url: jdbc:sqlserver://10.14.2.4:1433;SelectMethod=cursor;DatabaseName=dingtalk
-    username: DingTalk
-    password: Dingding2023@#
-    # JPA
-    jpa:
-      database: sql_server
-      properties:
-        hibernate:
-          default_schema: dbo
+    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: 2791266100
-  appKey: ding0dmmeh4ufremtbkp
-  appSecret: RHE6gWY-0PBUSFKNF6j__v1II9WD5h0u2TZDK3QwKKczivXid2br_SMWK-EKSDLT
-  corpId: dingbc79c496d04b812f35c2f4657eb6378f
-  aesKey: vBtjZT6yIJXPywnOqmMHYUyPBpglTostOMpdQIMrSHk
-  token: ngnPYIwW5RfZwxnb1OjQJMb6U62NYKbvxGtcVaYe1hRaaKM1j8qG
-  operator: "0220663466860353"   # OA管理员账号
+  agentId: 2691784047
+  appKey: dinghbynhnd2dbgypmsa
+  appSecret: Kl5Xw8x0TlEIlvcJuUkYZD18UTTShJmfdKrAIpY8oX-Q_tazyUKA28nQh7dG5-mq
+  corpId: ding321c72787fffc78b35c2f4657eb6378f
+  aesKey:
+  token:
+  operator: ""   # OA管理员账号
+
+# aliwork
+aliwork:
+  appType: "APP_H7WUJTKB448F9IBDC6C4"
+  systemToken: "DHA66081DN6GRFNC6GTRW5NIJS082ZF0UN9PLLF"

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

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

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

@@ -49,6 +49,9 @@ public class GWImplService implements GWService {
         ddClient_contacts.getDepartmentId_all(ddClient.getAccessToken(), true).forEach(deptId -> {
             List<String> userIds = ddClient_contacts.listDepartmentUserId(ddClient.getAccessToken(), deptId);
             log.info("dept, {}, userIds, {}", deptId, userIds.size());
+            if (userIds.size() == 0) {
+                return;
+            }
             // 员工花名册信息
             ddClient_personnel.getEmployeeInfos(ddClient.getAccessToken(), userIds, ddConf.getAgentId(), null).forEach(employeeInfo -> {
                 // 通过元数据字段code, 匹配员工花名册value

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

@@ -96,7 +96,7 @@
     "compIds": {
       "textField_lnmyiqad": "orderNumber",
       "textField_lnmyiqae": "supplierName",
-      "textField_lnmyiqag": "term",
+      "textField_lnmyiqag": "paymentTerm",
       "textField_lnmyiqak": "dtPersonId",
       "employeeField_lo5ggrvr": "dtPersonId",
       "textField_lnmyiqan": "dtPersonGroup"

+ 2 - 0
mjava-hangshi/src/main/java/com/malk/hangshi/controller/HSController.java

@@ -73,6 +73,8 @@ public class HSController {
                 .formUuid("FORM-3C866TC1RA29KJXD7XF4N4ZXW9932Z69W6CFLR")
                 .searchFieldJson(JSON.toJSONString(UtilMap.map("textField_lfw8yizv, selectField_lfw82zh0", type, param.get("corpName"))))
                 .build(), YDConf.FORM_QUERY.retrieve_search_form).getData();
+        // 存在前缀相同的公司, 后置过滤
+        list = list.stream().filter(item -> param.get("corpName").equals(((Map) item.get("formData")).get("selectField_lfw82zh0"))).collect(Collectors.toList());
         List<String> userIds = list.stream().map(item -> String.valueOf(((Map) item.get("formData")).get("textField_lfw8yizu"))).collect(Collectors.toList());
         return userIds;
     }

+ 54 - 0
mjava-lemeng/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-lemeng</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-lemeng/src/main/java/com/malk/lemeng/Boot.java

@@ -0,0 +1,32 @@
+package com.malk.lemeng;
+
+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);
+    }
+}

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

@@ -0,0 +1,61 @@
+package com.malk.lemeng.controller;
+
+/**
+ * 错误抛出与拦截详见 CatchException
+ */
+
+import com.malk.lemeng.service.LMService;
+import com.malk.server.common.McR;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@Slf4j
+@RestController
+@RequestMapping
+public class LMController {
+
+    @Autowired
+    private LMService lmService;
+
+    /**
+     * 同步花名册信息
+     */
+    @PostMapping("sync")
+    McR syncRoster() {
+
+        lmService.syncRoster();
+        return McR.success();
+    }
+
+    /**
+     * 同步部门信息
+     */
+    @PostMapping("dept")
+    McR syncDepartment() {
+
+        lmService.syncDepartment();
+        return McR.success();
+    }
+
+    /**
+     * 部门预算统计
+     */
+    @PostMapping("calc")
+    McR calcBudget() {
+
+        lmService.calcBudget();
+        return McR.success();
+    }
+    
+    /**
+     * test
+     */
+    @PostMapping("test")
+    McR test() {
+
+        return McR.success();
+    }
+}

+ 35 - 0
mjava-lemeng/src/main/java/com/malk/lemeng/schedule/LMScheduleTask.java

@@ -0,0 +1,35 @@
+package com.malk.lemeng.schedule;
+
+import com.malk.lemeng.service.LMService;
+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 LMScheduleTask {
+
+    @Autowired
+    private LMService lmService;
+
+    /**
+     * 每天凌晨4点同步
+     */
+    @Scheduled(cron = "0 0 4 * * ? ")
+    public void syncDingTalkFailedList() {
+        try {
+            lmService.syncRoster();
+        } catch (Exception e) {
+            // 记录错误信息
+            e.printStackTrace();
+        }
+    }
+}

+ 22 - 0
mjava-lemeng/src/main/java/com/malk/lemeng/service/LMService.java

@@ -0,0 +1,22 @@
+package com.malk.lemeng.service;
+
+public interface LMService {
+
+    /**
+     * 同步花名册信息
+     */
+    void syncRoster();
+
+    /**
+     * 同步部门信息
+     */
+    void syncDepartment();
+
+    /**
+     * 部门预算统计
+     */
+    void calcBudget();
+
+    // test
+    void test();
+}

+ 186 - 0
mjava-lemeng/src/main/java/com/malk/lemeng/service/impl/LMImplService.java

@@ -0,0 +1,186 @@
+package com.malk.lemeng.service.impl;
+
+import com.alibaba.fastjson.JSON;
+import com.malk.lemeng.service.LMService;
+import com.malk.server.aliwork.YDConf;
+import com.malk.server.aliwork.YDParam;
+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.service.dingtalk.DDClient_Personnel;
+import com.malk.utils.UtilFile;
+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.*;
+
+@Service
+@Slf4j
+public class LMImplService implements LMService {
+
+    @Autowired
+    private DDClient ddClient;
+
+    @Autowired
+    private DDConf ddConf;
+
+    @Autowired
+    private DDClient_Contacts ddClient_contacts;
+
+    @Autowired
+    private DDClient_Personnel ddClient_personnel;
+
+    @Autowired
+    private YDClient ydClient;
+
+    /**
+     * 同步花名册信息
+     */
+    @Override
+    public void syncRoster() {
+
+        // 花名册元数据
+        List<Map> metaList = (List<Map>) UtilFile.readJsonObjectFromResource("static/json/personnel.json"); // 本地匹配了宜搭组件ID
+//        List<Map> metaList = ddClient_personnel.getPersonnelMeta(ddClient.getAccessToken(), ddConf.getAgentId());
+        // 同步全量人员
+        ddClient_contacts.getDepartmentId_all(ddClient.getAccessToken(), true).forEach(deptId -> {
+            List<String> userIds = ddClient_contacts.listDepartmentUserId(ddClient.getAccessToken(), deptId);
+            log.info("dept, {}, userIds, {}", deptId, userIds.size());
+            if (userIds.size() == 0) {
+                return;
+            }
+            // 员工花名册信息
+            ddClient_personnel.getEmployeeInfos(ddClient.getAccessToken(), userIds, ddConf.getAgentId(), null).forEach(employeeInfo -> {
+                // 通过元数据字段code, 匹配员工花名册value
+                List<Map> employeeField = (List<Map>) employeeInfo.get("field_data_list");
+                // 宜搭表单数据
+                Map formData = UtilMap.map("employeeField_limrznyp", Arrays.asList(employeeInfo.get("userid"))); // 成员权限
+                metaList.forEach(meta -> {
+                    boolean isDetail = UtilMap.getBoolean(meta, "detail");
+                    List<Map> metaField = (List<Map>) meta.get("field_meta_info_list");
+                    Map detail = new HashMap(); // 明细行
+                    metaField.forEach(field -> {
+                        // 元数据内一些系统字段无 field_code, sys00 基本信息分组下 使用 field_name
+                        Optional optional = employeeField.stream().filter(employee -> field.get("field_code").equals(employee.get("field_code")) || employee.get("field_name").equals(field.get("field_name"))).findAny();
+                        if (optional.isPresent()) {
+                            // 数据组装
+                            Map employee = (Map) optional.get();
+                            String value = UtilMap.getString(((List<Map>) employee.get("field_value_list")).get(0), "label");
+                            log.info("分组 -> {}, 是否明细 -> {}; 字段 -> {}, 值 -> {}", meta.get("group_name"), meta.get("detail"), field.get("field_name"), value);
+                            // 值处理
+                            if (field.containsKey("comp_id")) {
+                                if (isDetail) {
+                                    detail.put(field.get("comp_id"), value);
+                                } else {
+                                    formData.put(field.get("comp_id"), value);
+                                }
+                            }
+                        }
+                    });
+                    // 明细表
+                    if (isDetail && meta.containsKey("comp_id")) {
+                        formData.put(meta.get("comp_id"), Arrays.asList(detail));
+                    }
+                });
+                // 宜搭更新 todo 查询同步数据, 代码内匹配, 而不是循环内查询
+                YDParam ydParam = YDParam.builder()
+                        .searchFieldJson(JSON.toJSONString(UtilMap.map("employeeField_limrznyp", formData.get("employeeField_limrznyp"))))
+                        .formUuid("FORM-54C47C335C054FFBAECCA0B92100A341PGD2")
+                        .build();
+                List<String> formInstIds = (List<String>) ydClient.queryData(ydParam, YDConf.FORM_QUERY.retrieve_search_form_id).getData();
+                if (formInstIds.size() > 0) {
+                    ydParam.setFormInstanceId(formInstIds.get(0));
+                    ydParam.setUpdateFormDataJson(JSON.toJSONString(formData));
+                    ydClient.operateData(ydParam, YDConf.FORM_OPERATION.update);
+                } else {
+                    ydParam.setFormDataJson(JSON.toJSONString(formData));
+                    ydClient.operateData(ydParam, YDConf.FORM_OPERATION.create);
+                }
+            });
+        });
+    }
+
+    @Autowired
+    private YDService ydService;
+
+    /**
+     * 同步部门信息
+     */
+    @Override
+    public void syncDepartment() {
+
+        // 部门已同步数据
+        List<Map> dataList = ydService.queryFormData_all(YDParam.builder()
+                .formUuid("FORM-B6F662B18C3F4D7D855CFE50243394AFQPOH")
+                .build());
+        // 匹配钉钉通讯录
+        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;
+            }
+            Map info = ddClient_contacts.getDepartmentInfo(ddClient.getAccessToken(), deptId);
+            Map formData = UtilMap.map("textField_lpcff8zu, numberField_lr70cqsl, numberField_lr70cqsk", info.get("name"), info.get("parent_id"), info.get("dept_id"));
+            // todo 部门ID写入需要是string集合\数组
+            formData.putAll(UtilMap.map("employeeField_lpmbmyaa, departmentSelectField_lpaks312", info.get("dept_manager_userid_list"), Arrays.asList(String.valueOf(info.get("dept_id")))));//            float budget = userIds.size() * 200; // 部门人均
+            float budget = userIds.size() * 200; // 部门人均
+            formData.putAll(UtilMap.map("numberField_lr71bkuj, employeeField_lr71bkui, numberField_lr71mvbk", userIds.size(), userIds, budget));
+            // Upsert操作, 记录当月预算
+            Optional optional = dataList.stream().filter(item -> deptId == UtilMap.getLong(item, "numberField_lr70cqsk")).findAny();
+            if (optional.isPresent()) {
+                formData.put("employeeField_lr71bkui", UtilMap.getString((Map) optional.get(), "employeeField_lr71bkui_id")); // 非常规组件数据处理
+                ydClient.operateData(YDParam.builder()
+                        .formInstanceId(UtilMap.getString((Map) optional.get(), "instanceId"))
+                        .updateFormDataJson(JSON.toJSONString(formData))
+                        .build(), YDConf.FORM_OPERATION.update);
+                continue;
+            }
+            formData.putAll(UtilMap.map("numberField_lpaks318, numberField_lpayajrp, numberField_lpayajrn", 0, 0, 0));
+            formData.putAll(UtilMap.map("radioField_lpaks316", "是")); // test
+            ydClient.operateData(YDParam.builder()
+                    .formUuid("FORM-B6F662B18C3F4D7D855CFE50243394AFQPOH")
+                    .formDataJson(JSON.toJSONString(formData))
+                    .build(), YDConf.FORM_OPERATION.create);
+        }
+    }
+
+    /**
+     * 部门预算统计
+     */
+    @Override
+    public void calcBudget() {
+
+        // 部门已同步数据
+        List<Map> dataList = ydService.queryFormData_all(YDParam.builder()
+                .formUuid("FORM-B6F662B18C3F4D7D855CFE50243394AFQPOH")
+                .build());
+        // prd 非独立预算部门, 累计到父部门, 若父部门为空, 持续往上累加
+        dataList.forEach(item -> {
+            if ("是".equals(item.get("radioField_lpaks316"))) {
+                int budget = UtilMap.getInt(item, "numberField_lr71mvbk");
+                // 冗余修改记录, 保留修改前金额并重置budget, 避免重复调用
+                item.putAll(UtilMap.map("numberField_lr7yq38c, numberField_lr71mvbk", UtilMap.getFloat(item, "numberField_lpaks318"), 0));
+                item.put("numberField_lpaks318", UtilMap.getFloat(item, "numberField_lpaks318") + budget);
+                item.put("numberField_lpayajrn", UtilMap.getFloat(item, "numberField_lpayajrn") + budget);
+                item.putAll(UtilMap.map("employeeField_lr71bkui, departmentSelectField_lpaks312", item.get("employeeField_lr71bkui_id"), item.get("departmentSelectField_lpaks312_id"))); // 非常规组件数据处理
+                ydClient.operateData(YDParam.builder()
+                        .formInstanceId(UtilMap.getString(item, "instanceId"))
+                        .updateFormDataJson(JSON.toJSONString(item))
+                        .build(), YDConf.FORM_OPERATION.update);
+            }
+            // todo 一人多部门情况
+
+        });
+    }
+
+    /// test
+    @Override
+    public void test() {
+        ddClient_personnel.getPersonnelMeta(ddClient.getAccessToken(), ddConf.getAgentId());
+    }
+}

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

@@ -0,0 +1,65 @@
+# 环境配置
+server:
+  port: 9001
+  servlet:
+    context-path: /api/lemeng
+
+# 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: 2854103578
+  appKey: dingdj7v5iusxvpj049e
+  appSecret: LB6FLLrdskwqMSMEaAa2gNazkbVQ1YtFNjgzGc1CpTlUfCOMeqhP73vx_gQkVyX0
+  corpId: ding321c72787fffc78b35c2f4657eb6378f
+  aesKey:
+  token:
+  operator: ""   # OA管理员账号
+
+# aliwork
+aliwork:
+  appType: APP_YPF7S88D9O139KRVM83J
+  systemToken: 3Z966P91XA1FTYVQ7UTVLDTGJ4613UJUR7ONLT7C
+
+

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

@@ -0,0 +1,38 @@
+# 环境配置
+server:
+  port: 9015
+  servlet:
+    context-path: /api/gewu
+
+# 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: 2660236361
+  appKey: dinguuieqv4lkvp3vkaf
+  appSecret: N5JjPU9RDk77pTze5vRWmiWLDjPKeYJV3sQrmYgN_SC57nOALmj570rVB0SGGcQQ
+  corpId: dingec9ee223c2b3a671
+  aesKey:
+  token:
+  operator: ""   # OA管理员账号
+
+# aliwork
+aliwork:
+  appType: APP_FX4PR3OWW4WCFOHI2ZSR
+  systemToken: 2G766HA1RDHC1C2CCRY62544NL8L21T5786KL27

File diff suppressed because it is too large
+ 855 - 0
mjava-lemeng/src/main/resources/static/json/personnel


File diff suppressed because it is too large
+ 855 - 0
mjava-lemeng/src/main/resources/static/json/personnel.json


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

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

+ 1 - 1
mjava-shangfeng/src/main/resources/application-dev.yml

@@ -43,6 +43,6 @@ dingtalk:
   corpId: ding4c21a1403de71296a1320dcb25e91351
   aesKey:
   token:
-  operator: "095358016621619612"   # 牧语 OA管理员账号 [0开头需要转一下字符串]
+  operator: "170820203423316720"   # 孙杰奇 OA管理员账号 [0开头需要转一下字符串]
 
 

+ 34 - 5
mjava-taisen/src/main/java/com/malk/taisen/controller/TSController.java

@@ -24,10 +24,7 @@ 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;
-import java.util.Map;
+import java.util.*;
 import java.util.stream.Collectors;
 
 @Slf4j
@@ -73,6 +70,7 @@ public class TSController {
 
         String cId = data.get("cId");
         String sId = data.get("sId");
+        log.info("恢复报销明细, {}", data);
 
         Map formData = ydClient.queryData(YDParam.builder()
                         .formInstanceId(cId)
@@ -84,7 +82,8 @@ public class TSController {
             return item;
         }).collect(Collectors.toList());
 
-        Map updateForm = UtilMap.map("tableField_l6lmxo5r, associationFormField_l83trmls", details, JSON.parse(String.valueOf(formData.get("associationFormField_l83trmls"))));
+        Map updateForm = UtilMap.map("tableField_l6lmxo5r, associationFormField_l83trmls, textField_l7a7bf8d", details, JSON.parse(String.valueOf(formData.get("associationFormField_l83trmls"))), formData.get("textField_l7a7bf8d"));
+        updateForm.putAll(UtilMap.map("numberField_l6lmxo63, numberField_l6lmxo6h, textField_l6bynqte", formData.get("numberField_l6lmxo63"), formData.get("numberField_l6lmxo6h"), formData.get("textField_l6bynqte")));
         ydClient.operateData(YDParam.builder()
                 .formInstanceId(sId)
                 .updateFormDataJson(JSON.toJSONString(updateForm)).build(), YDConf.FORM_OPERATION.update);
@@ -92,6 +91,36 @@ public class TSController {
         return McR.success(formData);
     }
 
+    /**
+     * 子表同步主表
+     */
+    @PostMapping("unify")
+    McR unify(HttpServletRequest request) {
+
+        Map data = UtilServlet.getParamMap(request);
+        log.info("子表同步主表, {}", data);
+
+        String instanceId = String.valueOf(data.get("instanceId"));
+        Map formData = ydClient.queryData(YDParam.builder()
+                        .formInstanceId(instanceId)
+                        .build(),
+                YDConf.FORM_QUERY.retrieve_id).getFormData();
+        List<Map> details = (List<Map>) formData.get("tableField_l6lmxo5r");
+        List<Map> associations = new ArrayList<>();
+        for (Map detail : details) {
+            List<Map> arr = ydConf.associationForm(String.valueOf(detail.get("associationFormField_l6vk2yxg_id")));
+            if (arr.size() > 0) {
+                associations.addAll(arr);
+            }
+        }
+        Map updateForm = UtilMap.map("associationFormField_l83trmls", associations);
+        ydClient.operateData(YDParam.builder()
+                .formInstanceId(instanceId)
+                .updateFormDataJson(JSON.toJSONString(updateForm)).build(), YDConf.FORM_OPERATION.update);
+
+        return McR.success(updateForm);
+    }
+
 
     ///// 数据修复
 

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

@@ -0,0 +1,61 @@
+package com.malk.controller;
+
+import com.alibaba.fastjson.JSONObject;
+import com.malk.delegate.TBEvent;
+import com.malk.server.teambition.TBConf;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * TB事件回调 3_1
+ * 1. 回调可选择加密与不加密方式, tb发送成功为上游, 注册服务为下游
+ * 2. 绑定回调需要安装后才会生效, 且回调范围更新后需要重新安装才会生效 [另外接口调用修改配置需要重新发布后生效]
+ */
+@Slf4j
+@RestController
+@RequestMapping("/mc/tb")
+public class TBCallBackController {
+
+    @Autowired
+    private TBEvent tbEvent;
+
+    /**
+     * * 回调说明 [ppExt: 字段更新回调, 判定字段ID, 避免循坏触发问题, 接口修改也会触发webhook]
+     * * 1. 通过接口更操作的数据,也会与手动创建一样触发相同的回调, 除了项目更新接口调用实测不会触发回调, 手动修改正常回调
+     * * 2. 项目创建会推送两次
+     * * - 1. 在第二次推送多 { data: { project: { operatorId, url }} } 这两个字段内容
+     * * - 2. 若是通过模板创建的项目,在两次项目更新回调中会回调一次 project.enable 回调, 其中任务只会回调创建, 不会回调更新
+     * * 3. 任务创建, 会先回调创建事件, 接着立即回调任务更新事件 [若是通过模板创建, 任务只会回调创建, 不会回调更新]
+     * * 4. 项目移入回收站,不会触发回调,删除后会触发项目与任务的 remove 事件; 若是将任务移入回收站, 会触发任务更新回调
+     */
+    @PostMapping("callback")
+    public String callback(@RequestBody JSONObject eventJson) {
+
+        String success = "success";
+        String eventName = eventJson.getString("event");
+
+        if (TBConf.EVENT_VERIFY_HOOK.equals(eventName)) {
+            log.info("----- [TB]验证注册 -----");
+            return success;
+        }
+
+        if (eventName.contains("task")) {
+            log.info("[TB]任务回调, {}, {}", eventName, eventJson);
+            tbEvent.callBackTask(eventJson);
+            return success;
+        }
+
+        if (eventName.contains("project")) {
+            log.info("[TB]项目回调, {}, {}", eventName, eventJson);
+            tbEvent.callBackProject(eventJson);
+            return success;
+        }
+
+        log.info("----- [TB]已注册, 未处理的其它回调 -----, {}, {}", eventName, eventJson);
+        return success;
+    }
+}

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

@@ -4,6 +4,9 @@ import org.springframework.scheduling.annotation.Async;
 
 public interface McDelegate {
 
+    /**
+     * 异步线程回调
+     */
     @Async
     void setTimeout(Invoke fn, long millis);
 

+ 23 - 0
mjava/src/main/java/com/malk/delegate/TBEvent.java

@@ -0,0 +1,23 @@
+package com.malk.delegate;
+
+import com.alibaba.fastjson.JSONObject;
+
+/**
+ * TB事件回调 3_2
+ * -
+ * [主项目若无实现, 项目启动异常; 若子项目有订阅需添加 @Primary 以实现优先注入]
+ * -
+ * 子项目实现接口 [静态代理], 添加对应 processCode 单据业务逻辑
+ */
+public interface TBEvent {
+
+    /**
+     * 任务回调事件  [异步]
+     */
+    void callBackTask(JSONObject eventJson);
+
+    /**
+     * 项目回调事件  [异步]
+     */
+    void callBackProject(JSONObject eventJson);
+}

+ 29 - 0
mjava/src/main/java/com/malk/delegate/impl/TBImplEvent.java

@@ -0,0 +1,29 @@
+package com.malk.delegate.impl;
+
+import com.alibaba.fastjson.JSONObject;
+import com.malk.delegate.TBEvent;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+
+@Slf4j
+@Service
+public class TBImplEvent implements TBEvent {
+
+    @Async
+    @Override
+    @SneakyThrows
+    public void callBackTask(JSONObject eventJson) {
+        String eventName = eventJson.getString("event");
+        log.info("callBackTask: 未被代理");
+    }
+
+    @Async
+    @Override
+    @SneakyThrows
+    public void callBackProject(JSONObject eventJson) {
+        String eventName = eventJson.getString("event");
+        log.info("callBackProject: 未被代理");
+    }
+}

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

@@ -2,11 +2,13 @@ package com.malk.server.aliwork;
 
 import com.alibaba.fastjson.JSON;
 import com.malk.utils.UtilMap;
+import com.malk.utils.UtilString;
 import lombok.Data;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.boot.context.properties.ConfigurationProperties;
 import org.springframework.stereotype.Component;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
@@ -93,6 +95,16 @@ public class YDConf {
         return Arrays.asList(UtilMap.map("appType, formUuid, instanceId, title, subTitle, formType", appType, formUuid, formInstanceId, title, subTitle, formType));
     }
 
+    /**
+     * 读取关联表单: ppExt 两层解析后才是List, 若是赋值取值转一层, 宜搭也可进行写入
+     */
+    public List<Map> associationForm(String associations) {
+        if (UtilString.isBlankCompatNull(associations)) {
+            return new ArrayList<>();
+        }
+        return (List<Map>) JSON.parse(String.valueOf(JSON.parse(String.valueOf(associations))));
+    }
+
     /**
      * 组件格式化取值 [取值] todo 明细表递归, 循环传递入参
      */

+ 90 - 0
mjava/src/main/java/com/malk/server/teambition/TBConf.java

@@ -0,0 +1,90 @@
+package com.malk.server.teambition;
+
+import com.malk.utils.UtilMap;
+import lombok.Data;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * 读取配置文件参考FilePah
+ */
+@Data
+@Component
+@ConfigurationProperties(prefix = "teambition")
+public class TBConf {
+
+    private String AppID;
+
+    private String AppSecret;
+
+    private String TenantId;
+
+    private String OperatorId;
+
+    private String ApiHost;
+
+    public String getApiHost() {
+        if (StringUtils.isNotBlank(ApiHost)) {
+            return ApiHost;
+        }
+        return "https://open.teambition.com/api"; // 公有云环境
+    }
+
+    /**
+     * 一个分页数量上限 [上限 1000]
+     */
+    public static final Integer PAGE_SIZE_LIMIT = 1000;
+
+    /**
+     * 租户类型: 默认是组织
+     */
+    public static final String TENANT_TYPE = "organization";
+
+    /**
+     * 推送事件验证
+     */
+    public static final String EVENT_VERIFY_HOOK = "VERIFY_HOOK";
+
+    /// 获取任务字段值
+    public static List<Map> getTaskFieldValue(List<Map> customfields, String fieldId) {
+
+        Optional optional = customfields.stream().filter(item -> fieldId.equals(item.get("cfId"))).findAny();
+        if (optional.isPresent()) {
+            return UtilMap.getList((Map) optional.get(), "value");
+        }
+        return Arrays.asList();
+    }
+
+    /// 获取任务字段集合第一个值
+    public static String getTaskFieldValue_First(List<Map> customfields, String fieldId) {
+
+        List<Map> value = getTaskFieldValue(customfields, fieldId);
+        if (value.size() > 0) {
+            return String.valueOf(value.get(0).get("title"));
+        }
+        return "";
+    }
+
+    /// 更新任务自定义字段值 [ppExt: 富文本不能解析, 知识库可写入]
+    public static Map assembleCustomField(String fieldName, String fieldValue, String value, Object meta) {
+        Map body = UtilMap.map(fieldName, fieldValue);
+        Map data = UtilMap.map("title", (Object) value);
+        UtilMap.putNotNull(data, "meta", meta);
+        body.put("value", Arrays.asList(data));
+        return body;
+    }
+
+    public static Map assembleCustomFieldName(String fieldValue, String value) {
+        return assembleCustomField("customfieldName", fieldValue, value, null);
+    }
+
+    public static Map assembleCustomFieldId(String fieldValue, String value) {
+        return assembleCustomField("customfieldId", fieldValue, value, null);
+    }
+}

+ 31 - 0
mjava/src/main/java/com/malk/server/teambition/TBR.java

@@ -0,0 +1,31 @@
+package com.malk.server.teambition;
+
+import com.malk.server.common.McException;
+import com.malk.server.common.VenR;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+public class TBR<T> extends VenR {
+
+    private String code;
+
+    private String errorMessage;
+
+    /**
+     * 接口返回数据
+     */
+    private T result;
+
+    // 成功状态标记
+    private final static String SUC_CODE = "200";
+
+    /**
+     * 断言错误信息
+     */
+    @Override
+    public void assertSuccess() {
+        McException.assertException(!code.equals(SUC_CODE), code, errorMessage, "TB");
+    }
+}

+ 1 - 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);
+    List<Map> queryDataList_FormData(String formUuid, Map conditions);
 
     /**
      * 字段复制 [服务注册传递参数都是 string 格式]

+ 133 - 0
mjava/src/main/java/com/malk/service/teambition/TBClient.java

@@ -0,0 +1,133 @@
+package com.malk.service.teambition;
+
+import java.util.List;
+import java.util.Map;
+
+public interface TBClient {
+
+    /**
+     * 查询企业项目模板
+     *
+     * @param ptIds 项目模版ID集合,逗号组合
+     * @param q     模糊查询名字
+     */
+    List<Map> templateSearch(String ptIds, String q);
+
+    /**
+     * 搜索企业成员: 条件为空返回也为空
+     *
+     * @param query 用户名、用户拼音、用户名简拼、邮箱、手机号码 [实测 name, email, phone 有效; pinyin, py 无效, 且这两个字段在个人信息页面不透出]
+     * @apiNote https://open.teambition.com/help/docs/5eb5431141b191001bcda95c
+     */
+    List<Map> userSearch(String query);
+
+    /**
+     * 通过 Teambition 用户 ID 查询钉钉信息
+     *
+     * @param refer 常见的 refer 类型: dingTalk-corp 钉钉企业 dingTalk-team 钉钉部门 dingTalk-user 钉钉用户
+     * @apiNote https://open.teambition.com/docs/apis/6321c6ce912d20d3b5a48e5f
+     */
+    List<Map> idMapQuery(String tbId, String refer, String refId);
+
+    /**
+     * 更新企业成员
+     *
+     * @param operatorId 操作人 ID
+     * @param members    格式: { userId, role, profile: { name, phone, email, ...} }, 成员角色,-1-外部成员 0-成员 1-管理员 2-拥有者
+     * @apiNote https://open.teambition.com/docs/apis/6321c6ce912d20d3b5a48a5b
+     */
+    Map updateUser(String operatorId, List<Map> members);
+
+    /**
+     * 创建空白项目
+     */
+    Map projectCreate(String name, String operatorId);
+
+    /**
+     * 通过模板创建项目
+     */
+    Map projectCreateWithTemplate(String name, String templateId, String operatorId);
+
+
+    /**
+     * 查询项目
+     *
+     * @param projectIds 项目ID集合,逗号分隔
+     * @param name       项目名字(模糊匹配)
+     * @param sourceId   原始项目ID
+     */
+    List<Map> projectDetail(String projectIds, String name, String sourceId);
+
+    /**
+     * 更新项目 [增量]
+     *
+     * @param data String name, description, logo, visibility, startDate, endDate [visibility 项目可见性,project | organization]
+     */
+    Map projectUpdate(String projectId, Map<String, String> data, String operatorId);
+
+    /**
+     * 创建项目成员
+     *
+     * @param projectId  项目ID, 路径参数
+     * @param userIds    项目成员userId列表
+     * @param operatorId 操作人 ID
+     * @apiNote https://open.teambition.com/docs/apis/6321c6d0912d20d3b5a498c0
+     */
+    List<Map> createProjectMember(List<String> userIds, String projectId, String operatorId);
+
+    /**
+     * 获取项目角色列表
+     *
+     * @return { result:  { level } }   项目角色等级: 0=成员, 1=管理员, 2=拥有者
+     * @apiNote https://open.teambition.com/docs/apis/6321c6d1912d20d3b5a49b8f
+     */
+    List<Map> queryProjectRoles(String projectId);
+
+    /**
+     * 修改项目成员的角色
+     *
+     * @param roleIds 角色ID列表 [通过 获取项目角色列表 获取]
+     * @return { result:  { role } }   项目角色: 0=成员, 1=管理员, 2=拥有者
+     */
+    List<Map> updateProjectMember(List<String> userIds, List<String> roleIds, String projectId);
+
+    /**
+     * 查询任务详情
+     *
+     * @param taskId       任务ID集合,使用逗号分隔,和parentTaskId冲突(选其一)
+     * @param shortIds     任务短ID集合,使用逗号分隔
+     * @param parentTaskId 父任务ID,和taskIds冲突(选其一)
+     * @return { priority } 默认排序:“较低”:-10,“普通”:0,“紧急”:1,“非常紧急”:2; 后台调整:调整后按照显示顺序排序,若是新增就会3,4往下。按照 -10,0,1,2,3,4
+     * @apiNote https://open.teambition.com/docs/apis/6321c6d2912d20d3b5a4a7b8
+     */
+    List<Map> queryTaskDetail(String taskId, String shortIds, String parentTaskId);
+
+    /**
+     * 更新任务截止时间
+     *
+     * @implNote 204 若是传入dueDate与相同, 则会直接返回204报错, 若原来是年月日, 修改为同一天添加时分秒, 也属于不同时间
+     */
+    Map updateTaskDueDate(String taskId, String dueDate, String operatorId);
+
+    /**
+     * 更新任务自定义字段值
+     * -
+     * ppExt:
+     * 1. 自定义字段传入name会自动匹配
+     * 2. 未匹配到返回400; 若是下拉框不在下拉选项也可赋值, 修改后选项消失. 注意name是必填
+     *
+     * @apiNote https://open.teambition.com/docs/apis/6321c6d2912d20d3b5a4a579
+     */
+    Map updateTaskCustomField(String taskId, String operatorId, Map body);
+
+    /**
+     * 更新任务自定义字段值
+     * -
+     * ppExt:
+     * 1. 自定义字段传入name会自动匹配
+     * 2. 未匹配到返回400; 若是下拉框不在下拉选项也可赋值, 修改后选项消失. 注意name是必填
+     *
+     * @apiNote https://open.teambition.com/docs/apis/6321c6d2912d20d3b5a4a579
+     */
+    Map updateTaskFlowStatus(String taskId, String operatorId, String taskflowstatusId, String tfsUpdateNote);
+}

+ 22 - 0
mjava/src/main/java/com/malk/service/teambition/TBService.java

@@ -0,0 +1,22 @@
+package com.malk.service.teambition;
+
+import java.util.List;
+import java.util.Map;
+
+public interface TBService {
+
+    /**
+     * 查询操作人在tb的userId
+     */
+    List<String> convertUserIdWithSearch(String search);
+
+    /**
+     * 钉钉用户唯一键转同步用户ID
+     */
+    List<String> convertUserIdWithSearch_bulk(Map<String, Object> data);
+
+    /**
+     * 更新创建项目: 宜搭创建有延迟
+     */
+    void updateProject_YD_TB(Map project, String userName);
+}

+ 205 - 0
mjava/src/main/java/com/malk/service/teambition/impl/TBClientImpl.java

@@ -0,0 +1,205 @@
+package com.malk.service.teambition.impl;
+
+import com.auth0.jwt.algorithms.Algorithm;
+import com.malk.server.teambition.TBConf;
+import com.malk.server.teambition.TBR;
+import com.malk.service.teambition.TBClient;
+import com.malk.utils.UtilHttp;
+import com.malk.utils.UtilMap;
+import com.malk.utils.UtilToken;
+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.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Slf4j
+@Service
+public class TBClientImpl implements TBClient {
+
+    @Autowired
+    private TBConf tbConf;
+
+    private static final Long EXPIRES_IN = 2 * 3600 * 1000L;
+    private static final String TOKEN_APP_ID = "_appId";
+
+    // 获取访问授权
+    @Synchronized
+    private String getAccessToken() {
+        String accessToken = UtilToken.get("invalid-token-teambition");
+        if (StringUtils.isNotBlank(accessToken)) return accessToken;
+        Algorithm algorithm = Algorithm.HMAC256(tbConf.getAppSecret());
+        long timestamp = System.currentTimeMillis();
+        Date issuedAt = new Date(timestamp);
+        Date expiresAt = new Date(timestamp + EXPIRES_IN);
+        accessToken = com.auth0.jwt.JWT.create()
+                .withClaim(TOKEN_APP_ID, tbConf.getAppID())
+                .withIssuedAt(issuedAt)
+                .withExpiresAt(expiresAt)
+                .sign(algorithm);
+        log.info("响应token, {}", accessToken);
+        // token失效自动重置: TB传递过期时间2h, 重复调用就会刷新
+        UtilToken.put("invalid-token-teambition", accessToken, EXPIRES_IN);
+        return accessToken;
+    }
+
+    // 初始化参数: 加载token
+    private Map<String, String> initHeaderToken() {
+        Map header = new HashMap();
+        header.put("Authorization", getAccessToken());
+        header.put("X-Tenant-Id", tbConf.getTenantId());
+        header.put("X-Tenant-Type", TBConf.TENANT_TYPE);
+        return header;
+    }
+
+    // 操作者ID: 若是查询, 以操作人视角作为权限
+    private Map<String, String> initHeaderToken(String operatorId) {
+        Map header = initHeaderToken();
+        header.put("x-operator-id", operatorId);
+        return header;
+    }
+
+    // 加载token, 老版本接口
+    private Map<String, String> initHeaderToken_Bearer() {
+        Map header = initHeaderToken();
+        header.put("Authorization", "Bearer " + header.get("Authorization"));
+        return header;
+    }
+
+//    // 操作者ID: 若是查询, 以操作人视角作为权限
+//    private Map<String, String> initHeaderToken_Bearer(String operatorId) {
+//        Map header = initHeaderToken_Bearer();
+//        header.put("x-operator-id", operatorId);
+//        return header;
+//    }
+
+    @Override
+    public List<Map> templateSearch(String ptIds, String q) {
+        Map param = new HashMap();
+        param.put("ptIds", ptIds);
+        param.put("q", q);
+        param.put("organizationId", TBConf.TENANT_TYPE);
+        TBR r = (TBR) UtilHttp.doGet(tbConf.getApiHost() + "/v3/project-template/search", initHeaderToken(), param, TBR.class);
+        return (List<Map>) r.getResult();
+    }
+
+    @Override
+    public Map projectCreate(String name, String operatorId) {
+        Map param = new HashMap();
+        param.put("organizationId", TBConf.TENANT_TYPE);
+        Map body = new HashMap();
+        body.put("name", name);
+        TBR tbr = (TBR) UtilHttp.doPost(tbConf.getApiHost() + "/v3/project/create", initHeaderToken(operatorId), param, body, TBR.class);
+        return (Map) tbr.getResult();
+    }
+
+    @Override
+    public Map projectCreateWithTemplate(String name, String templateId, String operatorId) {
+        Map param = new HashMap();
+        param.put("organizationId", TBConf.TENANT_TYPE);
+        Map body = new HashMap();
+        body.put("name", name);
+        body.put("templateId", templateId);
+        TBR tbr = (TBR) UtilHttp.doPost(tbConf.getApiHost() + "/v3/project/create-from-template", initHeaderToken(operatorId), param, body, TBR.class);
+        return (Map) tbr.getResult();
+    }
+
+    @Override
+    public List<Map> projectDetail(String projectIds, String name, String sourceId) {
+        Map param = new HashMap();
+        param.put("projectIds", projectIds);
+        param.put("name", name);
+        if (StringUtils.isNotBlank(sourceId)) param.put("sourceId", sourceId);
+        TBR tbr = (TBR) UtilHttp.doGet(tbConf.getApiHost() + "/v3/project/query", initHeaderToken(), param, TBR.class);
+        return (List<Map>) tbr.getResult();
+    }
+
+    @Override
+    public Map projectUpdate(String projectId, Map<String, String> data, String operatorId) {
+        TBR tbr = (TBR) UtilHttp.doPut(tbConf.getApiHost() + "/v3/project/" + projectId, initHeaderToken(operatorId), data, TBR.class);
+        return (Map) tbr.getResult();
+    }
+
+    @Override
+    public List<Map> userSearch(String query) {
+        Map param = new HashMap();
+        param.put("orgId", tbConf.getTenantId());
+        param.put("query", query);
+        param.put("pageSize", TBConf.PAGE_SIZE_LIMIT);
+        TBR tbr = (TBR) UtilHttp.doGet(tbConf.getApiHost() + "/org/member/search", initHeaderToken_Bearer(), param, TBR.class);
+        return (List<Map>) tbr.getResult();
+    }
+
+    @Override
+    public List<Map> idMapQuery(String tbId, String refer, String refId) {
+        Map param = UtilMap.map("tbId, refer, refId", tbId, refer, refId);
+        TBR tbr = (TBR) UtilHttp.doGet(tbConf.getApiHost() + "/idmap/query", initHeaderToken_Bearer(), param, TBR.class);
+        return (List<Map>) tbr.getResult();
+    }
+
+    @Override
+    public Map updateUser(String operatorId, List<Map> members) {
+        Map body = new HashMap();
+        body.put("operatorId", operatorId);
+        body.put("orgId", tbConf.getTenantId());
+        body.put("members", members);
+        TBR tbr = (TBR) UtilHttp.doPut(tbConf.getApiHost() + "/org/member/update", initHeaderToken_Bearer(), body, TBR.class);
+        return (Map) tbr.getResult();
+    }
+
+    @Override
+    public List<Map> createProjectMember(List<String> userIds, String projectId, String operatorId) {
+        Map body = new HashMap();
+        body.put("userIds", userIds);
+        TBR tbr = (TBR) UtilHttp.doPost(tbConf.getApiHost() + "/v3/project/".concat(projectId).concat("/member/create"), initHeaderToken(operatorId), body, TBR.class);
+        return (List<Map>) tbr.getResult();
+    }
+
+    @Override
+    public List<Map> updateProjectMember(List<String> userIds, List<String> roleIds, String projectId) {
+        Map body = new HashMap();
+        body.put("userIds", userIds);
+        body.put("roleIds", roleIds);
+        TBR tbr = (TBR) UtilHttp.doPost(tbConf.getApiHost() + "/v3/project/".concat(projectId).concat("/member/role/assign"), initHeaderToken(), body, TBR.class);
+        return (List<Map>) tbr.getResult();
+    }
+
+    @Override
+    public List<Map> queryProjectRoles(String projectId) {
+        TBR tbr = (TBR) UtilHttp.doGet(tbConf.getApiHost() + "/v3/project/" + projectId + "/role", initHeaderToken(), null, TBR.class);
+        return (List<Map>) tbr.getResult();
+    }
+
+    @Override
+    public List<Map> queryTaskDetail(String taskId, String shortIds, String parentTaskId) {
+        Map param = UtilMap.mapNotNull("taskId, shortIds, parentTaskId", taskId, shortIds, parentTaskId);
+        TBR tbr = (TBR) UtilHttp.doGet(tbConf.getApiHost() + "/v3/task/query", initHeaderToken(), param, TBR.class);
+        return (List<Map>) tbr.getResult();
+    }
+
+    @Override
+    public Map updateTaskDueDate(String taskId, String dueDate, String operatorId) {
+        Map body = new HashMap();
+        body.put("dueDate", dueDate);
+        TBR tbr = (TBR) UtilHttp.doPut(tbConf.getApiHost() + "/v3/task/" + taskId + "/dueDate", initHeaderToken(operatorId), body, TBR.class);
+        return (Map) tbr.getResult();
+    }
+
+    @Override
+    public Map updateTaskCustomField(String taskId, String operatorId, Map body) {
+        TBR tbr = (TBR) UtilHttp.doPost(tbConf.getApiHost() + "/v3/task/" + taskId + "/customfield/update", initHeaderToken(operatorId), body, TBR.class);
+        return (Map) tbr.getResult();
+    }
+
+    @Override
+    public Map updateTaskFlowStatus(String taskId, String operatorId, String taskflowstatusId, String tfsUpdateNote) {
+        Map body = UtilMap.map("taskflowstatusId, tfsUpdateNote", taskflowstatusId, tfsUpdateNote);
+        TBR tbr = (TBR) UtilHttp.doPut(tbConf.getApiHost() + "/v3/task/" + taskId + "/taskflowstatus", initHeaderToken(operatorId), body, TBR.class);
+        return (Map) tbr.getResult();
+    }
+}

+ 102 - 0
mjava/src/main/java/com/malk/service/teambition/impl/TBServiceImpl.java

@@ -0,0 +1,102 @@
+package com.malk.service.teambition.impl;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.alibaba.fastjson.JSONObject;
+import com.malk.server.aliwork.YDConf;
+import com.malk.server.aliwork.YDParam;
+import com.malk.server.common.McException;
+import com.malk.server.dingtalk.DDR_New;
+import com.malk.service.aliwork.YDClient;
+import com.malk.service.teambition.TBClient;
+import com.malk.service.teambition.TBService;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+@Slf4j
+@Service
+public class TBServiceImpl implements TBService {
+
+    @Autowired
+    private TBClient tbClient;
+
+    @Autowired
+    private YDConf ydConf;
+
+    @Autowired
+    private YDClient ydClient;
+
+
+    // 查询操作人在tb的userId
+    @Override
+    public List<String> convertUserIdWithSearch(String search) {
+        List<Map> users = tbClient.userSearch(search);
+        McException.assertAccessException(users.isEmpty(), search + ": 未匹配到TB用户, 请联系管理员添加");
+        return users.stream().map(item -> String.valueOf(item.get("userId"))).collect(Collectors.toList());
+    }
+
+    // 钉钉用户唯一键转同步用户ID
+    @Override
+    public List<String> convertUserIdWithSearch_bulk(Map<String, Object> data) {
+        List<String> userIds = new ArrayList<>();
+        for (String unique : (List<String>) data.get("usersUnique")) {
+            userIds.addAll(convertUserIdWithSearch(unique));
+        }
+        return userIds;
+    }
+
+    @SneakyThrows
+    @Async
+    @Override
+    public void updateProject_YD_TB(Map projectInfo, String userName) {
+        // Map projectInfo = tbClient.projectDetail(null, "R1", null).get(0);
+        Thread.sleep(2000);
+        // 查询宜搭项目
+        JSONObject condition = new JSONObject();
+        condition.put("textField_l9m4krcc", projectInfo.get("name"));
+        DDR_New projectData = ydClient.queryData(YDParam.builder()
+                .appType(ydConf.getAppType())
+                .formUuid("FORM-NC966W81NXY4NPSVAFXCC8THGDOX3VY1F3M9LI")
+                .searchCondition(JSONObject.toJSONString(condition))
+                .build(), YDConf.FORM_QUERY.retrieve_list);
+        List<Map> dataList = (List<Map>) projectData.getData();
+        // 更新宜搭项目
+        if (dataList.size() > 0) {
+            JSONObject update = new JSONObject();
+            update.put("textField_l9t7eqe1", projectInfo.get("id"));
+            update.put("textField_l9t7eqe3", projectInfo.get("logo"));
+            update.put("textField_l9t7eqe6", projectInfo.get("isSuspended"));
+            update.put("radioField_l9t7eqeb", Boolean.TRUE.equals(projectInfo.get("isSuspended")) ? "是" : "否");
+            update.put("textField_l9t7eqe4", projectInfo.get("isTemplate"));
+            update.put("radioField_l9t7eqec", Boolean.TRUE.equals(projectInfo.get("isTemplate")) ? "是" : "否");
+            update.put("textField_l9t7eqe5", projectInfo.get("creatorId"));
+            update.put("textField_l9t7eqed", projectInfo.get("tasklistIds"));
+            ydClient.operateData(YDParam.builder()
+                    .appType(ydConf.getAppType())
+                    .formInstanceId((String) dataList.get(0).get("formInstanceId"))
+                    .updateFormDataJson(JSONObject.toJSONString(update))
+                    .build(), YDConf.FORM_OPERATION.update);
+        }
+        // 更新tb项目
+        Map formData = (Map) (dataList.get(0)).get("formData");
+        Map info = new HashMap();
+        info.put("description", formData.get("textareaField_l9m4krcy"));
+        if (ObjectUtil.isNotNull(formData.get("dateField_l9t7eqe9"))) {
+            info.put("startDate", new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'").format((Long.valueOf(String.valueOf(formData.get("dateField_l9t7eqe9"))))));
+        }
+        if (ObjectUtil.isNotNull(formData.get("dateField_l9t7eqea"))) {
+            info.put("endDate", new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'").format((Long.valueOf(String.valueOf(formData.get("dateField_l9t7eqea"))))));
+        }
+        String operatorId = convertUserIdWithSearch(userName).get(0);
+        tbClient.projectUpdate(String.valueOf(projectInfo.get("id")), info, operatorId);
+    }
+}

+ 17 - 0
mjava/src/main/java/com/malk/utils/UtilMap.java

@@ -28,6 +28,23 @@ public abstract class UtilMap {
         return map;
     }
 
+    /**
+     * 快速创建map [key, Objects] todo, 添加枚举, 忽略空, 忽略0, 忽略""/null字符串 ...
+     */
+    public static Map<String, Object> mapNotNull(String keys, Object... values) {
+        String[] props = keys.split(", ");
+        if (props.length != values.length) {
+            McException.assertParamException_Null(keys);
+        }
+        Map<String, Object> map = new HashMap<>();
+        Arrays.stream(values).forEach(UtilMc.consumerWithIndex((item, index) -> {
+            if (ObjectUtil.isNotNull(item) && UtilString.isNotBlankCompatNull(String.valueOf(item))) {
+                map.put(props[index], item);
+            }
+        }));
+        return map;
+    }
+
     /**
      * 快速创建map [key, Strings]
      */

+ 7 - 0
mjava/src/main/java/com/malk/utils/UtilString.java

@@ -1,5 +1,6 @@
 package com.malk.utils;
 
+import cn.hutool.core.util.ObjectUtil;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
 
@@ -10,6 +11,12 @@ import java.util.Map;
 @Slf4j
 public abstract class UtilString {
 
+
+    // 避免str1为null触发空指针
+    public static boolean equal(Object str1, Object str2) {
+        return ObjectUtil.isNotNull(str1) && str1.equals(str2);
+    }
+
     // 字符串拆分为集合按照英文逗号
     public static List stringSplitToList(String str) {
         String reg = ",";

+ 2 - 2
mjava/src/main/resources/application-dev.yml

@@ -66,8 +66,8 @@ dingtalk:
 teambition:
   AppID: 63589b8bb6803e162f9a57d8
   AppSecret: 5mB3b73OFhSwo38xEVqahCLwQVhG1MW3
-  TenantId: 5ca44db8ca4fd40001b10559
-  OperatorId: 5e698cca21f5ad70dfba7d2b      # 公共账号, 需要有操作权限 [牧语]
+  TenantId: 5ca44db8ca4fd40001b10559        # 管理后台 - 企业xx - 企业ID
+  OperatorId: 5e698cca21f5ad70dfba7d2b      # 公共账号, 需要有操作权限
 
 # aliwork
 aliwork:

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

@@ -50,16 +50,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.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",
@@ -85,16 +75,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": "file.source",
-      "type": "com.malk.server.common.FilePath$Source",
-      "sourceType": "com.malk.server.common.FilePath$Source"
-    },
     {
       "name": "fxiaoke",
       "type": "com.malk.server.fxiaoke.FXKConf",
@@ -117,6 +97,11 @@
       "sourceType": "com.malk.config.mutilSource.DataSourceConfig",
       "sourceMethod": "slaveDataSource()"
     },
+    {
+      "name": "teambition",
+      "type": "com.malk.server.teambition.TBConf",
+      "sourceType": "com.malk.server.teambition.TBConf"
+    },
     {
       "name": "vika",
       "type": "com.malk.server.vika.VKConf",
@@ -254,16 +239,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.file",
-      "type": "java.lang.String",
-      "sourceType": "com.malk.server.common.FilePath$Path"
-    },
     {
       "name": "file.path.image",
       "type": "java.lang.String",
@@ -284,21 +259,6 @@
       "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",
-      "sourceType": "com.malk.server.common.FilePath$Path"
-    },
-    {
-      "name": "file.path.tmp",
-      "type": "java.lang.String",
-      "sourceType": "com.malk.server.common.FilePath$Path"
-    },
     {
       "name": "file.path.tmp",
       "type": "java.lang.String",
@@ -319,21 +279,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",
-      "sourceType": "com.malk.server.common.FilePath$Source"
-    },
     {
       "name": "file.source.fonts",
       "type": "java.lang.String",
@@ -384,6 +329,31 @@
       "type": "java.lang.String",
       "sourceType": "com.malk.server.h3yun.CYConf"
     },
+    {
+      "name": "teambition.api-host",
+      "type": "java.lang.String",
+      "sourceType": "com.malk.server.teambition.TBConf"
+    },
+    {
+      "name": "teambition.app-i-d",
+      "type": "java.lang.String",
+      "sourceType": "com.malk.server.teambition.TBConf"
+    },
+    {
+      "name": "teambition.app-secret",
+      "type": "java.lang.String",
+      "sourceType": "com.malk.server.teambition.TBConf"
+    },
+    {
+      "name": "teambition.operator-id",
+      "type": "java.lang.String",
+      "sourceType": "com.malk.server.teambition.TBConf"
+    },
+    {
+      "name": "teambition.tenant-id",
+      "type": "java.lang.String",
+      "sourceType": "com.malk.server.teambition.TBConf"
+    },
     {
       "name": "vika.api-token",
       "type": "java.lang.String",

+ 2 - 2
mjava/target/classes/application-dev.yml

@@ -66,8 +66,8 @@ dingtalk:
 teambition:
   AppID: 63589b8bb6803e162f9a57d8
   AppSecret: 5mB3b73OFhSwo38xEVqahCLwQVhG1MW3
-  TenantId: 5ca44db8ca4fd40001b10559
-  OperatorId: 5e698cca21f5ad70dfba7d2b      # 公共账号, 需要有操作权限 [牧语]
+  TenantId: 5ca44db8ca4fd40001b10559        # 管理后台 - 企业xx - 企业ID
+  OperatorId: 5e698cca21f5ad70dfba7d2b      # 公共账号, 需要有操作权限
 
 # aliwork
 aliwork:

+ 15 - 14
pom.xml

@@ -30,6 +30,7 @@
         <module>mjava-aipocloud</module>
         <module>mjava-dongfangxinhua</module>
         <module>mjava-aiwei</module>
+        <module>mjava-lemeng</module>
     </modules>
     <packaging>pom</packaging>
 
@@ -58,6 +59,7 @@
         <querydsl-jpa.version>4.2.1</querydsl-jpa.version>
         <spring-boot-starter-jdbc.version>2.2.13.RELEASE</spring-boot-starter-jdbc.version>
         <easyexcel.version>2.2.7</easyexcel.version>
+        <java-jwt.version>3.4.0</java-jwt.version>
         <!-- 数据库连接 [仅mysql为全局依赖] -->
         <mysql-connector-java.version>8.0.22</mysql-connector-java.version>
         <mssql-jdbc.version>6.4.0.jre8</mssql-jdbc.version>
@@ -70,7 +72,6 @@
         <javax.servlet-api.version>4.0.1</javax.servlet-api.version>
         <javax.servlet.jsp-api.version>2.3.1</javax.servlet.jsp-api.version>
         <!-- jwt [非全局依赖] -->
-        <java-jwt.version>3.4.0</java-jwt.version>
         <!-- swagger3: todo -->
         <springfox-boot-starter.version>3.0.0</springfox-boot-starter.version>
         <!-- 网页转pdf [非全局依赖] -->
@@ -177,6 +178,13 @@
                 <version>${easyexcel.version}</version>
             </dependency>
 
+            <!-- jwt [teambition] -->
+            <dependency>
+                <groupId>com.auth0</groupId>
+                <artifactId>java-jwt</artifactId>
+                <version>${java-jwt.version}</version>
+            </dependency>
+
             <!-- mySql 驱动 -->
             <dependency>
                 <groupId>mysql</groupId>
@@ -242,13 +250,6 @@
                 <version>${javax.servlet.jsp-api.version}</version>
             </dependency>
 
-            <!-- jwt -->
-            <dependency>
-                <groupId>com.auth0</groupId>
-                <artifactId>java-jwt</artifactId>
-                <version>${java-jwt.version}</version>
-            </dependency>
-
             <!-- 腾讯云 -->
             <dependency>
                 <groupId>com.tencentcloudapi</groupId>
@@ -351,6 +352,12 @@
             <artifactId>easyexcel</artifactId>
         </dependency>
 
+        <!-- jwt [teambition] -->
+        <dependency>
+            <groupId>com.auth0</groupId>
+            <artifactId>java-jwt</artifactId>
+        </dependency>
+
         <!-- mySql 驱动 -->
         <dependency>
             <groupId>mysql</groupId>
@@ -404,12 +411,6 @@
         <!--            <artifactId>javax.servlet.jsp-api</artifactId>-->
         <!--        </dependency>-->
 
-        <!-- jwt -->
-        <!--        <dependency>-->
-        <!--            <groupId>com.auth0</groupId>-->
-        <!--            <artifactId>java-jwt</artifactId>-->
-        <!--        </dependency>-->
-
         <!-- 腾讯云 [go to https://search.maven.org/search?q=tencentcloud-sdk-java and get the latest version.] -->
         <!--        <dependency>-->
         <!--            <groupId>com.tencentcloudapi</groupId>-->