组织同步逻辑修改
This commit is contained in:
+19
@@ -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;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+14
@@ -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按页同步公司
|
||||||
*/
|
*/
|
||||||
|
|||||||
+10
@@ -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();
|
||||||
|
|||||||
+12
@@ -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();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+211
-29
@@ -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
|
||||||
|
|||||||
+48
-18
@@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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`);
|
||||||
Reference in New Issue
Block a user