fix user group permission scope

This commit is contained in:
baiyanyun
2026-06-02 19:51:45 +08:00
parent 873cd6ef53
commit f69fb46c59
14 changed files with 174 additions and 67 deletions

View File

@@ -216,13 +216,6 @@ public class AuthInterceptor implements HandlerInterceptor {
requestContext.setLangMap(userDto.getLangMap()); requestContext.setLangMap(userDto.getLangMap());
requestContext.setLang(userDto.getLang()); requestContext.setLang(userDto.getLang());
log.debug("JWT token OK, userId: {} ", userDto.getId()); log.debug("JWT token OK, userId: {} ", userDto.getId());
if (originalRequestUri.startsWith("/api/system/")) {
//判断是不是管理员
if (userDto.getRole() != User.Role.Admin) {
throw BizException.of(ErrorCodeEnum.PERMISSION_DENIED, BizExceptionCodeEnum.permissionDenied);
}
}
String header = request.getHeader("X-Client-Type"); String header = request.getHeader("X-Client-Type");
if (header != null || token.startsWith("ticket")) { if (header != null || token.startsWith("ticket")) {
authService.renewToken(token); authService.renewToken(token);
@@ -358,4 +351,4 @@ public class AuthInterceptor implements HandlerInterceptor {
throw BizException.of(HttpStatusEnum.UNAUTHORIZED, ErrorCodeEnum.UNAUTHORIZED, throw BizException.of(HttpStatusEnum.UNAUTHORIZED, ErrorCodeEnum.UNAUTHORIZED,
BizExceptionCodeEnum.systemUnauthorizedOrSessionExpired); BizExceptionCodeEnum.systemUnauthorizedOrSessionExpired);
} }
} }

View File

@@ -5,6 +5,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
import java.io.Serializable; import java.io.Serializable;
import java.util.List;
@Data @Data
public class UserQueryDto implements Serializable { public class UserQueryDto implements Serializable {
@@ -29,4 +30,7 @@ public class UserQueryDto implements Serializable {
@Schema(description = "角色") @Schema(description = "角色")
private User.Role role; private User.Role role;
@Schema(description = "用户ID范围后端数据权限过滤使用")
private List<Long> userIds;
} }

View File

@@ -3,6 +3,7 @@ package com.xspaceagi.system.application.dto.permission;
import java.io.Serializable; import java.io.Serializable;
import java.util.Date; import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
@@ -28,6 +29,7 @@ public class SysGroupAddDto implements Serializable {
private Integer authEnabled; private Integer authEnabled;
@Schema(description = "授权到期时间,空表示不过期") @Schema(description = "授权到期时间,空表示不过期")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date expireTime; private Date expireTime;
@Schema(description = "来源,1:系统内置 2:用户自定义", hidden = true) @Schema(description = "来源,1:系统内置 2:用户自定义", hidden = true)
@@ -38,4 +40,4 @@ public class SysGroupAddDto implements Serializable {
@Schema(description = "排序") @Schema(description = "排序")
private Integer sortIndex; private Integer sortIndex;
} }

View File

@@ -1,5 +1,6 @@
package com.xspaceagi.system.application.dto.permission; package com.xspaceagi.system.application.dto.permission;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
@@ -31,6 +32,7 @@ public class SysGroupUpdateDto implements Serializable {
private Integer authEnabled; private Integer authEnabled;
@Schema(description = "授权到期时间,空表示不过期") @Schema(description = "授权到期时间,空表示不过期")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date expireTime; private Date expireTime;
@Schema(description = "来源,1:系统内置 2:用户自定义", hidden = true) @Schema(description = "来源,1:系统内置 2:用户自定义", hidden = true)
@@ -41,4 +43,4 @@ public class SysGroupUpdateDto implements Serializable {
@Schema(description = "排序") @Schema(description = "排序")
private Integer sortIndex; private Integer sortIndex;
} }

View File

@@ -247,6 +247,12 @@ public class UserApplicationServiceImpl implements UserApplicationService {
UserQueryDto userQueryDto = pageQueryVo.getQueryFilter(); UserQueryDto userQueryDto = pageQueryVo.getQueryFilter();
if (userQueryDto != null) { if (userQueryDto != null) {
BeanUtils.copyProperties(userQueryDto, user); BeanUtils.copyProperties(userQueryDto, user);
if (userQueryDto.getUserIds() != null) {
if (userQueryDto.getUserIds().isEmpty()) {
return new Page<>(pageQueryVo.getPageNo(), pageQueryVo.getPageSize(), 0);
}
queryWrapper.in(User::getId, userQueryDto.getUserIds());
}
} }
if ("".equals(user.getUserName())) { if ("".equals(user.getUserName())) {
user.setUserName(null); user.setUserName(null);

View File

@@ -5,15 +5,20 @@ import com.xspaceagi.system.application.dto.SendNotifyMessageDto;
import com.xspaceagi.system.application.dto.UserDto; import com.xspaceagi.system.application.dto.UserDto;
import com.xspaceagi.system.application.dto.UserQueryDto; import com.xspaceagi.system.application.dto.UserQueryDto;
import com.xspaceagi.system.application.service.NotifyMessageApplicationService; import com.xspaceagi.system.application.service.NotifyMessageApplicationService;
import com.xspaceagi.system.application.service.SysGroupApplicationService;
import com.xspaceagi.system.application.service.UserApplicationService; import com.xspaceagi.system.application.service.UserApplicationService;
import com.xspaceagi.system.infra.dao.entity.SysGroup;
import com.xspaceagi.system.infra.dao.entity.NotifyMessage; import com.xspaceagi.system.infra.dao.entity.NotifyMessage;
import com.xspaceagi.system.infra.dao.entity.User; import com.xspaceagi.system.infra.dao.entity.User;
import com.xspaceagi.system.infra.dao.mapper.UserMapper; import com.xspaceagi.system.infra.dao.mapper.UserMapper;
import com.xspaceagi.system.infra.dao.service.UserService;
import com.xspaceagi.system.spec.annotation.RequireResource; import com.xspaceagi.system.spec.annotation.RequireResource;
import com.xspaceagi.system.spec.common.RequestContext; import com.xspaceagi.system.spec.common.RequestContext;
import com.xspaceagi.system.spec.common.UserContext;
import com.xspaceagi.system.spec.dto.PageQueryVo; import com.xspaceagi.system.spec.dto.PageQueryVo;
import com.xspaceagi.system.spec.dto.ReqResult; import com.xspaceagi.system.spec.dto.ReqResult;
import com.xspaceagi.system.spec.enums.ErrorCodeEnum; import com.xspaceagi.system.spec.enums.ErrorCodeEnum;
import com.xspaceagi.system.spec.enums.GroupEnum;
import com.xspaceagi.system.spec.exception.BizException; import com.xspaceagi.system.spec.exception.BizException;
import com.xspaceagi.system.spec.exception.BizExceptionCodeEnum; import com.xspaceagi.system.spec.exception.BizExceptionCodeEnum;
import com.xspaceagi.system.web.dto.UserAddDto; import com.xspaceagi.system.web.dto.UserAddDto;
@@ -28,6 +33,7 @@ import org.springframework.beans.BeanUtils;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static com.xspaceagi.system.spec.enums.ResourceEnum.*; import static com.xspaceagi.system.spec.enums.ResourceEnum.*;
@@ -40,17 +46,32 @@ public class UserManageController {
@Resource @Resource
private UserApplicationService userApplicationService; private UserApplicationService userApplicationService;
@Resource
private SysGroupApplicationService sysGroupApplicationService;
@Resource @Resource
private NotifyMessageApplicationService notifyMessageApplicationService; private NotifyMessageApplicationService notifyMessageApplicationService;
@Resource @Resource
private UserMapper userMapper; private UserMapper userMapper;
@Resource
private UserService userService;
@RequireResource(USER_MANAGE_QUERY) @RequireResource(USER_MANAGE_QUERY)
@Operation(summary = "查询用户列表") @Operation(summary = "查询用户列表")
@RequestMapping(path = "/list", method = RequestMethod.POST) @RequestMapping(path = "/list", method = RequestMethod.POST)
public ReqResult<IPage<UserDto>> listQuery(@RequestBody PageQueryVo<UserQueryDto> pageQueryVo) { public ReqResult<IPage<UserDto>> listQuery(@RequestBody PageQueryVo<UserQueryDto> pageQueryVo) {
checkAdmin(); if (pageQueryVo == null) {
pageQueryVo = new PageQueryVo<>();
}
if (pageQueryVo.getPageNo() == null) {
pageQueryVo.setPageNo(1L);
}
if (pageQueryVo.getPageSize() == null) {
pageQueryVo.setPageSize(10L);
}
applyUserManageScope(pageQueryVo);
return ReqResult.success(userApplicationService.listQuery(pageQueryVo)); return ReqResult.success(userApplicationService.listQuery(pageQueryVo));
} }
@@ -58,8 +79,6 @@ public class UserManageController {
@Operation(summary = "获取用户统计") @Operation(summary = "获取用户统计")
@RequestMapping(path = "/stats", method = RequestMethod.GET) @RequestMapping(path = "/stats", method = RequestMethod.GET)
public ReqResult<UserStatsDto> getUserStats() { public ReqResult<UserStatsDto> getUserStats() {
checkAdmin();
Long totalUserCount = userMapper.countTotalUsers(); Long totalUserCount = userMapper.countTotalUsers();
Long todayNewUserCount = userMapper.countTodayNewUsers(); Long todayNewUserCount = userMapper.countTodayNewUsers();
@@ -90,13 +109,17 @@ public class UserManageController {
@Operation(summary = "添加用户") @Operation(summary = "添加用户")
@RequestMapping(path = "/add", method = RequestMethod.POST) @RequestMapping(path = "/add", method = RequestMethod.POST)
public ReqResult<Void> addUser(@RequestBody UserAddDto userAddDto) { public ReqResult<Void> addUser(@RequestBody UserAddDto userAddDto) {
checkAdmin();
UserDto userDto = new UserDto(); UserDto userDto = new UserDto();
BeanUtils.copyProperties(userAddDto, userDto); BeanUtils.copyProperties(userAddDto, userDto);
if (StringUtils.isNotBlank(userAddDto.getPassword())) { if (StringUtils.isNotBlank(userAddDto.getPassword())) {
userDto.setResetPass(1); userDto.setResetPass(1);
} }
userDto.setRole(User.Role.User);
userApplicationService.add(userDto); userApplicationService.add(userDto);
List<Long> groupIds = currentUserGroupIds().stream().toList();
if (CollectionUtils.isNotEmpty(groupIds)) {
groupIds.forEach(groupId -> sysGroupApplicationService.groupAddUser(groupId, userDto.getId(), getCurrentUserContext()));
}
return ReqResult.success(); return ReqResult.success();
} }
@@ -104,7 +127,7 @@ public class UserManageController {
@Operation(summary = "更新用户信息") @Operation(summary = "更新用户信息")
@RequestMapping(path = "/updateById/{id}", method = RequestMethod.POST) @RequestMapping(path = "/updateById/{id}", method = RequestMethod.POST)
public ReqResult<Void> updateUserById(@PathVariable Long id, @RequestBody UserUpdateDto userUpdateDto) { public ReqResult<Void> updateUserById(@PathVariable Long id, @RequestBody UserUpdateDto userUpdateDto) {
checkAdmin(); checkCurrentTenantUser(id);
UserDto userUpdate = new UserDto(); UserDto userUpdate = new UserDto();
userUpdate.setId(id); userUpdate.setId(id);
userUpdate.setAvatar(userUpdateDto.getAvatar()); userUpdate.setAvatar(userUpdateDto.getAvatar());
@@ -122,7 +145,7 @@ public class UserManageController {
@Operation(summary = "禁用用户") @Operation(summary = "禁用用户")
@RequestMapping(path = "/disable/{id}", method = RequestMethod.POST) @RequestMapping(path = "/disable/{id}", method = RequestMethod.POST)
public ReqResult<Void> disableUserById(@PathVariable Long id) { public ReqResult<Void> disableUserById(@PathVariable Long id) {
checkAdmin(); checkCurrentTenantUser(id);
UserDto userUpdate = new UserDto(); UserDto userUpdate = new UserDto();
userUpdate.setId(id); userUpdate.setId(id);
userUpdate.setStatus(User.Status.Disabled); userUpdate.setStatus(User.Status.Disabled);
@@ -134,7 +157,7 @@ public class UserManageController {
@Operation(summary = "启用用户") @Operation(summary = "启用用户")
@RequestMapping(path = "/enable/{id}", method = RequestMethod.POST) @RequestMapping(path = "/enable/{id}", method = RequestMethod.POST)
public ReqResult<Void> enableUserById(@PathVariable Long id) { public ReqResult<Void> enableUserById(@PathVariable Long id) {
checkAdmin(); checkCurrentTenantUser(id);
UserDto userUpdate = new UserDto(); UserDto userUpdate = new UserDto();
userUpdate.setId(id); userUpdate.setId(id);
userUpdate.setStatus(User.Status.Enabled); userUpdate.setStatus(User.Status.Enabled);
@@ -146,7 +169,7 @@ public class UserManageController {
@Operation(summary = "删除用户") @Operation(summary = "删除用户")
@RequestMapping(path = "/delete/{id}", method = RequestMethod.POST) @RequestMapping(path = "/delete/{id}", method = RequestMethod.POST)
public ReqResult<Void> deleteUserById(@PathVariable Long id) { public ReqResult<Void> deleteUserById(@PathVariable Long id) {
checkAdmin(); checkCurrentTenantUser(id);
userApplicationService.logicDelete(id); userApplicationService.logicDelete(id);
return ReqResult.success(); return ReqResult.success();
} }
@@ -165,12 +188,53 @@ public class UserManageController {
return ReqResult.success(); return ReqResult.success();
} }
/** private void checkCurrentTenantUser(Long userId) {
* 当前简单的在商家范围支持普通用户和管理员两种角色;后续再迭代完整的权限角色 if (userId == null || userService.getById(userId) == null || !visibleUserIds().contains(userId)) {
*/
private void checkAdmin() {
if (((UserDto) RequestContext.get().getUser()).getRole() != User.Role.Admin) {
throw BizException.of(ErrorCodeEnum.PERMISSION_DENIED, BizExceptionCodeEnum.permissionDenied); throw BizException.of(ErrorCodeEnum.PERMISSION_DENIED, BizExceptionCodeEnum.permissionDenied);
} }
} }
}
private void applyUserManageScope(PageQueryVo<UserQueryDto> pageQueryVo) {
if (pageQueryVo.getQueryFilter() == null) {
pageQueryVo.setQueryFilter(new UserQueryDto());
}
pageQueryVo.getQueryFilter().setUserIds(visibleUserIds().stream().toList());
}
private Set<Long> visibleUserIds() {
return currentUserGroupIds().stream()
.flatMap(groupId -> sysGroupApplicationService.getUserListByGroupId(groupId).stream())
.map(User::getId)
.collect(Collectors.toSet());
}
private Set<Long> currentUserGroupIds() {
return sysGroupApplicationService.getGroupListByUserId(getCurrentUserDto().getId()).stream()
.filter(group -> !GroupEnum.DEFAULT_GROUP.getCode().equals(group.getCode()))
.map(SysGroup::getId)
.collect(Collectors.toSet());
}
private UserDto getCurrentUserDto() {
UserDto userDto = (UserDto) RequestContext.get().getUser();
if (userDto == null) {
throw BizException.of(ErrorCodeEnum.UNAUTHORIZED, BizExceptionCodeEnum.systemUserNotLoggedInWeb);
}
return userDto;
}
private UserContext getCurrentUserContext() {
UserDto userDto = getCurrentUserDto();
return UserContext.builder()
.userId(userDto.getId())
.userName(userDto.getUserName())
.nickName(userDto.getNickName())
.avatar(userDto.getAvatar())
.email(userDto.getEmail())
.phone(userDto.getPhone())
.status(userDto.getStatus() == User.Status.Enabled ? 1 : -1)
.tenantId(userDto.getTenantId())
.roleType(userDto.getRole() == User.Role.Admin ? 1 : 2)
.build();
}
}

View File

@@ -344,8 +344,8 @@ public class SysGroupController extends BaseController {
return ReqResult.error("参数不能为空"); return ReqResult.error("参数不能为空");
} }
checkGroupManageScope(dto.getGroupId()); checkGroupManageScope(dto.getGroupId());
// 用户组不允许绑定 生态市场/系统管理 // 用户组不允许绑定生态市场系统管理整棵子树。
checkForbiddenMenuCodes(dto.getMenuTree()); checkForbiddenMenuCodes(dto.getMenuTree(), false);
GroupBindMenuModel model = GroupBindMenuModelConverter.convertToModel(dto); GroupBindMenuModel model = GroupBindMenuModelConverter.convertToModel(dto);
sysGroupApplicationService.bindMenu(model, getUser()); sysGroupApplicationService.bindMenu(model, getUser());
@@ -370,29 +370,31 @@ public class SysGroupController extends BaseController {
return ReqResult.success(menuDtoList); return ReqResult.success(menuDtoList);
} }
private void checkForbiddenMenuCodes(List<MenuNodeDto> nodes) { private void checkForbiddenMenuCodes(List<MenuNodeDto> nodes, boolean underForbiddenMenu) {
if (CollectionUtils.isEmpty(nodes)) { if (CollectionUtils.isEmpty(nodes)) {
return; return;
} }
for (MenuNodeDto node : nodes) { for (MenuNodeDto node : nodes) {
boolean currentForbidden = underForbiddenMenu;
if (StringUtils.isNotBlank(node.getCode())) { if (StringUtils.isNotBlank(node.getCode())) {
if (node.getCode().equals(MenuEnum.ECO_MARKET.getCode()) && node.getMenuBindType() > 0) { if (node.getCode().equals(MenuEnum.ECO_MARKET.getCode()) && node.getMenuBindType() > 0) {
throw BizException.of(ErrorCodeEnum.INVALID_PARAM, BizExceptionCodeEnum.systemGroupCannotBindForbiddenMenu, throw BizException.of(ErrorCodeEnum.INVALID_PARAM, BizExceptionCodeEnum.systemGroupCannotBindForbiddenMenu,
MenuEnum.ECO_MARKET.getName()); MenuEnum.ECO_MARKET.getName());
} }
if (node.getCode().equals(MenuEnum.SYSTEM_MANAGE.getCode()) && node.getMenuBindType() > 0) { currentForbidden = currentForbidden || node.getCode().equals(MenuEnum.SYSTEM_MANAGE.getCode());
if (currentForbidden && node.getMenuBindType() > 0) {
throw BizException.of(ErrorCodeEnum.INVALID_PARAM, BizExceptionCodeEnum.systemGroupCannotBindForbiddenMenu, throw BizException.of(ErrorCodeEnum.INVALID_PARAM, BizExceptionCodeEnum.systemGroupCannotBindForbiddenMenu,
MenuEnum.SYSTEM_MANAGE.getName()); MenuEnum.SYSTEM_MANAGE.getName());
} }
} }
if (CollectionUtils.isNotEmpty(node.getChildren())) { if (CollectionUtils.isNotEmpty(node.getChildren())) {
checkForbiddenMenuCodes(node.getChildren()); checkForbiddenMenuCodes(node.getChildren(), currentForbidden);
} }
} }
} }
private List<SysGroup> filterGroupManageScope(List<SysGroup> groupList) { private List<SysGroup> filterGroupManageScope(List<SysGroup> groupList) {
if (isPlatformAdmin() || CollectionUtils.isEmpty(groupList)) { if (CollectionUtils.isEmpty(groupList)) {
return groupList; return groupList;
} }
Set<Long> allowedGroupIds = currentUserGroupIds(); Set<Long> allowedGroupIds = currentUserGroupIds();
@@ -402,24 +404,18 @@ public class SysGroupController extends BaseController {
} }
private void checkGroupManageScope(Long groupId) { private void checkGroupManageScope(Long groupId) {
if (isPlatformAdmin()) {
return;
}
if (groupId == null || !currentUserGroupIds().contains(groupId)) { if (groupId == null || !currentUserGroupIds().contains(groupId)) {
throw BizException.of(ErrorCodeEnum.PERMISSION_DENIED, BizExceptionCodeEnum.permissionDenied); throw BizException.of(ErrorCodeEnum.PERMISSION_DENIED, BizExceptionCodeEnum.permissionDenied);
} }
} }
private Set<Long> currentUserGroupIds() { private Set<Long> currentUserGroupIds() {
return sysGroupApplicationService.getEffectiveGroupListByUserId(getCurrentUserDto().getId()).stream() return sysGroupApplicationService.getGroupListByUserId(getCurrentUserDto().getId()).stream()
.filter(group -> !GroupEnum.DEFAULT_GROUP.getCode().equals(group.getCode()))
.map(SysGroup::getId) .map(SysGroup::getId)
.collect(Collectors.toSet()); .collect(Collectors.toSet());
} }
private boolean isPlatformAdmin() {
return getCurrentUserDto().getRole() == User.Role.Admin;
}
private UserDto getCurrentUserDto() { private UserDto getCurrentUserDto() {
UserDto userDto = (UserDto) RequestContext.get().getUser(); UserDto userDto = (UserDto) RequestContext.get().getUser();
if (userDto == null) { if (userDto == null) {

View File

@@ -5,6 +5,7 @@ import com.xspaceagi.system.application.dto.permission.*;
import com.xspaceagi.system.application.service.SysDataPermissionApplicationService; import com.xspaceagi.system.application.service.SysDataPermissionApplicationService;
import com.xspaceagi.system.application.service.SysGroupApplicationService; import com.xspaceagi.system.application.service.SysGroupApplicationService;
import com.xspaceagi.system.application.service.SysRoleApplicationService; import com.xspaceagi.system.application.service.SysRoleApplicationService;
import com.xspaceagi.system.application.service.UserApplicationService;
import com.xspaceagi.system.application.service.impl.SysUserPermissionCacheServiceImpl; import com.xspaceagi.system.application.service.impl.SysUserPermissionCacheServiceImpl;
import com.xspaceagi.system.infra.dao.entity.SysGroup; import com.xspaceagi.system.infra.dao.entity.SysGroup;
import com.xspaceagi.system.infra.dao.entity.SysRole; import com.xspaceagi.system.infra.dao.entity.SysRole;
@@ -15,6 +16,7 @@ import com.xspaceagi.system.spec.annotation.SaasAdmin;
import com.xspaceagi.system.spec.common.RequestContext; import com.xspaceagi.system.spec.common.RequestContext;
import com.xspaceagi.system.spec.dto.ReqResult; import com.xspaceagi.system.spec.dto.ReqResult;
import com.xspaceagi.system.spec.enums.ErrorCodeEnum; import com.xspaceagi.system.spec.enums.ErrorCodeEnum;
import com.xspaceagi.system.spec.enums.GroupEnum;
import com.xspaceagi.system.spec.exception.BizException; import com.xspaceagi.system.spec.exception.BizException;
import com.xspaceagi.system.spec.exception.BizExceptionCodeEnum; import com.xspaceagi.system.spec.exception.BizExceptionCodeEnum;
import com.xspaceagi.system.spec.utils.I18nUtil; import com.xspaceagi.system.spec.utils.I18nUtil;
@@ -28,6 +30,7 @@ import org.springframework.beans.BeanUtils;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -47,6 +50,8 @@ public class SysUserController extends BaseController {
private SysDataPermissionApplicationService sysDataPermissionApplicationService; private SysDataPermissionApplicationService sysDataPermissionApplicationService;
@Resource @Resource
private SysUserPermissionCacheServiceImpl sysUserPermissionCacheService; private SysUserPermissionCacheServiceImpl sysUserPermissionCacheService;
@Resource
private UserApplicationService userApplicationService;
@RequireResource(USER_MANAGE_BIND_ROLE) @RequireResource(USER_MANAGE_BIND_ROLE)
@Operation(summary = "查询用户绑定的角色列表") @Operation(summary = "查询用户绑定的角色列表")
@@ -56,6 +61,7 @@ public class SysUserController extends BaseController {
return ReqResult.error("参数不能为空"); return ReqResult.error("参数不能为空");
} }
checkUserManageScope(userId); checkUserManageScope(userId);
checkRoleAuthorizationTarget(userId);
List<SysRole> roleList = sysRoleApplicationService.getRoleListByUserId(userId); List<SysRole> roleList = sysRoleApplicationService.getRoleListByUserId(userId);
if (CollectionUtils.isEmpty(roleList)) { if (CollectionUtils.isEmpty(roleList)) {
@@ -79,6 +85,7 @@ public class SysUserController extends BaseController {
return ReqResult.error("参数不能为空"); return ReqResult.error("参数不能为空");
} }
checkUserManageScope(dto.getUserId()); checkUserManageScope(dto.getUserId());
checkRoleAuthorizationTarget(dto.getUserId());
checkRoleBindScope(dto.getRoleIds()); checkRoleBindScope(dto.getRoleIds());
sysRoleApplicationService.userBindRole(dto.getUserId(), dto.getRoleIds(), getUser()); sysRoleApplicationService.userBindRole(dto.getUserId(), dto.getRoleIds(), getUser());
return ReqResult.success(); return ReqResult.success();
@@ -146,11 +153,13 @@ public class SysUserController extends BaseController {
} }
private void checkUserManageScope(Long userId) { private void checkUserManageScope(Long userId) {
if (isPlatformAdmin()) { UserDto targetUser = userApplicationService.queryById(userId);
return; if (targetUser == null || !Objects.equals(targetUser.getTenantId(), RequestContext.get().getTenantId())) {
throw BizException.of(ErrorCodeEnum.PERMISSION_DENIED, BizExceptionCodeEnum.permissionDenied);
} }
Set<Long> allowedGroupIds = currentUserGroupIds(); Set<Long> allowedGroupIds = currentUserGroupIds();
boolean inScope = sysGroupApplicationService.getEffectiveGroupListByUserId(userId).stream() boolean inScope = sysGroupApplicationService.getGroupListByUserId(userId).stream()
.map(SysGroup::getId) .map(SysGroup::getId)
.anyMatch(allowedGroupIds::contains); .anyMatch(allowedGroupIds::contains);
if (!inScope) { if (!inScope) {
@@ -159,7 +168,7 @@ public class SysUserController extends BaseController {
} }
private void checkGroupBindScope(List<Long> groupIds) { private void checkGroupBindScope(List<Long> groupIds) {
if (isPlatformAdmin() || CollectionUtils.isEmpty(groupIds)) { if (CollectionUtils.isEmpty(groupIds)) {
return; return;
} }
Set<Long> allowedGroupIds = currentUserGroupIds(); Set<Long> allowedGroupIds = currentUserGroupIds();
@@ -170,7 +179,7 @@ public class SysUserController extends BaseController {
} }
private void checkRoleBindScope(List<Long> roleIds) { private void checkRoleBindScope(List<Long> roleIds) {
if (isPlatformAdmin() || CollectionUtils.isEmpty(roleIds)) { if (CollectionUtils.isEmpty(roleIds)) {
return; return;
} }
Set<Long> allowedRoleIds = sysRoleApplicationService.getRoleListByUserId(getCurrentUserDto().getId()).stream() Set<Long> allowedRoleIds = sysRoleApplicationService.getRoleListByUserId(getCurrentUserDto().getId()).stream()
@@ -182,14 +191,18 @@ public class SysUserController extends BaseController {
} }
} }
private Set<Long> currentUserGroupIds() { private void checkRoleAuthorizationTarget(Long userId) {
return sysGroupApplicationService.getEffectiveGroupListByUserId(getCurrentUserDto().getId()).stream() UserDto targetUser = userApplicationService.queryById(userId);
.map(SysGroup::getId) if (targetUser == null || targetUser.getRole() != User.Role.Admin) {
.collect(Collectors.toSet()); throw BizException.of(ErrorCodeEnum.PERMISSION_DENIED, BizExceptionCodeEnum.permissionDenied);
}
} }
private boolean isPlatformAdmin() { private Set<Long> currentUserGroupIds() {
return getCurrentUserDto().getRole() == User.Role.Admin; return sysGroupApplicationService.getGroupListByUserId(getCurrentUserDto().getId()).stream()
.filter(group -> !GroupEnum.DEFAULT_GROUP.getCode().equals(group.getCode()))
.map(SysGroup::getId)
.collect(Collectors.toSet());
} }
private UserDto getCurrentUserDto() { private UserDto getCurrentUserDto() {

View File

@@ -22,6 +22,7 @@ const CustomFormModal: React.FC<PropsWithChildren<CustomFormModalProps>> = ({
onCancel, onCancel,
onConfirm, onConfirm,
okDisabled, okDisabled,
skipFormValidation,
children, children,
}) => { }) => {
return ( return (
@@ -45,6 +46,7 @@ const CustomFormModal: React.FC<PropsWithChildren<CustomFormModalProps>> = ({
form={form} form={form}
okText={okText} okText={okText}
disabled={okDisabled} disabled={okDisabled}
skipFormValidation={skipFormValidation}
/> />
</> </>
} }

View File

@@ -14,18 +14,24 @@ const SubmitButton: React.FC<SubmitButtonProps> = ({
okText, okText,
onConfirm, onConfirm,
disabled, disabled,
skipFormValidation = false,
}) => { }) => {
const [submittable, setSubmittable] = useState<boolean>(false); const [submittable, setSubmittable] = useState<boolean>(skipFormValidation);
// Watch all values // Watch all values
const values = Form.useWatch([], { form, preserve: true }); const values = Form.useWatch([], { form, preserve: true });
useEffect(() => { useEffect(() => {
if (skipFormValidation) {
setSubmittable(true);
return;
}
form form
.validateFields({ validateOnly: true }) .validateFields({ validateOnly: true })
.then(() => setSubmittable(true)) .then(() => setSubmittable(true))
.catch(() => setSubmittable(false)); .catch(() => setSubmittable(false));
}, [form, values]); }, [form, values, skipFormValidation]);
return ( return (
<Button <Button

View File

@@ -46,6 +46,8 @@ const UserAuthModal: React.FC<UserAuthModalProps> = ({
onCancel, onCancel,
}) => { }) => {
const { hasPermission } = useModel('menuModel'); const { hasPermission } = useModel('menuModel');
const canBindRole = hasPermission('user_manage_bind_role');
const canBindGroup = hasPermission('user_manage_bind_group');
// 如果是普通用户,默认显示用户组 tab否则显示角色 tab // 如果是普通用户,默认显示用户组 tab否则显示角色 tab
const [activeTab, setActiveTab] = useState<TabKey>('role'); const [activeTab, setActiveTab] = useState<TabKey>('role');
// 创建一个空的 Form 实例,用于 CustomFormModal不使用表单功能 // 创建一个空的 Form 实例,用于 CustomFormModal不使用表单功能
@@ -114,9 +116,13 @@ const UserAuthModal: React.FC<UserAuthModalProps> = ({
useEffect(() => { useEffect(() => {
if (open) { if (open) {
setActiveTab(role === UserRoleEnum.User ? 'group' : 'role'); setActiveTab(
role === UserRoleEnum.User || !hasPermission('user_manage_bind_role')
? 'group'
: 'role',
);
// 如果不是普通用户,才查询角色列表 // 如果不是普通用户,才查询角色列表
if (role !== UserRoleEnum.User) { if (role !== UserRoleEnum.User && canBindRole) {
// 查询角色列表 // 查询角色列表
runGetRoleList(); runGetRoleList();
// 查询用户绑定的角色列表 // 查询用户绑定的角色列表
@@ -136,7 +142,7 @@ const UserAuthModal: React.FC<UserAuthModalProps> = ({
setGroupLoading(false); setGroupLoading(false);
setRoleLoading(false); setRoleLoading(false);
} }
}, [open, targetId, role]); }, [open, targetId, role, canBindRole, hasPermission]);
// 处理角色选择变化 // 处理角色选择变化
const handleRoleChange = (checkedValues: number[]) => { const handleRoleChange = (checkedValues: number[]) => {
@@ -151,7 +157,7 @@ const UserAuthModal: React.FC<UserAuthModalProps> = ({
// 提交数据 // 提交数据
const handlerSubmit = async () => { const handlerSubmit = async () => {
// 如果不是普通用户,需要同时提交角色和用户组 // 如果不是普通用户,需要同时提交角色和用户组
if (role !== UserRoleEnum.User) { if (role !== UserRoleEnum.User && canBindRole) {
setRoleLoading(true); setRoleLoading(true);
await runBindRole({ await runBindRole({
userId: targetId, userId: targetId,
@@ -161,12 +167,14 @@ const UserAuthModal: React.FC<UserAuthModalProps> = ({
} }
// 用户组总是需要提交 // 用户组总是需要提交
setGroupLoading(true); if (canBindGroup) {
await runBindGroup({ setGroupLoading(true);
userId: targetId, await runBindGroup({
groupIds: selectedGroupIds, userId: targetId,
}); groupIds: selectedGroupIds,
setGroupLoading(false); });
setGroupLoading(false);
}
onCancel(); onCancel();
}; };
@@ -298,6 +306,7 @@ const UserAuthModal: React.FC<UserAuthModalProps> = ({
loading={loading} loading={loading}
onCancel={onCancel} onCancel={onCancel}
onConfirm={handlerSubmit} onConfirm={handlerSubmit}
skipFormValidation
okDisabled={ okDisabled={
activeTab === 'role' activeTab === 'role'
? !hasPermission('user_manage_bind_role') ? !hasPermission('user_manage_bind_role')

View File

@@ -34,6 +34,7 @@ const UserFormModal: React.FC<UserFormModalProps> = ({
form.setFieldsValue(record); form.setFieldsValue(record);
} else { } else {
form.resetFields(); form.resetFields();
form.setFieldsValue({ role: UserRoleEnum.User });
} }
} }
}, [open, isEdit, record, form]); }, [open, isEdit, record, form]);
@@ -50,6 +51,7 @@ const UserFormModal: React.FC<UserFormModalProps> = ({
} else { } else {
await apiAddSystemUser({ await apiAddSystemUser({
...values, ...values,
role: UserRoleEnum.User,
}); });
} }
onSuccess(isEdit); onSuccess(isEdit);
@@ -195,12 +197,14 @@ const UserFormModal: React.FC<UserFormModalProps> = ({
<Form.Item <Form.Item
label={dict('PC.Pages.UserManage.UserFormModal.userType')} label={dict('PC.Pages.UserManage.UserFormModal.userType')}
name="role" name="role"
initialValue={UserRoleEnum.Admin} initialValue={UserRoleEnum.User}
> >
<Radio.Group> <Radio.Group>
<Radio value={UserRoleEnum.Admin}> {isEdit && (
{dict('PC.Pages.UserManage.UserFormModal.admin')} <Radio value={UserRoleEnum.Admin}>
</Radio> {dict('PC.Pages.UserManage.UserFormModal.admin')}
</Radio>
)}
<Radio value={UserRoleEnum.User}> <Radio value={UserRoleEnum.User}>
{dict('PC.Pages.UserManage.UserFormModal.normalUser')} {dict('PC.Pages.UserManage.UserFormModal.normalUser')}
</Radio> </Radio>

View File

@@ -161,8 +161,10 @@ const UserManage: React.FC = () => {
key: 'auth', key: 'auth',
label: dict('PC.Pages.UserManage.Index.auth'), label: dict('PC.Pages.UserManage.Index.auth'),
disabled: disabled:
!hasPermissionByMenuCode('user_manage', 'user_manage_bind_role') && record.role === UserRoleEnum.Admin
!hasPermissionByMenuCode('user_manage', 'user_manage_bind_group'), ? !hasPermissionByMenuCode('user_manage', 'user_manage_bind_role') &&
!hasPermissionByMenuCode('user_manage', 'user_manage_bind_group')
: !hasPermissionByMenuCode('user_manage', 'user_manage_bind_group'),
onClick: handleAuth, onClick: handleAuth,
}, },
{ {

View File

@@ -188,6 +188,8 @@ export interface CustomFormModalProps {
onConfirm: () => void; onConfirm: () => void;
// 确定按钮是否禁用 // 确定按钮是否禁用
okDisabled?: boolean; okDisabled?: boolean;
// 非表单场景跳过 Form 校验,仅由 okDisabled 控制按钮状态
skipFormValidation?: boolean;
} }
// Form.Item 验证rule // Form.Item 验证rule
@@ -456,6 +458,8 @@ export interface SubmitButtonProps {
onConfirm: () => void; onConfirm: () => void;
// 确定按钮是否禁用 // 确定按钮是否禁用
disabled?: boolean; disabled?: boolean;
// 非表单场景跳过 Form 校验,仅由 disabled 控制按钮状态
skipFormValidation?: boolean;
} }
// 上传文件信息 // 上传文件信息