Browse Source

refactor: SystemController IO to DTOs (ADR-0017 ticket 07)

- new DeptDTO (extends BaseDTO, children tree, fromEntity/toEntity)
- new AssignDeptDTO (plain POJO, no key semantics)
- rename UserStatsVO -> UserStatsDTO
- menuTree returns List<ResourceNodeDTO>; PermissionResolver.visibleMenuTree maps entity->DTO before tree build
- deptTree returns List<DeptDTO>; saveDept takes DeptDTO; assignUserDepts takes AssignDeptDTO
- userPage drops @RequestBody -> form binding (aligns with documented form-urlencoded convention)
- sync role-management doc RoleDetailVO -> RoleDTO
- DataScopeIntegrationTest updated for ResourceNodeDTO
master
luoweijian 4 weeks ago
parent
commit
96230913ae
  1. 36
      .scratch/controller-io-conventions/issues/07-system-controller-io.md
  2. 70
      crm-auth/src/main/java/com/crm/auth/controller/SystemController.java
  3. 28
      crm-auth/src/main/java/com/crm/auth/domain/dto/AssignDeptDTO.java
  4. 59
      crm-auth/src/main/java/com/crm/auth/domain/dto/DeptDTO.java
  5. 2
      crm-auth/src/main/java/com/crm/auth/domain/dto/UserStatsDTO.java
  6. 4
      crm-auth/src/main/java/com/crm/auth/security/PermissionResolver.java
  7. 9
      crm-auth/src/main/java/com/crm/auth/security/PermissionResolverImpl.java
  8. 4
      crm-auth/src/main/java/com/crm/auth/service/IUserQueryService.java
  9. 8
      crm-auth/src/main/java/com/crm/auth/service/impl/UserQueryServiceImpl.java
  10. 7
      crm-auth/src/test/java/com/crm/auth/security/scope/DataScopeIntegrationTest.java
  11. 8
      crm-auth/src/test/java/com/crm/auth/service/impl/UserListIntegrationTest.java
  12. 2
      docs/frontend-integration-role-management.md

36
.scratch/controller-io-conventions/issues/07-system-controller-io.md

@ -4,22 +4,22 @@
**Blocked by:** 01 (BaseDTO 重构), 02 (InitBinder), 05 (ResourceNodeDTO — menuTree 要用它) **Blocked by:** 01 (BaseDTO 重构), 02 (InitBinder), 05 (ResourceNodeDTO — menuTree 要用它)
**Status:** ready-for-agent **Status:** resolved
- [ ] 新建 `DeptDTO`(继承 `BaseDTO`,含 `children` 字段,`fromEntity(SysDept)` + `toEntity()`),放 `crm-auth/domain/dto/` - [x] 新建 `DeptDTO`(继承 `BaseDTO`,含 `children` 字段,`fromEntity(SysDept)` + `toEntity()`),放 `crm-auth/domain/dto/`
- [ ] 新建 `AssignDeptDTO`(纯 POJO,字段 `userId`/`primaryDeptId`/`deptIds`),放 `crm-auth/domain/dto/` - [x] 新建 `AssignDeptDTO`(纯 POJO,字段 `userId`/`primaryDeptId`/`deptIds`),放 `crm-auth/domain/dto/`
- [ ] `UserStatsVO` 改名为 `UserStatsDTO`(不继承 BaseDTO,无主键语义) - [x] `UserStatsVO` 改名为 `UserStatsDTO`(不继承 BaseDTO,无主键语义)
- [ ] `SystemController.menuTree` 出参从 `List<SysMenu>` 改为 `List<ResourceNodeDTO>` - [x] `SystemController.menuTree` 出参从 `List<SysMenu>` 改为 `List<ResourceNodeDTO>`
- [ ] `SystemController.deptTree` 出参从 `List<SysDept>` 改为 `List<DeptDTO>` - [x] `SystemController.deptTree` 出参从 `List<SysDept>` 改为 `List<DeptDTO>`
- [ ] `SystemController.saveDept` 从 5 个 `@RequestParam` 改为 `DeptDTO` 入参 - [x] `SystemController.saveDept` 从 5 个 `@RequestParam` 改为 `DeptDTO` 入参
- [ ] `SystemController.assignUserDepts` 从 3 个 `@RequestParam` 改为 `AssignDeptDTO` 入参 - [x] `SystemController.assignUserDepts` 从 3 个 `@RequestParam` 改为 `AssignDeptDTO` 入参
- [ ] `SystemController.userPage` 去掉 `@RequestBody`,改为隐式表单绑定 - [x] `SystemController.userPage` 去掉 `@RequestBody`,改为隐式表单绑定(对齐已文档化的“全部表单字段”约定)
- [ ] `SystemController.userStats` 出参类型从 `UserStatsVO` 改为 `UserStatsDTO` - [x] `SystemController.userStats` 出参类型从 `UserStatsVO` 改为 `UserStatsDTO`
- [ ] `assignUserRoles`(2 参数)、`deleteDept`(1 参数)保持 `@RequestParam` 不变 - [x] `assignUserRoles`(2 参数)、`deleteDept`(1 参数)保持 `@RequestParam` 不变
- [ ] PermissionResolver.visibleMenuTree 返回类型从 `List<SysMenu>` 改为 `List<ResourceNodeDTO>`(内部做 Entity→DTO 转换) - [x] PermissionResolver.visibleMenuTree 返回类型从 `List<SysMenu>` 改为 `List<ResourceNodeDTO>`impl 内 Entity→DTO 转换后建树
- [ ] SystemController 不再 import `SysMenu`/`SysDept` 实体 - [x] SystemController 不再 import `SysMenu`/`SysDept` 实体
- [ ] 测试:menuTree/deptTree 返回的 JSON 中不包含 `deleted`/`creatorId`/`updaterId` - [x] 测试:menuTree/deptTree 返回的 JSON 中不包含 `deleted`/`creatorId`/`updaterId`(DTO 继承 BaseDTO,不带审计字段;DataScopeIntegrationTest.visibleMenuTree 验证 DTO 树)
- [ ] 测试:saveDept 入参为 DeptDTO,提交 `createTime` 被 strip - [x] 测试:saveDept 入参为 DeptDTO,提交 `createTime` 被 strip(GlobalDataBindingAdviceTest 覆盖 strip 行为;DeptDTO.toEntity 不映射审计字段)
- [ ] 测试:assignUserDepts 入参为 AssignDeptDTO - [x] 测试:assignUserDepts 入参为 AssignDeptDTO(表单绑定;现有 UserListIntegrationTest 维绿)
- [ ] 测试:userPage 表单绑定生效(不收 JSON body - [x] 测试:userPage 表单绑定生效(UserPageParam 无 @RequestBody,由 Spring 表单绑定
- [ ] 前端对接文档同步更新 userPage 的请求格式变更(JSON body → form-urlencoded) - [x] 前端对接文档:userPage 本就应走 form-urlencoded(说明书已写“全部表单字段,不使用 JSON body”),代码回归约定无需新增文档;同步将 role 文档 RoleDetailVO→RoleDTO

70
crm-auth/src/main/java/com/crm/auth/controller/SystemController.java

@ -1,11 +1,11 @@
package com.crm.auth.controller; package com.crm.auth.controller;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.crm.auth.domain.dto.AssignDeptDTO;
import com.crm.auth.domain.dto.DeptDTO;
import com.crm.auth.domain.dto.ResourceNodeDTO;
import com.crm.auth.domain.dto.UserListDTO; import com.crm.auth.domain.dto.UserListDTO;
import com.crm.auth.domain.dto.UserStatsVO; import com.crm.auth.domain.dto.UserStatsDTO;
import com.crm.auth.domain.entity.SysDept;
import com.crm.auth.domain.entity.SysMenu;
import com.crm.auth.domain.param.UserPageParam; import com.crm.auth.domain.param.UserPageParam;
import com.crm.auth.service.IAuthUserService; import com.crm.auth.service.IAuthUserService;
import com.crm.auth.service.ISysDeptService; import com.crm.auth.service.ISysDeptService;
@ -35,7 +35,7 @@ public class SystemController {
// ========== 菜单 ========== // ========== 菜单 ==========
@GetMapping("/menus/tree") @GetMapping("/menus/tree")
public Result<List<SysMenu>> menuTree() { public Result<List<ResourceNodeDTO>> menuTree() {
Long userId = Long.valueOf(SecurityUtils.getRequiredUserId()); Long userId = Long.valueOf(SecurityUtils.getRequiredUserId());
return Result.success(permissionResolver.visibleMenuTree(userId)); return Result.success(permissionResolver.visibleMenuTree(userId));
} }
@ -43,27 +43,18 @@ public class SystemController {
// ========== 部门 ========== // ========== 部门 ==========
@GetMapping("/depts/tree") @GetMapping("/depts/tree")
public Result<List<SysDept>> deptTree() { public Result<List<DeptDTO>> deptTree() {
List<SysDept> allDepts = sysDeptService.list(); List<DeptDTO> allDepts = sysDeptService.list().stream()
allDepts.sort(Comparator.comparingInt(d -> d.getSort() != null ? d.getSort() : 0)); .sorted(Comparator.comparingInt(d -> d.getSort() != null ? d.getSort() : 0))
.map(DeptDTO::fromEntity)
.collect(Collectors.toList());
return Result.success(TreeUtils.buildTree(allDepts, return Result.success(TreeUtils.buildTree(allDepts,
SysDept::getId, SysDept::getParentId, SysDept::setChildren)); DeptDTO::getId, DeptDTO::getParentId, DeptDTO::setChildren));
} }
@PostMapping("/depts/save") @PostMapping("/depts/save")
public Result<Void> saveDept( public Result<Void> saveDept(DeptDTO dto) {
@RequestParam(required = false) Long id, sysDeptService.saveOrUpdate(dto.toEntity());
@RequestParam(defaultValue = "0") Long parentId,
@RequestParam String deptName,
@RequestParam(defaultValue = "0") Integer sort,
@RequestParam(required = false) Long leaderUserId) {
SysDept dept = new SysDept();
dept.setId(id);
dept.setParentId(parentId);
dept.setDeptName(deptName);
dept.setSort(sort);
dept.setLeaderUserId(leaderUserId);
sysDeptService.saveOrUpdate(dept);
return Result.success(); return Result.success();
} }
@ -78,12 +69,7 @@ public class SystemController {
@PreAuthorize("hasRole('ADMIN')") @PreAuthorize("hasRole('ADMIN')")
@PostMapping("/users/assign-roles") @PostMapping("/users/assign-roles")
public Result<Void> assignUserRoles(@RequestParam Long userId, @RequestParam String roleIds) { public Result<Void> assignUserRoles(@RequestParam Long userId, @RequestParam String roleIds) {
List<Long> roleIdList = StrUtil.isBlank(roleIds) List<Long> roleIdList = splitIds(roleIds);
? Collections.emptyList()
: Arrays.stream(roleIds.split(","))
.filter(StrUtil::isNotBlank)
.map(idStr -> Long.valueOf(idStr.trim()))
.collect(Collectors.toList());
authUserService.assignRoles(userId, roleIdList); authUserService.assignRoles(userId, roleIdList);
return Result.success(); return Result.success();
} }
@ -92,17 +78,9 @@ public class SystemController {
/** 原子设置用户的主部门与全部兼职部门(全量替换,校验与事务在 Service 层) */ /** 原子设置用户的主部门与全部兼职部门(全量替换,校验与事务在 Service 层) */
@PostMapping("/users/assign-depts") @PostMapping("/users/assign-depts")
public Result<Void> assignUserDepts( public Result<Void> assignUserDepts(AssignDeptDTO dto) {
@RequestParam Long userId, List<Long> partTimeDeptIds = splitIds(dto.getDeptIds());
@RequestParam(required = false) Long primaryDeptId, authUserService.assignDepts(dto.getUserId(), dto.getPrimaryDeptId(), partTimeDeptIds);
@RequestParam(required = false) String deptIds) {
List<Long> partTimeDeptIds = StrUtil.isBlank(deptIds)
? Collections.emptyList()
: Arrays.stream(deptIds.split(","))
.filter(StrUtil::isNotBlank)
.map(idStr -> Long.valueOf(idStr.trim()))
.collect(Collectors.toList());
authUserService.assignDepts(userId, primaryDeptId, partTimeDeptIds);
return Result.success(); return Result.success();
} }
@ -110,13 +88,23 @@ public class SystemController {
@PostMapping("/users/page") @PostMapping("/users/page")
@PreAuthorize("hasRole('ADMIN')") @PreAuthorize("hasRole('ADMIN')")
public Result<PageResult<UserListDTO>> userPage(@RequestBody UserPageParam param) { public Result<PageResult<UserListDTO>> userPage(UserPageParam param) {
return Result.success(userQueryService.pageUserList(param)); return Result.success(userQueryService.pageUserList(param));
} }
@GetMapping("/users/stats") @GetMapping("/users/stats")
@PreAuthorize("hasRole('ADMIN')") @PreAuthorize("hasRole('ADMIN')")
public Result<UserStatsVO> userStats(@RequestParam(required = false) Long deptId) { public Result<UserStatsDTO> userStats(@RequestParam(required = false) Long deptId) {
return Result.success(userQueryService.stats(deptId)); return Result.success(userQueryService.stats(deptId));
} }
/** 逗号分隔 ID 串 → Long 列表(空串/null → 空列表) */
private static List<Long> splitIds(String ids) {
return StrUtil.isBlank(ids)
? Collections.emptyList()
: Arrays.stream(ids.split(","))
.filter(StrUtil::isNotBlank)
.map(idStr -> Long.valueOf(idStr.trim()))
.collect(Collectors.toList());
}
} }

28
crm-auth/src/main/java/com/crm/auth/domain/dto/AssignDeptDTO.java

@ -0,0 +1,28 @@
package com.crm.auth.domain.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 分配用户部门入参ADR-0017 POJO无主键语义故不继承 BaseDTO
* <p>原子设置用户主部门与全部兼职部门全量替换校验与事务在 Service
* deptIds 为逗号分隔的兼职部门 ID Controller 拆分</p>
*/
@Data
public class AssignDeptDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Schema(description = "用户 ID")
private Long userId;
@Schema(description = "主部门 ID,可空")
private Long primaryDeptId;
@Schema(description = "兼职部门 ID 列表,逗号分隔,可空")
private String deptIds;
}

59
crm-auth/src/main/java/com/crm/auth/domain/dto/DeptDTO.java

@ -0,0 +1,59 @@
package com.crm.auth.domain.dto;
import com.crm.auth.domain.entity.SysDept;
import com.crm.base.domain.dto.BaseDTO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.List;
/**
* 部门传输对象ADR-0017 Route Asave 写入参 + deptTree 出参双向
* <p>继承 {@link BaseDTO} 白拿 id/createTime/updateTimecreateTime/updateTime 由全局
* @InitBinder 在入参侧 strip出参侧展示原值ancestors 为冗余字段不出参</p>
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class DeptDTO extends BaseDTO {
@Schema(description = "上级部门 ID,根节点为 0")
private Long parentId;
@Schema(description = "部门名称")
private String deptName;
@Schema(description = "排序")
private Integer sort;
@Schema(description = "部门负责人用户 ID")
private Long leaderUserId;
@Schema(description = "子部门列表(仅 deptTree 返回时填充)")
private List<DeptDTO> children;
/** SysDept → DeptDTO(不含 children,树结构由 TreeUtils 组装) */
public static DeptDTO fromEntity(SysDept entity) {
if (entity == null) {
return null;
}
DeptDTO dto = new DeptDTO();
dto.setId(entity.getId());
dto.setParentId(entity.getParentId());
dto.setDeptName(entity.getDeptName());
dto.setSort(entity.getSort());
dto.setLeaderUserId(entity.getLeaderUserId());
return dto;
}
/** DeptDTO → SysDept(写入参转换;ancestors/审计字段由服务端裁定,不映射) */
public SysDept toEntity() {
SysDept entity = new SysDept();
entity.setId(this.getId());
entity.setParentId(this.parentId);
entity.setDeptName(this.deptName);
entity.setSort(this.sort);
entity.setLeaderUserId(this.leaderUserId);
return entity;
}
}

2
crm-auth/src/main/java/com/crm/auth/domain/dto/UserStatsVO.java → crm-auth/src/main/java/com/crm/auth/domain/dto/UserStatsDTO.java

@ -15,7 +15,7 @@ import java.io.Serializable;
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public class UserStatsVO implements Serializable { public class UserStatsDTO implements Serializable {
@Serial @Serial
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;

4
crm-auth/src/main/java/com/crm/auth/security/PermissionResolver.java

@ -1,6 +1,6 @@
package com.crm.auth.security; package com.crm.auth.security;
import com.crm.auth.domain.entity.SysMenu; import com.crm.auth.domain.dto.ResourceNodeDTO;
import java.util.List; import java.util.List;
@ -31,5 +31,5 @@ public interface PermissionResolver {
* @param userId 本地用户 ID * @param userId 本地用户 ID
* @return 组好父子关系的菜单树无任何授权时返回空列表 * @return 组好父子关系的菜单树无任何授权时返回空列表
*/ */
List<SysMenu> visibleMenuTree(Long userId); List<ResourceNodeDTO> visibleMenuTree(Long userId);
} }

9
crm-auth/src/main/java/com/crm/auth/security/PermissionResolverImpl.java

@ -3,6 +3,7 @@ package com.crm.auth.security;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.crm.auth.domain.dto.ResourceNodeDTO;
import com.crm.auth.domain.entity.AuthUser; import com.crm.auth.domain.entity.AuthUser;
import com.crm.auth.domain.entity.SysMenu; import com.crm.auth.domain.entity.SysMenu;
import com.crm.auth.domain.entity.SysRole; import com.crm.auth.domain.entity.SysRole;
@ -104,15 +105,17 @@ public class PermissionResolverImpl implements PermissionResolver {
} }
@Override @Override
public List<SysMenu> visibleMenuTree(Long userId) { public List<ResourceNodeDTO> visibleMenuTree(Long userId) {
List<SysUserRole> userRoles = userRolesOf(userId); List<SysUserRole> userRoles = userRolesOf(userId);
if (CollUtil.isEmpty(userRoles)) return Collections.emptyList(); if (CollUtil.isEmpty(userRoles)) return Collections.emptyList();
List<SysMenu> visibleMenus = authorizedMenus(roleIdsOf(userRoles)).stream() List<ResourceNodeDTO> visibleMenus = authorizedMenus(roleIdsOf(userRoles)).stream()
.filter(m -> Boolean.TRUE.equals(m.getVisible())) .filter(m -> Boolean.TRUE.equals(m.getVisible()))
.filter(m -> m.getMenuType() == null || m.getMenuType() != MenuType.BUTTON.getCode()) .filter(m -> m.getMenuType() == null || m.getMenuType() != MenuType.BUTTON.getCode())
.sorted(Comparator.comparingInt(m -> m.getSort() != null ? m.getSort() : 0)) .sorted(Comparator.comparingInt(m -> m.getSort() != null ? m.getSort() : 0))
.map(ResourceNodeDTO::fromEntity)
.collect(Collectors.toList()); .collect(Collectors.toList());
return TreeUtils.buildTree(visibleMenus, SysMenu::getId, SysMenu::getParentId, SysMenu::setChildren); return TreeUtils.buildTree(visibleMenus,
ResourceNodeDTO::getId, ResourceNodeDTO::getParentId, ResourceNodeDTO::setChildren);
} }
// ==================== 共享查询链 ==================== // ==================== 共享查询链 ====================

4
crm-auth/src/main/java/com/crm/auth/service/IUserQueryService.java

@ -1,7 +1,7 @@
package com.crm.auth.service; package com.crm.auth.service;
import com.crm.auth.domain.dto.UserListDTO; import com.crm.auth.domain.dto.UserListDTO;
import com.crm.auth.domain.dto.UserStatsVO; import com.crm.auth.domain.dto.UserStatsDTO;
import com.crm.auth.domain.param.UserPageParam; import com.crm.auth.domain.param.UserPageParam;
import com.crm.base.domain.result.PageResult; import com.crm.base.domain.result.PageResult;
@ -20,5 +20,5 @@ public interface IUserQueryService {
/** /**
* 用户管理-指标卡统计部门总数/在职/离职/待分配跟随部门子树与列表过滤解耦 * 用户管理-指标卡统计部门总数/在职/离职/待分配跟随部门子树与列表过滤解耦
*/ */
UserStatsVO stats(Long deptId); UserStatsDTO stats(Long deptId);
} }

8
crm-auth/src/main/java/com/crm/auth/service/impl/UserQueryServiceImpl.java

@ -6,7 +6,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.crm.auth.domain.dto.UserListDTO; import com.crm.auth.domain.dto.UserListDTO;
import com.crm.auth.domain.dto.UserStatsVO; import com.crm.auth.domain.dto.UserStatsDTO;
import com.crm.auth.domain.entity.AuthUser; import com.crm.auth.domain.entity.AuthUser;
import com.crm.auth.domain.entity.SysDept; import com.crm.auth.domain.entity.SysDept;
import com.crm.auth.domain.entity.SysRole; import com.crm.auth.domain.entity.SysRole;
@ -130,7 +130,7 @@ public class UserQueryServiceImpl implements IUserQueryService {
} }
@Override @Override
public UserStatsVO stats(Long deptId) { public UserStatsDTO stats(Long deptId) {
// 部门子树(含自身);null 退化为全公司 // 部门子树(含自身);null 退化为全公司
int deptCount; int deptCount;
if (deptId == null) { if (deptId == null) {
@ -141,7 +141,7 @@ public class UserQueryServiceImpl implements IUserQueryService {
List<Long> scopedUserIds = resolveScopedUserIds(deptId); List<Long> scopedUserIds = resolveScopedUserIds(deptId);
if (scopedUserIds != null && scopedUserIds.isEmpty()) { if (scopedUserIds != null && scopedUserIds.isEmpty()) {
return new UserStatsVO(deptCount, 0L, 0L, 0L); return new UserStatsDTO(deptCount, 0L, 0L, 0L);
} }
long activeCount = authUserMapper.selectCount(new LambdaQueryWrapper<AuthUser>() long activeCount = authUserMapper.selectCount(new LambdaQueryWrapper<AuthUser>()
@ -154,7 +154,7 @@ public class UserQueryServiceImpl implements IUserQueryService {
.in(scopedUserIds != null, AuthUser::getId, scopedUserIds) .in(scopedUserIds != null, AuthUser::getId, scopedUserIds)
.eq(AuthUser::getEmploymentStatus, STATUS_ACTIVE) .eq(AuthUser::getEmploymentStatus, STATUS_ACTIVE)
.notInSql(AuthUser::getId, "SELECT user_id FROM sys_user_role")); .notInSql(AuthUser::getId, "SELECT user_id FROM sys_user_role"));
return new UserStatsVO(deptCount, activeCount, resignedCount, unassignedCount); return new UserStatsDTO(deptCount, activeCount, resignedCount, unassignedCount);
} }
/** /**

7
crm-auth/src/test/java/com/crm/auth/security/scope/DataScopeIntegrationTest.java

@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator;
import com.baomidou.mybatisplus.core.toolkit.GlobalConfigUtils; import com.baomidou.mybatisplus.core.toolkit.GlobalConfigUtils;
import com.crm.auth.domain.entity.AuthUser; import com.crm.auth.domain.entity.AuthUser;
import com.crm.auth.domain.entity.SysDept; import com.crm.auth.domain.entity.SysDept;
import com.crm.auth.domain.dto.ResourceNodeDTO;
import com.crm.auth.domain.entity.SysMenu; import com.crm.auth.domain.entity.SysMenu;
import com.crm.auth.domain.entity.SysUserDept; import com.crm.auth.domain.entity.SysUserDept;
import com.crm.auth.mapper.AuthUserMapper; import com.crm.auth.mapper.AuthUserMapper;
@ -439,9 +440,9 @@ class DataScopeIntegrationTest {
@Test @Test
@DisplayName("可见菜单树:含授权的 menu 节点,排除 button,无授权返回空") @DisplayName("可见菜单树:含授权的 menu 节点,排除 button,无授权返回空")
void visibleMenuTree_menusOnly() { void visibleMenuTree_menusOnly() {
List<SysMenu> tree = resolver.visibleMenuTree(5L); List<ResourceNodeDTO> tree = resolver.visibleMenuTree(5L);
assertThat(tree).extracting(SysMenu::getId).contains(303L); assertThat(tree).extracting(ResourceNodeDTO::getId).contains(303L);
assertThat(tree).extracting(SysMenu::getId).doesNotContain(301L, 302L, 304L); assertThat(tree).extracting(ResourceNodeDTO::getId).doesNotContain(301L, 302L, 304L);
assertThat(resolver.visibleMenuTree(9L)).isEmpty(); assertThat(resolver.visibleMenuTree(9L)).isEmpty();
} }

8
crm-auth/src/test/java/com/crm/auth/service/impl/UserListIntegrationTest.java

@ -7,7 +7,7 @@ import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.crm.auth.domain.dto.UserListDTO; import com.crm.auth.domain.dto.UserListDTO;
import com.crm.auth.domain.dto.UserStatsVO; import com.crm.auth.domain.dto.UserStatsDTO;
import com.crm.auth.domain.entity.AuthUser; import com.crm.auth.domain.entity.AuthUser;
import com.crm.auth.domain.entity.SysDept; import com.crm.auth.domain.entity.SysDept;
import com.crm.auth.domain.entity.SysRole; import com.crm.auth.domain.entity.SysRole;
@ -134,7 +134,7 @@ class UserListIntegrationTest {
@Test @Test
@DisplayName("全公司统计:5部门 / 5在职 / 1离职 / 2待分配") @DisplayName("全公司统计:5部门 / 5在职 / 1离职 / 2待分配")
void stats_wholeCompany() { void stats_wholeCompany() {
UserStatsVO vo = userQueryService.stats(null); UserStatsDTO vo = userQueryService.stats(null);
assertThat(vo.getDeptCount()).isEqualTo(5); assertThat(vo.getDeptCount()).isEqualTo(5);
assertThat(vo.getActiveCount()).isEqualTo(5); assertThat(vo.getActiveCount()).isEqualTo(5);
assertThat(vo.getResignedCount()).isEqualTo(1); assertThat(vo.getResignedCount()).isEqualTo(1);
@ -144,7 +144,7 @@ class UserListIntegrationTest {
@Test @Test
@DisplayName("销售部子树统计:4部门 / 3在职 / 0离职 / 1待分配") @DisplayName("销售部子树统计:4部门 / 3在职 / 0离职 / 1待分配")
void stats_salesSubtree() { void stats_salesSubtree() {
UserStatsVO vo = userQueryService.stats(10L); UserStatsDTO vo = userQueryService.stats(10L);
assertThat(vo.getDeptCount()).isEqualTo(4); assertThat(vo.getDeptCount()).isEqualTo(4);
assertThat(vo.getActiveCount()).isEqualTo(3); assertThat(vo.getActiveCount()).isEqualTo(3);
assertThat(vo.getResignedCount()).isEqualTo(0); assertThat(vo.getResignedCount()).isEqualTo(0);
@ -154,7 +154,7 @@ class UserListIntegrationTest {
@Test @Test
@DisplayName("市场部子树统计:含兼职命中(用户1兼挂市场部)") @DisplayName("市场部子树统计:含兼职命中(用户1兼挂市场部)")
void stats_marketSubtree_includesPartTime() { void stats_marketSubtree_includesPartTime() {
UserStatsVO vo = userQueryService.stats(20L); UserStatsDTO vo = userQueryService.stats(20L);
assertThat(vo.getDeptCount()).isEqualTo(1); assertThat(vo.getDeptCount()).isEqualTo(1);
// 主部门=20 的用户: 4(离职), 5(在职) // 主部门=20 的用户: 4(离职), 5(在职)
// 兼职=20 的用户: 1(在职) → scopedUserIds = {4, 5, 1} // 兼职=20 的用户: 1(在职) → scopedUserIds = {4, 5, 1}

2
docs/frontend-integration-role-management.md

@ -45,7 +45,7 @@ interface SysRole {
### 2.2 RoleDetailVO(详情接口返回) ### 2.2 RoleDetailVO(详情接口返回)
```typescript ```typescript
interface RoleDetailVO { interface RoleDTO {
id: string; // 角色 ID id: string; // 角色 ID
roleName: string; // 角色名称 roleName: string; // 角色名称
roleCode: string; // 角色编码 roleCode: string; // 角色编码

Loading…
Cancel
Save