package cn.iocoder.yudao.module.system.service.dept; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.exception.ServiceException; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission; import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptListReqVO; import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptSaveReqVO; import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptImportExcelVO; import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptImportRespVO; 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 com.google.common.annotations.VisibleForTesting; import lombok.extern.slf4j.Slf4j; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; import jakarta.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.*; /** * 部门 Service 实现类 * * @author 超级管理员 */ @Service @Validated @Slf4j public class DeptServiceImpl implements DeptService { @Resource private DeptMapper deptMapper; @Override @CacheEvict(cacheNames = RedisKeyConstants.DEPT_CHILDREN_ID_LIST, allEntries = true) // allEntries 清空所有缓存,因为操作一个部门,涉及到多个缓存 public Long createDept(DeptSaveReqVO createReqVO) { if (createReqVO.getParentId() == null) { createReqVO.setParentId(DeptDO.PARENT_ID_ROOT); } // 校验父部门的有效性 validateParentDept(null, createReqVO.getParentId()); // 校验部门名的唯一性 validateDeptNameUnique(null, createReqVO.getParentId(), createReqVO.getName()); // 插入部门 DeptDO dept = BeanUtils.toBean(createReqVO, DeptDO.class); deptMapper.insert(dept); return dept.getId(); } @Override @CacheEvict(cacheNames = RedisKeyConstants.DEPT_CHILDREN_ID_LIST, allEntries = true) // allEntries 清空所有缓存,因为操作一个部门,涉及到多个缓存 public void updateDept(DeptSaveReqVO updateReqVO) { if (updateReqVO.getParentId() == null) { updateReqVO.setParentId(DeptDO.PARENT_ID_ROOT); } // 校验自己存在 validateDeptExists(updateReqVO.getId()); // 校验父部门的有效性 validateParentDept(updateReqVO.getId(), updateReqVO.getParentId()); // 校验部门名的唯一性 validateDeptNameUnique(updateReqVO.getId(), updateReqVO.getParentId(), updateReqVO.getName()); // 更新部门 DeptDO updateObj = BeanUtils.toBean(updateReqVO, DeptDO.class); deptMapper.updateById(updateObj); } @Override @CacheEvict(cacheNames = RedisKeyConstants.DEPT_CHILDREN_ID_LIST, allEntries = true) // allEntries 清空所有缓存,因为操作一个部门,涉及到多个缓存 public void deleteDept(Long id) { // 校验是否存在 validateDeptExists(id); // 校验是否有子部门 if (deptMapper.selectCountByParentId(id) > 0) { throw exception(DEPT_EXITS_CHILDREN); } // 删除部门 deptMapper.deleteById(id); } @Override @CacheEvict(cacheNames = RedisKeyConstants.DEPT_CHILDREN_ID_LIST, allEntries = true) // allEntries 清空所有缓存,因为操作一个部门,涉及到多个缓存 public void deleteDeptList(List ids) { // 校验是否有子部门 for (Long id : ids) { if (deptMapper.selectCountByParentId(id) > 0) { throw exception(DEPT_EXITS_CHILDREN); } } // 批量删除部门 deptMapper.deleteByIds(ids); } @VisibleForTesting void validateDeptExists(Long id) { if (id == null) { return; } DeptDO dept = deptMapper.selectById(id); if (dept == null) { throw exception(DEPT_NOT_FOUND); } } @VisibleForTesting void validateParentDept(Long id, Long parentId) { if (parentId == null || DeptDO.PARENT_ID_ROOT.equals(parentId)) { return; } // 1. 不能设置自己为父部门 if (Objects.equals(id, parentId)) { throw exception(DEPT_PARENT_ERROR); } // 2. 父部门不存在 DeptDO parentDept = deptMapper.selectById(parentId); if (parentDept == null) { throw exception(DEPT_PARENT_NOT_EXITS); } // 3. 递归校验父部门,如果父部门是自己的子部门,则报错,避免形成环路 if (id == null) { // id 为空,说明新增,不需要考虑环路 return; } for (int i = 0; i < Short.MAX_VALUE; i++) { // 3.1 校验环路 parentId = parentDept.getParentId(); if (Objects.equals(id, parentId)) { throw exception(DEPT_PARENT_IS_CHILD); } // 3.2 继续递归下一级父部门 if (parentId == null || DeptDO.PARENT_ID_ROOT.equals(parentId)) { break; } parentDept = deptMapper.selectById(parentId); if (parentDept == null) { break; } } } @VisibleForTesting void validateDeptNameUnique(Long id, Long parentId, String name) { DeptDO dept = deptMapper.selectByParentIdAndName(parentId, name); if (dept == null) { return; } // 如果 id 为空,说明不用比较是否为相同 id 的部门 if (id == null) { throw exception(DEPT_NAME_DUPLICATE); } if (ObjectUtil.notEqual(dept.getId(), id)) { throw exception(DEPT_NAME_DUPLICATE); } } @Override public DeptDO getDept(Long id) { return deptMapper.selectById(id); } @Override public List getDeptList(Collection ids) { if (CollUtil.isEmpty(ids)) { return Collections.emptyList(); } return deptMapper.selectByIds(ids); } @Override public List getDeptList(DeptListReqVO reqVO) { List list = deptMapper.selectList(reqVO); list.sort(Comparator.comparing(DeptDO::getSort)); return list; } @Override public List getChildDeptList(Collection ids) { List children = new LinkedList<>(); // 遍历每一层 Collection parentIds = ids; for (int i = 0; i < Short.MAX_VALUE; i++) { // 使用 Short.MAX_VALUE 避免 bug 场景下,存在死循环 // 查询当前层,所有的子部门 List depts = deptMapper.selectListByParentId(parentIds); // 1. 如果没有子部门,则结束遍历 if (CollUtil.isEmpty(depts)) { break; } // 2. 如果有子部门,继续遍历 children.addAll(depts); parentIds = convertSet(depts, DeptDO::getId); } return children; } @Override public List getDeptListByLeaderUserId(Long id) { return deptMapper.selectListByLeaderUserId(id); } @Override @DataPermission(enable = false) // 禁用数据权限,避免建立不正确的缓存 @Cacheable(cacheNames = RedisKeyConstants.DEPT_CHILDREN_ID_LIST, key = "#id") public Set getChildDeptIdListFromCache(Long id) { List children = getChildDeptList(id); return convertSet(children, DeptDO::getId); } @Override public void validateDeptList(Collection ids) { if (CollUtil.isEmpty(ids)) { return; } // 获得科室信息 Map deptMap = getDeptMap(ids); // 校验 ids.forEach(id -> { DeptDO dept = deptMap.get(id); if (dept == null) { throw exception(DEPT_NOT_FOUND); } if (!CommonStatusEnum.ENABLE.getStatus().equals(dept.getStatus())) { throw exception(DEPT_NOT_ENABLE, dept.getName()); } }); } @Override public DeptImportRespVO importDeptList(List importDepts, boolean isUpdateSupport) { if (CollUtil.isEmpty(importDepts)) { throw exception(DEPT_IMPORT_LIST_IS_EMPTY); } DeptImportRespVO respVO = DeptImportRespVO.builder().createDeptNames(new ArrayList<>()) .updateDeptNames(new ArrayList<>()).failureDeptNames(new LinkedHashMap<>()).build(); // 1. 初始化现有的全路径与部门ID映射 List allDepts = getDeptList(new DeptListReqVO()); Map idDeptMap = cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap(allDepts, DeptDO::getId); Map fullPathDeptIdMap = new HashMap<>(); for (DeptDO dept : allDepts) { fullPathDeptIdMap.put(getFullPath(dept, idDeptMap), dept.getId()); } // 2. 将导入的部门按照父部门路径深度排序,确保先处理父部门 importDepts.sort(Comparator.comparingInt(dept -> StrUtil.isEmpty(dept.getParentPath()) ? 0 : dept.getParentPath().split("/").length)); // 3. 遍历导入 for (DeptImportExcelVO importDept : importDepts) { // 3.1 获取父部门 ID Long parentId = DeptDO.PARENT_ID_ROOT; if (StrUtil.isNotEmpty(importDept.getParentPath())) { parentId = fullPathDeptIdMap.get(importDept.getParentPath()); if (parentId == null) { respVO.getFailureDeptNames().put(importDept.getName(), "上级部门不存在: " + importDept.getParentPath()); continue; } } // 3.2 校验部门名称是否唯一 try { validateDeptNameUnique(null, parentId, importDept.getName()); } catch (ServiceException ex) { if (!isUpdateSupport) { respVO.getFailureDeptNames().put(importDept.getName(), ex.getMessage()); continue; } // 支持更新,执行更新 DeptDO existDept = deptMapper.selectByParentIdAndName(parentId, importDept.getName()); DeptDO updateDept = BeanUtils.toBean(importDept, DeptDO.class); updateDept.setId(existDept.getId()); updateDept.setParentId(parentId); deptMapper.updateById(updateDept); respVO.getUpdateDeptNames().add(importDept.getName()); String myFullPath = StrUtil.isEmpty(importDept.getParentPath()) ? importDept.getName() : importDept.getParentPath() + "/" + importDept.getName(); fullPathDeptIdMap.put(myFullPath, existDept.getId()); continue; } // 3.3 新增部门 DeptDO newDept = BeanUtils.toBean(importDept, DeptDO.class); newDept.setParentId(parentId); deptMapper.insert(newDept); respVO.getCreateDeptNames().add(importDept.getName()); // 更新缓存,供后续子部门使用 String myFullPath = StrUtil.isEmpty(importDept.getParentPath()) ? importDept.getName() : importDept.getParentPath() + "/" + importDept.getName(); fullPathDeptIdMap.put(myFullPath, newDept.getId()); } return respVO; } private String getFullPath(DeptDO dept, Map idDeptMap) { StringBuilder path = new StringBuilder(dept.getName()); Long parentId = dept.getParentId(); while (parentId != null && !DeptDO.PARENT_ID_ROOT.equals(parentId)) { DeptDO parent = idDeptMap.get(parentId); if (parent == null) { break; } path.insert(0, parent.getName() + "/"); parentId = parent.getParentId(); } return path.toString(); } }