|
@@ -2,29 +2,25 @@ package cn.iocoder.yudao.module.system.service.dept;
|
|
|
|
|
|
import cn.hutool.core.collection.CollUtil;
|
|
|
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
|
|
-import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
|
|
|
-import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
|
|
|
+import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
|
|
|
import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptCreateReqVO;
|
|
|
import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptListReqVO;
|
|
|
import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptUpdateReqVO;
|
|
|
import cn.iocoder.yudao.module.system.convert.dept.DeptConvert;
|
|
|
import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
|
|
|
import cn.iocoder.yudao.module.system.dal.mysql.dept.DeptMapper;
|
|
|
+import cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants;
|
|
|
import cn.iocoder.yudao.module.system.enums.dept.DeptIdEnum;
|
|
|
-import cn.iocoder.yudao.module.system.mq.producer.dept.DeptProducer;
|
|
|
-import com.google.common.collect.ImmutableMap;
|
|
|
-import com.google.common.collect.ImmutableMultimap;
|
|
|
-import com.google.common.collect.Multimap;
|
|
|
-import lombok.Getter;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.cache.annotation.Cacheable;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
import org.springframework.validation.annotation.Validated;
|
|
|
|
|
|
-import javax.annotation.PostConstruct;
|
|
|
import javax.annotation.Resource;
|
|
|
import java.util.*;
|
|
|
|
|
|
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
|
|
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
|
|
|
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
|
|
|
|
|
|
/**
|
|
@@ -37,54 +33,9 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
|
|
|
@Slf4j
|
|
|
public class DeptServiceImpl implements DeptService {
|
|
|
|
|
|
- /**
|
|
|
- * 部门缓存
|
|
|
- * key:部门编号 {@link DeptDO#getId()}
|
|
|
- *
|
|
|
- * 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
|
|
|
- */
|
|
|
- @Getter
|
|
|
- private volatile Map<Long, DeptDO> deptCache;
|
|
|
- /**
|
|
|
- * 父部门缓存
|
|
|
- * key:部门编号 {@link DeptDO#getParentId()}
|
|
|
- * value: 直接子部门列表
|
|
|
- *
|
|
|
- * 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
|
|
|
- */
|
|
|
- @Getter
|
|
|
- private volatile Multimap<Long, DeptDO> parentDeptCache;
|
|
|
-
|
|
|
@Resource
|
|
|
private DeptMapper deptMapper;
|
|
|
|
|
|
- @Resource
|
|
|
- private DeptProducer deptProducer;
|
|
|
-
|
|
|
- /**
|
|
|
- * 初始化 {@link #parentDeptCache} 和 {@link #deptCache} 缓存
|
|
|
- */
|
|
|
- @Override
|
|
|
- @PostConstruct
|
|
|
- public synchronized void initLocalCache() {
|
|
|
- // 注意:忽略自动多租户,因为要全局初始化缓存
|
|
|
- TenantUtils.executeIgnore(() -> {
|
|
|
- // 第一步:查询数据
|
|
|
- List<DeptDO> depts = deptMapper.selectList();
|
|
|
- log.info("[initLocalCache][缓存部门,数量为:{}]", depts.size());
|
|
|
-
|
|
|
- // 第二步:构建缓存
|
|
|
- ImmutableMap.Builder<Long, DeptDO> builder = ImmutableMap.builder();
|
|
|
- ImmutableMultimap.Builder<Long, DeptDO> parentBuilder = ImmutableMultimap.builder();
|
|
|
- depts.forEach(sysRoleDO -> {
|
|
|
- builder.put(sysRoleDO.getId(), sysRoleDO);
|
|
|
- parentBuilder.put(sysRoleDO.getParentId(), sysRoleDO);
|
|
|
- });
|
|
|
- deptCache = builder.build();
|
|
|
- parentDeptCache = parentBuilder.build();
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
@Override
|
|
|
public Long createDept(DeptCreateReqVO reqVO) {
|
|
|
// 校验正确性
|
|
@@ -95,8 +46,6 @@ public class DeptServiceImpl implements DeptService {
|
|
|
// 插入部门
|
|
|
DeptDO dept = DeptConvert.INSTANCE.convert(reqVO);
|
|
|
deptMapper.insert(dept);
|
|
|
- // 发送刷新消息
|
|
|
- deptProducer.sendDeptRefreshMessage();
|
|
|
return dept.getId();
|
|
|
}
|
|
|
|
|
@@ -110,8 +59,6 @@ public class DeptServiceImpl implements DeptService {
|
|
|
// 更新部门
|
|
|
DeptDO updateObj = DeptConvert.INSTANCE.convert(reqVO);
|
|
|
deptMapper.updateById(updateObj);
|
|
|
- // 发送刷新消息
|
|
|
- deptProducer.sendDeptRefreshMessage();
|
|
|
}
|
|
|
|
|
|
@Override
|
|
@@ -124,8 +71,6 @@ public class DeptServiceImpl implements DeptService {
|
|
|
}
|
|
|
// 删除部门
|
|
|
deptMapper.deleteById(id);
|
|
|
- // 发送刷新消息
|
|
|
- deptProducer.sendDeptRefreshMessage();
|
|
|
}
|
|
|
|
|
|
@Override
|
|
@@ -134,48 +79,35 @@ public class DeptServiceImpl implements DeptService {
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- public List<DeptDO> getDeptListByParentIdFromCache(Long parentId, boolean recursive) {
|
|
|
- if (parentId == null) {
|
|
|
- return Collections.emptyList();
|
|
|
+ public List<DeptDO> getChildDeptList(Long id) {
|
|
|
+ List<DeptDO> children = new LinkedList<>();
|
|
|
+ // 遍历每一层
|
|
|
+ Collection<Long> parentIds = Collections.singleton(id);
|
|
|
+ for (int i = 0; i < Short.MAX_VALUE; i++) { // 使用 Short.MAX_VALUE 避免 bug 场景下,存在死循环
|
|
|
+ // 查询当前层,所有的子部门
|
|
|
+ List<DeptDO> depts = deptMapper.selectListByParentId(parentIds);
|
|
|
+ // 1. 如果没有子部门,则结束遍历
|
|
|
+ if (CollUtil.isEmpty(depts)) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ // 2. 如果有子部门,继续遍历
|
|
|
+ children.addAll(depts);
|
|
|
+ parentIds = convertSet(depts, DeptDO::getId);
|
|
|
}
|
|
|
- List<DeptDO> result = new ArrayList<>();
|
|
|
- // 递归,简单粗暴
|
|
|
- getDeptsByParentIdFromCache(result, parentId,
|
|
|
- recursive ? Integer.MAX_VALUE : 1, // 如果递归获取,则无限;否则,只递归 1 次
|
|
|
- parentDeptCache);
|
|
|
- return result;
|
|
|
+ return children;
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 递归获取所有的子部门,添加到 result 结果
|
|
|
- *
|
|
|
- * @param result 结果
|
|
|
- * @param parentId 父编号
|
|
|
- * @param recursiveCount 递归次数
|
|
|
- * @param parentDeptMap 父部门 Map,使用缓存,避免变化
|
|
|
- */
|
|
|
- private void getDeptsByParentIdFromCache(List<DeptDO> result, Long parentId, int recursiveCount,
|
|
|
- Multimap<Long, DeptDO> parentDeptMap) {
|
|
|
- // 递归次数为 0,结束!
|
|
|
- if (recursiveCount == 0) {
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- // 获得子部门
|
|
|
- Collection<DeptDO> depts = parentDeptMap.get(parentId);
|
|
|
- if (CollUtil.isEmpty(depts)) {
|
|
|
- return;
|
|
|
- }
|
|
|
- // 针对多租户,过滤掉非当前租户的部门
|
|
|
- Long tenantId = TenantContextHolder.getTenantId();
|
|
|
- if (tenantId != null) {
|
|
|
- depts = CollUtil.filterNew(depts, dept -> tenantId.equals(dept.getTenantId()));
|
|
|
- }
|
|
|
- result.addAll(depts);
|
|
|
-
|
|
|
- // 继续递归
|
|
|
- depts.forEach(dept -> getDeptsByParentIdFromCache(result, dept.getId(),
|
|
|
- recursiveCount - 1, parentDeptMap));
|
|
|
+ @Override
|
|
|
+ @DataPermission(enable = false) // 禁用数据权限,避免简历不正确的缓存
|
|
|
+ @Cacheable(cacheNames = RedisKeyConstants.DEPT_CHILDREN_ID_LIST
|
|
|
+ + "#" + RedisKeyConstants.DEPT_CHILDREN_ID_LIST_EXPIRE, key = "#id")
|
|
|
+ public Set<Long> getChildDeptIdListFromCache(Long id) {
|
|
|
+ // 补充说明:为什么该缓存会有 1 分钟的延迟?主要有两点:
|
|
|
+ // 1. Spring Cache 无法方便的批量清理,所以使用 Redis 自动过期的方式。
|
|
|
+ // 2. 变更父节点的时候,影响父子节点的数量很多,包括原父节点及其父节点,以及新父节点及其父节点。
|
|
|
+ // 如果你真的对延迟比较敏感,可以考虑采用使用 allEntries = true 的方式,清理所有缓存。
|
|
|
+ List<DeptDO> children = getChildDeptList(id);
|
|
|
+ return convertSet(children, DeptDO::getId);
|
|
|
}
|
|
|
|
|
|
private void validateForCreateOrUpdate(Long id, Long parentId, String name) {
|
|
@@ -205,7 +137,7 @@ public class DeptServiceImpl implements DeptService {
|
|
|
throw exception(DEPT_NOT_ENABLE);
|
|
|
}
|
|
|
// 父部门不能是原来的子部门
|
|
|
- List<DeptDO> children = getDeptListByParentIdFromCache(id, true);
|
|
|
+ List<DeptDO> children = getChildDeptList(id);
|
|
|
if (children.stream().anyMatch(dept1 -> dept1.getId().equals(parentId))) {
|
|
|
throw exception(DEPT_PARENT_IS_CHILD);
|
|
|
}
|