feat(system): 超管绕过 user_project + 项目成员分页/增量 API
后端为配合前端"项目管理成员"从 Modal 改 Drawer 重构接口: - ProjectServiceImpl.getAuthorizedProjectIds 新增超管分支: 若 hasAnySuperAdmin(userRoleIds) 成立,直接返回本租户全部项目 ID 连带影响 getAuthorizedEnabledProjects / getDefaultProjectId / ProjectSecurityWebFilter.authorizedProjectIds.contains 全部自动生效 - 新增 UserProjectService 三个方法: * getProjectUserPage(reqVO) 分页返回成员 AdminUserDO,过滤超管 * addProjectUsers(projectId, userIds) 增量添加,已在的用户跳过 * removeProjectUser(projectId, userId) 单删,带超管/自踢守卫 - 新增 Controller 三个端点: * GET /system/user-project/project-user-page * POST /system/user-project/add-project-users * DELETE /system/user-project/remove-project-user - 新增 VO:UserProjectPageReqVO / UserProjectAddProjectUsersReqVO - 权限点沿用 system:project:assign-user Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,14 @@
|
||||
package com.viewsh.module.system.controller.admin.project;
|
||||
|
||||
import com.viewsh.framework.common.pojo.CommonResult;
|
||||
import com.viewsh.framework.common.pojo.PageResult;
|
||||
import com.viewsh.framework.common.util.object.BeanUtils;
|
||||
import com.viewsh.module.system.controller.admin.project.vo.UserProjectAddProjectUsersReqVO;
|
||||
import com.viewsh.module.system.controller.admin.project.vo.UserProjectAssignProjectUsersReqVO;
|
||||
import com.viewsh.module.system.controller.admin.project.vo.UserProjectAssignUserProjectsReqVO;
|
||||
import com.viewsh.module.system.controller.admin.project.vo.UserProjectPageReqVO;
|
||||
import com.viewsh.module.system.controller.admin.user.vo.user.UserRespVO;
|
||||
import com.viewsh.module.system.dal.dataobject.user.AdminUserDO;
|
||||
import com.viewsh.module.system.service.project.UserProjectService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
@@ -10,6 +16,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
@@ -70,4 +77,32 @@ public class UserProjectController {
|
||||
return success(userProjectService.getUserIdsByProjectId(projectId));
|
||||
}
|
||||
|
||||
@GetMapping("/project-user-page")
|
||||
@Operation(summary = "分页查询项目成员", description = "自动过滤超级管理员;支持按 username/nickname/mobile 模糊搜索")
|
||||
@PreAuthorize("@ss.hasPermission('system:project:assign-user')")
|
||||
public CommonResult<PageResult<UserRespVO>> getProjectUserPage(@Valid UserProjectPageReqVO reqVO) {
|
||||
PageResult<AdminUserDO> page = userProjectService.getProjectUserPage(reqVO);
|
||||
return success(BeanUtils.toBean(page, UserRespVO.class));
|
||||
}
|
||||
|
||||
@PostMapping("/add-project-users")
|
||||
@Operation(summary = "增量给项目添加成员",
|
||||
description = "已经是成员的用户跳过,只插入新成员;不影响已有绑定")
|
||||
@PreAuthorize("@ss.hasPermission('system:project:assign-user')")
|
||||
public CommonResult<Boolean> addProjectUsers(@Valid @RequestBody UserProjectAddProjectUsersReqVO reqVO) {
|
||||
userProjectService.addProjectUsers(reqVO.getProjectId(), reqVO.getUserIds());
|
||||
return success(true);
|
||||
}
|
||||
|
||||
@DeleteMapping("/remove-project-user")
|
||||
@Operation(summary = "从项目中移除单个成员")
|
||||
@Parameter(name = "projectId", description = "项目编号", required = true, example = "101")
|
||||
@Parameter(name = "userId", description = "用户编号", required = true, example = "11")
|
||||
@PreAuthorize("@ss.hasPermission('system:project:assign-user')")
|
||||
public CommonResult<Boolean> removeProjectUser(@RequestParam("projectId") Long projectId,
|
||||
@RequestParam("userId") Long userId) {
|
||||
userProjectService.removeProjectUser(projectId, userId);
|
||||
return success(true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.viewsh.module.system.controller.admin.project.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 管理后台 - 增量给项目添加成员 Req VO
|
||||
*
|
||||
* @author lzh
|
||||
*/
|
||||
@Schema(description = "管理后台 - 增量给项目添加成员 Req VO")
|
||||
@Data
|
||||
public class UserProjectAddProjectUsersReqVO {
|
||||
|
||||
@Schema(description = "项目编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "101")
|
||||
@NotNull(message = "项目编号不能为空")
|
||||
private Long projectId;
|
||||
|
||||
@Schema(description = "要新增的用户编号集合", requiredMode = Schema.RequiredMode.REQUIRED, example = "11,12,13")
|
||||
@NotEmpty(message = "用户编号集合不能为空")
|
||||
private Set<Long> userIds;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.viewsh.module.system.controller.admin.project.vo;
|
||||
|
||||
import com.viewsh.framework.common.pojo.PageParam;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* 管理后台 - 项目成员分页查询 Req VO
|
||||
*
|
||||
* @author lzh
|
||||
*/
|
||||
@Schema(description = "管理后台 - 项目成员分页查询 Req VO")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
public class UserProjectPageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "项目编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "101")
|
||||
@NotNull(message = "项目编号不能为空")
|
||||
private Long projectId;
|
||||
|
||||
@Schema(description = "关键字(按 username / nickname / mobile 模糊)", example = "张三")
|
||||
private String keyword;
|
||||
|
||||
}
|
||||
@@ -12,6 +12,8 @@ import com.viewsh.module.system.dal.dataobject.project.UserProjectDO;
|
||||
import com.viewsh.module.system.dal.dataobject.user.AdminUserDO;
|
||||
import com.viewsh.module.system.dal.mysql.project.ProjectMapper;
|
||||
import com.viewsh.module.system.dal.mysql.project.UserProjectMapper;
|
||||
import com.viewsh.module.system.service.permission.PermissionService;
|
||||
import com.viewsh.module.system.service.permission.RoleService;
|
||||
import com.viewsh.module.system.service.user.AdminUserService;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
@@ -21,6 +23,7 @@ import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.viewsh.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static com.viewsh.module.system.enums.ErrorCodeConstants.*;
|
||||
@@ -44,6 +47,14 @@ public class ProjectServiceImpl implements ProjectService {
|
||||
@Lazy // 延迟,避免循环依赖报错
|
||||
private AdminUserService userService;
|
||||
|
||||
@Resource
|
||||
@Lazy
|
||||
private PermissionService permissionService;
|
||||
|
||||
@Resource
|
||||
@Lazy
|
||||
private RoleService roleService;
|
||||
|
||||
@Override
|
||||
public Long createProject(ProjectSaveReqVO createReqVO) {
|
||||
// 校验项目名称是否重复
|
||||
@@ -148,10 +159,30 @@ public class ProjectServiceImpl implements ProjectService {
|
||||
|
||||
@Override
|
||||
public List<Long> getAuthorizedProjectIds(Long userId) {
|
||||
// 超管绕过 user_project 绑定校验:直接返回当前租户下所有项目 ID
|
||||
// 与 TenantSecurityWebFilter 对超管的处理语义一致
|
||||
if (isSuperAdmin(userId)) {
|
||||
return CollectionUtils.convertList(projectMapper.selectList(), ProjectDO::getId);
|
||||
}
|
||||
List<UserProjectDO> userProjects = userProjectMapper.selectListByUserId(userId);
|
||||
return CollectionUtils.convertList(userProjects, UserProjectDO::getProjectId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断用户是否为超级管理员
|
||||
* 查角色列表 → hasAnySuperAdmin 判别
|
||||
*/
|
||||
private boolean isSuperAdmin(Long userId) {
|
||||
if (userId == null) {
|
||||
return false;
|
||||
}
|
||||
Set<Long> roleIds = permissionService.getUserRoleIdListByUserId(userId);
|
||||
if (CollUtil.isEmpty(roleIds)) {
|
||||
return false;
|
||||
}
|
||||
return roleService.hasAnySuperAdmin(roleIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProjectDO> getAuthorizedEnabledProjects(Long userId) {
|
||||
List<Long> authorizedIds = getAuthorizedProjectIds(userId);
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
package com.viewsh.module.system.service.project;
|
||||
|
||||
import com.viewsh.framework.common.pojo.PageResult;
|
||||
import com.viewsh.module.system.controller.admin.project.vo.UserProjectPageReqVO;
|
||||
import com.viewsh.module.system.dal.dataobject.user.AdminUserDO;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@@ -59,4 +63,29 @@ public interface UserProjectService {
|
||||
*/
|
||||
void deleteByUserId(Long userId);
|
||||
|
||||
/**
|
||||
* 分页查询项目的成员列表,自动过滤超级管理员
|
||||
* 超管通过角色天然拥有所有项目,不应该出现在成员管理界面
|
||||
*
|
||||
* @param reqVO 分页查询参数
|
||||
* @return 用户分页结果(返回 AdminUserDO 便于前端渲染用户名/昵称/部门)
|
||||
*/
|
||||
PageResult<AdminUserDO> getProjectUserPage(UserProjectPageReqVO reqVO);
|
||||
|
||||
/**
|
||||
* 增量把一组用户加入到项目(已在的用户跳过,不影响现有绑定)
|
||||
*
|
||||
* @param projectId 项目编号
|
||||
* @param userIds 要新增的用户编号集合
|
||||
*/
|
||||
void addProjectUsers(Long projectId, Set<Long> userIds);
|
||||
|
||||
/**
|
||||
* 从项目中移除单个成员(带超管/自踢守卫)
|
||||
*
|
||||
* @param projectId 项目编号
|
||||
* @param userId 用户编号
|
||||
*/
|
||||
void removeProjectUser(Long projectId, Long userId);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
package com.viewsh.module.system.service.project;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.viewsh.framework.common.pojo.PageResult;
|
||||
import com.viewsh.framework.common.util.collection.CollectionUtils;
|
||||
import com.viewsh.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import com.viewsh.framework.security.core.util.SecurityFrameworkUtils;
|
||||
import com.viewsh.framework.tenant.core.context.ProjectContextHolder;
|
||||
import com.viewsh.module.system.controller.admin.project.vo.UserProjectPageReqVO;
|
||||
import com.viewsh.module.system.dal.dataobject.project.UserProjectDO;
|
||||
import com.viewsh.module.system.dal.dataobject.user.AdminUserDO;
|
||||
import com.viewsh.module.system.dal.mysql.project.UserProjectMapper;
|
||||
import com.viewsh.module.system.dal.mysql.user.AdminUserMapper;
|
||||
import com.viewsh.module.system.service.permission.PermissionService;
|
||||
import com.viewsh.module.system.service.permission.RoleService;
|
||||
import jakarta.annotation.Resource;
|
||||
@@ -15,6 +21,7 @@ import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -45,6 +52,9 @@ public class UserProjectServiceImpl implements UserProjectService {
|
||||
@Lazy
|
||||
private RoleService roleService;
|
||||
|
||||
@Resource
|
||||
private AdminUserMapper userMapper;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void assignUserProjects(Long userId, Set<Long> projectIds) {
|
||||
@@ -170,4 +180,94 @@ public class UserProjectServiceImpl implements UserProjectService {
|
||||
.eq(UserProjectDO::getUserId, userId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<AdminUserDO> getProjectUserPage(UserProjectPageReqVO reqVO) {
|
||||
// 1. 查项目下所有 userIds
|
||||
Set<Long> memberIds = convertSet(
|
||||
userProjectMapper.selectListByProjectId(reqVO.getProjectId()),
|
||||
UserProjectDO::getUserId);
|
||||
if (CollUtil.isEmpty(memberIds)) {
|
||||
return PageResult.empty();
|
||||
}
|
||||
|
||||
// 2. 过滤超管(超管靠角色天然拥有所有项目,不应出现在成员列表)
|
||||
List<Long> filteredIds = new ArrayList<>(memberIds.size());
|
||||
for (Long uid : memberIds) {
|
||||
if (!isSuperAdmin(uid)) {
|
||||
filteredIds.add(uid);
|
||||
}
|
||||
}
|
||||
if (CollUtil.isEmpty(filteredIds)) {
|
||||
return PageResult.empty();
|
||||
}
|
||||
|
||||
// 3. 按 ids + keyword 分页查 AdminUserDO
|
||||
LambdaQueryWrapperX<AdminUserDO> wrapper = new LambdaQueryWrapperX<AdminUserDO>()
|
||||
.in(AdminUserDO::getId, filteredIds);
|
||||
if (StrUtil.isNotBlank(reqVO.getKeyword())) {
|
||||
String kw = reqVO.getKeyword();
|
||||
wrapper.and(q -> q.like(AdminUserDO::getUsername, kw)
|
||||
.or().like(AdminUserDO::getNickname, kw)
|
||||
.or().like(AdminUserDO::getMobile, kw));
|
||||
}
|
||||
wrapper.orderByDesc(AdminUserDO::getId);
|
||||
return userMapper.selectPage(reqVO, wrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void addProjectUsers(Long projectId, Set<Long> userIds) {
|
||||
Set<Long> target = CollUtil.emptyIfNull(userIds);
|
||||
if (target.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
// 查当前已绑定,避免重复插入
|
||||
Set<Long> existing = convertSet(
|
||||
userProjectMapper.selectListByProjectId(projectId),
|
||||
UserProjectDO::getUserId);
|
||||
Collection<Long> toInsert = CollUtil.subtract(target, existing);
|
||||
if (CollUtil.isEmpty(toInsert)) {
|
||||
return;
|
||||
}
|
||||
userProjectMapper.insertBatch(CollectionUtils.convertList(toInsert, uid -> {
|
||||
UserProjectDO entity = new UserProjectDO();
|
||||
entity.setUserId(uid);
|
||||
entity.setProjectId(projectId);
|
||||
return entity;
|
||||
}));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void removeProjectUser(Long projectId, Long userId) {
|
||||
// 超管守卫:不让把超管从项目里踢掉(本身超管就不该在 user_project 里;即使被历史数据污染也保护)
|
||||
if (isSuperAdmin(userId)) {
|
||||
throw exception(USER_PROJECT_CANNOT_REMOVE_SUPER_ADMIN);
|
||||
}
|
||||
// 自踢守卫:当前登录人 && 当前项目 == 目标项目 → 拒绝
|
||||
Long currentLoginUserId = SecurityFrameworkUtils.getLoginUserId();
|
||||
Long currentProjectId = ProjectContextHolder.getProjectId();
|
||||
if (currentLoginUserId != null && currentLoginUserId.equals(userId)
|
||||
&& currentProjectId != null && currentProjectId.equals(projectId)) {
|
||||
throw exception(USER_PROJECT_CANNOT_REMOVE_SELF_CURRENT);
|
||||
}
|
||||
userProjectMapper.delete(Wrappers.<UserProjectDO>lambdaQuery()
|
||||
.eq(UserProjectDO::getProjectId, projectId)
|
||||
.eq(UserProjectDO::getUserId, userId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断用户是否超管
|
||||
*/
|
||||
private boolean isSuperAdmin(Long userId) {
|
||||
if (userId == null) {
|
||||
return false;
|
||||
}
|
||||
Set<Long> roleIds = permissionService.getUserRoleIdListByUserId(userId);
|
||||
if (CollUtil.isEmpty(roleIds)) {
|
||||
return false;
|
||||
}
|
||||
return roleService.hasAnySuperAdmin(roleIds);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user