Sfoglia il codice sorgente

优化 dept 部门的实现逻辑,以及相关的单元测试

YunaiV 2 anni fa
parent
commit
6c9ef97122

+ 0 - 4
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/redis/RedisKeyConstants.java

@@ -57,10 +57,6 @@ public interface RedisKeyConstants {
      * 数据类型:String 子部门编号集合
      */
     String DEPT_CHILDREN_ID_LIST = "dept_children_ids";
-    /**
-     * {@link #DEPT_CHILDREN_ID_LIST} 的过期时间
-     */
-    String DEPT_CHILDREN_ID_LIST_EXPIRE = "30s";
 
     /**
      * 拥有权限对应的菜单编号数组的缓存

+ 22 - 25
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dept/DeptService.java

@@ -39,31 +39,12 @@ public interface DeptService {
     void deleteDept(Long id);
 
     /**
-     * 筛选部门列表
-     *
-     * @param reqVO 筛选条件请求 VO
-     * @return 部门列表
-     */
-    List<DeptDO> getDeptList(DeptListReqVO reqVO);
-
-    /**
-     * 获得指定部门的所有子部门
+     * 获得部门信息
      *
      * @param id 部门编号
-     * @return 子部门列表
-     */
-    List<DeptDO> getChildDeptList(Long id);
-
-    /**
-     * 获得所有子部门,从缓存中
-     *
-     * 注意,该缓存不是实时更新,最多会有 1 分钟延迟。
-     * 一般来说,不会影响使用,因为部门的变更,不会频繁发生。
-     *
-     * @param id 父部门编号
-     * @return 子部门列表
+     * @return 部门信息
      */
-    Set<Long> getChildDeptIdListFromCache(Long id);
+    DeptDO getDept(Long id);
 
     /**
      * 获得部门信息数组
@@ -73,6 +54,14 @@ public interface DeptService {
      */
     List<DeptDO> getDeptList(Collection<Long> ids);
 
+    /**
+     * 筛选部门列表
+     *
+     * @param reqVO 筛选条件请求 VO
+     * @return 部门列表
+     */
+    List<DeptDO> getDeptList(DeptListReqVO reqVO);
+
     /**
      * 获得指定编号的部门 Map
      *
@@ -88,12 +77,20 @@ public interface DeptService {
     }
 
     /**
-     * 获得部门信息
+     * 获得指定部门的所有子部门
      *
      * @param id 部门编号
-     * @return 部门信息
+     * @return 子部门列表
      */
-    DeptDO getDept(Long id);
+    List<DeptDO> getChildDeptList(Long id);
+
+    /**
+     * 获得所有子部门,从缓存中
+     *
+     * @param id 父部门编号
+     * @return 子部门列表
+     */
+    Set<Long> getChildDeptIdListFromCache(Long id);
 
     /**
      * 校验部门们是否有效。如下情况,视为无效:

+ 61 - 58
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dept/DeptServiceImpl.java

@@ -1,6 +1,7 @@
 package cn.iocoder.yudao.module.system.service.dept;
 
 import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjectUtil;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
 import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptCreateReqVO;
@@ -11,7 +12,9 @@ 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 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;
@@ -37,6 +40,8 @@ public class DeptServiceImpl implements DeptService {
     private DeptMapper deptMapper;
 
     @Override
+    @CacheEvict(cacheNames = RedisKeyConstants.DEPT_CHILDREN_ID_LIST,
+            allEntries = true) // allEntries 清空所有缓存,因为操作一个部门,涉及到多个缓存
     public Long createDept(DeptCreateReqVO reqVO) {
         // 校验正确性
         if (reqVO.getParentId() == null) {
@@ -50,6 +55,8 @@ public class DeptServiceImpl implements DeptService {
     }
 
     @Override
+    @CacheEvict(cacheNames = RedisKeyConstants.DEPT_CHILDREN_ID_LIST,
+            allEntries = true) // allEntries 清空所有缓存,因为操作一个部门,涉及到多个缓存
     public void updateDept(DeptUpdateReqVO reqVO) {
         // 校验正确性
         if (reqVO.getParentId() == null) {
@@ -62,6 +69,8 @@ public class DeptServiceImpl implements DeptService {
     }
 
     @Override
+    @CacheEvict(cacheNames = RedisKeyConstants.DEPT_CHILDREN_ID_LIST,
+            allEntries = true) // allEntries 清空所有缓存,因为操作一个部门,涉及到多个缓存
     public void deleteDept(Long id) {
         // 校验是否存在
         validateDeptExists(id);
@@ -73,53 +82,28 @@ public class DeptServiceImpl implements DeptService {
         deptMapper.deleteById(id);
     }
 
-    @Override
-    public List<DeptDO> getDeptList(DeptListReqVO reqVO) {
-        return deptMapper.selectList(reqVO);
-    }
-
-    @Override
-    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);
-        }
-        return children;
-    }
-
-    @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) {
         // 校验自己存在
         validateDeptExists(id);
         // 校验父部门的有效性
-        validateParentDeptEnable(id, parentId);
+        validateParentDept(id, parentId);
         // 校验部门名的唯一性
         validateDeptNameUnique(id, parentId, name);
     }
 
-    private void validateParentDeptEnable(Long id, Long parentId) {
+    @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 || DeptIdEnum.ROOT.getId().equals(parentId)) {
             return;
         }
@@ -132,10 +116,6 @@ public class DeptServiceImpl implements DeptService {
         if (dept == null) {
             throw exception(DEPT_PARENT_NOT_EXITS);
         }
-        // 父部门被禁用
-        if (!CommonStatusEnum.ENABLE.getStatus().equals(dept.getStatus())) {
-            throw exception(DEPT_NOT_ENABLE);
-        }
         // 父部门不能是原来的子部门
         List<DeptDO> children = getChildDeptList(id);
         if (children.stream().anyMatch(dept1 -> dept1.getId().equals(parentId))) {
@@ -143,38 +123,61 @@ public class DeptServiceImpl implements DeptService {
         }
     }
 
-    private void validateDeptExists(Long id) {
-        if (id == null) {
-            return;
-        }
-        DeptDO dept = deptMapper.selectById(id);
+    @VisibleForTesting
+    void validateDeptNameUnique(Long id, Long parentId, String name) {
+        DeptDO dept = deptMapper.selectByParentIdAndName(parentId, name);
         if (dept == null) {
-            throw exception(DEPT_NOT_FOUND);
-        }
-    }
-
-    private void validateDeptNameUnique(Long id, Long parentId, String name) {
-        DeptDO menu = deptMapper.selectByParentIdAndName(parentId, name);
-        if (menu == null) {
             return;
         }
         // 如果 id 为空,说明不用比较是否为相同 id 的岗位
         if (id == null) {
             throw exception(DEPT_NAME_DUPLICATE);
         }
-        if (!menu.getId().equals(id)) {
+        if (ObjectUtil.notEqual(dept.getId(), id)) {
             throw exception(DEPT_NAME_DUPLICATE);
         }
     }
 
+    @Override
+    public DeptDO getDept(Long id) {
+        return deptMapper.selectById(id);
+    }
+
     @Override
     public List<DeptDO> getDeptList(Collection<Long> ids) {
         return deptMapper.selectBatchIds(ids);
     }
 
     @Override
-    public DeptDO getDept(Long id) {
-        return deptMapper.selectById(id);
+    public List<DeptDO> getDeptList(DeptListReqVO reqVO) {
+        return deptMapper.selectList(reqVO);
+    }
+
+    @Override
+    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);
+        }
+        return children;
+    }
+
+    @Override
+    @DataPermission(enable = false) // 禁用数据权限,避免简历不正确的缓存
+    @Cacheable(cacheNames = RedisKeyConstants.DEPT_CHILDREN_ID_LIST, key = "#id")
+    public Set<Long> getChildDeptIdListFromCache(Long id) {
+        List<DeptDO> children = getChildDeptList(id);
+        return convertSet(children, DeptDO::getId);
     }
 
     @Override

+ 113 - 166
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/dept/DeptServiceImplTest.java

@@ -1,9 +1,7 @@
 package cn.iocoder.yudao.module.system.service.dept;
 
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
-import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils;
 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
-import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
 import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptCreateReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptListReqVO;
@@ -11,26 +9,20 @@ import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptUpdateRe
 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.enums.dept.DeptIdEnum;
-import com.google.common.collect.Multimap;
-import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
-import org.springframework.boot.test.mock.mockito.MockBean;
 import org.springframework.context.annotation.Import;
 
 import javax.annotation.Resource;
 import java.util.Arrays;
 import java.util.List;
-import java.util.Map;
-import java.util.function.Consumer;
+import java.util.Set;
 
-import static cn.hutool.core.util.RandomUtil.randomEle;
 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
 import static java.util.Collections.singletonList;
 import static org.junit.jupiter.api.Assertions.*;
-import static org.mockito.Mockito.verify;
 
 /**
  * {@link DeptServiceImpl} 的单元测试类
@@ -44,63 +36,9 @@ public class DeptServiceImplTest extends BaseDbUnitTest {
     private DeptServiceImpl deptService;
     @Resource
     private DeptMapper deptMapper;
-    @MockBean
-    private DeptProducer deptProducer;
-
-    @BeforeEach
-    public void setUp() {
-        // 清理租户上下文
-        TenantContextHolder.clear();
-    }
-
-    @Test
-    public void testInitLocalCache() {
-        // mock 数据
-        DeptDO deptDO1 = randomDeptDO();
-        deptMapper.insert(deptDO1);
-        DeptDO deptDO2 = randomDeptDO();
-        deptMapper.insert(deptDO2);
-
-        // 调用
-        deptService.initLocalCache();
-        // 断言 deptCache 缓存
-        Map<Long, DeptDO> deptCache = deptService.getDeptCache();
-        assertEquals(2, deptCache.size());
-        assertPojoEquals(deptDO1, deptCache.get(deptDO1.getId()));
-        assertPojoEquals(deptDO2, deptCache.get(deptDO2.getId()));
-        // 断言 parentDeptCache 缓存
-        Multimap<Long, DeptDO> parentDeptCache = deptService.getParentDeptCache();
-        assertEquals(2, parentDeptCache.size());
-        assertPojoEquals(deptDO1, parentDeptCache.get(deptDO1.getParentId()));
-        assertPojoEquals(deptDO2, parentDeptCache.get(deptDO2.getParentId()));
-    }
-
-    @Test
-    public void testListDepts() {
-        // mock 数据
-        DeptDO dept = randomPojo(DeptDO.class, o -> { // 等会查询到
-            o.setName("开发部");
-            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
-        });
-        deptMapper.insert(dept);
-        // 测试 name 不匹配
-        deptMapper.insert(ObjectUtils.cloneIgnoreId(dept, o -> o.setName("发")));
-        // 测试 status 不匹配
-        deptMapper.insert(ObjectUtils.cloneIgnoreId(dept, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
-        // 准备参数
-        DeptListReqVO reqVO = new DeptListReqVO();
-        reqVO.setName("开");
-        reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
-
-        // 调用
-        List<DeptDO> sysDeptDOS = deptService.getDeptList(reqVO);
-        // 断言
-        assertEquals(1, sysDeptDOS.size());
-        assertPojoEquals(dept, sysDeptDOS.get(0));
-    }
 
     @Test
-    public void testCreateDept_success() {
+    public void testCreateDept() {
         // 准备参数
         DeptCreateReqVO reqVO = randomPojo(DeptCreateReqVO.class, o -> {
             o.setParentId(DeptIdEnum.ROOT.getId());
@@ -114,12 +52,10 @@ public class DeptServiceImplTest extends BaseDbUnitTest {
         // 校验记录的属性是否正确
         DeptDO deptDO = deptMapper.selectById(deptId);
         assertPojoEquals(reqVO, deptDO);
-        // 校验调用
-        verify(deptProducer).sendDeptRefreshMessage();
     }
 
     @Test
-    public void testUpdateDept_success() {
+    public void testUpdateDept() {
         // mock 数据
         DeptDO dbDeptDO = randomPojo(DeptDO.class, o -> o.setStatus(randomCommonStatus()));
         deptMapper.insert(dbDeptDO);// @Sql: 先插入出一条存在的数据
@@ -136,14 +72,12 @@ public class DeptServiceImplTest extends BaseDbUnitTest {
         // 校验是否更新正确
         DeptDO deptDO = deptMapper.selectById(reqVO.getId()); // 获取最新的
         assertPojoEquals(reqVO, deptDO);
-        // 校验调用
-        verify(deptProducer).sendDeptRefreshMessage();
     }
 
     @Test
     public void testDeleteDept_success() {
         // mock 数据
-        DeptDO dbDeptDO = randomPojo(DeptDO.class, o -> o.setStatus(randomCommonStatus()));
+        DeptDO dbDeptDO = randomPojo(DeptDO.class);
         deptMapper.insert(dbDeptDO);// @Sql: 先插入出一条存在的数据
         // 准备参数
         Long id = dbDeptDO.getId();
@@ -152,134 +86,99 @@ public class DeptServiceImplTest extends BaseDbUnitTest {
         deptService.deleteDept(id);
         // 校验数据不存在了
         assertNull(deptMapper.selectById(id));
-        // 校验调用
-        verify(deptProducer).sendDeptRefreshMessage();
     }
 
     @Test
-    public void testValidateDept_nameDuplicateForUpdate() {
+    public void testDeleteDept_exitsChildren() {
         // mock 数据
-        DeptDO deptDO = randomDeptDO();
-        // 设置根节点部门
-        deptDO.setParentId(DeptIdEnum.ROOT.getId());
-        deptMapper.insert(deptDO);
-        // mock 数据 稍后模拟重复它的 name
-        DeptDO nameDeptDO = randomDeptDO();
-        // 设置根节点部门
-        nameDeptDO.setParentId(DeptIdEnum.ROOT.getId());
-        deptMapper.insert(nameDeptDO);
+        DeptDO parentDept = randomPojo(DeptDO.class);
+        deptMapper.insert(parentDept);// @Sql: 先插入出一条存在的数据
         // 准备参数
-        DeptUpdateReqVO reqVO = randomPojo(DeptUpdateReqVO.class, o -> {
-            // 设置根节点部门
-            o.setParentId(DeptIdEnum.ROOT.getId());
-            // 设置更新的 ID
-            o.setId(deptDO.getId());
-            // 模拟 name 重复
-            o.setName(nameDeptDO.getName());
+        DeptDO childrenDeptDO = randomPojo(DeptDO.class, o -> {
+            o.setParentId(parentDept.getId());
+            o.setStatus(randomCommonStatus());
         });
+        // 插入子部门
+        deptMapper.insert(childrenDeptDO);
 
         // 调用, 并断言异常
-        assertServiceException(() -> deptService.updateDept(reqVO), DEPT_NAME_DUPLICATE);
+        assertServiceException(() -> deptService.deleteDept(parentDept.getId()), DEPT_EXITS_CHILDREN);
     }
 
     @Test
-    public void testValidateDept_parentNotExitsForCreate() {
+    public void testValidateDeptExists_notFound() {
         // 准备参数
-        DeptCreateReqVO reqVO = randomPojo(DeptCreateReqVO.class,
-            o -> o.setStatus(randomCommonStatus()));
+        Long id = randomLongId();
 
-        // 调用,并断言异常
-        assertServiceException(() -> deptService.createDept(reqVO), DEPT_PARENT_NOT_EXITS);
+        // 调用, 并断言异常
+        assertServiceException(() -> deptService.validateDeptExists(id), DEPT_NOT_FOUND);
     }
 
     @Test
-    public void testValidateDept_notFoundForDelete() {
+    public void testValidateParentDept_parentError() {
         // 准备参数
         Long id = randomLongId();
 
         // 调用, 并断言异常
-        assertServiceException(() -> deptService.deleteDept(id), DEPT_NOT_FOUND);
+        assertServiceException(() -> deptService.validateParentDept(id, id),
+                DEPT_PARENT_ERROR);
     }
 
     @Test
-   public void testValidateDept_exitsChildrenForDelete() {
-        // mock 数据
-        DeptDO parentDept = randomPojo(DeptDO.class, o -> o.setStatus(randomCommonStatus()));
-        deptMapper.insert(parentDept);// @Sql: 先插入出一条存在的数据
-        // 准备参数
-        DeptDO childrenDeptDO = randomPojo(DeptDO.class, o -> {
+    public void testValidateParentDept_parentIsChild() {
+        // mock 数据(父节点)
+        DeptDO parentDept = randomPojo(DeptDO.class);
+        deptMapper.insert(parentDept);
+        // mock 数据(子节点)
+        DeptDO childDept = randomPojo(DeptDO.class, o -> {
             o.setParentId(parentDept.getId());
-            o.setStatus(randomCommonStatus());
         });
-        // 插入子部门
-        deptMapper.insert(childrenDeptDO);
-        // 调用, 并断言异常
-        assertServiceException(() -> deptService.deleteDept(parentDept.getId()), DEPT_EXITS_CHILDREN);
-    }
+        deptMapper.insert(childDept);
 
-    @Test
-    public void testValidateDept_parentErrorForUpdate() {
-        // mock 数据
-        DeptDO dbDeptDO = randomPojo(DeptDO.class, o -> o.setStatus(randomCommonStatus()));
-        deptMapper.insert(dbDeptDO);
         // 准备参数
-        DeptUpdateReqVO reqVO = randomPojo(DeptUpdateReqVO.class, o -> {
-            // 设置自己为父部门
-            o.setParentId(dbDeptDO.getId());
-            // 设置更新的 ID
-            o.setId(dbDeptDO.getId());
-        });
+        Long id = parentDept.getId();
+        Long parentId = childDept.getId();
 
         // 调用, 并断言异常
-        assertServiceException(() -> deptService.updateDept(reqVO), DEPT_PARENT_ERROR);
+        assertServiceException(() -> deptService.validateParentDept(id, parentId), DEPT_PARENT_IS_CHILD);
     }
 
     @Test
-    public void testValidateDept_notEnableForCreate() {
+    public void testValidateNameUnique_duplicate() {
         // mock 数据
-        DeptDO deptDO = randomPojo(DeptDO.class, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus()));
+        DeptDO deptDO = randomPojo(DeptDO.class);
         deptMapper.insert(deptDO);
+
         // 准备参数
-        DeptCreateReqVO reqVO = randomPojo(DeptCreateReqVO.class, o -> {
-            // 设置未启用的部门为父部门
-            o.setParentId(deptDO.getId());
-        });
+        Long id = randomLongId();
+        Long parentId = deptDO.getParentId();
+        String name = deptDO.getName();
 
         // 调用, 并断言异常
-        assertServiceException(() -> deptService.createDept(reqVO), DEPT_NOT_ENABLE);
+        assertServiceException(() -> deptService.validateDeptNameUnique(id, parentId, name),
+                DEPT_NAME_DUPLICATE);
     }
 
     @Test
-    public void testCheckDept_parentIsChildForUpdate() {
+    public void testGetDept() {
         // mock 数据
-        DeptDO parentDept = randomPojo(DeptDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus()));
-        deptMapper.insert(parentDept);
-        DeptDO childDept = randomPojo(DeptDO.class, o -> {
-            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
-            o.setParentId(parentDept.getId());
-        });
-        deptMapper.insert(childDept);
-        // 初始化本地缓存
-        deptService.initLocalCache();
-
+        DeptDO deptDO = randomPojo(DeptDO.class);
+        deptMapper.insert(deptDO);
         // 准备参数
-        DeptUpdateReqVO reqVO = randomPojo(DeptUpdateReqVO.class, o -> {
-            // 设置自己的子部门为父部门
-            o.setParentId(childDept.getId());
-            // 设置更新的 ID
-            o.setId(parentDept.getId());
-        });
+        Long id = deptDO.getId();
 
-        // 调用, 并断言异常
-        assertServiceException(() -> deptService.updateDept(reqVO), DEPT_PARENT_IS_CHILD);
+        // 调用
+        DeptDO dbDept = deptService.getDept(id);
+        // 断言
+        assertEquals(deptDO, dbDept);
     }
 
     @Test
-    public void testGetDeptList() {
+    public void testGetDeptList_ids() {
         // mock 数据
-        DeptDO deptDO01 = randomDeptDO();
+        DeptDO deptDO01 = randomPojo(DeptDO.class);
         deptMapper.insert(deptDO01);
-        DeptDO deptDO02 = randomDeptDO();
+        DeptDO deptDO02 = randomPojo(DeptDO.class);
         deptMapper.insert(deptDO02);
         // 准备参数
         List<Long> ids = Arrays.asList(deptDO01.getId(), deptDO02.getId());
@@ -293,23 +192,79 @@ public class DeptServiceImplTest extends BaseDbUnitTest {
     }
 
     @Test
-    public void testGetDept() {
+    public void testGetDeptList_reqVO() {
         // mock 数据
-        DeptDO deptDO = randomDeptDO();
-        deptMapper.insert(deptDO);
+        DeptDO dept = randomPojo(DeptDO.class, o -> { // 等会查询到
+            o.setName("开发部");
+            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
+        });
+        deptMapper.insert(dept);
+        // 测试 name 不匹配
+        deptMapper.insert(ObjectUtils.cloneIgnoreId(dept, o -> o.setName("发")));
+        // 测试 status 不匹配
+        deptMapper.insert(ObjectUtils.cloneIgnoreId(dept, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
         // 准备参数
-        Long id = deptDO.getId();
+        DeptListReqVO reqVO = new DeptListReqVO();
+        reqVO.setName("开");
+        reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
 
         // 调用
-        DeptDO dbDept = deptService.getDept(id);
+        List<DeptDO> sysDeptDOS = deptService.getDeptList(reqVO);
         // 断言
-        assertEquals(deptDO, dbDept);
+        assertEquals(1, sysDeptDOS.size());
+        assertPojoEquals(dept, sysDeptDOS.get(0));
+    }
+
+    @Test
+    public void testGetChildDeptList() {
+        // mock 数据(1 级别子节点)
+        DeptDO dept1 = randomPojo(DeptDO.class, o -> o.setName("1"));
+        deptMapper.insert(dept1);
+        DeptDO dept2 = randomPojo(DeptDO.class, o -> o.setName("2"));
+        deptMapper.insert(dept2);
+        // mock 数据(2 级子节点)
+        DeptDO dept1a = randomPojo(DeptDO.class, o -> o.setName("1-a").setParentId(dept1.getId()));
+        deptMapper.insert(dept1a);
+        DeptDO dept2a = randomPojo(DeptDO.class, o -> o.setName("2-a").setParentId(dept2.getId()));
+        deptMapper.insert(dept2a);
+        // 准备参数
+        Long id = dept1.getParentId();
+
+        // 调用
+        List<DeptDO> result = deptService.getChildDeptList(id);
+        // 断言
+        assertEquals(result.size(), 2);
+        assertPojoEquals(dept1, result.get(0));
+        assertPojoEquals(dept1a, result.get(1));
+    }
+
+    @Test
+    public void testGetChildDeptListFromCache() {
+        // mock 数据(1 级别子节点)
+        DeptDO dept1 = randomPojo(DeptDO.class, o -> o.setName("1"));
+        deptMapper.insert(dept1);
+        DeptDO dept2 = randomPojo(DeptDO.class, o -> o.setName("2"));
+        deptMapper.insert(dept2);
+        // mock 数据(2 级子节点)
+        DeptDO dept1a = randomPojo(DeptDO.class, o -> o.setName("1-a").setParentId(dept1.getId()));
+        deptMapper.insert(dept1a);
+        DeptDO dept2a = randomPojo(DeptDO.class, o -> o.setName("2-a").setParentId(dept2.getId()));
+        deptMapper.insert(dept2a);
+        // 准备参数
+        Long id = dept1.getParentId();
+
+        // 调用
+        Set<Long> result = deptService.getChildDeptIdListFromCache(id);
+        // 断言
+        assertEquals(result.size(), 2);
+        assertTrue(result.contains(dept1.getId()));
+        assertTrue(result.contains(dept1a.getId()));
     }
 
     @Test
     public void testValidateDeptList_success() {
         // mock 数据
-        DeptDO deptDO = randomDeptDO().setStatus(CommonStatusEnum.ENABLE.getStatus());
+        DeptDO deptDO = randomPojo(DeptDO.class).setStatus(CommonStatusEnum.ENABLE.getStatus());
         deptMapper.insert(deptDO);
         // 准备参数
         List<Long> ids = singletonList(deptDO.getId());
@@ -330,7 +285,7 @@ public class DeptServiceImplTest extends BaseDbUnitTest {
     @Test
     public void testValidateDeptList_notEnable() {
         // mock 数据
-        DeptDO deptDO = randomDeptDO().setStatus(CommonStatusEnum.DISABLE.getStatus());
+        DeptDO deptDO = randomPojo(DeptDO.class).setStatus(CommonStatusEnum.DISABLE.getStatus());
         deptMapper.insert(deptDO);
         // 准备参数
         List<Long> ids = singletonList(deptDO.getId());
@@ -339,12 +294,4 @@ public class DeptServiceImplTest extends BaseDbUnitTest {
         assertServiceException(() -> deptService.validateDeptList(ids), DEPT_NOT_ENABLE, deptDO.getName());
     }
 
-    @SafeVarargs
-    private static DeptDO randomDeptDO(Consumer<DeptDO>... consumers) {
-        Consumer<DeptDO> consumer = (o) -> {
-            o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围
-        };
-        return randomPojo(DeptDO.class, ArrayUtils.append(consumer, consumers));
-    }
-
 }