chore: initialize qiming workspace repository

This commit is contained in:
Codex
2026-05-29 14:22:48 +08:00
commit bfd67a0f2c
10750 changed files with 1885711 additions and 0 deletions

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.xspaceagi</groupId>
<artifactId>app-platform-sandbox</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<groupId>com.xspaceagi</groupId>
<artifactId>app-platform-sandbox-api</artifactId>
<dependencies>
<dependency>
<groupId>com.xspaceagi</groupId>
<artifactId>app-platform-sandbox-spec</artifactId>
</dependency>
<dependency>
<groupId>com.xspaceagi</groupId>
<artifactId>app-platform-sandbox-application</artifactId>
</dependency>
<dependency>
<groupId>com.xspaceagi</groupId>
<artifactId>app-platform-sandbox-sdk</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,286 @@
package com.xspaceagi.sandbox.api;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.xspaceagi.sandbox.api.vo.SandboxServerConfig;
import com.xspaceagi.sandbox.application.dto.SandboxBindInfoDto;
import com.xspaceagi.sandbox.application.dto.SandboxConfigDto;
import com.xspaceagi.sandbox.application.service.SandboxConfigApplicationService;
import com.xspaceagi.sandbox.infra.dao.entity.SandboxConfig;
import com.xspaceagi.sandbox.infra.dao.service.SandboxConfigService;
import com.xspaceagi.sandbox.infra.network.ReverseServerContainer;
import com.xspaceagi.sandbox.sdk.server.ISandboxConfigRpcService;
import com.xspaceagi.sandbox.sdk.service.dto.SandboxConfigRpcDto;
import com.xspaceagi.sandbox.sdk.service.dto.SandboxConfigValue;
import com.xspaceagi.sandbox.sdk.service.dto.SandboxGlobalConfigDto;
import com.xspaceagi.sandbox.sdk.service.dto.SandboxServerInfo;
import com.xspaceagi.sandbox.sdk.service.enums.IsolationEnum;
import com.xspaceagi.sandbox.spec.enums.SandboxScopeEnum;
import com.xspaceagi.system.application.dto.TenantConfigDto;
import com.xspaceagi.system.application.service.TenantConfigApplicationService;
import com.xspaceagi.system.spec.common.RequestContext;
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.tenant.thread.TenantFunctions;
import com.xspaceagi.system.spec.utils.RedisUtil;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
/**
* 沙盒配置 RPC 服务实现
*/
@Slf4j
@Service("iSandboxConfigRpcService")
public class SandboxConfigRpcServiceImpl implements ISandboxConfigRpcService {
@Resource
private SandboxConfigService sandboxConfigService;
@Resource
private SandboxConfigApplicationService sandboxConfigApplicationService;
@Resource
private ReverseServerContainer reverseServerContainer;
@Resource
private TenantConfigApplicationService tenantConfigApplicationService;
@Resource
private RedisUtil redisUtil;
private final AtomicInteger sandboxIndex = new AtomicInteger(0);
@PostConstruct
public void init() {
TenantFunctions.runWithIgnoreCheck(() -> {
try {
List<SandboxConfigRpcDto> sandboxConfigRpcDtos = queryGlobalConfigs(1L);
if (!sandboxConfigRpcDtos.isEmpty()) {
return;
}
//初始化租户id为1的全局配置私有化部署的id为1L
TenantConfigDto tenantConfig = tenantConfigApplicationService.getTenantConfig(1L);
//全局没有配置时读取系统设置
String sandboxServerConfigStr = tenantConfig.getSandboxConfig();
SandboxServerConfig sandboxServerConfig = JSON.parseObject(sandboxServerConfigStr, SandboxServerConfig.class);
if (sandboxServerConfig != null && sandboxServerConfig.getSandboxServers() != null) {
com.xspaceagi.sandbox.application.dto.SandboxGlobalConfigDto sandboxGlobalConfigDto = new com.xspaceagi.sandbox.application.dto.SandboxGlobalConfigDto();
sandboxGlobalConfigDto.setPerUserCpuCores(sandboxServerConfig.getPerUserCpuCores() != 0 ? String.valueOf(sandboxServerConfig.getPerUserCpuCores()) : "2");
sandboxGlobalConfigDto.setPerUserMemoryGB(sandboxServerConfig.getPerUserMemoryGB() != 0 ? String.valueOf(sandboxServerConfig.getPerUserMemoryGB()) : "4");
sandboxConfigApplicationService.updateGlobalConfig(1L, sandboxGlobalConfigDto);
sandboxServerConfig.getSandboxServers().forEach(sandboxServer -> {
try {
SandboxConfig sandboxConfig = new SandboxConfig();
sandboxConfig.setId(Long.valueOf(sandboxServer.getServerId()));
sandboxConfig.setScope(SandboxScopeEnum.GLOBAL);
sandboxConfig.setTenantId(tenantConfig.getTenantId());
sandboxConfig.setName(sandboxServer.getServerName());
sandboxConfig.setDescription(sandboxServer.getServerName());
sandboxConfig.setConfigKey(UUID.randomUUID().toString().replace("-", ""));
SandboxConfigValue sandboxConfigValue = new SandboxConfigValue();
URL serverUrl = new URL(sandboxServer.getServerAgentUrl());
sandboxConfigValue.setHostWithScheme(serverUrl.getProtocol() + "://" + serverUrl.getHost());
sandboxConfigValue.setAgentPort(serverUrl.getPort());
serverUrl = new URL(sandboxServer.getServerFileUrl());
sandboxConfigValue.setFileServerPort(serverUrl.getPort());
serverUrl = new URL(sandboxServer.getServerVncUrl());
sandboxConfigValue.setVncPort(serverUrl.getPort());
sandboxConfigValue.setApiKey(sandboxServer.getServerApiKey());
sandboxConfigValue.setMaxUsers(sandboxServer.getMaxUsers());
sandboxConfig.setConfigValue(JSON.toJSONString(sandboxConfigValue));
sandboxConfig.setIsActive(true);
sandboxConfigService.save(sandboxConfig);
} catch (MalformedURLException e) {
log.error("初始化沙箱配置失败 {}", sandboxServer, e);
}
});
}
} catch (Exception e) {
log.error("初始化全局沙箱配置失败", e);
}
});
}
@Override
public List<SandboxConfigRpcDto> queryUserConfigs(Long userId) {
LambdaQueryWrapper<SandboxConfig> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(SandboxConfig::getScope, SandboxScopeEnum.USER)
.eq(SandboxConfig::getUserId, userId)
.eq(SandboxConfig::getIsActive, true)
.orderByAsc(SandboxConfig::getId);
List<SandboxConfig> configs = sandboxConfigService.list(queryWrapper);
return convertToRpcDtoList(configs);
}
@Override
public List<SandboxConfigRpcDto> queryGlobalConfigs(Long tenantId) {
LambdaQueryWrapper<SandboxConfig> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(SandboxConfig::getScope, SandboxScopeEnum.GLOBAL)
.eq(SandboxConfig::getIsActive, true)
.eq(SandboxConfig::getTenantId, tenantId)
.eq(SandboxConfig::getType, "Agent")
.orderByAsc(SandboxConfig::getId);
List<SandboxConfig> configs = TenantFunctions.callWithIgnoreCheck(() -> sandboxConfigService.list(queryWrapper));
return convertToRpcDtoList(configs);
}
@Override
public SandboxConfigRpcDto queryUserConfigByKey(String configKey) {
SandboxConfig config = sandboxConfigService.queryUserConfigByKey(configKey);
return convertToRpcDto(config);
}
@Override
public SandboxConfigRpcDto queryGlobalConfigByKey(String configKey) {
SandboxConfig config = sandboxConfigService.queryGlobalConfigByKey(configKey);
return convertToRpcDto(config);
}
@Override
public SandboxConfigRpcDto queryById(Long id) {
SandboxConfig config = TenantFunctions.callWithIgnoreCheck(() -> sandboxConfigService.getById(id));
return convertToRpcDto(config);
}
/**
* 转换为 RPC DTO 列表
*/
private List<SandboxConfigRpcDto> convertToRpcDtoList(List<SandboxConfig> configs) {
if (CollectionUtils.isEmpty(configs)) {
return List.of();
}
return configs.stream()
.map(this::convertToRpcDto)
.collect(Collectors.toList());
}
/**
* 转换为 RPC DTO
*/
private SandboxConfigRpcDto convertToRpcDto(SandboxConfig entity) {
if (entity == null) {
return null;
}
SandboxConfigRpcDto dto = new SandboxConfigRpcDto();
BeanUtils.copyProperties(entity, dto);
// 将 JSON 字符串转换为对象
if (StringUtils.isNotBlank(entity.getConfigValue())) {
try {
dto.setConfigValue(JSON.parseObject(entity.getConfigValue(), SandboxConfigValue.class));
} catch (Exception e) {
log.error("Failed to parse config value", e);
}
}
if (StringUtils.isNotBlank(entity.getServerInfo())) {
try {
dto.setSandboxServerInfo(JSON.parseObject(entity.getServerInfo(), SandboxServerInfo.class));
} catch (Exception e) {
log.error("Failed to parse config value", e);
}
}
if (entity.getScope() == SandboxScopeEnum.USER) {
dto.setOnline(reverseServerContainer.getUserSandboxAliveTime(entity.getConfigKey()) != null);
}
return dto;
}
@Override
public SandboxGlobalConfigDto getGlobalConfig(Long tenantId) {
com.xspaceagi.sandbox.application.dto.SandboxGlobalConfigDto globalConfig = sandboxConfigApplicationService.getGlobalConfig(tenantId);
if (globalConfig != null) {
SandboxGlobalConfigDto dto = new SandboxGlobalConfigDto();
BeanUtils.copyProperties(globalConfig, dto);
return dto;
}
return null;
}
@Override
public Long queryUserSelectedSandboxId(Long userId, Long agentId) {
if (agentId == null || userId == null) {
return null;
}
Object val = redisUtil.hashGet("user-sandbox-selected:" + RequestContext.get().getUserId(), agentId.toString());
if (val != null) {
try {
return Long.parseLong(val.toString());
} catch (NumberFormatException ignored) {
}
}
return null;
}
@Override
public SandboxConfigRpcDto selectAppDevelopmentSandbox(Long tenantId, Long userId, Long spaceId, Long projectId, Long sandboxId) {
SandboxConfigDto configDto = null;
if (sandboxId != null) {
configDto = sandboxConfigApplicationService.getById(sandboxId);
} else {
List<SandboxConfigDto> sandboxConfigs = sandboxConfigApplicationService.listPageDevelopmentSandboxes();
if (CollectionUtils.isEmpty(sandboxConfigs)) {
return null;
}
List<SandboxConfigDto> collect = sandboxConfigs.stream().filter(sandboxConfigDto -> {
if (CollectionUtils.isEmpty(sandboxConfigDto.getBindItems())) {
return false;
}
return sandboxConfigDto.getBindItems().stream().anyMatch(bindItem -> {
if (bindItem.getTargetType() == SandboxBindInfoDto.BindTargetType.User && userId.equals(bindItem.getTargetId())) {
return true;
}
return bindItem.getTargetType() == SandboxBindInfoDto.BindTargetType.Space && spaceId.equals(bindItem.getTargetId());
});
}).toList();
if (!collect.isEmpty()) {
configDto = collect.get(sandboxIndex.incrementAndGet() % collect.size());
} else {
sandboxConfigs.removeIf(sandboxConfigDto -> !CollectionUtils.isEmpty(sandboxConfigDto.getBindItems()));
if (!sandboxConfigs.isEmpty()) {
configDto = sandboxConfigs.get(sandboxIndex.incrementAndGet() % sandboxConfigs.size());
}
}
}
if (configDto == null) {
throw BizException.of(ErrorCodeEnum.INVALID_PARAM, BizExceptionCodeEnum.configNotFound);
}
SandboxConfigRpcDto rpcDto = new SandboxConfigRpcDto();
BeanUtils.copyProperties(configDto, rpcDto);
SandboxConfigValue configValue = new SandboxConfigValue();
BeanUtils.copyProperties(configDto.getConfigValue(), configValue);
rpcDto.setConfigValue(configValue);
if (configDto.getIsolation() != null) {
rpcDto.setIsolation(IsolationEnum.valueOf(configDto.getIsolation()));
} else {
rpcDto.setIsolation(IsolationEnum.Space);
}
if (rpcDto.getIsolation() == IsolationEnum.Tenant) {
rpcDto.setIsolationKey(tenantId.toString());
}
if (rpcDto.getIsolation() == IsolationEnum.Space) {
rpcDto.setIsolationKey(tenantId + "-" + spaceId.toString());
}
if (rpcDto.getIsolation() == IsolationEnum.Project) {
rpcDto.setIsolationKey(tenantId + "-" + spaceId.toString() + "-" + projectId.toString());
}
return rpcDto;
}
}

View File

@@ -0,0 +1,27 @@
package com.xspaceagi.sandbox.api.vo;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
@Data
public class SandboxServerConfig implements Serializable {
private List<SandboxServer> sandboxServers;
private double perUserMemoryGB;
private int perUserCpuCores;
@Data
public static class SandboxServer {
private String serverId;
private String serverName;
private String serverAgentUrl;
private String serverVncUrl;
private String serverFileUrl;
private String serverApiKey;
private int maxUsers;
private double perUserMemoryGB;
private int perUserCpuCores;
}
}