Преглед изворни кода

花名册人员数据同步

fyz пре 2 недеља
родитељ
комит
3f2c718457

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

@@ -62,9 +62,11 @@ public class DDDelegate implements DDEvent {
             log.info("开始执行撤销回调");
             timerService.terminateDataById(processInstanceId);
         } else if (isAgree &&
-                "PROC-15DD2D3E-1432-42A9-83B8-3B443AEA110F".equals(processCode)) {
-//            int updatedCount = this.dingTalkService.syncApprovalFormToRoster(processInstanceId);
-//            log.info("processInstanceId={}, updatedCount={}", processInstanceId, Integer.valueOf(updatedCount));
+                "PROC-4C9C2939-211B-4CF2-BB4E-D2DE35DF185A".equals(processCode)) {
+//                "PROC-15DD2D3E-1432-42A9-83B8-3B443AEA110F".equals(processCode)) {
+            int updatedCount = this.dingTalkService.upsertApprovalUser(processInstanceId);
+            log.info("蓝云用户同步完成, processInstanceId={}, updatedCount={}",
+                    processInstanceId, Integer.valueOf(updatedCount));
         }
         log.info("审批实例回调执行业务逻辑, {}", approveResult);
     }

+ 58 - 0
mjava-lanyun/src/main/java/com/malk/lanyun/delegate/DDPersonDelegate.java

@@ -0,0 +1,58 @@
+package com.malk.lanyun.delegate;
+
+import com.malk.delegate.DDPersonEvent;
+import com.malk.lanyun.service.DingTalkService;
+import com.malk.lanyun.service.TimerService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Primary;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * OA审批事件 [主项目也有实现, 添加 @Primary 优先注入主项目实现]
+ * -
+ * 取消方案: 撤销和拒绝流程不继续执行连接器, 因此不使用连接器与轮询审批记录方案 [低效而且占用较高钉钉api调次数];
+ * 优化方案: 通过事件订阅实现实时同步所有审批状态, 定时查询钉钉回调失败记录 [配置钉钉事件Delegate, 添加定时任务]
+ */
+@Slf4j
+@Service
+@Primary
+public class DDPersonDelegate implements DDPersonEvent {
+
+    @Autowired
+    TimerService timerService;
+
+    @Autowired
+    DingTalkService dingTalkService;
+
+
+    @Override
+    public void executeEvent_Person_Create(List personIds) {
+        log.info("新增用户idList:{}", personIds);
+        for (Object personId : personIds) {
+            String userId = personId == null ? null : String.valueOf(personId).trim();
+            if (userId == null || userId.isEmpty()) {
+                log.warn("新增用户回调包含空userId, 已忽略");
+                continue;
+            }
+            try {
+                String processInstanceId = this.dingTalkService.findProcessInstanceIdByUserId(userId);
+                if (processInstanceId == null) {
+                    continue;
+                }
+                int updatedCount = this.dingTalkService.upsertApprovalUser(processInstanceId, userId);
+                log.info("新增用户花名册同步完成, userId={}, processInstanceId={}, updatedCount={}",
+                        userId, processInstanceId, Integer.valueOf(updatedCount));
+            } catch (Exception exception) {
+                log.error("新增用户花名册同步失败, userId={}", userId, exception);
+            }
+        }
+    }
+
+    @Override
+    public void executeEvent_person_Remove(List deptIds) {
+        log.info("删除用户idList:{}",deptIds);
+    }
+}

+ 26 - 0
mjava-lanyun/src/main/java/com/malk/lanyun/service/DingTalkService.java

@@ -30,4 +30,30 @@ public interface DingTalkService {
 
 
     int syncApprovalFormToRoster(String paramString);
+
+    /**
+     * 将审批单中的姓名、手机号和流程实例ID写入蓝云用户表。
+     * 手机号已存在时更新,未存在时新增。
+     *
+     * @param processInstanceId 审批实例ID
+     * @return 受影响的记录数
+     */
+    int upsertApprovalUser(String processInstanceId);
+
+    /**
+     * 根据通讯录用户ID查询手机号,并通过手机号查找关联的审批实例ID。
+     *
+     * @param userId 通讯录用户ID
+     * @return 审批实例ID;没有匹配记录时返回 null
+     */
+    String findProcessInstanceIdByUserId(String userId);
+
+    /**
+     * 将审批单中的字段同步到指定通讯录用户的花名册。
+     *
+     * @param processInstanceId 审批实例ID
+     * @param userId            需要更新花名册的通讯录用户ID
+     * @return 成功写入花名册的字段数量
+     */
+    int upsertApprovalUser(String processInstanceId, String userId);
 }

+ 144 - 10
mjava-lanyun/src/main/java/com/malk/lanyun/service/impl/DingTalkServiceImpl.java

@@ -5,11 +5,14 @@ import com.malk.lanyun.service.DingTalkService;
 import com.malk.server.dingtalk.DDConf;
 import com.malk.server.dingtalk.DDR;
 import com.malk.service.dingtalk.DDClient;
+import com.malk.service.dingtalk.DDClient_Contacts;
 import com.malk.service.dingtalk.DDClient_Personnel;
 import com.malk.service.dingtalk.DDClient_Workflow;
 import com.malk.utils.UtilMap;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -27,16 +30,23 @@ public class DingTalkServiceImpl implements DingTalkService {
 
     private final DDClient_Workflow ddClientWorkflow;
 
+    private final DDClient_Contacts ddClientContacts;
+
     private final DDClient_Personnel ddClientPersonnel;
 
     private final DDConf ddConf;
 
+    private final JdbcTemplate jdbcTemplate;
+
     public DingTalkServiceImpl(DDClient ddClient, DDClient_Workflow ddClientWorkflow,
-                               DDClient_Personnel ddClientPersonnel, DDConf ddConf) {
+                               DDClient_Contacts ddClientContacts, DDClient_Personnel ddClientPersonnel, DDConf ddConf,
+                               JdbcTemplate jdbcTemplate) {
         this.ddClient = ddClient;
         this.ddClientWorkflow = ddClientWorkflow;
+        this.ddClientContacts = ddClientContacts;
         this.ddClientPersonnel = ddClientPersonnel;
         this.ddConf = ddConf;
+        this.jdbcTemplate = jdbcTemplate;
     }
 
     @Override
@@ -61,19 +71,57 @@ public class DingTalkServiceImpl implements DingTalkService {
      */
     @Override
     public int syncApprovalFormToRoster(String processInstanceId) {
-        if (isBlank(processInstanceId)) {
-            throw new IllegalArgumentException("processInstanceId不能为空");
-        }
-        // 审批应用凭证: 用于查询审批实例
-        String approvalAccessToken = this.ddClient.getAccessToken(this.ddConf.getApprovalAppKey(), this.ddConf.getApprovalAppSecret());
-        Map processData = this.ddClientWorkflow.getProcessInstanceId(approvalAccessToken, processInstanceId);
-        if (processData == null || processData.isEmpty()) {
-            throw new IllegalStateException("未查询到审批实例, processInstanceId=" + processInstanceId);
-        }
+        Map processData = getApprovalProcessData(processInstanceId);
         String userId = stringValue(processData.get("originatorUserId"));
         if (isBlank(userId)) {
             throw new IllegalStateException("审批实例缺少发起人, processInstanceId=" + processInstanceId);
         }
+        return updateEmployeeRoster(processInstanceId, userId, processData);
+    }
+
+    @Override
+    public String findProcessInstanceIdByUserId(String userId) {
+        if (isBlank(userId)) {
+            throw new IllegalArgumentException("userId不能为空");
+        }
+
+        String contactsAccessToken = this.ddClient.getAccessToken(
+                this.ddConf.getAppKey(), this.ddConf.getAppSecret());
+        Map userInfo = this.ddClientContacts.getUserInfoById(contactsAccessToken, userId);
+        if (userInfo == null || userInfo.isEmpty()) {
+            throw new IllegalStateException("未查询到通讯录用户, userId=" + userId);
+        }
+        String phone = normalizePhone(firstNotBlank(
+                stringValue(userInfo.get("mobile")), stringValue(userInfo.get("telephone"))));
+        if (isBlank(phone)) {
+            throw new IllegalStateException("通讯录用户缺少手机号, userId=" + userId);
+        }
+
+        List<Map<String, Object>> rows = this.jdbcTemplate.queryForList(
+                "SELECT processInstanceId AS process_instance_id "
+                        + "FROM add_user_lanyun WHERE phone = ? ORDER BY id DESC LIMIT 1",
+                phone);
+        if (rows.isEmpty()) {
+            log.info("新增通讯录用户没有匹配的审批记录, userId={}", userId);
+            return null;
+        }
+        String processInstanceId = stringValue(rows.get(0).get("process_instance_id"));
+        if (isBlank(processInstanceId)) {
+            throw new IllegalStateException("用户审批记录缺少processInstanceId, userId=" + userId);
+        }
+        return processInstanceId;
+    }
+
+    @Override
+    public int upsertApprovalUser(String processInstanceId, String userId) {
+        if (isBlank(userId)) {
+            throw new IllegalArgumentException("userId不能为空");
+        }
+        Map processData = getApprovalProcessData(processInstanceId);
+        return updateEmployeeRoster(processInstanceId, userId, processData);
+    }
+
+    private int updateEmployeeRoster(String processInstanceId, String userId, Map processData) {
         // 花名册应用凭证: 用于读写智能人事花名册
         String personnelAccessToken = this.ddClient.getAccessToken(this.ddConf.getAppKey(), this.ddConf.getAppSecret());
         List<Map> formComponentValues = asMapList(processData.get("formComponentValues"));
@@ -111,6 +159,92 @@ public class DingTalkServiceImpl implements DingTalkService {
         return matchedCount;
     }
 
+    /**
+     * 审批通过后,将审批单中的姓名和手机号写入 add_user_lanyun。
+     */
+    @Override
+    @Transactional
+    public int upsertApprovalUser(String processInstanceId) {
+        Map processData = getApprovalProcessData(processInstanceId);
+
+        List<Map> formComponentValues = asMapList(processData.get("formComponentValues"));
+        String name = requiredFormValue(formComponentValues, "姓名", "人员姓名", "name", "入职员工姓名");
+        String phone = requiredFormValue(formComponentValues, "手机号", "手机号码", "联系电话", "phone", "mobile")
+                .replaceAll("\\s+", "");
+
+        int updatedCount = this.jdbcTemplate.update(
+                "UPDATE add_user_lanyun SET name = ?, processInstanceId = ? WHERE phone = ?",
+                name, processInstanceId, phone);
+        if (updatedCount > 0) {
+            log.info("审批用户已更新, processInstanceId={}, affectedRows={}", processInstanceId, updatedCount);
+            return updatedCount;
+        }
+
+        int insertedCount = this.jdbcTemplate.update(
+                "INSERT INTO add_user_lanyun (name, phone, processInstanceId) VALUES (?, ?, ?)",
+                name, phone, processInstanceId);
+        log.info("审批用户已新增, processInstanceId={}, affectedRows={}", processInstanceId, insertedCount);
+        return insertedCount;
+    }
+
+    private Map getApprovalProcessData(String processInstanceId) {
+        if (isBlank(processInstanceId)) {
+            throw new IllegalArgumentException("processInstanceId不能为空");
+        }
+        String accessToken = this.ddClient.getAccessToken(
+                this.ddConf.getAppKey(), this.ddConf.getAppSecret());
+        Map processData = this.ddClientWorkflow.getProcessInstanceId(accessToken, processInstanceId);
+        if (processData == null || processData.isEmpty()) {
+            throw new IllegalStateException("未查询到审批实例, processInstanceId=" + processInstanceId);
+        }
+        return processData;
+    }
+
+    private String normalizePhone(String phone) {
+        return isBlank(phone) ? null : phone.replaceAll("\\s+", "");
+    }
+
+    private String firstNotBlank(String... values) {
+        for (String value : values) {
+            if (!isBlank(value)) {
+                return value;
+            }
+        }
+        return null;
+    }
+
+    private String requiredFormValue(List<Map> formComponentValues, String... fieldNames) {
+        for (Map component : safeList(formComponentValues)) {
+            if (!matchesField(component, fieldNames)) {
+                continue;
+            }
+            String value = stringValue(component.get("value"));
+            if (!isBlank(value)) {
+                return value.trim();
+            }
+        }
+        throw new IllegalStateException("审批单缺少必填字段: " + String.join("/", fieldNames));
+    }
+
+    private boolean matchesField(Map component, String... fieldNames) {
+        String[] componentNames = {
+                stringValue(component.get("name")),
+                stringValue(component.get("label")),
+                stringValue(component.get("bizAlias"))
+        };
+        for (String componentName : componentNames) {
+            if (isBlank(componentName)) {
+                continue;
+            }
+            for (String fieldName : fieldNames) {
+                if (fieldName.equalsIgnoreCase(componentName.trim())) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
     /**
      * 花名册字段索引: 字段名称 -> 分组ID + 字段编码
      */

+ 7 - 7
mjava-lanyun/src/main/resources/application-dev.yml

@@ -46,15 +46,15 @@ spring:
 
 # dingtalk
 dingtalk:
-  agentId: 4005967931
-  appKey: dingwgp5gch0q6w7dkpd
-  appSecret: 4hNChWyxdA3nqjhVVqSerhTday-haHTsfWYH2TfqvgarLda7c4rejOzP1EzfyusR
+  agentId: 4326533184
+  appKey: dingum9maa6vaeqi5jgv
+  appSecret: fxsacsd9MHKnaxkFxP3QOjMe9FrqMTTfZU7InN1DJoM1WUoK6S3usH1EzhSpzgGy
   # 审批应用凭证 [审批完成后同步花名册, 与当前应用为同一应用时沿用上面的凭证]
-  approvalAppKey: ${dingtalk.appKey}
-  approvalAppSecret: ${dingtalk.appSecret}
+#  approvalAppKey: ${dingtalk.appKey}
+#  approvalAppSecret: ${dingtalk.appSecret}
   corpId: dingef2eb0ecba261b8935c2f4657eb6378f
-  aesKey: cfzM5OQusToWofDd4wx8J8s2yyLYHekDGQAjCAt4FRD
-  token: onG7mhhVNjtD7Z5vl
+  aesKey: uLbNXSzj16uYK6nuA0mMuOJVtMSVOGBwfvwhbh2ugOf
+  token: zcJKprGXPkztJ7q3RK
   operator: ""   # OA管理员账号 [首字符若为0需要转一下字符串]
 
 #SqlServer