组织同步逻辑修改

This commit is contained in:
2026-09-21 16:03:28 +08:00
parent 2404281bff
commit 427e4b0c84
8 changed files with 324 additions and 48 deletions
@@ -25,10 +25,13 @@
*/ */
package org.springblade.system.pojo.entity; package org.springblade.system.pojo.entity;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic; import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
@@ -185,4 +188,20 @@ public class Dept extends TenantEntity {
@Schema(description = "是否平台公司:0否,1是") @Schema(description = "是否平台公司:0否,1是")
private Integer isPlatformCompany; private Integer isPlatformCompany;
/**
* OA主键,仅同步记录,不对外展示
*/
@JsonIgnore
@Schema(description = "OA主键", hidden = true)
@TableField(updateStrategy = FieldStrategy.NOT_NULL)
private String oaId;
/**
* OA上级主键,仅同步记录,不对外展示
*/
@JsonIgnore
@Schema(description = "OA上级主键", hidden = true)
@TableField(updateStrategy = FieldStrategy.NOT_NULL)
private String oaSupSubComId;
} }
@@ -173,6 +173,20 @@ public class DeptController extends BladeController {
return R.data(deptService.syncIamOrganizations()); return R.data(deptService.syncIamOrganizations());
} }
/**
* 清除非顶级组织,供自动同步前确认后调用
*/
@IsAdmin
@PostMapping("/clear-non-top")
@ApiOperationSupport(order = 8)
@Operation(summary = "清除非顶级组织")
public R<Integer> clearNonTopDepts() {
int clearedCount = deptService.clearNonTopDepts();
CacheUtil.clear(SYS_CACHE);
CacheUtil.clear(SYS_CACHE, Boolean.FALSE);
return R.data(clearedCount);
}
/** /**
* 从OA按页同步公司 * 从OA按页同步公司
*/ */
@@ -80,6 +80,9 @@ public interface DeptConvert {
} }
// 公司所属公司编码就是他自己的部门编码 // 公司所属公司编码就是他自己的部门编码
dept.setBelongCompanyCode(dept.getDeptCode()); dept.setBelongCompanyCode(dept.getDeptCode());
// OA主键与上级OA主键,分批同步结束后用于重建层级
dept.setOaId(company.getId());
dept.setOaSupSubComId(company.getSupsubcomid());
// 显示顺序处理 // 显示顺序处理
dept.setSort(OAUtils.parseInt(company.getShoworder())); dept.setSort(OAUtils.parseInt(company.getShoworder()));
// 部门类型,公司 // 部门类型,公司
@@ -100,6 +103,13 @@ public interface DeptConvert {
Dept dept = this.baseConvert(oaDept); Dept dept = this.baseConvert(oaDept);
// 部门编码添加前缀 // 部门编码添加前缀
dept.setDeptCode(OAConvertConstant.DEPARTMENT_OA_PREFIX + oaDept.getId()); dept.setDeptCode(OAConvertConstant.DEPARTMENT_OA_PREFIX + oaDept.getId());
// OA主键;上级为根部门时记录所属公司OA主键,否则记录上级部门OA主键
dept.setOaId(oaDept.getId());
if (OAConstant.ROOT_COMPANY_ID.equals(oaDept.getSupdepid())) {
dept.setOaSupSubComId(oaDept.getSubcompanyid1());
} else {
dept.setOaSupSubComId(oaDept.getSupdepid());
}
// 部门所属公司编码就是他所属公司编码加前缀 // 部门所属公司编码就是他所属公司编码加前缀
dept.setBelongCompanyCode(OAConvertConstant.COMPANY_OA_PREFIX + dept.getBelongCompanyCode()); dept.setBelongCompanyCode(OAConvertConstant.COMPANY_OA_PREFIX + dept.getBelongCompanyCode());
String parentCode = dept.getParentCode(); String parentCode = dept.getParentCode();
@@ -146,6 +146,13 @@ public interface IDeptService extends IService<Dept> {
*/ */
boolean removeDept(String ids); boolean removeDept(String ids);
/**
* 删除非顶级组织,顶级组织(父级为 0)保留
*
* @return 删除数量
*/
int clearNonTopDepts();
/** /**
* 新增或修改部门 * 新增或修改部门
* <p> * <p>
@@ -187,4 +194,9 @@ public interface IDeptService extends IService<Dept> {
*/ */
void updateAncestors(Date startTime); void updateAncestors(Date startTime);
/**
* 按 OA 主键与上级 OA 主键重建组织、部门父子关系,并刷新祖级列表
*/
void rebuildOaHierarchy();
} }
@@ -46,6 +46,7 @@ import org.springblade.system.cache.SysCache;
import org.springblade.system.mapper.DeptMapper; import org.springblade.system.mapper.DeptMapper;
import org.springblade.system.pojo.entity.Dept; import org.springblade.system.pojo.entity.Dept;
import org.springblade.system.pojo.entity.User; import org.springblade.system.pojo.entity.User;
import org.springblade.system.pojo.enums.DeptCategory;
import org.springblade.system.pojo.vo.DeptVO; import org.springblade.system.pojo.vo.DeptVO;
import org.springblade.system.pojo.vo.UserVO; import org.springblade.system.pojo.vo.UserVO;
import org.springblade.system.props.IamSyncProperties; import org.springblade.system.props.IamSyncProperties;
@@ -53,6 +54,7 @@ import org.springblade.system.service.IDeptService;
import org.springblade.system.service.IUserService; import org.springblade.system.service.IUserService;
import org.springblade.system.wrapper.DeptWrapper; import org.springblade.system.wrapper.DeptWrapper;
import org.springblade.system.wrapper.UserWrapper; import org.springblade.system.wrapper.UserWrapper;
import org.springblade.thirdparty.oa.constant.OAConstant;
import org.springblade.thirdparty.oa.constant.OAConvertConstant; import org.springblade.thirdparty.oa.constant.OAConvertConstant;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@@ -232,6 +234,21 @@ public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements ID
return removeByIds(idList); return removeByIds(idList);
} }
@Override
@Transactional(rollbackFor = Exception.class)
public int clearNonTopDepts() {
Long nonTopCount = this.count(Wrappers.<Dept>lambdaQuery()
.isNotNull(Dept::getParentId)
.ne(Dept::getParentId, BladeConstant.TOP_PARENT_ID));
if (nonTopCount == null || nonTopCount == 0L) {
return 0;
}
this.remove(Wrappers.<Dept>lambdaQuery()
.isNotNull(Dept::getParentId)
.ne(Dept::getParentId, BladeConstant.TOP_PARENT_ID));
return nonTopCount.intValue();
}
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public boolean submit(Dept dept) { public boolean submit(Dept dept) {
@@ -451,8 +468,12 @@ public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements ID
} }
private void validateDeptCategory(Dept dept, Dept parent) { private void validateDeptCategory(Dept dept, Dept parent) {
// 顶级组织不要求选择上级组织
if (parent == null) { if (parent == null) {
throw new ServiceException("请选择上级组织"); if (Integer.valueOf(6).equals(dept.getDeptCategory()) && Func.isEmpty(dept.getCarrierCustomerId())) {
throw new ServiceException("请选择承运商");
}
return;
} }
List<Integer> allowedCategories; List<Integer> allowedCategories;
if (BladeConstant.TOP_PARENT_ID.equals(parent.getParentId())) { if (BladeConstant.TOP_PARENT_ID.equals(parent.getParentId())) {
@@ -483,13 +504,24 @@ public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements ID
if (deptCode.length() > 30) { if (deptCode.length() > 30) {
throw new ServiceException("组织编码不能超过30个字符"); throw new ServiceException("组织编码不能超过30个字符");
} }
if (parent == null || StringUtil.isBlank(parent.getDeptCode())) { // 顶级组织不依赖上级编码
if (parent == null) {
this.ensureDeptCodeUnique(dept, deptCode);
dept.setDeptCode(deptCode);
return;
}
if (StringUtil.isBlank(parent.getDeptCode())) {
throw new ServiceException("请选择已配置组织编码的上级组织"); throw new ServiceException("请选择已配置组织编码的上级组织");
} }
String pattern = Pattern.quote(parent.getDeptCode()) + "-\\d+"; String pattern = Pattern.quote(parent.getDeptCode()) + "-\\d+";
if (!deptCode.matches(pattern)) { if (!deptCode.matches(pattern)) {
throw new ServiceException("组织编码格式应为:上级编码-分段数字"); throw new ServiceException("组织编码格式应为:上级编码-分段数字");
} }
this.ensureDeptCodeUnique(dept, deptCode);
dept.setDeptCode(deptCode);
}
private void ensureDeptCodeUnique(Dept dept, String deptCode) {
LambdaQueryWrapper<Dept> queryWrapper = Wrappers.<Dept>lambdaQuery().eq(Dept::getDeptCode, deptCode); LambdaQueryWrapper<Dept> queryWrapper = Wrappers.<Dept>lambdaQuery().eq(Dept::getDeptCode, deptCode);
if (Func.isNotEmpty(dept.getId())) { if (Func.isNotEmpty(dept.getId())) {
queryWrapper.ne(Dept::getId, dept.getId()); queryWrapper.ne(Dept::getId, dept.getId());
@@ -497,7 +529,6 @@ public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements ID
if (baseMapper.selectCount(queryWrapper) > 0) { if (baseMapper.selectCount(queryWrapper) > 0) {
throw new ServiceException("组织编码已存在"); throw new ServiceException("组织编码已存在");
} }
dept.setDeptCode(deptCode);
} }
@Override @Override
@@ -540,24 +571,170 @@ public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements ID
return UserWrapper.build().listVO(userList); return UserWrapper.build().listVO(userList);
} }
/**
* 统一的租户范围过滤:超管放行,其他用户强制限定为当前会话租户。
*/
private void applyTenantScope(QueryWrapper<Dept> queryWrapper) {
if (!AuthUtil.isAdministrator()) {
queryWrapper.lambda().eq(Dept::getTenantId, AuthUtil.getTenantId());
}
}
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
@Override @Override
public void updateAncestors(Date startTime) { public void updateAncestors(Date startTime) {
// 1. 查询需要更新祖级列表的部门 this.refreshAncestors(false, startTime);
List<Dept> list = this.list(Wrappers.<Dept>lambdaQuery() }
// 祖级列表为空
@Transactional(rollbackFor = Exception.class)
@Override
public void rebuildOaHierarchy() {
List<Dept> oaDeptList = this.list(Wrappers.<Dept>lambdaQuery()
.isNotNull(Dept::getOaId)
.ne(Dept::getOaId, ""));
if (CollectionUtil.isEmpty(oaDeptList)) {
log.info("没有记录OA主键的组织,跳过层级重建");
return;
}
Map<String, Dept> companyByOaId = this.indexByOaId(oaDeptList, DeptCategory.COMPANY.getCode());
Map<String, Dept> departmentByOaId = this.indexByOaId(oaDeptList, DeptCategory.DEPT.getCode());
Map<Long, Long> desiredParentIdMap = new LinkedHashMap<>();
Map<Long, String> desiredParentCodeMap = new HashMap<>();
for (Dept dept : oaDeptList) {
OaParentLink parentLink = this.resolveOaParentLink(dept, companyByOaId, departmentByOaId);
if (parentLink == null) {
continue;
}
desiredParentIdMap.put(dept.getId(), parentLink.parentId());
desiredParentCodeMap.put(dept.getId(), parentLink.parentCode());
}
Set<Long> cyclicIds = this.findCyclicDeptIds(desiredParentIdMap);
if (CollectionUtil.isNotEmpty(cyclicIds)) {
log.warn("检测到OA组织循环引用,跳过层级重建 ids={}", cyclicIds);
cyclicIds.forEach(cyclicId -> {
desiredParentIdMap.remove(cyclicId);
desiredParentCodeMap.remove(cyclicId);
});
}
List<Dept> updateList = new ArrayList<>();
for (Dept dept : oaDeptList) {
Long parentId = desiredParentIdMap.get(dept.getId());
if (parentId == null) {
continue;
}
String parentCode = desiredParentCodeMap.get(dept.getId());
if (Objects.equals(parentId, dept.getParentId()) && StringUtil.equals(parentCode, dept.getParentCode())) {
continue;
}
Dept updateParam = new Dept();
updateParam.setId(dept.getId());
updateParam.setParentId(parentId);
updateParam.setParentCode(parentCode);
updateList.add(updateParam);
}
if (CollectionUtil.isNotEmpty(updateList)) {
this.updateBatchById(updateList);
log.info("按OA主键重建组织层级,更新父子关系{}条", updateList.size());
} else {
log.info("OA组织层级已是最新,无需调整父子关系");
}
this.refreshAncestors(true, null);
}
/**
* 按机构类型建立 OA 主键索引
*/
private Map<String, Dept> indexByOaId(List<Dept> oaDeptList, Integer deptCategory) {
Map<String, Dept> oaIdMap = new HashMap<>();
for (Dept dept : oaDeptList) {
if (!Objects.equals(deptCategory, dept.getDeptCategory()) || StringUtil.isBlank(dept.getOaId())) {
continue;
}
Dept previous = oaIdMap.put(dept.getOaId(), dept);
if (previous != null) {
log.warn("OA主键重复,层级重建使用后一条 category={} oaId={} id={} previousId={}",
dept.getDeptCategory(), dept.getOaId(), dept.getId(), previous.getId());
}
}
return oaIdMap;
}
/**
* 根据 OA 上级主键解析本系统父级
*/
private OaParentLink resolveOaParentLink(Dept dept, Map<String, Dept> companyByOaId, Map<String, Dept> departmentByOaId) {
String parentOaId = dept.getOaSupSubComId();
boolean rootParent = StringUtil.isBlank(parentOaId) || OAConstant.ROOT_COMPANY_ID.equals(parentOaId);
if (DeptCategory.COMPANY.getCode().equals(dept.getDeptCategory())) {
if (rootParent) {
return new OaParentLink(OAConvertConstant.ROOT_PARENT_ID, OAConstant.ROOT_COMPANY_ID);
}
Dept parent = companyByOaId.get(parentOaId);
if (parent == null || parent.getId() == null || Objects.equals(parent.getId(), dept.getId())) {
log.warn("未找到上级公司,跳过层级重建 oaId={} oaSupSubComId={} deptId={}",
dept.getOaId(), parentOaId, dept.getId());
return null;
}
return new OaParentLink(parent.getId(), this.resolveParentCode(parent, OAConvertConstant.COMPANY_OA_PREFIX, parentOaId));
}
if (!DeptCategory.DEPT.getCode().equals(dept.getDeptCategory())) {
return null;
}
if (rootParent) {
log.warn("部门缺少上级OA主键,跳过层级重建 oaId={} deptId={}", dept.getOaId(), dept.getId());
return null;
}
boolean parentIsDepartment = StringUtil.isNotBlank(dept.getParentCode())
&& dept.getParentCode().startsWith(OAConvertConstant.DEPARTMENT_OA_PREFIX);
boolean parentIsCompany = StringUtil.isNotBlank(dept.getParentCode())
&& dept.getParentCode().startsWith(OAConvertConstant.COMPANY_OA_PREFIX);
Dept parent;
if (parentIsDepartment) {
parent = departmentByOaId.get(parentOaId);
} else if (parentIsCompany) {
parent = companyByOaId.get(parentOaId);
} else {
parent = departmentByOaId.get(parentOaId);
if (parent == null) {
parent = companyByOaId.get(parentOaId);
}
}
if (parent == null || parent.getId() == null || Objects.equals(parent.getId(), dept.getId())) {
log.warn("未找到上级组织,跳过层级重建 oaId={} oaSupSubComId={} deptId={}",
dept.getOaId(), parentOaId, dept.getId());
return null;
}
String prefix = DeptCategory.DEPT.getCode().equals(parent.getDeptCategory())
? OAConvertConstant.DEPARTMENT_OA_PREFIX : OAConvertConstant.COMPANY_OA_PREFIX;
return new OaParentLink(parent.getId(), this.resolveParentCode(parent, prefix, parentOaId));
}
private String resolveParentCode(Dept parent, String prefix, String parentOaId) {
if (StringUtil.isNotBlank(parent.getDeptCode())) {
return parent.getDeptCode();
}
return prefix + parentOaId;
}
/**
* 找出父子关系成环的部门
*/
private Set<Long> findCyclicDeptIds(Map<Long, Long> parentIdMap) {
Set<Long> cyclicIds = new HashSet<>();
for (Long deptId : parentIdMap.keySet()) {
Set<Long> visiting = new HashSet<>();
Long currentId = deptId;
while (currentId != null && !OAConvertConstant.ROOT_PARENT_ID.equals(currentId)) {
if (!visiting.add(currentId)) {
cyclicIds.addAll(visiting);
break;
}
currentId = parentIdMap.get(currentId);
}
}
return cyclicIds;
}
/**
* 刷新祖级列表
*
* @param refreshAll 是否刷新全部部门
* @param startTime 增量起始时间,仅 refreshAll 为 false 时生效
*/
private void refreshAncestors(boolean refreshAll, Date startTime) {
List<Dept> list = refreshAll ? this.list() : this.list(Wrappers.<Dept>lambdaQuery()
.isNull(Dept::getAncestors) .isNull(Dept::getAncestors)
// 开始时间不为空,查询同步时间大于开始时间或修改时间大于开始时间的数据
.or(startTime != null, wrapper -> wrapper.ge(Dept::getSyncTime, startTime)) .or(startTime != null, wrapper -> wrapper.ge(Dept::getSyncTime, startTime))
.or(startTime != null, wrapper -> wrapper.ge(Dept::getUpdateTime, startTime)) .or(startTime != null, wrapper -> wrapper.ge(Dept::getUpdateTime, startTime))
); );
@@ -565,29 +742,19 @@ public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements ID
log.info("需要更新祖级列表的部门为空"); log.info("需要更新祖级列表的部门为空");
return; return;
} }
// 所有公司部门列表 List<DeptVO> allList = (refreshAll ? list : this.list()).stream()
List<DeptVO> allList = this.list().stream()
.map(dept -> BeanUtil.copyProperties(dept, DeptVO.class)) .map(dept -> BeanUtil.copyProperties(dept, DeptVO.class))
.toList(); .toList();
// id对应的部门map
Map<Long, DeptVO> idDeptMap = allList.stream() Map<Long, DeptVO> idDeptMap = allList.stream()
.collect(Collectors.toMap(DeptVO::getId, Function.identity(), (a, b) -> b)); .collect(Collectors.toMap(DeptVO::getId, Function.identity(), (a, b) -> b));
// 获取根节点列表
List<DeptVO> rootList = getRootList(allList, idDeptMap); List<DeptVO> rootList = getRootList(allList, idDeptMap);
// 计算祖级列表
setAncestors(rootList); setAncestors(rootList);
// 3. 更新祖级列表
List<Dept> updateParams = list.stream() List<Dept> updateParams = list.stream()
.filter(dept -> { .filter(dept -> {
DeptVO vo = idDeptMap.get(dept.getId()); DeptVO vo = idDeptMap.get(dept.getId());
if (vo == null) { if (vo == null || vo.getAncestors() == null) {
return false; return false;
} }
if (vo.getAncestors() == null) {
return false;
}
// 祖级列表和数据库的不一样的才需要更新
return !StringUtil.equals(vo.getAncestors(), dept.getAncestors()); return !StringUtil.equals(vo.getAncestors(), dept.getAncestors());
}) })
.map(dept -> { .map(dept -> {
@@ -602,6 +769,21 @@ public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements ID
} }
} }
/**
* OA 上级链接
*/
private record OaParentLink(Long parentId, String parentCode) {
}
/**
* 统一的租户范围过滤:超管放行,其他用户强制限定为当前会话租户。
*/
private void applyTenantScope(QueryWrapper<Dept> queryWrapper) {
if (!AuthUtil.isAdministrator()) {
queryWrapper.lambda().eq(Dept::getTenantId, AuthUtil.getTenantId());
}
}
/** /**
* 获取根节点列表 * 获取根节点列表
* @param allList * @param allList
@@ -76,8 +76,12 @@ public class OASyncServiceImpl implements IOASyncService {
this.syncCompany(queryStartTime); this.syncCompany(queryStartTime);
// 2. 同步部门 // 2. 同步部门
this.syncDept(queryStartTime); this.syncDept(queryStartTime);
// 2. 更新部门祖级列表 // 3. 分批写入完成后,按 OA 主键重建组织、部门层级
deptService.rebuildOaHierarchy();
// 4. 更新部门祖级列表
deptService.updateAncestors(queryStartTime); deptService.updateAncestors(queryStartTime);
CacheUtil.clear(SYS_CACHE);
CacheUtil.clear(SYS_CACHE, Boolean.FALSE);
}, syncAll); }, syncAll);
// 2. 推送mk // 2. 推送mk
mkPushService.pushOrgAndRecord(startTime); mkPushService.pushOrgAndRecord(startTime);
@@ -338,9 +342,12 @@ public class OASyncServiceImpl implements IOASyncService {
if (CollectionUtil.isNotEmpty(syncCount.getNotHandledList())) { if (CollectionUtil.isNotEmpty(syncCount.getNotHandledList())) {
ComposeLogUtil.getLastLog().warn("同步公司,本页未处理数据:{}", JSON.toJSONString(syncCount.getNotHandledList())); ComposeLogUtil.getLastLog().warn("同步公司,本页未处理数据:{}", JSON.toJSONString(syncCount.getNotHandledList()));
} }
OaOrgSyncPageVO pageVO = buildOrgSyncPageVO("company", pageNo, pageSize, totalSize, oaCompanies.size(), syncCount);
if (Boolean.TRUE.equals(pageVO.getFinished())) {
deptService.rebuildOaHierarchy();
}
CacheUtil.clear(SYS_CACHE); CacheUtil.clear(SYS_CACHE);
CacheUtil.clear(SYS_CACHE, Boolean.FALSE); CacheUtil.clear(SYS_CACHE, Boolean.FALSE);
OaOrgSyncPageVO pageVO = buildOrgSyncPageVO("company", pageNo, pageSize, totalSize, oaCompanies.size(), syncCount);
ComposeLogUtil.getLastLog().info("OA公司分页同步完成 {}/{},成功{},跳过{}", ComposeLogUtil.getLastLog().info("OA公司分页同步完成 {}/{},成功{},跳过{}",
pageNo, totalSize, syncCount.getSyncedCount(), syncCount.getSkippedCount()); pageNo, totalSize, syncCount.getSyncedCount(), syncCount.getSkippedCount());
return pageVO; return pageVO;
@@ -379,13 +386,14 @@ public class OASyncServiceImpl implements IOASyncService {
if (CollectionUtil.isNotEmpty(syncCount.getNotHandledList())) { if (CollectionUtil.isNotEmpty(syncCount.getNotHandledList())) {
ComposeLogUtil.getLastLog().warn("同步部门,本页未处理数据:{}", JSON.toJSONString(syncCount.getNotHandledList())); ComposeLogUtil.getLastLog().warn("同步部门,本页未处理数据:{}", JSON.toJSONString(syncCount.getNotHandledList()));
} }
CacheUtil.clear(SYS_CACHE);
CacheUtil.clear(SYS_CACHE, Boolean.FALSE);
OaOrgSyncPageVO pageVO = buildOrgSyncPageVO("department", pageNo, pageSize, totalSize, oaDepartments.size(), syncCount); OaOrgSyncPageVO pageVO = buildOrgSyncPageVO("department", pageNo, pageSize, totalSize, oaDepartments.size(), syncCount);
if (Boolean.TRUE.equals(pageVO.getFinished())) { if (Boolean.TRUE.equals(pageVO.getFinished())) {
// 部门同步完成后新祖级列表 // 部门分批同步完成后,按 OA 主键重建层级并刷新祖级列表
deptService.rebuildOaHierarchy();
deptService.updateAncestors(null); deptService.updateAncestors(null);
} }
CacheUtil.clear(SYS_CACHE);
CacheUtil.clear(SYS_CACHE, Boolean.FALSE);
ComposeLogUtil.getLastLog().info("OA部门分页同步完成 {}/{},成功{},跳过{}", ComposeLogUtil.getLastLog().info("OA部门分页同步完成 {}/{},成功{},跳过{}",
pageNo, totalSize, syncCount.getSyncedCount(), syncCount.getSkippedCount()); pageNo, totalSize, syncCount.getSyncedCount(), syncCount.getSkippedCount());
return pageVO; return pageVO;
@@ -527,11 +535,17 @@ public class OASyncServiceImpl implements IOASyncService {
if (CollectionUtil.isEmpty(allParam)) { if (CollectionUtil.isEmpty(allParam)) {
return new OrgSyncCount(0, filteredSkipCount, Collections.emptyList()); return new OrgSyncCount(0, filteredSkipCount, Collections.emptyList());
} }
Set<String> deptCodes = allParam.stream() Set<String> deptCodes = new HashSet<>();
.map(Dept::getDeptCode) allParam.forEach(dept -> {
.collect(Collectors.toSet()); if (StringUtils.isNotEmpty(dept.getDeptCode())) {
// 查询数据库的部门,转换成map deptCodes.add(dept.getDeptCode());
Map<String, Long> deptMap = deptService.list(Wrappers.<Dept>lambdaQuery() }
if (StringUtils.isNotEmpty(dept.getParentCode())) {
deptCodes.add(dept.getParentCode());
}
});
// 查询数据库的部门,转换成map。包含本页及上级编码,便于命中已入库的上级
Map<String, Long> deptMap = deptCodes.isEmpty() ? new HashMap<>() : deptService.list(Wrappers.<Dept>lambdaQuery()
.eq(Dept::getDeptCategory, DeptCategory.DEPT.getCode()) .eq(Dept::getDeptCategory, DeptCategory.DEPT.getCode())
.in(Dept::getDeptCode, deptCodes) .in(Dept::getDeptCode, deptCodes)
).stream() ).stream()
@@ -564,14 +578,30 @@ public class OASyncServiceImpl implements IOASyncService {
} }
}); });
// 重新把没有父id的数据设置一下父id // 重新把没有父id的数据设置一下父id;上级尚未入库时,新增记录先挂到根节点,分批结束后再重建
allParam.stream() int unresolvedParentCount = 0;
.filter(dept -> dept.getParentId() == null && deptMap.containsKey(dept.getDeptCode())) for (Dept dept : allParam) {
.forEach(dept -> dept.setParentId(deptMap.get(dept.getParentCode()))); if (dept.getParentId() != null) {
continue;
}
Long parentId = deptMap.get(dept.getParentCode());
if (parentId != null) {
dept.setParentId(parentId);
continue;
}
unresolvedParentCount++;
if (!existsDeptIds.contains(dept.getId())) {
dept.setParentId(OAConvertConstant.ROOT_PARENT_ID);
}
}
if (unresolvedParentCount > 0) {
ComposeLogUtil.getLastLog().warn("同步{}时有{}条上级尚未入库,已先保存,分批结束后按OA主键重建层级",
deptCategory.getName(), unresolvedParentCount);
}
// 父id不为空,保存数据 // 父id不为空才新增;已存在的记录即使父id未解析也更新(空父id不会覆盖原值)
List<Dept> list = allParam.stream() List<Dept> list = allParam.stream()
.filter(dept -> dept.getParentId() != null) .filter(dept -> dept.getParentId() != null || existsDeptIds.contains(dept.getId()))
.toList(); .toList();
// 不存在的新增 // 不存在的新增
List<Dept> addList = list.stream() List<Dept> addList = list.stream()
@@ -589,9 +619,9 @@ public class OASyncServiceImpl implements IOASyncService {
ComposeLogUtil.getLastLog().info("批量修改{}{}", deptCategory.getName(), updateList.size()); ComposeLogUtil.getLastLog().info("批量修改{}{}", deptCategory.getName(), updateList.size());
deptService.updateBatchById(updateList); deptService.updateBatchById(updateList);
} }
// 返回未处理的 // 返回仍未保存的数据
return allParam.stream() return allParam.stream()
.filter(dept -> dept.getParentId() == null) .filter(dept -> dept.getParentId() == null && !existsDeptIds.contains(dept.getId()))
.toList(); .toList();
} }
+4 -1
View File
@@ -308,12 +308,15 @@ CREATE TABLE `blade_dept` (
`mnemonic_code` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '助记码', `mnemonic_code` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '助记码',
`carrier_customer_id` bigint NULL DEFAULT NULL COMMENT '承运商客商档案主键', `carrier_customer_id` bigint NULL DEFAULT NULL COMMENT '承运商客商档案主键',
`is_platform_company` tinyint NOT NULL DEFAULT 0 COMMENT '是否平台公司:0否,1是', `is_platform_company` tinyint NOT NULL DEFAULT 0 COMMENT '是否平台公司:0否,1是',
`oa_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'OA主键',
`oa_sup_sub_com_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'OA上级主键',
`sort` int NULL DEFAULT NULL COMMENT '排序', `sort` int NULL DEFAULT NULL COMMENT '排序',
`remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注',
`status` int NULL DEFAULT 1 COMMENT '状态', `status` int NULL DEFAULT 1 COMMENT '状态',
`is_deleted` int NULL DEFAULT 0 COMMENT '是否已删除', `is_deleted` int NULL DEFAULT 0 COMMENT '是否已删除',
PRIMARY KEY (`id`) USING BTREE, PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `uk_blade_dept_dept_code` (`dept_code`) UNIQUE KEY `uk_blade_dept_dept_code` (`dept_code`),
INDEX `idx_blade_dept_oa_id` (`oa_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '机构表'; ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '机构表';
-- ---------------------------- -- ----------------------------
@@ -0,0 +1,6 @@
-- 机构表:记录 OA 主键与上级 OA 主键,仅用于同步后重建组织层级,不用于前端展示
ALTER TABLE `blade_dept`
ADD COLUMN `oa_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'OA主键' AFTER `is_platform_company`,
ADD COLUMN `oa_sup_sub_com_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'OA上级主键' AFTER `oa_id`,
ADD INDEX `idx_blade_dept_oa_id` (`oa_id`);