feat(system): 扩展用户部门权限功能

- 在 AdminUserService 中新增 listEnabledUserIdsByDeptIds 方法获取指定部门集合下启用且未离职的用户 ID 集合
- 在 DeptService 中新增 listDescendantDeptIds 方法获得指定部门集合及其所有子孙部门的 ID 集合
- 在 DeptService 中新增 listCodesByIds 方法按 id 集合批量查询部门 code 集合
- 在 OrgLeaderRelationService 中新增 listEffectiveDeptIdsByUserId 方法查询指定用户当前生效的负责人关系所对应的 dept_id 集合
- 在 PermissionApi 中新增 isSuperAdmin 接口判断用户是否超管
- 在 ObjectPermissionApi 中新增 getObjectRolePermissionDetailMerged 接口按 roleId 列表聚合菜单 + 权限码
- 扩展 ProductContextRoleRespVO 添加多角色场景的附加角色名称列表
- 扩展 ProductCreateWithTeamReqVO 支持创建时添加关心人用户 ID 列表
- 优化 ProductMemberServiceImpl 支持同一用户多角色显示,区分主角色和附加角色
- 新增 MEMBER_ACTION_REACTIVATE 复活动作类型用于处理 INACTIVE 成员行重新激活场景
- 在 ObjectStatusModelDO 中新增 progressExcludedFlag 字段控制是否参与上层进度统计
- 更新 AGENTS.md 和 CLAUDE.md 添加 Git 操作纪律规范
- 在 rdms-project-api 中新增多个错误码常量支持角色转移和内置角色配置验证
This commit is contained in:
2026-05-14 13:58:40 +08:00
parent 3946c0a0aa
commit 8f6b762bf3
85 changed files with 3908 additions and 277 deletions

View File

@@ -0,0 +1,27 @@
package com.njcn.rdms.module.system.api.dept;
import com.njcn.rdms.framework.common.pojo.CommonResult;
import com.njcn.rdms.module.system.enums.ApiConstants;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.Set;
@FeignClient(name = ApiConstants.NAME)
@Tag(name = "RPC 服务 - 组织负责人")
public interface OrgLeaderApi {
String PREFIX = ApiConstants.PREFIX + "/org-leader";
/**
* 反推:当前 user 作为 leader 能"看到"的下属 user_id 集合(含递归子节点)。
* 自己默认不在结果里(自己看自己走通道 1
* 无 leader 关系返回空集。
*/
@GetMapping(PREFIX + "/get-reachable-user-ids")
@Operation(summary = "反推 leader 可见的下属 user 集合")
CommonResult<Set<Long>> getReachableUserIds(@RequestParam("currentUserId") Long currentUserId);
}

View File

@@ -70,6 +70,15 @@ public interface ObjectPermissionApi {
@RequestParam("scopeType") String scopeType,
@RequestParam("objectType") String objectType);
@GetMapping(PREFIX + "/role-permission-detail-merged")
@Operation(summary = "按 roleId 列表聚合菜单 + 权限码(多角色场景);主角色按 system_role.sort 升序取首个,菜单/权限取并集")
@Parameter(name = "roleIds", description = "角色 ID 集合", example = "1,2", required = true)
@Parameter(name = "scopeType", description = "权限作用域类型", example = "object", required = true)
@Parameter(name = "objectType", description = "对象类型", example = "product", required = true)
CommonResult<ObjectRolePermissionRespDTO> getObjectRolePermissionDetailMerged(@RequestParam("roleIds") Collection<Long> roleIds,
@RequestParam("scopeType") String scopeType,
@RequestParam("objectType") String objectType);
/**
* 按角色 ID 返回对象作用域角色摘要映射,便于业务模块批量对齐本地成员数据。
*

View File

@@ -24,4 +24,8 @@ public interface PermissionApi extends PermissionCommonApi {
@Parameter(name = "roleIds", description = "角色编号集合", example = "1,2", required = true)
CommonResult<Set<Long>> getUserRoleIdListByRoleIds(@RequestParam("roleIds") Collection<Long> roleIds);
@GetMapping(PREFIX + "/is-super-admin")
@Operation(summary = "判断用户是否超管")
CommonResult<Boolean> isSuperAdmin(@RequestParam("userId") Long userId);
}

View File

@@ -0,0 +1,25 @@
package com.njcn.rdms.module.system.api.permission;
import com.njcn.rdms.framework.common.pojo.CommonResult;
import com.njcn.rdms.module.system.api.permission.dto.UserVisibilityConfigRespDTO;
import com.njcn.rdms.module.system.enums.ApiConstants;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(name = ApiConstants.NAME)
@Tag(name = "RPC 服务 - 用户可见性配置")
public interface UserVisibilityConfigApi {
String PREFIX = ApiConstants.PREFIX + "/permission/user-visibility-config";
/**
* 拿用户的可见性配置(通道 3
* 无配置返回 null不抛异常
*/
@GetMapping(PREFIX + "/get-config")
@Operation(summary = "拿用户的可见性配置;无配置返回 null")
CommonResult<UserVisibilityConfigRespDTO> getConfig(@RequestParam("userId") Long userId);
}

View File

@@ -4,6 +4,7 @@ import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.Collections;
import java.util.List;
import java.util.Set;
@@ -20,4 +21,11 @@ public class ObjectRolePermissionRespDTO {
@ArraySchema(schema = @Schema(description = "基于同一批有效菜单资源归一化提取出的权限标识集合,供对象权限校验直接消费", example = "project:product:query"))
private Set<String> permissions;
/**
* 非主角色的中文名列表(多角色场景)。单角色或无角色时为空数组。
* 前端展示"创建者"等次要角色标签时读这个字段;权限判断仍按 currentRole.code。
*/
@ArraySchema(schema = @Schema(description = "非主角色的中文名列表,多角色场景使用", example = "创建者"))
private List<String> additionalRoleNames = Collections.emptyList();
}

View File

@@ -0,0 +1,27 @@
package com.njcn.rdms.module.system.api.permission.dto;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.Set;
/**
* 用户可见性配置(通道 3跨模块响应 DTO。
*
* directions 类型已由 API 端把 directionId → directionCode 转换好;业务侧不用再 join system_dept。
* projects 类型字段保留,当前业务不消费。
*/
@Schema(description = "RPC 服务 - 用户可见性配置 Response DTO")
@Data
public class UserVisibilityConfigRespDTO {
@Schema(description = "\"all\" / \"directions\" / \"projects\"", example = "all")
private String type;
@ArraySchema(schema = @Schema(description = "type=directions 时为方向 code 集合API 端已转换)", example = "direction_code_1"))
private Set<String> directionCodes;
@ArraySchema(schema = @Schema(description = "type=projects 时为项目 id 集合(保留位,当前业务不消费)", example = "1"))
private Set<Long> projectIds;
}

View File

@@ -152,4 +152,8 @@ public interface ErrorCodeConstants {
ErrorCode ORG_LEADER_EFFECTIVE_RANGE_INVALID = new ErrorCode(1_002_004_102, "负责人生效时间区间不合法");
ErrorCode ORG_LEADER_RELATION_OVERLAP = new ErrorCode(1_002_004_103, "同一组织下该用户的负责人时间区间存在重叠");
// ========== 用户可见性配置 1-002-003-200 ==========
ErrorCode USER_VISIBILITY_CONFIG_TYPE_FIELD_MISMATCH = new ErrorCode(1_002_003_200,
"可见性类型与字段不匹配type={}, 详情:{}");
}

View File

@@ -0,0 +1,40 @@
package com.njcn.rdms.module.system.enums.permission;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 内置对象角色清单。
*
* 业务代码按 code 查 system_role 拿 role_id创建者自动落地、关心人、隐式 observer 兜底等都用);
* 启动时按本枚举校验 system_role 必须存在;这些 code 同时已加入 {@link RoleCodeEnum#isBuiltIn(String)} 锁住改 code / 删除。
*
* 不含 product_manager / project_manager / visitor —— 已分别定义在
* ProductObjectConstants / ProjectObjectConstants避免重复维护 code 字面量。
*
* code / name 直接引用 {@link RoleCodeEnum},两处只有一份字符串。
*/
@Getter
@AllArgsConstructor
public enum ObjectRoleConstants {
PRODUCT_CREATOR(RoleCodeEnum.PRODUCT_CREATOR, "product"),
PRODUCT_WATCHER(RoleCodeEnum.PRODUCT_WATCHER, "product"),
IMPLICIT_OBSERVER_PRODUCT(RoleCodeEnum.IMPLICIT_OBSERVER_PRODUCT, "product"),
PROJECT_CREATOR(RoleCodeEnum.PROJECT_CREATOR, "project"),
PROJECT_WATCHER(RoleCodeEnum.PROJECT_WATCHER, "project"),
IMPLICIT_OBSERVER_PROJECT(RoleCodeEnum.IMPLICIT_OBSERVER_PROJECT, "project");
private final RoleCodeEnum roleCode;
private final String objectType;
public String getCode() {
return roleCode.getCode();
}
public String getName() {
return roleCode.getName();
}
}

View File

@@ -9,7 +9,18 @@ import lombok.Getter;
public enum RoleCodeEnum {
SUPER_ADMIN("super_admin", "超级管理员"),
CRM_ADMIN("crm_admin", "CRM 管理员");
CRM_ADMIN("crm_admin", "CRM 管理员"),
// 对象域内置角色:被业务代码硬编码引用(按 code 查 system_role改 code 或删除会让对应业务功能炸
PRODUCT_MANAGER("product_manager", "产品经理"),
PRODUCT_CREATOR("product_creator", "产品创建者"),
PRODUCT_WATCHER("product_watcher", "产品关心人"),
IMPLICIT_OBSERVER_PRODUCT("implicit_observer_product", "产品隐式观察者"),
PROJECT_MANAGER("project_manager", "项目经理"),
PROJECT_CREATOR("project_creator", "项目创建者"),
PROJECT_WATCHER("project_watcher", "项目关心人"),
IMPLICIT_OBSERVER_PROJECT("implicit_observer_project", "项目隐式观察者"),
VISITOR("visitor", "游客");
private final String code;
private final String name;
@@ -19,7 +30,13 @@ public enum RoleCodeEnum {
}
public static boolean isBuiltIn(String code) {
return ObjectUtils.equalsAny(code, SUPER_ADMIN.getCode(), CRM_ADMIN.getCode());
return ObjectUtils.equalsAny(code,
SUPER_ADMIN.getCode(), CRM_ADMIN.getCode(),
PRODUCT_MANAGER.getCode(), PRODUCT_CREATOR.getCode(),
PRODUCT_WATCHER.getCode(), IMPLICIT_OBSERVER_PRODUCT.getCode(),
PROJECT_MANAGER.getCode(), PROJECT_CREATOR.getCode(),
PROJECT_WATCHER.getCode(), IMPLICIT_OBSERVER_PROJECT.getCode(),
VISITOR.getCode());
}
}