支持单位授权与客户端限制
This commit is contained in:
@@ -25,6 +25,9 @@ public class SandboxConfigDto {
|
||||
@Schema(description = "用户ID(scope为user时必填)")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "用户组/单位ID")
|
||||
private Long groupId;
|
||||
|
||||
@Schema(description = "配置名称")
|
||||
private String name;
|
||||
|
||||
@@ -81,4 +84,4 @@ public class SandboxConfigDto {
|
||||
|
||||
@Schema(description = "更新时间")
|
||||
private Date modified;
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,8 @@ import com.xspaceagi.sandbox.spec.enums.SandboxScopeEnum;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 沙盒配置查询 DTO
|
||||
*/
|
||||
@@ -17,6 +19,12 @@ public class SandboxConfigQueryDto {
|
||||
@Schema(description = "用户ID")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "用户组/单位ID")
|
||||
private Long groupId;
|
||||
|
||||
@Schema(description = "用户组/单位ID列表")
|
||||
private List<Long> groupIds;
|
||||
|
||||
@Schema(description = "配置名称(模糊查询)")
|
||||
private String name;
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@ import com.xspaceagi.sandbox.infra.dao.vo.SandboxServerInfo;
|
||||
import com.xspaceagi.sandbox.infra.network.ReverseServerContainer;
|
||||
import com.xspaceagi.sandbox.spec.enums.SandboxScopeEnum;
|
||||
import com.xspaceagi.system.application.dto.TenantConfigDto;
|
||||
import com.xspaceagi.system.application.service.SysGroupApplicationService;
|
||||
import com.xspaceagi.system.infra.dao.entity.SysGroup;
|
||||
import com.xspaceagi.system.spec.common.RequestContext;
|
||||
import com.xspaceagi.system.spec.enums.ErrorCodeEnum;
|
||||
import com.xspaceagi.system.spec.exception.BizException;
|
||||
@@ -27,6 +29,7 @@ import com.xspaceagi.system.spec.exception.BizExceptionCodeEnum;
|
||||
import com.xspaceagi.system.spec.utils.RedisUtil;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -41,6 +44,7 @@ import java.net.http.HttpResponse;
|
||||
import java.time.Duration;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@@ -65,6 +69,9 @@ public class SandboxConfigApplicationServiceImpl implements SandboxConfigApplica
|
||||
@Resource
|
||||
private IAgentRpcService iAgentRpcService;
|
||||
|
||||
@Resource
|
||||
private SysGroupApplicationService sysGroupApplicationService;
|
||||
|
||||
static {
|
||||
// disable keep alive,暂不使用连接池
|
||||
System.setProperty("jdk.httpclient.keepalive.timeout", "0");
|
||||
@@ -201,6 +208,7 @@ public class SandboxConfigApplicationServiceImpl implements SandboxConfigApplica
|
||||
if (entity.getScope() == SandboxScopeEnum.USER && entity.getUserId() == null) {
|
||||
entity.setUserId(RequestContext.get().getUserId());
|
||||
}
|
||||
bindAuthorizedGroupForUserConfig(entity);
|
||||
sandboxConfigService.save(entity);
|
||||
|
||||
// 创建用户沙盒代理
|
||||
@@ -240,6 +248,14 @@ public class SandboxConfigApplicationServiceImpl implements SandboxConfigApplica
|
||||
SandboxConfig entity = convertToEntity(dto);
|
||||
entity.setId(dto.getId());
|
||||
entity.setModified(new Date());
|
||||
entity.setScope(existingEntity.getScope());
|
||||
entity.setUserId(existingEntity.getUserId());
|
||||
entity.setGroupId(existingEntity.getGroupId());
|
||||
if (existingEntity.getGroupId() == null) {
|
||||
bindAuthorizedGroupForUserConfig(entity);
|
||||
} else {
|
||||
validateExistingGroupForUserConfig(entity);
|
||||
}
|
||||
|
||||
sandboxConfigService.updateById(entity);
|
||||
|
||||
@@ -337,6 +353,14 @@ public class SandboxConfigApplicationServiceImpl implements SandboxConfigApplica
|
||||
queryWrapper.eq(SandboxConfig::getUserId, queryDto.getUserId());
|
||||
}
|
||||
|
||||
if (queryDto.getGroupId() != null) {
|
||||
queryWrapper.eq(SandboxConfig::getGroupId, queryDto.getGroupId());
|
||||
}
|
||||
|
||||
if (CollectionUtils.isNotEmpty(queryDto.getGroupIds())) {
|
||||
queryWrapper.in(SandboxConfig::getGroupId, queryDto.getGroupIds());
|
||||
}
|
||||
|
||||
if (StringUtils.isNotBlank(queryDto.getName())) {
|
||||
queryWrapper.like(SandboxConfig::getName, queryDto.getName());
|
||||
}
|
||||
@@ -350,6 +374,44 @@ public class SandboxConfigApplicationServiceImpl implements SandboxConfigApplica
|
||||
return queryWrapper;
|
||||
}
|
||||
|
||||
private void bindAuthorizedGroupForUserConfig(SandboxConfig entity) {
|
||||
if (entity.getScope() != SandboxScopeEnum.USER || entity.getUserId() == null) {
|
||||
return;
|
||||
}
|
||||
List<SysGroup> groups = sysGroupApplicationService.getEffectiveGroupListByUserId(entity.getUserId());
|
||||
if (groups.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Map<Long, Long> clientCountByGroupId = groups.stream()
|
||||
.filter(group -> group.getId() != null)
|
||||
.collect(Collectors.toMap(
|
||||
SysGroup::getId,
|
||||
group -> countUserClientsByGroupId(group.getId()),
|
||||
(a, b) -> a));
|
||||
SysGroup group = sysGroupApplicationService.selectAuthorizedGroupForClient(entity.getUserId(), clientCountByGroupId);
|
||||
if (group == null) {
|
||||
throw BizException.of(ErrorCodeEnum.PERMISSION_DENIED, BizExceptionCodeEnum.systemGroupClientLimitExceeded);
|
||||
}
|
||||
entity.setGroupId(group.getId());
|
||||
}
|
||||
|
||||
private long countUserClientsByGroupId(Long groupId) {
|
||||
return sandboxConfigService.count(new LambdaQueryWrapper<SandboxConfig>()
|
||||
.eq(SandboxConfig::getScope, SandboxScopeEnum.USER)
|
||||
.eq(SandboxConfig::getGroupId, groupId));
|
||||
}
|
||||
|
||||
private void validateExistingGroupForUserConfig(SandboxConfig entity) {
|
||||
if (entity.getScope() != SandboxScopeEnum.USER || entity.getUserId() == null || entity.getGroupId() == null) {
|
||||
return;
|
||||
}
|
||||
boolean authorized = sysGroupApplicationService.getAuthorizedGroupListForLogin(entity.getUserId()).stream()
|
||||
.anyMatch(group -> entity.getGroupId().equals(group.getId()));
|
||||
if (!authorized) {
|
||||
throw BizException.of(ErrorCodeEnum.PERMISSION_DENIED, BizExceptionCodeEnum.systemGroupAuthorizationUnavailable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证配置键唯一性
|
||||
*/
|
||||
|
||||
@@ -37,6 +37,9 @@ public class SandboxConfig implements Serializable {
|
||||
@TableField(value = "user_id")
|
||||
private Long userId;
|
||||
|
||||
@TableField(value = "group_id")
|
||||
private Long groupId;
|
||||
|
||||
@TableField(value = "name")
|
||||
private String name;
|
||||
|
||||
@@ -75,4 +78,4 @@ public class SandboxConfig implements Serializable {
|
||||
|
||||
@TableField(value = "modified")
|
||||
private Date modified;
|
||||
}
|
||||
}
|
||||
@@ -199,15 +199,16 @@ public class SandboxConfigController {
|
||||
RequestContext.get().setLangMap(user.getLangMap());
|
||||
}
|
||||
SandboxConfigDto dto = new SandboxConfigDto();
|
||||
String clientName = StringUtils.trimToNull(sandboxRegDto.getName());
|
||||
dto.setUserId(user.getId());
|
||||
dto.setScope(SandboxScopeEnum.USER);
|
||||
dto.setConfigKey(UUID.randomUUID().toString().replace("-", ""));
|
||||
dto.setName(I18nUtil.systemMessage("Backend.Sandbox.MyComputer"));
|
||||
dto.setName(clientName != null ? clientName : I18nUtil.systemMessage("Backend.Sandbox.MyComputer"));
|
||||
dto.setConfigValue(sandboxRegDto.getSandboxConfigValue());
|
||||
dto.setDescription("");
|
||||
dto.setIsActive(true);
|
||||
dto.setOnline(false);
|
||||
sandboxConfigApplicationService.create(dto, false);
|
||||
sandboxConfigApplicationService.create(dto, clientName != null);
|
||||
String token = authService.createToken(user, StringUtils.isNotBlank(sandboxRegDto.getDeviceId()) ? sandboxRegDto.getDeviceId() : UUID.randomUUID().toString().replace("-", ""));
|
||||
dto.setToken(token);
|
||||
return ReqResult.success(sandboxConfigApplicationService.getByKey(dto.getConfigKey()));
|
||||
|
||||
@@ -5,9 +5,16 @@ import com.xspaceagi.sandbox.application.dto.SandboxConfigDto;
|
||||
import com.xspaceagi.sandbox.application.dto.SandboxConfigQueryDto;
|
||||
import com.xspaceagi.sandbox.application.dto.SandboxGlobalConfigDto;
|
||||
import com.xspaceagi.sandbox.application.service.SandboxConfigApplicationService;
|
||||
import com.xspaceagi.system.application.dto.UserDto;
|
||||
import com.xspaceagi.system.application.service.SysGroupApplicationService;
|
||||
import com.xspaceagi.system.infra.dao.entity.SysGroup;
|
||||
import com.xspaceagi.system.infra.dao.entity.User;
|
||||
import com.xspaceagi.system.spec.annotation.RequireResource;
|
||||
import com.xspaceagi.system.spec.common.RequestContext;
|
||||
import com.xspaceagi.system.spec.dto.ReqResult;
|
||||
import com.xspaceagi.system.spec.enums.ErrorCodeEnum;
|
||||
import com.xspaceagi.system.spec.exception.BizException;
|
||||
import com.xspaceagi.system.spec.exception.BizExceptionCodeEnum;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
@@ -15,6 +22,8 @@ import jakarta.annotation.Resource;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.xspaceagi.system.spec.enums.ResourceEnum.*;
|
||||
|
||||
@@ -29,10 +38,15 @@ public class SandboxConfigManageController {
|
||||
@Resource
|
||||
private SandboxConfigApplicationService sandboxConfigApplicationService;
|
||||
|
||||
@Resource
|
||||
private SysGroupApplicationService sysGroupApplicationService;
|
||||
|
||||
@RequireResource(SANDBOX_CONFIG_QUERY)
|
||||
@Operation(summary = "分页查询配置列表")
|
||||
@PostMapping("/page")
|
||||
public ReqResult<Page<SandboxConfigDto>> pageQuery(@RequestBody SandboxConfigQueryDto queryDto) {
|
||||
queryDto = queryDto == null ? new SandboxConfigQueryDto() : queryDto;
|
||||
applySandboxConfigManageScope(queryDto);
|
||||
return ReqResult.success(sandboxConfigApplicationService.pageQuery(queryDto));
|
||||
}
|
||||
|
||||
@@ -41,13 +55,16 @@ public class SandboxConfigManageController {
|
||||
@GetMapping("/get/{id}")
|
||||
public ReqResult<SandboxConfigDto> getById(
|
||||
@Parameter(description = "配置ID") @PathVariable Long id) {
|
||||
return ReqResult.success(sandboxConfigApplicationService.getById(id));
|
||||
SandboxConfigDto dto = sandboxConfigApplicationService.getById(id);
|
||||
checkSandboxConfigManageScope(dto);
|
||||
return ReqResult.success(dto);
|
||||
}
|
||||
|
||||
@Operation(summary = "沙箱连通性测试")
|
||||
@GetMapping("/test/{id}")
|
||||
public ReqResult<Void> testConnection(@Parameter(description = "配置ID") @PathVariable Long id) {
|
||||
try {
|
||||
checkSandboxConfigManageScope(sandboxConfigApplicationService.getById(id));
|
||||
sandboxConfigApplicationService.testConnection(id);
|
||||
} catch (Exception e) {
|
||||
return ReqResult.error("0007", e.getMessage());
|
||||
@@ -60,7 +77,14 @@ public class SandboxConfigManageController {
|
||||
@GetMapping("/user/list")
|
||||
public ReqResult<List<SandboxConfigDto>> listUserConfigsByType(
|
||||
@Parameter(description = "用户ID(为空时查询当前用户)") @RequestParam(required = false) Long userId) {
|
||||
return ReqResult.success(sandboxConfigApplicationService.listUserConfigsByType(userId));
|
||||
List<SandboxConfigDto> configs = sandboxConfigApplicationService.listUserConfigsByType(userId);
|
||||
if (!isPlatformAdmin()) {
|
||||
Set<Long> allowedGroupIds = currentUserGroupIds();
|
||||
configs = configs.stream()
|
||||
.filter(config -> config.getGroupId() != null && allowedGroupIds.contains(config.getGroupId()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
return ReqResult.success(configs);
|
||||
}
|
||||
|
||||
@RequireResource(SANDBOX_CONFIG_QUERY)
|
||||
@@ -74,6 +98,7 @@ public class SandboxConfigManageController {
|
||||
@Operation(summary = "创建配置")
|
||||
@PostMapping("/create")
|
||||
public ReqResult<Void> create(@RequestBody SandboxConfigDto dto) {
|
||||
checkSandboxConfigManageScope(dto);
|
||||
sandboxConfigApplicationService.create(dto, false);
|
||||
return ReqResult.success();
|
||||
}
|
||||
@@ -82,6 +107,7 @@ public class SandboxConfigManageController {
|
||||
@Operation(summary = "更新配置")
|
||||
@PostMapping("/update")
|
||||
public ReqResult<Void> update(@RequestBody SandboxConfigDto dto) {
|
||||
checkSandboxConfigManageScope(sandboxConfigApplicationService.getById(dto.getId()));
|
||||
sandboxConfigApplicationService.update(dto);
|
||||
return ReqResult.success();
|
||||
}
|
||||
@@ -91,6 +117,7 @@ public class SandboxConfigManageController {
|
||||
@PostMapping("/delete/{id}")
|
||||
public ReqResult<Void> delete(
|
||||
@Parameter(description = "配置ID") @PathVariable Long id) {
|
||||
checkSandboxConfigManageScope(sandboxConfigApplicationService.getById(id));
|
||||
sandboxConfigApplicationService.delete(id);
|
||||
return ReqResult.success();
|
||||
}
|
||||
@@ -100,6 +127,7 @@ public class SandboxConfigManageController {
|
||||
@PostMapping("/toggle/{id}")
|
||||
public ReqResult<Void> toggle(
|
||||
@Parameter(description = "配置ID") @PathVariable Long id) {
|
||||
checkSandboxConfigManageScope(sandboxConfigApplicationService.getById(id));
|
||||
sandboxConfigApplicationService.toggle(id);
|
||||
return ReqResult.success();
|
||||
}
|
||||
@@ -119,4 +147,45 @@ public class SandboxConfigManageController {
|
||||
SandboxGlobalConfigDto globalConfig = sandboxConfigApplicationService.getGlobalConfig(RequestContext.get().getTenantId());
|
||||
return ReqResult.success(globalConfig);
|
||||
}
|
||||
|
||||
private void applySandboxConfigManageScope(SandboxConfigQueryDto queryDto) {
|
||||
if (queryDto == null || isPlatformAdmin()) {
|
||||
return;
|
||||
}
|
||||
Set<Long> allowedGroupIds = currentUserGroupIds();
|
||||
if (queryDto.getGroupId() != null) {
|
||||
if (!allowedGroupIds.contains(queryDto.getGroupId())) {
|
||||
throw BizException.of(ErrorCodeEnum.PERMISSION_DENIED, BizExceptionCodeEnum.permissionDenied);
|
||||
}
|
||||
return;
|
||||
}
|
||||
queryDto.setGroupIds(allowedGroupIds.isEmpty() ? List.of(-1L) : allowedGroupIds.stream().toList());
|
||||
}
|
||||
|
||||
private void checkSandboxConfigManageScope(SandboxConfigDto dto) {
|
||||
if (dto == null || isPlatformAdmin()) {
|
||||
return;
|
||||
}
|
||||
if (dto.getGroupId() == null || !currentUserGroupIds().contains(dto.getGroupId())) {
|
||||
throw BizException.of(ErrorCodeEnum.PERMISSION_DENIED, BizExceptionCodeEnum.permissionDenied);
|
||||
}
|
||||
}
|
||||
|
||||
private Set<Long> currentUserGroupIds() {
|
||||
return sysGroupApplicationService.getEffectiveGroupListByUserId(getCurrentUserDto().getId()).stream()
|
||||
.map(SysGroup::getId)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
private boolean isPlatformAdmin() {
|
||||
return getCurrentUserDto().getRole() == User.Role.Admin;
|
||||
}
|
||||
|
||||
private UserDto getCurrentUserDto() {
|
||||
UserDto userDto = (UserDto) RequestContext.get().getUser();
|
||||
if (userDto == null) {
|
||||
throw BizException.of(ErrorCodeEnum.UNAUTHORIZED, BizExceptionCodeEnum.systemUserNotLoggedInWeb);
|
||||
}
|
||||
return userDto;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,9 @@ public class SandboxRegDto {
|
||||
@Schema(description = "设备ID")
|
||||
private String deviceId;
|
||||
|
||||
@Schema(description = "客户端注册名称")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "终端配置信息")
|
||||
private SandboxConfigValue sandboxConfigValue;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.xspaceagi.system.application.dto.permission;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
@@ -20,6 +21,15 @@ public class SysGroupAddDto implements Serializable {
|
||||
@Schema(description = "最大用户数")
|
||||
private Integer maxUserCount;
|
||||
|
||||
@Schema(description = "最大客户端数,-1表示不限制")
|
||||
private Integer maxClientCount;
|
||||
|
||||
@Schema(description = "授权开关:1-已授权,0-未授权")
|
||||
private Integer authEnabled;
|
||||
|
||||
@Schema(description = "授权到期时间,空表示不过期")
|
||||
private Date expireTime;
|
||||
|
||||
@Schema(description = "来源,1:系统内置 2:用户自定义", hidden = true)
|
||||
private Integer source;
|
||||
|
||||
|
||||
@@ -31,6 +31,15 @@ public class SysGroupDto implements Serializable {
|
||||
@Schema(description = "最大用户数")
|
||||
private Integer maxUserCount;
|
||||
|
||||
@Schema(description = "最大客户端数,-1表示不限制")
|
||||
private Integer maxClientCount;
|
||||
|
||||
@Schema(description = "授权开关:1-已授权,0-未授权")
|
||||
private Integer authEnabled;
|
||||
|
||||
@Schema(description = "授权到期时间,空表示不过期")
|
||||
private Date expireTime;
|
||||
|
||||
@Schema(description = "来源,1:系统内置 2:用户自定义")
|
||||
private Integer source;
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
@Data
|
||||
public class SysGroupUpdateDto implements Serializable {
|
||||
@@ -23,6 +24,15 @@ public class SysGroupUpdateDto implements Serializable {
|
||||
@Schema(description = "最大用户数")
|
||||
private Integer maxUserCount;
|
||||
|
||||
@Schema(description = "最大客户端数,-1表示不限制")
|
||||
private Integer maxClientCount;
|
||||
|
||||
@Schema(description = "授权开关:1-已授权,0-未授权")
|
||||
private Integer authEnabled;
|
||||
|
||||
@Schema(description = "授权到期时间,空表示不过期")
|
||||
private Date expireTime;
|
||||
|
||||
@Schema(description = "来源,1:系统内置 2:用户自定义", hidden = true)
|
||||
private Integer source;
|
||||
|
||||
|
||||
@@ -76,6 +76,16 @@ public interface SysGroupApplicationService {
|
||||
*/
|
||||
List<SysGroup> getEffectiveGroupListByUserId(Long userId);
|
||||
|
||||
/**
|
||||
* 查询用户可用于系统登录的已授权用户组(启用、授权开启、未过期)
|
||||
*/
|
||||
List<SysGroup> getAuthorizedGroupListForLogin(Long userId);
|
||||
|
||||
/**
|
||||
* 为用户选择一个可注册客户端的已授权用户组(同时满足客户端数量限制)
|
||||
*/
|
||||
SysGroup selectAuthorizedGroupForClient(Long userId, java.util.Map<Long, Long> clientCountByGroupId);
|
||||
|
||||
/**
|
||||
* 用户组绑定用户(全量覆盖)
|
||||
*/
|
||||
|
||||
@@ -3,13 +3,16 @@ package com.xspaceagi.system.application.service.impl;
|
||||
import com.xspaceagi.system.application.dto.TenantConfigDto;
|
||||
import com.xspaceagi.system.application.dto.UserDto;
|
||||
import com.xspaceagi.system.application.service.AuthService;
|
||||
import com.xspaceagi.system.application.service.SysGroupApplicationService;
|
||||
import com.xspaceagi.system.application.service.UserApplicationService;
|
||||
import com.xspaceagi.system.infra.dao.entity.User;
|
||||
import com.xspaceagi.system.infra.rpc.WeChatMpService;
|
||||
import com.xspaceagi.system.infra.verify.VerifyCodeSendAndCheckService;
|
||||
import com.xspaceagi.system.spec.common.RequestContext;
|
||||
import com.xspaceagi.system.spec.enums.CodeTypeEnum;
|
||||
import com.xspaceagi.system.spec.enums.ErrorCodeEnum;
|
||||
import com.xspaceagi.system.spec.exception.BizException;
|
||||
import com.xspaceagi.system.spec.exception.BizExceptionCodeEnum;
|
||||
import com.xspaceagi.system.spec.utils.I18nUtil;
|
||||
import com.xspaceagi.system.spec.utils.JwtUtils;
|
||||
import com.xspaceagi.system.spec.utils.RedisUtil;
|
||||
@@ -28,6 +31,9 @@ public class AuthServiceImpl implements AuthService {
|
||||
@Resource
|
||||
private UserApplicationService userApplicationService;
|
||||
|
||||
@Resource
|
||||
private SysGroupApplicationService sysGroupApplicationService;
|
||||
|
||||
@Resource
|
||||
private VerifyCodeSendAndCheckService verifyCodeSendAndCheckService;
|
||||
|
||||
@@ -66,9 +72,7 @@ public class AuthServiceImpl implements AuthService {
|
||||
userDto.setEmail(email);
|
||||
userApplicationService.add(userDto);
|
||||
}
|
||||
if (userDto.getStatus() == User.Status.Disabled || userDto.getStatus() == User.Status.Deleted) {
|
||||
throw new BizException(I18nUtil.systemMessage("Backend.Auth.Login.AccountDisabled"));
|
||||
}
|
||||
validateLoginUser(userDto);
|
||||
updateLastLoginTime(userDto);
|
||||
return createToken(userDto, UUID.randomUUID().toString().replace("-", ""));
|
||||
}
|
||||
@@ -87,13 +91,23 @@ public class AuthServiceImpl implements AuthService {
|
||||
userDto.setPhone(phone);
|
||||
userApplicationService.add(userDto);
|
||||
}
|
||||
if (userDto.getStatus() == User.Status.Disabled) {
|
||||
throw new BizException(I18nUtil.systemMessage("Backend.Auth.Login.AccountDisabled"));
|
||||
}
|
||||
validateLoginUser(userDto);
|
||||
updateLastLoginTime(userDto);
|
||||
return createToken(userDto, UUID.randomUUID().toString().replace("-", ""));
|
||||
}
|
||||
|
||||
private void validateLoginUser(UserDto userDto) {
|
||||
if (userDto.getStatus() == User.Status.Disabled || userDto.getStatus() == User.Status.Deleted) {
|
||||
throw new BizException(I18nUtil.systemMessage("Backend.Auth.Login.AccountDisabled"));
|
||||
}
|
||||
if (userDto.getRole() == User.Role.Admin) {
|
||||
return;
|
||||
}
|
||||
if (sysGroupApplicationService.getAuthorizedGroupListForLogin(userDto.getId()).isEmpty()) {
|
||||
throw BizException.of(ErrorCodeEnum.PERMISSION_DENIED, BizExceptionCodeEnum.systemGroupAuthorizationUnavailable);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateLastLoginTime(UserDto userDto) {
|
||||
UserDto update = new UserDto();
|
||||
update.setId(userDto.getId());
|
||||
@@ -105,6 +119,7 @@ public class AuthServiceImpl implements AuthService {
|
||||
}
|
||||
|
||||
public String createToken(UserDto userDto, String clientId) {
|
||||
validateLoginUser(userDto);
|
||||
//过期token检查
|
||||
Map<String, Object> map = redisUtil.hashGetAll("user-token:" + userDto.getId());
|
||||
for (Map.Entry<String, Object> entry : map.entrySet()) {
|
||||
@@ -127,6 +142,7 @@ public class AuthServiceImpl implements AuthService {
|
||||
Assert.notNull(emailOrPhone, "emailOrPhone must be non-null");
|
||||
UserDto userDto = userApplicationService.queryUserByPhoneOrEmailWithPassword(emailOrPhone, password);
|
||||
if (userDto != null) {
|
||||
validateLoginUser(userDto);
|
||||
updateLastLoginTime(userDto);
|
||||
return createToken(userDto, UUID.randomUUID().toString().replace("-", ""));
|
||||
}
|
||||
|
||||
@@ -2,9 +2,11 @@ package com.xspaceagi.system.application.service.impl;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -23,6 +25,7 @@ import com.xspaceagi.system.domain.model.GroupBindMenuModel;
|
||||
import com.xspaceagi.system.domain.model.MenuNode;
|
||||
import com.xspaceagi.system.domain.model.SortIndex;
|
||||
import com.xspaceagi.system.domain.service.SysGroupDomainService;
|
||||
import com.xspaceagi.system.domain.service.impl.GroupAuthorizationPolicy;
|
||||
import com.xspaceagi.system.infra.dao.entity.SysDataPermission;
|
||||
import com.xspaceagi.system.infra.dao.entity.SysGroup;
|
||||
import com.xspaceagi.system.infra.dao.entity.User;
|
||||
@@ -141,6 +144,39 @@ public class SysGroupApplicationServiceImpl implements SysGroupApplicationServic
|
||||
: groupList.stream().filter(g -> StatusEnum.isEnabled(g.getStatus())).toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SysGroup> getAuthorizedGroupListForLogin(Long userId) {
|
||||
return filterAuthorizedGroupsForLogin(getEffectiveGroupListByUserId(userId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public SysGroup selectAuthorizedGroupForClient(Long userId, Map<Long, Long> clientCountByGroupId) {
|
||||
return selectAuthorizedGroupForClient(getEffectiveGroupListByUserId(userId), clientCountByGroupId);
|
||||
}
|
||||
|
||||
List<SysGroup> filterAuthorizedGroupsForLogin(List<SysGroup> groups) {
|
||||
if (CollectionUtils.isEmpty(groups)) {
|
||||
return List.of();
|
||||
}
|
||||
Date now = new Date();
|
||||
return groups.stream()
|
||||
.filter(group -> GroupAuthorizationPolicy.evaluate(group, -1, now).allowed())
|
||||
.toList();
|
||||
}
|
||||
|
||||
SysGroup selectAuthorizedGroupForClient(List<SysGroup> groups, Map<Long, Long> clientCountByGroupId) {
|
||||
if (CollectionUtils.isEmpty(groups)) {
|
||||
return null;
|
||||
}
|
||||
Date now = new Date();
|
||||
Map<Long, Long> counts = clientCountByGroupId == null ? Map.of() : clientCountByGroupId;
|
||||
return groups.stream()
|
||||
.filter(group -> group.getId() != null)
|
||||
.filter(group -> GroupAuthorizationPolicy.evaluate(group, counts.getOrDefault(group.getId(), 0L), now).allowed())
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
private List<Long> getSubscriptionPlanGroupIds(Long userId) {
|
||||
try {
|
||||
UserSubscriptionDTO subscription = subscriptionRpcService.getUserCurrentSystemSubscription(userId);
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.xspaceagi.system.application.service.impl;
|
||||
|
||||
import com.xspaceagi.system.infra.dao.entity.SysGroup;
|
||||
import com.xspaceagi.system.spec.enums.StatusEnum;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class SysGroupApplicationServiceImplAuthorizationTest {
|
||||
|
||||
@Test
|
||||
void filtersGroupsWithoutActiveAuthorizationForLogin() {
|
||||
SysGroupApplicationServiceImpl service = new SysGroupApplicationServiceImpl();
|
||||
|
||||
List<SysGroup> result = service.filterAuthorizedGroupsForLogin(List.of(
|
||||
group(1L, 0, future(), -1),
|
||||
group(2L, 1, past(), -1),
|
||||
group(3L, 1, future(), -1)
|
||||
));
|
||||
|
||||
assertThat(result).extracting(SysGroup::getId).containsExactly(3L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectsAuthorizedGroupWhoseClientQuotaIsAvailable() {
|
||||
SysGroupApplicationServiceImpl service = new SysGroupApplicationServiceImpl();
|
||||
|
||||
SysGroup result = service.selectAuthorizedGroupForClient(List.of(
|
||||
group(1L, 1, future(), 1),
|
||||
group(2L, 1, future(), 2)
|
||||
), Map.of(1L, 1L, 2L, 1L));
|
||||
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.getId()).isEqualTo(2L);
|
||||
}
|
||||
|
||||
private SysGroup group(Long id, Integer authEnabled, Date expireTime, Integer maxClientCount) {
|
||||
SysGroup group = new SysGroup();
|
||||
group.setId(id);
|
||||
group.setStatus(StatusEnum.ENABLED.getCode());
|
||||
group.setAuthEnabled(authEnabled);
|
||||
group.setExpireTime(expireTime);
|
||||
group.setMaxClientCount(maxClientCount);
|
||||
return group;
|
||||
}
|
||||
|
||||
private Date future() {
|
||||
return new Date(System.currentTimeMillis() + 86_400_000L);
|
||||
}
|
||||
|
||||
private Date past() {
|
||||
return new Date(System.currentTimeMillis() - 86_400_000L);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.xspaceagi.system.domain.service.impl;
|
||||
|
||||
import com.xspaceagi.system.infra.dao.entity.SysGroup;
|
||||
import com.xspaceagi.system.spec.enums.StatusEnum;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Centralized department authorization rule for login and client registration.
|
||||
*/
|
||||
public final class GroupAuthorizationPolicy {
|
||||
|
||||
private GroupAuthorizationPolicy() {
|
||||
}
|
||||
|
||||
public static Result evaluate(SysGroup group, long currentClientCount, Date now) {
|
||||
if (group == null) {
|
||||
return Result.deny(Reason.NO_GROUP);
|
||||
}
|
||||
if (!StatusEnum.isEnabled(group.getStatus())) {
|
||||
return Result.deny(Reason.GROUP_DISABLED);
|
||||
}
|
||||
if (!Integer.valueOf(1).equals(group.getAuthEnabled())) {
|
||||
return Result.deny(Reason.AUTH_DISABLED);
|
||||
}
|
||||
Date expireTime = group.getExpireTime();
|
||||
if (expireTime != null && !expireTime.after(now)) {
|
||||
return Result.deny(Reason.EXPIRED);
|
||||
}
|
||||
Integer maxClientCount = group.getMaxClientCount();
|
||||
if (maxClientCount != null && maxClientCount > -1 && currentClientCount >= maxClientCount) {
|
||||
return Result.deny(Reason.CLIENT_QUOTA_REACHED);
|
||||
}
|
||||
return Result.allow();
|
||||
}
|
||||
|
||||
public enum Reason {
|
||||
ALLOWED,
|
||||
NO_GROUP,
|
||||
GROUP_DISABLED,
|
||||
AUTH_DISABLED,
|
||||
EXPIRED,
|
||||
CLIENT_QUOTA_REACHED
|
||||
}
|
||||
|
||||
public record Result(boolean allowed, Reason reason) {
|
||||
private static Result allow() {
|
||||
return new Result(true, Reason.ALLOWED);
|
||||
}
|
||||
|
||||
private static Result deny(Reason reason) {
|
||||
return new Result(false, reason);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.xspaceagi.system.domain.service.impl;
|
||||
|
||||
import com.xspaceagi.system.infra.dao.entity.SysGroup;
|
||||
import com.xspaceagi.system.spec.enums.StatusEnum;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class GroupAuthorizationPolicyTest {
|
||||
|
||||
@Test
|
||||
void allowsEnabledAuthorizedGroupBeforeExpirationWhenClientQuotaAvailable() {
|
||||
SysGroup group = group(StatusEnum.ENABLED.getCode(), 1, future(), 2);
|
||||
|
||||
GroupAuthorizationPolicy.Result result = GroupAuthorizationPolicy.evaluate(group, 1, now());
|
||||
|
||||
assertThat(result.allowed()).isTrue();
|
||||
assertThat(result.reason()).isEqualTo(GroupAuthorizationPolicy.Reason.ALLOWED);
|
||||
}
|
||||
|
||||
@Test
|
||||
void deniesDisabledGroup() {
|
||||
SysGroup group = group(StatusEnum.DISABLED.getCode(), 1, future(), 2);
|
||||
|
||||
GroupAuthorizationPolicy.Result result = GroupAuthorizationPolicy.evaluate(group, 0, now());
|
||||
|
||||
assertThat(result.allowed()).isFalse();
|
||||
assertThat(result.reason()).isEqualTo(GroupAuthorizationPolicy.Reason.GROUP_DISABLED);
|
||||
}
|
||||
|
||||
@Test
|
||||
void deniesGroupWithoutAuthorization() {
|
||||
SysGroup group = group(StatusEnum.ENABLED.getCode(), 0, future(), 2);
|
||||
|
||||
GroupAuthorizationPolicy.Result result = GroupAuthorizationPolicy.evaluate(group, 0, now());
|
||||
|
||||
assertThat(result.allowed()).isFalse();
|
||||
assertThat(result.reason()).isEqualTo(GroupAuthorizationPolicy.Reason.AUTH_DISABLED);
|
||||
}
|
||||
|
||||
@Test
|
||||
void deniesExpiredGroup() {
|
||||
SysGroup group = group(StatusEnum.ENABLED.getCode(), 1, past(), 2);
|
||||
|
||||
GroupAuthorizationPolicy.Result result = GroupAuthorizationPolicy.evaluate(group, 0, now());
|
||||
|
||||
assertThat(result.allowed()).isFalse();
|
||||
assertThat(result.reason()).isEqualTo(GroupAuthorizationPolicy.Reason.EXPIRED);
|
||||
}
|
||||
|
||||
@Test
|
||||
void deniesWhenClientQuotaReached() {
|
||||
SysGroup group = group(StatusEnum.ENABLED.getCode(), 1, future(), 2);
|
||||
|
||||
GroupAuthorizationPolicy.Result result = GroupAuthorizationPolicy.evaluate(group, 2, now());
|
||||
|
||||
assertThat(result.allowed()).isFalse();
|
||||
assertThat(result.reason()).isEqualTo(GroupAuthorizationPolicy.Reason.CLIENT_QUOTA_REACHED);
|
||||
}
|
||||
|
||||
private SysGroup group(Integer status, Integer authEnabled, Date expireTime, Integer maxClientCount) {
|
||||
SysGroup group = new SysGroup();
|
||||
group.setStatus(status);
|
||||
group.setAuthEnabled(authEnabled);
|
||||
group.setExpireTime(expireTime);
|
||||
group.setMaxClientCount(maxClientCount);
|
||||
return group;
|
||||
}
|
||||
|
||||
private Date now() {
|
||||
return new Date(1_700_000_000_000L);
|
||||
}
|
||||
|
||||
private Date future() {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.setTime(now());
|
||||
calendar.add(Calendar.DATE, 1);
|
||||
return calendar.getTime();
|
||||
}
|
||||
|
||||
private Date past() {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.setTime(now());
|
||||
calendar.add(Calendar.DATE, -1);
|
||||
return calendar.getTime();
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,21 @@ public class SysGroup {
|
||||
*/
|
||||
private Integer maxUserCount;
|
||||
|
||||
/**
|
||||
* 最大客户端数,-1 表示不限制
|
||||
*/
|
||||
private Integer maxClientCount;
|
||||
|
||||
/**
|
||||
* 授权开关:1-已授权,0-未授权
|
||||
*/
|
||||
private Integer authEnabled;
|
||||
|
||||
/**
|
||||
* 授权到期时间,空表示不过期
|
||||
*/
|
||||
private Date expireTime;
|
||||
|
||||
/**
|
||||
* 来源:1-系统内置,2-用户自定义
|
||||
* @see SourceEnum
|
||||
|
||||
@@ -120,6 +120,8 @@ public enum BizExceptionCodeEnum implements IBizExceptionCodeEnum {
|
||||
systemRoleNotFoundWithRoleId("3122", "角色不存在,id=%s", "Role does not exist, id=%s", "RBAC"),
|
||||
systemGroupMaxUsersBelowBound("3123", "最大用户数不能小于已绑定用户数,当前已绑定%s人", "Max users cannot be below the number already bound; currently %s user(s) bound.", "RBAC"),
|
||||
systemGroupUserLimitExceeded("3124", "组用户数量超出限制", "Group user count exceeds the limit.", "RBAC"),
|
||||
systemGroupAuthorizationUnavailable("3186", "当前用户组未授权或授权已过期", "The current user group is not authorized or the authorization has expired.", "RBAC"),
|
||||
systemGroupClientLimitExceeded("3187", "用户组客户端数量超出限制", "Group client count exceeds the limit.", "RBAC"),
|
||||
systemGroupNotFoundWithRowId("3125", "用户组不存在: id=%s", "User group does not exist: id=%s", "RBAC"),
|
||||
systemGroupNotFoundWithGroupId("3126", "组不存在,id=%s", "Group does not exist, id=%s", "RBAC"),
|
||||
systemGroupNotFoundWithGroupIdAlt("3127", "用户组不存在,id=%s", "User group does not exist, id=%s", "RBAC"),
|
||||
|
||||
@@ -9,6 +9,7 @@ import com.xspaceagi.system.application.dto.permission.*;
|
||||
import com.xspaceagi.system.application.service.SysDataPermissionApplicationService;
|
||||
import com.xspaceagi.system.application.service.SysGroupApplicationService;
|
||||
import com.xspaceagi.system.application.service.SysSubjectPermissionApplicationService;
|
||||
import com.xspaceagi.system.application.dto.UserDto;
|
||||
import com.xspaceagi.system.domain.model.GroupBindMenuModel;
|
||||
import com.xspaceagi.system.domain.model.MenuNode;
|
||||
import com.xspaceagi.system.domain.model.SortIndex;
|
||||
@@ -16,6 +17,7 @@ import com.xspaceagi.system.infra.dao.entity.SysDataPermission;
|
||||
import com.xspaceagi.system.infra.dao.entity.SysGroup;
|
||||
import com.xspaceagi.system.infra.dao.entity.User;
|
||||
import com.xspaceagi.system.spec.annotation.RequireResource;
|
||||
import com.xspaceagi.system.spec.common.RequestContext;
|
||||
import com.xspaceagi.system.spec.dto.PageQueryVo;
|
||||
import com.xspaceagi.system.spec.dto.ReqResult;
|
||||
import com.xspaceagi.system.spec.enums.*;
|
||||
@@ -36,6 +38,7 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.xspaceagi.system.spec.enums.ResourceEnum.*;
|
||||
@@ -89,6 +92,7 @@ public class SysGroupController extends BaseController {
|
||||
|
||||
SysGroup group = new SysGroup();
|
||||
BeanUtils.copyProperties(dto, group);
|
||||
checkGroupManageScope(group.getId());
|
||||
group.setCode(null); // code不允许更新
|
||||
sysGroupApplicationService.updateGroup(group, getUser());
|
||||
return ReqResult.success();
|
||||
@@ -101,6 +105,7 @@ public class SysGroupController extends BaseController {
|
||||
if (groupId == null) {
|
||||
return ReqResult.error("参数不能为空");
|
||||
}
|
||||
checkGroupManageScope(groupId);
|
||||
sysGroupApplicationService.deleteGroup(groupId, getUser());
|
||||
return ReqResult.success();
|
||||
}
|
||||
@@ -112,6 +117,7 @@ public class SysGroupController extends BaseController {
|
||||
if (groupId == null) {
|
||||
return ReqResult.error("参数不能为空");
|
||||
}
|
||||
checkGroupManageScope(groupId);
|
||||
|
||||
SysGroup group = sysGroupApplicationService.getGroupById(groupId);
|
||||
if (group == null) {
|
||||
@@ -141,6 +147,7 @@ public class SysGroupController extends BaseController {
|
||||
if (group == null) {
|
||||
return ReqResult.error("用户组不存在");
|
||||
}
|
||||
checkGroupManageScope(group.getId());
|
||||
SysGroupDto groupDto = new SysGroupDto();
|
||||
BeanUtils.copyProperties(group, groupDto);
|
||||
|
||||
@@ -162,6 +169,7 @@ public class SysGroupController extends BaseController {
|
||||
}
|
||||
List<SortIndex> sortIndexList = dto.getItems().stream()
|
||||
.map(item -> {
|
||||
checkGroupManageScope(item.getId());
|
||||
SortIndex model = new SortIndex();
|
||||
model.setId(item.getId());
|
||||
model.setParentId(item.getParentId());
|
||||
@@ -183,6 +191,7 @@ public class SysGroupController extends BaseController {
|
||||
}
|
||||
|
||||
List<SysGroup> groupList = sysGroupApplicationService.getGroupList(sysGroup);
|
||||
groupList = filterGroupManageScope(groupList);
|
||||
if (CollectionUtils.isEmpty(groupList)) {
|
||||
return ReqResult.success();
|
||||
}
|
||||
@@ -214,6 +223,7 @@ public class SysGroupController extends BaseController {
|
||||
}
|
||||
SysGroupUserQueryDto queryDto = pageQueryVo.getQueryFilter();
|
||||
Long groupId = queryDto.getGroupId();
|
||||
checkGroupManageScope(groupId);
|
||||
String userName = queryDto.getUserName();
|
||||
long pageNo = pageQueryVo.getPageNo() != null ? pageQueryVo.getPageNo() : 1L;
|
||||
long pageSize = pageQueryVo.getPageSize() != null ? pageQueryVo.getPageSize() : 10L;
|
||||
@@ -238,6 +248,7 @@ public class SysGroupController extends BaseController {
|
||||
if (groupId == null) {
|
||||
return ReqResult.error("参数不能为空");
|
||||
}
|
||||
checkGroupManageScope(groupId);
|
||||
SysDataPermission dataPermission = sysDataPermissionApplicationService.getByTarget(PermissionTargetTypeEnum.GROUP, groupId);
|
||||
SysDataPermissionBindDto result = SysDataPermissionConverter.toDto(dataPermission);
|
||||
result = result == null ? new SysDataPermissionBindDto() : result;
|
||||
@@ -282,6 +293,7 @@ public class SysGroupController extends BaseController {
|
||||
if (dto == null || dto.getGroupId() == null) {
|
||||
return ReqResult.error("用户组ID不能为空");
|
||||
}
|
||||
checkGroupManageScope(dto.getGroupId());
|
||||
SysDataPermissionBindDto bindDto = dto.getDataPermission();
|
||||
SysDataPermission dataPermission = SysDataPermissionConverter.toEntity(bindDto);
|
||||
sysGroupApplicationService.bindDataPermission(dto.getGroupId(), dataPermission, getUser());
|
||||
@@ -295,6 +307,7 @@ public class SysGroupController extends BaseController {
|
||||
if (dto == null) {
|
||||
return ReqResult.error("参数不能为空");
|
||||
}
|
||||
checkGroupManageScope(dto.getGroupId());
|
||||
sysGroupApplicationService.groupBindUser(dto.getGroupId(), dto.getUserIds(), getUser());
|
||||
return ReqResult.success();
|
||||
}
|
||||
@@ -306,6 +319,7 @@ public class SysGroupController extends BaseController {
|
||||
if (dto == null || dto.getGroupId() == null || dto.getUserId() == null) {
|
||||
return ReqResult.error("用户组ID和用户ID不能为空");
|
||||
}
|
||||
checkGroupManageScope(dto.getGroupId());
|
||||
sysGroupApplicationService.groupAddUser(dto.getGroupId(), dto.getUserId(), getUser());
|
||||
return ReqResult.success();
|
||||
}
|
||||
@@ -317,6 +331,7 @@ public class SysGroupController extends BaseController {
|
||||
if (dto == null || dto.getGroupId() == null || dto.getUserId() == null) {
|
||||
return ReqResult.error("用户组ID和用户ID不能为空");
|
||||
}
|
||||
checkGroupManageScope(dto.getGroupId());
|
||||
sysGroupApplicationService.groupRemoveUser(dto.getGroupId(), dto.getUserId(), getUser());
|
||||
return ReqResult.success();
|
||||
}
|
||||
@@ -328,6 +343,7 @@ public class SysGroupController extends BaseController {
|
||||
if (dto == null) {
|
||||
return ReqResult.error("参数不能为空");
|
||||
}
|
||||
checkGroupManageScope(dto.getGroupId());
|
||||
// 用户组不允许绑定 生态市场/系统管理
|
||||
checkForbiddenMenuCodes(dto.getMenuTree());
|
||||
|
||||
@@ -343,6 +359,7 @@ public class SysGroupController extends BaseController {
|
||||
if (groupId == null) {
|
||||
return ReqResult.error("参数不能为空");
|
||||
}
|
||||
checkGroupManageScope(groupId);
|
||||
// 获取处理好的菜单树(已包含资源详细信息)
|
||||
List<MenuNode> menuNodeList = sysGroupApplicationService.getMenuTreeByGroupId(groupId);
|
||||
|
||||
@@ -373,4 +390,41 @@ public class SysGroupController extends BaseController {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<SysGroup> filterGroupManageScope(List<SysGroup> groupList) {
|
||||
if (isPlatformAdmin() || CollectionUtils.isEmpty(groupList)) {
|
||||
return groupList;
|
||||
}
|
||||
Set<Long> allowedGroupIds = currentUserGroupIds();
|
||||
return groupList.stream()
|
||||
.filter(group -> group.getId() != null && allowedGroupIds.contains(group.getId()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private void checkGroupManageScope(Long groupId) {
|
||||
if (isPlatformAdmin()) {
|
||||
return;
|
||||
}
|
||||
if (groupId == null || !currentUserGroupIds().contains(groupId)) {
|
||||
throw BizException.of(ErrorCodeEnum.PERMISSION_DENIED, BizExceptionCodeEnum.permissionDenied);
|
||||
}
|
||||
}
|
||||
|
||||
private Set<Long> currentUserGroupIds() {
|
||||
return sysGroupApplicationService.getEffectiveGroupListByUserId(getCurrentUserDto().getId()).stream()
|
||||
.map(SysGroup::getId)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
private boolean isPlatformAdmin() {
|
||||
return getCurrentUserDto().getRole() == User.Role.Admin;
|
||||
}
|
||||
|
||||
private UserDto getCurrentUserDto() {
|
||||
UserDto userDto = (UserDto) RequestContext.get().getUser();
|
||||
if (userDto == null) {
|
||||
throw BizException.of(ErrorCodeEnum.UNAUTHORIZED, BizExceptionCodeEnum.systemUserNotLoggedInWeb);
|
||||
}
|
||||
return userDto;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.xspaceagi.system.web.controller.permission;
|
||||
|
||||
import com.xspaceagi.system.application.dto.UserDto;
|
||||
import com.xspaceagi.system.application.dto.permission.*;
|
||||
import com.xspaceagi.system.application.service.SysDataPermissionApplicationService;
|
||||
import com.xspaceagi.system.application.service.SysGroupApplicationService;
|
||||
@@ -7,10 +8,15 @@ import com.xspaceagi.system.application.service.SysRoleApplicationService;
|
||||
import com.xspaceagi.system.application.service.impl.SysUserPermissionCacheServiceImpl;
|
||||
import com.xspaceagi.system.infra.dao.entity.SysGroup;
|
||||
import com.xspaceagi.system.infra.dao.entity.SysRole;
|
||||
import com.xspaceagi.system.infra.dao.entity.User;
|
||||
import com.xspaceagi.system.sdk.service.dto.UserDataPermissionDto;
|
||||
import com.xspaceagi.system.spec.annotation.RequireResource;
|
||||
import com.xspaceagi.system.spec.annotation.SaasAdmin;
|
||||
import com.xspaceagi.system.spec.common.RequestContext;
|
||||
import com.xspaceagi.system.spec.dto.ReqResult;
|
||||
import com.xspaceagi.system.spec.enums.ErrorCodeEnum;
|
||||
import com.xspaceagi.system.spec.exception.BizException;
|
||||
import com.xspaceagi.system.spec.exception.BizExceptionCodeEnum;
|
||||
import com.xspaceagi.system.spec.utils.I18nUtil;
|
||||
import com.xspaceagi.system.web.controller.base.BaseController;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
@@ -22,6 +28,7 @@ import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.xspaceagi.system.spec.enums.ResourceEnum.*;
|
||||
@@ -48,6 +55,7 @@ public class SysUserController extends BaseController {
|
||||
if (userId == null) {
|
||||
return ReqResult.error("参数不能为空");
|
||||
}
|
||||
checkUserManageScope(userId);
|
||||
|
||||
List<SysRole> roleList = sysRoleApplicationService.getRoleListByUserId(userId);
|
||||
if (CollectionUtils.isEmpty(roleList)) {
|
||||
@@ -70,6 +78,8 @@ public class SysUserController extends BaseController {
|
||||
if (dto == null) {
|
||||
return ReqResult.error("参数不能为空");
|
||||
}
|
||||
checkUserManageScope(dto.getUserId());
|
||||
checkRoleBindScope(dto.getRoleIds());
|
||||
sysRoleApplicationService.userBindRole(dto.getUserId(), dto.getRoleIds(), getUser());
|
||||
return ReqResult.success();
|
||||
}
|
||||
@@ -81,6 +91,7 @@ public class SysUserController extends BaseController {
|
||||
if (userId == null) {
|
||||
return ReqResult.error("参数不能为空");
|
||||
}
|
||||
checkUserManageScope(userId);
|
||||
List<SysGroup> groupList = sysGroupApplicationService.getGroupListByUserId(userId);
|
||||
if (CollectionUtils.isEmpty(groupList)) {
|
||||
return ReqResult.success();
|
||||
@@ -102,6 +113,8 @@ public class SysUserController extends BaseController {
|
||||
if (dto == null) {
|
||||
return ReqResult.error("参数不能为空");
|
||||
}
|
||||
checkUserManageScope(dto.getUserId());
|
||||
checkGroupBindScope(dto.getGroupIds());
|
||||
sysGroupApplicationService.userBindGroup(dto.getUserId(), dto.getGroupIds(), getUser());
|
||||
return ReqResult.success();
|
||||
}
|
||||
@@ -113,6 +126,7 @@ public class SysUserController extends BaseController {
|
||||
if (userId == null) {
|
||||
return ReqResult.error("参数不能为空");
|
||||
}
|
||||
checkUserManageScope(userId);
|
||||
List<MenuNodeDto> menuTree = sysUserPermissionCacheService.getUserMenuTree(userId);
|
||||
|
||||
I18nUtil.replaceSystemMessage(menuTree);
|
||||
@@ -126,10 +140,66 @@ public class SysUserController extends BaseController {
|
||||
if (userId == null) {
|
||||
return ReqResult.error("参数不能为空");
|
||||
}
|
||||
checkUserManageScope(userId);
|
||||
UserDataPermissionDto dataPermission = sysDataPermissionApplicationService.getUserDataPermission(userId);
|
||||
return ReqResult.success(dataPermission);
|
||||
}
|
||||
|
||||
private void checkUserManageScope(Long userId) {
|
||||
if (isPlatformAdmin()) {
|
||||
return;
|
||||
}
|
||||
Set<Long> allowedGroupIds = currentUserGroupIds();
|
||||
boolean inScope = sysGroupApplicationService.getEffectiveGroupListByUserId(userId).stream()
|
||||
.map(SysGroup::getId)
|
||||
.anyMatch(allowedGroupIds::contains);
|
||||
if (!inScope) {
|
||||
throw BizException.of(ErrorCodeEnum.PERMISSION_DENIED, BizExceptionCodeEnum.permissionDenied);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkGroupBindScope(List<Long> groupIds) {
|
||||
if (isPlatformAdmin() || CollectionUtils.isEmpty(groupIds)) {
|
||||
return;
|
||||
}
|
||||
Set<Long> allowedGroupIds = currentUserGroupIds();
|
||||
boolean allInScope = groupIds.stream().allMatch(allowedGroupIds::contains);
|
||||
if (!allInScope) {
|
||||
throw BizException.of(ErrorCodeEnum.PERMISSION_DENIED, BizExceptionCodeEnum.permissionDenied);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkRoleBindScope(List<Long> roleIds) {
|
||||
if (isPlatformAdmin() || CollectionUtils.isEmpty(roleIds)) {
|
||||
return;
|
||||
}
|
||||
Set<Long> allowedRoleIds = sysRoleApplicationService.getRoleListByUserId(getCurrentUserDto().getId()).stream()
|
||||
.map(SysRole::getId)
|
||||
.collect(Collectors.toSet());
|
||||
boolean allInScope = roleIds.stream().allMatch(allowedRoleIds::contains);
|
||||
if (!allInScope) {
|
||||
throw BizException.of(ErrorCodeEnum.PERMISSION_DENIED, BizExceptionCodeEnum.permissionDenied);
|
||||
}
|
||||
}
|
||||
|
||||
private Set<Long> currentUserGroupIds() {
|
||||
return sysGroupApplicationService.getEffectiveGroupListByUserId(getCurrentUserDto().getId()).stream()
|
||||
.map(SysGroup::getId)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
private boolean isPlatformAdmin() {
|
||||
return getCurrentUserDto().getRole() == User.Role.Admin;
|
||||
}
|
||||
|
||||
private UserDto getCurrentUserDto() {
|
||||
UserDto userDto = (UserDto) RequestContext.get().getUser();
|
||||
if (userDto == null) {
|
||||
throw BizException.of(ErrorCodeEnum.UNAUTHORIZED, BizExceptionCodeEnum.systemUserNotLoggedInWeb);
|
||||
}
|
||||
return userDto;
|
||||
}
|
||||
|
||||
@SaasAdmin
|
||||
@Operation(summary = "清除指定用户权限缓存")
|
||||
@GetMapping("/cache/clear-user")
|
||||
@@ -148,4 +218,4 @@ public class SysUserController extends BaseController {
|
||||
sysUserPermissionCacheService.clearCacheAllByTenant(tenantId);
|
||||
return ReqResult.success();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -929,6 +929,7 @@ CREATE TABLE `sandbox_config`
|
||||
`_tenant_id` bigint(20) NOT NULL COMMENT '租户id',
|
||||
`scope` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '配置范围:global-全局配置 user-个人配置',
|
||||
`user_id` bigint(20) DEFAULT NULL COMMENT '用户ID(scope为user时必填)',
|
||||
`group_id` bigint(20) DEFAULT NULL COMMENT '用户组/单位ID',
|
||||
`agent_id` bigint(20) DEFAULT NULL COMMENT '智能体电脑绑定的agentId',
|
||||
`name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '配置名称(用于界面显示)',
|
||||
`config_key` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '唯一标识',
|
||||
@@ -1120,6 +1121,9 @@ CREATE TABLE `sys_group`
|
||||
`name` varchar(128) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '名称',
|
||||
`description` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '描述',
|
||||
`max_user_count` int(11) DEFAULT NULL COMMENT '最大用户数',
|
||||
`max_client_count` int(11) DEFAULT -1 COMMENT '最大客户端数,-1表示不限制',
|
||||
`auth_enabled` tinyint(4) DEFAULT 0 COMMENT '授权开关:1-已授权,0-未授权',
|
||||
`expire_time` datetime DEFAULT NULL COMMENT '授权到期时间,空表示不过期',
|
||||
`source` tinyint(4) NOT NULL COMMENT '来源:1-系统内置,2-用户自定义,对应 SourceEnum',
|
||||
`status` tinyint(4) NOT NULL COMMENT '状态:1-启用,0-禁用',
|
||||
`sort_index` int(11) DEFAULT NULL COMMENT '排序',
|
||||
@@ -1906,6 +1910,7 @@ ALTER TABLE `sandbox_config`
|
||||
ADD UNIQUE KEY `uk_scope_user_key` (`scope`,`user_id`,`config_key`),
|
||||
ADD KEY `idx_scope` (`scope`),
|
||||
ADD KEY `idx_user_id` (`user_id`),
|
||||
ADD KEY `idx_group_id` (`group_id`),
|
||||
ADD KEY `idx_config_key` (`config_key`),
|
||||
ADD KEY `idx_is_active` (`is_active`);
|
||||
|
||||
@@ -3180,4 +3185,4 @@ ALTER TABLE `pay_order`
|
||||
ALTER TABLE `pay_order`
|
||||
ADD KEY `idx_reconcile_scan` (`gateway_sync_status`, `gateway_order_status`, `modified`, `id`);
|
||||
ALTER TABLE `pay_order`
|
||||
ADD UNIQUE KEY `uk_tenant_biz_order` (`_tenant_id`, `biz_order_no`);
|
||||
ADD UNIQUE KEY `uk_tenant_biz_order` (`_tenant_id`, `biz_order_no`);
|
||||
|
||||
8
qiming-backend/sql/update-20260530.sql
Normal file
8
qiming-backend/sql/update-20260530.sql
Normal file
@@ -0,0 +1,8 @@
|
||||
-- 修改表: sys_group
|
||||
ALTER TABLE `sys_group` ADD COLUMN `max_client_count` int(11) DEFAULT -1 COMMENT '最大客户端数,-1表示不限制' AFTER `max_user_count`;
|
||||
ALTER TABLE `sys_group` ADD COLUMN `auth_enabled` tinyint(4) DEFAULT 0 COMMENT '授权开关:1-已授权,0-未授权' AFTER `max_client_count`;
|
||||
ALTER TABLE `sys_group` ADD COLUMN `expire_time` datetime DEFAULT NULL COMMENT '授权到期时间,空表示不过期' AFTER `auth_enabled`;
|
||||
|
||||
-- 修改表: sandbox_config
|
||||
ALTER TABLE `sandbox_config` ADD COLUMN `group_id` bigint(20) DEFAULT NULL COMMENT '用户组/单位ID' AFTER `user_id`;
|
||||
ALTER TABLE `sandbox_config` ADD KEY `idx_group_id` (`group_id`);
|
||||
Reference in New Issue
Block a user