支持单位授权与客户端限制

This commit is contained in:
Codex
2026-06-01 11:58:14 +08:00
parent 28adc8143a
commit c136cc2cc8
36 changed files with 861 additions and 49 deletions

View File

@@ -25,6 +25,9 @@ public class SandboxConfigDto {
@Schema(description = "用户IDscope为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;
}
}

View File

@@ -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;

View File

@@ -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);
}
}
/**
* 验证配置键唯一性
*/