完善企业微信同步器同步,修正 已同步的组织被删除后 后续同步无法恢复

This commit is contained in:
link2fun
2025-11-05 17:01:49 +08:00
parent 866d42c278
commit bc54cb256c
3 changed files with 214 additions and 66 deletions

View File

@@ -31,5 +31,11 @@ public interface SynchroRelatedService extends IJpaService<SynchroRelated>{
public SynchroRelated findByOriginId(Synchronizers synchronizer,String originId,String classType) ;
/**
* 根据 同步器 + originId + classType 查询同步关系, 如果存在则更新, 不存在则插入
* @param synchronizer 同步器
* @param synchroRelated 同步关系
* @param classType 对象类型
*/
public void updateSynchroRelated(Synchronizers synchronizer,SynchroRelated synchroRelated,String classType) ;
}

View File

@@ -18,11 +18,11 @@
package org.dromara.maxkey.synchronizer.workweixin;
import org.dromara.maxkey.constants.ConstsStatus;
import org.dromara.maxkey.entity.SyncJobConfigField;
import org.dromara.maxkey.entity.SynchroRelated;
import org.dromara.maxkey.entity.idm.Organizations;
import org.dromara.maxkey.synchronizer.AbstractSynchronizerService;
import org.dromara.maxkey.synchronizer.ISynchronizerService;
import org.dromara.maxkey.entity.SyncJobConfigField;
import org.dromara.maxkey.synchronizer.service.SyncJobConfigFieldService;
import org.dromara.maxkey.synchronizer.workweixin.entity.WorkWeixinDepts;
import org.dromara.maxkey.synchronizer.workweixin.entity.WorkWeixinDeptsResponse;
@@ -32,8 +32,11 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import java.lang.reflect.InvocationTargetException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -57,33 +60,81 @@ public class WorkweixinOrganizationService extends AbstractSynchronizerService i
try {
WorkWeixinDeptsResponse rsp = requestDepartmentList(access_token);
for(WorkWeixinDepts dept : rsp.getDepartment()) {
_logger.debug("dept : " + dept.getId()+" "+ dept.getName()+" "+ dept.getParentid());
// 需要对企业微信部门列表进行一次重排,保证父节点在前,子节点在后
List<WorkWeixinDepts> deptWxListAfterLevelSort = sortDepartments(rsp.getDepartment());
// 关键字段不能依赖映射关系,否则映射数据有问题会导致功能异常
// 先拿出字段映射关系
Map<String, String> fieldMap = getFieldMap(Long.parseLong(synchronizer.getId()));
for (WorkWeixinDepts deptWxCur : deptWxListAfterLevelSort) {
_logger.debug("sync workweixin dept : {} {} {}", deptWxCur.getId(), deptWxCur.getName(), deptWxCur.getParentid());
//root
if(dept.getId() == ROOT_DEPT_ID) {
if (deptWxCur.getId() == ROOT_DEPT_ID) {
// 当前根节点
// 先查询本地根节点, 这里可能有问题, ROOT_ORG_ID的组织可能不存在(原本的被删除了), 这里先假设存在
Organizations rootOrganization = organizationsService.get(Organizations.ROOT_ORG_ID);
SynchroRelated rootSynchroRelated = buildSynchroRelated(rootOrganization,dept);
// 构建同步关系
SynchroRelated rootSynchroRelated = buildSynchroRelated(rootOrganization, deptWxCur);
// 更新同步关系
synchroRelatedService.updateSynchroRelated(
this.synchronizer, rootSynchroRelated, Organizations.CLASS_TYPE);
// 是否更新根节点的编码待确认, 这里先更新名称
rootOrganization.setOrgName(deptWxCur.getName());
organizationsService.update(rootOrganization);
} else {
//synchro Related
// 现在不是根组织
//synchro Related 查询当前部门是否有同步记录 这里只是查有没有关系, 不是查组织
SynchroRelated synchroRelated =
synchroRelatedService.findByOriginId(
this.synchronizer,dept.getId() + "",Organizations.CLASS_TYPE );
//Parent
this.synchronizer, deptWxCur.getId() + "", Organizations.CLASS_TYPE);
//Parent 查询当前部门父部门是否有同步记录 这里只是查有没有关系, 不是查组织
SynchroRelated synchroRelatedParent =
synchroRelatedService.findByOriginId(
this.synchronizer,dept.getParentid() + "",Organizations.CLASS_TYPE);
Organizations organization = buildOrgByFiledMap(dept,synchroRelatedParent);
if(synchroRelated == null) {
organization.setId(organization.generateId());
organizationsService.insert(organization);
_logger.debug("Organizations : " + organization);
this.synchronizer, deptWxCur.getParentid() + "", Organizations.CLASS_TYPE);
synchroRelated = buildSynchroRelated(organization,dept);
// 根据字段映射构建当前组织的实体
Organizations orgCurrent = buildOrgByFiledMap(deptWxCur, synchroRelatedParent, fieldMap);
// 这里需要修正一下层级关系, 防止因为映射关系错误导致的层级错乱
String deptWxParentId = String.valueOf(deptWxCur.getParentid());
// 进入到这个节点的应该都是有上级的, 现在只需要根据上级Id查询上级的组织档案
String targetIdField = getLocalFieldMappingByWx(fieldMap, "id"); // 从映射里面拿到企业微信Id映射后的本地组织的字段
Organizations parentOrg = organizationsService.findOne(targetIdField + " = ? and instId = ? ",
new Object[]{deptWxParentId, this.synchronizer.getInstId()}, new int[]{Types.VARCHAR, Types.VARCHAR});
// 这里父级不应该为 null
if (parentOrg == null) {
throw new RuntimeException("无法找到上级组织, 同步失败! 企业微信父部门Id: " + deptWxParentId);
}
orgCurrent.setParentId(parentOrg.getId());
orgCurrent.setParentCode(parentOrg.getOrgCode());
orgCurrent.setParentName(parentOrg.getOrgName());
if (ObjectUtils.isEmpty(orgCurrent.getFullName())) {
// 兜底设置一下组织全称
orgCurrent.setFullName(orgCurrent.getOrgName());
}
if (synchroRelated == null) {
// 当前部门还没有同步过
orgCurrent.setId(orgCurrent.generateId());
organizationsService.insert(orgCurrent);
_logger.debug("Organizations : " + orgCurrent);
synchroRelated = buildSynchroRelated(orgCurrent, deptWxCur);
} else {
organization.setId(synchroRelated.getObjectId());
organizationsService.update(organization);
// 部门曾经同步过, 但是不能保证没被删除过, 所以还需要判定一次
Organizations currentOrg = organizationsService.findOne(targetIdField + " = ? and instId = ? ",
new Object[]{deptWxCur.getId(), this.synchronizer.getInstId()}, new int[]{Types.VARCHAR, Types.VARCHAR});
if (currentOrg == null) {
// 当前部门已经被删除, 那就需要重新写入一次
orgCurrent.setId(synchroRelated.getObjectId());
organizationsService.insert(orgCurrent);
}
orgCurrent.setId(synchroRelated.getObjectId());
organizationsService.update(orgCurrent);
}
synchroRelatedService.updateSynchroRelated(
@@ -97,18 +148,25 @@ public class WorkweixinOrganizationService extends AbstractSynchronizerService i
}
/**
* 构建同步关系
*
* @param organization 组织实体
* @param dept 企业微信部门实体
* @return 同步关系
*/
public SynchroRelated buildSynchroRelated(Organizations organization, WorkWeixinDepts dept) {
return new SynchroRelated(
organization.getId(),
organization.getOrgName(),
organization.getOrgName(),
Organizations.CLASS_TYPE,
synchronizer.getId(),
synchronizer.getName(),
dept.getId()+"",
dept.getName(),
organization.getId(), // objectId 系统内组织ID
organization.getOrgName(), // objectName 系统内组织名称
organization.getOrgName(), // objectDisplayName 系统内组织显示名称
Organizations.CLASS_TYPE, // objectType 对象类型
synchronizer.getId(), // syncId 同步器ID
synchronizer.getName(), // syncName 同步器名称
dept.getId() + "", // originId 企业微信部门ID
dept.getName(), // originName 企业微信部门名称
"",
dept.getParentid()+"",
dept.getParentid() + "", // originId3 父部门ID
synchronizer.getInstId());
}
@@ -138,11 +196,33 @@ public class WorkweixinOrganizationService extends AbstractSynchronizerService i
return org;
}
/**
* 从字段映射中获取企业微信字段映射后的本地字段
* @param fieldMap 字段映射
* @param expectField 企业微信字段
* @return 本地字段
*/
public String getLocalFieldMappingByWx(Map<String, String> fieldMap, String expectField) {
for (Map.Entry<String, String> entry : fieldMap.entrySet()) {
String orgProperty = entry.getKey();
String sourceProperty = entry.getValue();
if (sourceProperty.equals(expectField)) {
return orgProperty;
}
}
throw new RuntimeException("未找到企业微信字段映射后的本地字段");
}
public Organizations buildOrgByFiledMap(WorkWeixinDepts dept, SynchroRelated synchroRelatedParent){
/**
* 根据字段映射构建组织实体
*
* @param dept 企业微信部门实体
* @param synchroRelatedParent 父部门同步关系
* @param fieldMap 同步器配置的字段映射
* @return 组织实体
*/
public Organizations buildOrgByFiledMap(WorkWeixinDepts dept, SynchroRelated synchroRelatedParent, Map<String, String> fieldMap) {
Organizations org = new Organizations();
//fieldMap
Map<String, String> fieldMap = getFieldMap(Long.parseLong(synchronizer.getId()));
for (Map.Entry<String, String> entry : fieldMap.entrySet()) {
@@ -153,8 +233,7 @@ public class WorkweixinOrganizationService extends AbstractSynchronizerService i
if (hasField(dept.getClass(), sourceProperty)) {
sourceValue = getFieldValue(dept, sourceProperty);
}
else if (synchroRelatedParent != null && hasField(SynchroRelated.class, sourceProperty)) {
} else if (synchroRelatedParent != null && hasField(SynchroRelated.class, sourceProperty)) {
sourceValue = getFieldValue(synchroRelatedParent, sourceProperty);
}
if (sourceValue != null) {
@@ -178,15 +257,68 @@ public class WorkweixinOrganizationService extends AbstractSynchronizerService i
List<SyncJobConfigField> syncJobConfigFieldList = syncJobConfigFieldService.findByJobId(jobId);
//获取组织属性映射
for (SyncJobConfigField element : syncJobConfigFieldList) {
if(Integer.parseInt(element.getObjectType()) == ORG_TYPE.intValue()){
if (Integer.parseInt(element.getObjectType()) == ORG_TYPE) {
filedMap.put(element.getTargetField(), element.getSourceField());
}
}
return filedMap;
}
/**
* 对部门列表进行排序,确保父节点在前,子节点在后
* 使用拓扑排序算法,按照层级顺序遍历部门树
*
* @param departments 原始部门列表
* @return 排序后的部门列表
*/
private List<WorkWeixinDepts> sortDepartments(List<WorkWeixinDepts> departments) {
if (departments == null || departments.isEmpty()) {
return departments;
}
// 构建部门ID到部门对象的映射
Map<Long, WorkWeixinDepts> deptMap = new HashMap<>();
// 构建父ID到子部门列表的映射
Map<Long, List<WorkWeixinDepts>> parentToChildrenMap = new HashMap<>();
for (WorkWeixinDepts dept : departments) {
deptMap.put(dept.getId(), dept);
parentToChildrenMap.computeIfAbsent(dept.getParentid(), k -> new ArrayList<>()).add(dept);
}
// 结果列表
List<WorkWeixinDepts> sortedList = new ArrayList<>();
// 从根节点开始遍历
List<Long> queue = new ArrayList<>();
// 找到所有根节点(没有父节点的部门,或者父节点不在列表中的部门)
for (WorkWeixinDepts dept : departments) {
if (!deptMap.containsKey(dept.getParentid())) {
queue.add(dept.getId());
}
}
// 遍历
while (!queue.isEmpty()) {
Long currentId = queue.remove(0);
WorkWeixinDepts currentDept = deptMap.get(currentId);
if (currentDept != null) {
sortedList.add(currentDept);
// 将当前部门的所有子部门加入队列
List<WorkWeixinDepts> children = parentToChildrenMap.get(currentId);
if (children != null) {
for (WorkWeixinDepts child : children) {
queue.add(child.getId());
}
}
}
}
return sortedList;
}
public String getAccess_token() {

View File

@@ -69,4 +69,14 @@ public class WorkWeixinDepts {
this.order = order;
}
@Override
public String toString() {
return "WorkWeixinDepts{" +
"id=" + id +
", name='" + name + '\'' +
", name_en='" + name_en + '\'' +
", parentid=" + parentid +
", order=" + order +
'}';
}
}