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,30 @@
<?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-ui</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-api</artifactId>
</dependency>
<dependency>
<groupId>com.xspaceagi</groupId>
<artifactId>system-web</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,72 @@
package com.xspaceagi.sandbox.ui.web.controller;
import com.xspaceagi.sandbox.application.dto.SandboxProxyDto;
import com.xspaceagi.sandbox.application.service.SandboxProxyApplicationService;
import com.xspaceagi.sandbox.ui.web.dto.SandboxTempLinkDto;
import com.xspaceagi.system.application.dto.TenantConfigDto;
import com.xspaceagi.system.spec.common.RequestContext;
import com.xspaceagi.system.spec.dto.ReqResult;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 沙箱内部可调用接口
*/
@Tag(name = "沙箱内部可调用接口")
@RestController
@RequestMapping("/api/v1/4sandbox")
public class ApiForSandboxController {
@Resource
private SandboxProxyApplicationService sandboxProxyApplicationService;
@Value("${sandbox.temp-link.base-domain:}")
private String baseDomain;
@Operation(summary = "创建临时代理配置")
@PostMapping("/proxy/create")
public ReqResult<SandboxTempLinkDto> create(@RequestBody SandboxProxyDto dto) {
dto.setUserId(RequestContext.get().getUserId());
SandboxProxyDto sandboxProxyDto = sandboxProxyApplicationService.create(dto);
SandboxTempLinkDto sandboxTempLinkDto = convertToSandboxTempLinkDto(sandboxProxyDto);
return ReqResult.success(sandboxTempLinkDto);
}
@Operation(summary = "删除临时代理配置")
@PostMapping("/proxy/delete/{id}")
public ReqResult<Void> delete(
@Parameter(description = "代理ID") @PathVariable Long id) {
sandboxProxyApplicationService.deleteById(RequestContext.get().getUserId(), id);
return ReqResult.success();
}
@Operation(summary = "查询所有临时代理配置")
@PostMapping("/proxy/list/{sandboxId}")
public ReqResult<List<SandboxTempLinkDto>> listAll(
@Parameter(description = "代理ID") @PathVariable Long sandboxId) {
List<SandboxTempLinkDto> list = sandboxProxyApplicationService.querySandboxyProxyList(RequestContext.get().getUserId(), sandboxId).stream().map(this::convertToSandboxTempLinkDto).toList();
return ReqResult.success(list);
}
private SandboxTempLinkDto convertToSandboxTempLinkDto(SandboxProxyDto sandboxProxyDto) {
SandboxTempLinkDto sandboxTempLinkDto = new SandboxTempLinkDto();
sandboxTempLinkDto.setId(sandboxProxyDto.getId());
if (StringUtils.isNotBlank(baseDomain)) {
sandboxTempLinkDto.setTempLink(sandboxProxyDto.getProxyKey() + "." + baseDomain);
} else {
TenantConfigDto tenantConfigDto = (TenantConfigDto) RequestContext.get().getTenantConfig();
String siteUrl = tenantConfigDto.getSiteUrl();
siteUrl = siteUrl.endsWith("/") ? siteUrl.substring(0, siteUrl.length() - 1) : siteUrl;
sandboxTempLinkDto.setTempLink(siteUrl + "/api/proxy/sandbox/" + sandboxProxyDto.getProxyKey());
}
return sandboxTempLinkDto;
}
}

View File

@@ -0,0 +1,462 @@
package com.xspaceagi.sandbox.ui.web.controller;
import com.xspaceagi.agent.core.adapter.application.AgentApplicationService;
import com.xspaceagi.agent.core.adapter.application.ConversationApplicationService;
import com.xspaceagi.agent.core.adapter.dto.ConversationDto;
import com.xspaceagi.agent.core.adapter.dto.UserAgentDto;
import com.xspaceagi.sandbox.application.dto.SandboxConfigDto;
import com.xspaceagi.sandbox.application.service.SandboxConfigApplicationService;
import com.xspaceagi.sandbox.infra.dao.vo.SandboxConfigValue;
import com.xspaceagi.sandbox.infra.dao.vo.SandboxServerInfo;
import com.xspaceagi.sandbox.spec.enums.SandboxScopeEnum;
import com.xspaceagi.sandbox.ui.web.dto.SandboxConfigCreateDto;
import com.xspaceagi.sandbox.ui.web.dto.SandboxRegDto;
import com.xspaceagi.sandbox.ui.web.dto.UserSandBoxSelectDto;
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.I18nApplicationService;
import com.xspaceagi.system.application.service.UserApplicationService;
import com.xspaceagi.system.infra.dao.entity.User;
import com.xspaceagi.system.spec.common.RequestContext;
import com.xspaceagi.system.spec.dto.ReqResult;
import com.xspaceagi.system.spec.enums.I18nSideEnum;
import com.xspaceagi.system.spec.utils.I18nUtil;
import com.xspaceagi.system.spec.utils.MD5;
import com.xspaceagi.system.spec.utils.RedisUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
* 沙盒配置管理接口
*/
@Tag(name = "用户沙盒相关接口")
@RestController
@RequestMapping("/api/sandbox/config")
public class SandboxConfigController {
private static final int START_VERSION = 90000;
@Resource
private SandboxConfigApplicationService sandboxConfigApplicationService;
@Resource
private UserApplicationService userApplicationService;
@Resource
private RedisUtil redisUtil;
@Resource
private AgentApplicationService agentApplicationService;
@Resource
private ConversationApplicationService conversationApplicationService;
@Resource
private AuthService authService;
@Resource
private I18nApplicationService i18nApplicationService;
@Value("${installation-source}")
private String installationSource;
static {
// disable keep alive暂不使用连接池
System.setProperty("jdk.httpclient.keepalive.timeout", "0");
}
private final HttpClient httpClient = java.net.http.HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build();
@Operation(summary = "查询用户沙盒列表")
@GetMapping("/list")
public ReqResult<List<SandboxConfigDto>> listUserConfigs() {
List<SandboxConfigDto> sandboxConfigs = sandboxConfigApplicationService.listUserConfigsByType(RequestContext.get().getUserId());
sandboxConfigs.forEach(sandboxConfigDto -> {
if (sandboxConfigDto.getConfigValue() != null && sandboxConfigDto.getConfigValue().getHostWithScheme() != null) {
sandboxConfigDto.setConfigKey(null);
}
});
return ReqResult.success(sandboxConfigs);
}
@Operation(summary = "用户可选沙盒选择列表")
@GetMapping("/select/list")
public ReqResult<UserSandBoxSelectDto> listUserSelectConfigs() {
UserSandBoxSelectDto dto = new UserSandBoxSelectDto();
List<SandboxConfigDto> sandboxConfigDtos = sandboxConfigApplicationService.listUserConfigsByType(RequestContext.get().getUserId()).stream().filter(SandboxConfigDto::isOnline).toList();
Map<String, Object> stringObjectMap = redisUtil.hashGetAll("user-sandbox-selected:" + RequestContext.get().getUserId());
Map<String, String> agentSelectedMap = new HashMap<>();
if (stringObjectMap != null) {
//移除stringObjectMap中value不在sandboxConfigDtos这里的项
stringObjectMap.forEach((key, value) -> {
if (sandboxConfigDtos.stream().anyMatch(sandboxConfigDto -> sandboxConfigDto.getId().equals(Long.parseLong(value.toString())))) {
agentSelectedMap.put(key, value.toString());
}
});
}
dto.setAgentSelected(agentSelectedMap);
//过滤出在线的
dto.setSandboxes(sandboxConfigDtos.stream().map(sandboxConfigDto -> {
UserSandBoxSelectDto.SelectDto selectDto = new UserSandBoxSelectDto.SelectDto();
selectDto.setSandboxId(sandboxConfigDto.getId().toString());
selectDto.setName(sandboxConfigDto.getName());
String notice = I18nUtil.systemMessage("Backend.Sandbox.RiskNotice");
if (StringUtils.isNotBlank(sandboxConfigDto.getDescription())) {
selectDto.setDescription(sandboxConfigDto.getDescription() + "\n");
} else {
selectDto.setDescription(notice);
}
return selectDto;
}).collect(Collectors.toList()));
if ("saas".equals(installationSource) || sandboxConfigApplicationService.hasGlobalConfigsForSelect()) {
UserSandBoxSelectDto.SelectDto selectDto = new UserSandBoxSelectDto.SelectDto();
selectDto.setSandboxId("-1");
selectDto.setName(I18nUtil.systemMessage("Backend.Sandbox.CloudComputer"));
selectDto.setDescription(I18nUtil.systemMessage("Backend.Sandbox.CloudComputerDesc"));
dto.getSandboxes().add(0, selectDto);
}
return ReqResult.success(dto);
}
@Operation(summary = "用户沙箱选中记录更新")
@PostMapping("/selected/{agentId}/{sandboxId}")
public ReqResult<Void> selected(@PathVariable Long agentId, @PathVariable String sandboxId) {
redisUtil.hashPut("user-sandbox-selected:" + RequestContext.get().getUserId(), agentId.toString(), sandboxId);
return ReqResult.success();
}
@Operation(summary = "客户端注册")
@PostMapping("/reg")
public ReqResult<SandboxConfigDto> create(@RequestBody SandboxRegDto sandboxRegDto, HttpServletRequest request) {
int versionNum = toVersionNumber(extractQimingVersion(request.getHeader("User-Agent")));
UserDto user = null;
if (versionNum > START_VERSION && StringUtils.isNotBlank(sandboxRegDto.getUsername()) && StringUtils.isNotBlank(sandboxRegDto.getPassword())) {
user = checkLoginInfo(sandboxRegDto);
}
Assert.notNull(sandboxRegDto.getSandboxConfigValue(), I18nUtil.systemMessage("Backend.Sandbox.TerminalConfigRequired"));
TenantConfigDto tenantConfigDto = (TenantConfigDto) RequestContext.get().getTenantConfig();
String host = request.getHeader("Host");
if (host != null) {
host = host.split(":")[0];
tenantConfigDto.setSiteUrl("https://" + host);//后续使用
}
if (StringUtils.isBlank(sandboxRegDto.getSandboxConfigValue().getHostWithScheme())) {
sandboxRegDto.getSandboxConfigValue().setHostWithScheme("http://127.0.0.1");
}
if (StringUtils.isNotBlank(sandboxRegDto.getSavedKey())) {
SandboxConfigDto byKey = sandboxConfigApplicationService.getByKey(sandboxRegDto.getSavedKey());
if (byKey != null) {
if (sandboxRegDto.getSandboxConfigValue().getVncPort() <= 0) {
sandboxRegDto.getSandboxConfigValue().setVncPort(6080);
}
SandboxConfigDto sandboxConfigUpdate = new SandboxConfigDto();
sandboxConfigUpdate.setId(byKey.getId());
sandboxConfigUpdate.setConfigValue(sandboxRegDto.getSandboxConfigValue());
sandboxConfigApplicationService.update(sandboxConfigUpdate);
byKey.setConfigValue(sandboxRegDto.getSandboxConfigValue());
if (user != null) {
String token = authService.createToken(user, StringUtils.isNotBlank(sandboxRegDto.getDeviceId()) ? sandboxRegDto.getDeviceId() : UUID.randomUUID().toString().replace("-", ""));
byKey.setToken(token);
}
return ReqResult.success(byKey);
}
}
Assert.isTrue(StringUtils.isNotBlank(sandboxRegDto.getUsername()), I18nUtil.systemMessage("Backend.Sandbox.UsernameRequired"));
Assert.isTrue(StringUtils.isNotBlank(sandboxRegDto.getPassword()), I18nUtil.systemMessage("Backend.Sandbox.PasswordRequired"));
if (user == null) {
user = checkLoginInfo(sandboxRegDto);
}
RequestContext.get().setUser(user);
RequestContext.get().setUserId(user.getId());
if (user.getLang() != null) {
user.setLangMap(i18nApplicationService.querySystemLangMap(user.getTenantId(), I18nSideEnum.Backend.getSide(), user.getLang()));
RequestContext.get().setLang(user.getLang());
RequestContext.get().setLangMap(user.getLangMap());
}
SandboxConfigDto dto = new SandboxConfigDto();
dto.setUserId(user.getId());
dto.setScope(SandboxScopeEnum.USER);
dto.setConfigKey(UUID.randomUUID().toString().replace("-", ""));
dto.setName(I18nUtil.systemMessage("Backend.Sandbox.MyComputer"));
dto.setConfigValue(sandboxRegDto.getSandboxConfigValue());
dto.setDescription("");
dto.setIsActive(true);
dto.setOnline(false);
sandboxConfigApplicationService.create(dto, false);
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()));
}
private UserDto checkLoginInfo(SandboxRegDto sandboxRegDto) {
TenantConfigDto tenantConfigDto = (TenantConfigDto) RequestContext.get().getTenantConfig();
String key = "sb:reg:" + tenantConfigDto.getTenantId() + ":" + MD5.MD5Encode(sandboxRegDto.getUsername());
Long increment = redisUtil.increment(key, 1);
if (increment <= 5) {
redisUtil.expire(key, 1800);
}
Assert.isTrue(increment <= 5, I18nUtil.systemMessage("Backend.Sandbox.LoginAttemptsExceeded"));
UserDto user;
if (sandboxRegDto.getUsername().matches("^[a-zA-Z0-9._-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$")) {
user = userApplicationService.queryUserByEmail(sandboxRegDto.getUsername());
Assert.isTrue(user != null, I18nUtil.systemMessage("Backend.Sandbox.UserNotFoundPasswordError", increment.toString()));
} else {
user = userApplicationService.queryUserByPhone(sandboxRegDto.getUsername());
if (user == null) {
user = userApplicationService.queryUserByUserName(sandboxRegDto.getUsername());
}
Assert.isTrue(user != null, I18nUtil.systemMessage("Backend.Sandbox.UserNotFoundPasswordError", increment.toString()));
}
String userDynamicCode = userApplicationService.getUserDynamicCode(user.getId());
if (!userDynamicCode.equals(sandboxRegDto.getPassword().trim())) {
UserDto userDto = userApplicationService.queryUserByPhoneOrEmailWithPassword(sandboxRegDto.getUsername(), sandboxRegDto.getPassword().trim());
Assert.isTrue(userDto != null, I18nUtil.systemMessage("Backend.Sandbox.UserNotFoundAuthCodeError", increment.toString()));
}
redisUtil.expire(key, 0);
return user;
}
@Operation(summary = "创建个人电脑(客户端配置)")
@PostMapping("/create")
public ReqResult<Void> create(@RequestBody SandboxConfigCreateDto configCreateDto) {
Assert.notNull(configCreateDto.getName(), I18nUtil.systemMessage("Backend.Sandbox.NameRequired"));
SandboxConfigDto dto = new SandboxConfigDto();
dto.setName(configCreateDto.getName());
dto.setDescription(configCreateDto.getDescription());
dto.setUserId(RequestContext.get().getUserId());
dto.setScope(SandboxScopeEnum.USER);
dto.setConfigKey(UUID.randomUUID().toString().replace("-", ""));
dto.setConfigValue(new SandboxConfigValue());
sandboxConfigApplicationService.create(dto, true);
return ReqResult.success();
}
@Operation(summary = "更新配置")
@PostMapping("/update")
public ReqResult<Void> update(@RequestBody SandboxConfigDto dto) {
Assert.notNull(dto.getId(), I18nUtil.systemMessage("Backend.Sandbox.IdRequired"));
checkPermission(dto.getId());
sandboxConfigApplicationService.update(dto);
return ReqResult.success();
}
@Operation(summary = "删除配置")
@PostMapping("/delete/{id}")
public ReqResult<Void> delete(
@Parameter(description = "配置ID") @PathVariable Long id) {
checkPermission(id);
sandboxConfigApplicationService.delete(id);
return ReqResult.success();
}
@Operation(summary = "跳转到具体的电脑会话界面")
@GetMapping("/redirect/{id}")
public void redirect(HttpServletResponse response, @Parameter(description = "配置ID") @PathVariable Long id) {
String siteUrl = buildSiteUrl();
SandboxConfigDto byId = sandboxConfigApplicationService.getById(id);
if (byId == null) {
try {
response.sendRedirect(siteUrl);
} catch (IOException e) {
throw new RuntimeException(e);
}
return;
}
String redirectUri;
UserAgentDto userAgentDto = agentApplicationService.queryUserAgentRecentUse(byId.getUserId(), byId.getAgentId());
if (userAgentDto != null && userAgentDto.getLastConversationId() != null) {
redirectUri = "home/chat/" + userAgentDto.getLastConversationId() + "/" + byId.getAgentId() + "?hideMenu=true";
} else {
redirectUri = "agent/" + byId.getAgentId() + "?hideMenu=true";
}
try {
response.sendRedirect(siteUrl + redirectUri);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Operation(summary = "跳转到具体的会话界面")
@GetMapping("/redirect/chat/{id}")
public void redirectConversation(HttpServletResponse response, @Parameter(description = "会话ID") @PathVariable Long id) {
String siteUrl = buildSiteUrl();
ConversationDto byId = conversationApplicationService.getConversationByCid(id);
if (byId == null) {
try {
response.sendRedirect(siteUrl);
} catch (IOException e) {
throw new RuntimeException(e);
}
return;
}
try {
response.sendRedirect(siteUrl + "home/chat/" + id + "/" + byId.getAgentId() + "?hideMenu=true");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Operation(summary = "跳转到智能体的新会话界面")
@GetMapping("/redirect/new/{id}")
public void redirectNewChat(HttpServletResponse response, @Parameter(description = "配置ID") @PathVariable Long id) {
SandboxConfigDto byId = sandboxConfigApplicationService.getById(id);
String siteUrl = buildSiteUrl();
if (byId == null) {
try {
response.sendRedirect(siteUrl);
} catch (IOException e) {
throw new RuntimeException(e);
}
return;
}
try {
response.sendRedirect(siteUrl + "agent/" + byId.getAgentId() + "?hideMenu=true");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private String buildSiteUrl() {
TenantConfigDto tenantConfigDto = (TenantConfigDto) RequestContext.get().getTenantConfig();
String siteUrl = tenantConfigDto.getSiteUrl();
return siteUrl.trim().endsWith("/") ? siteUrl.trim() : siteUrl.trim() + "/";
}
@Operation(summary = "客户端健康检查")
@GetMapping("/health/{key}")
public ReqResult<Object> health(@Parameter(description = "配置ID") @PathVariable String key) {
SandboxConfigDto sandboxConfigDto = sandboxConfigApplicationService.getByKey(key);
if (sandboxConfigDto == null) {
return ReqResult.error(I18nUtil.systemMessage("Backend.Sandbox.ConfigNotFound"));
}
if (sandboxConfigDto.getIsActive() != null && !sandboxConfigDto.getIsActive()) {
return ReqResult.error(I18nUtil.systemMessage("Backend.Sandbox.ClientDisabled"));
}
if (!sandboxConfigDto.isOnline()) {
return ReqResult.error(I18nUtil.systemMessage("Backend.Sandbox.ClientOffline"));
}
SandboxServerInfo sandboxServerInfo = sandboxConfigDto.getServerInfo();
if (sandboxServerInfo == null) {
return ReqResult.error(I18nUtil.systemMessage("Backend.Sandbox.ConfigError"));
}
String healthUrl = sandboxServerInfo.getScheme() + "://" + sandboxServerInfo.getHost() + ":" + sandboxServerInfo.getAgentPort() + "/health";
HttpRequest request = HttpRequest.newBuilder().uri(URI.create(healthUrl))
.header("x-api-key", sandboxConfigDto.getConfigValue().getApiKey() == null ? "" : sandboxConfigDto.getConfigValue().getApiKey())
.timeout(java.time.Duration.ofSeconds(10))
.GET().build();
try {
String serverStatusStr = httpClient.send(request, HttpResponse.BodyHandlers.ofString()).body();
return ReqResult.success(serverStatusStr);
} catch (Exception e) {
return ReqResult.error(e.getMessage());
}
}
@Operation(summary = "启用/禁用配置")
@PostMapping("/toggle/{id}")
public ReqResult<Void> toggle(
@Parameter(description = "配置ID") @PathVariable Long id) {
checkPermission(id);
sandboxConfigApplicationService.toggle(id);
return ReqResult.success();
}
private void checkPermission(Long id) {
UserDto user = (UserDto) RequestContext.get().getUser();
if (user.getRole() == User.Role.Admin) {
return;
}
SandboxConfigDto byId = sandboxConfigApplicationService.getById(id);
Assert.isTrue(byId != null && byId.getUserId().equals(user.getId()), I18nUtil.systemMessage("Backend.Sandbox.NoPermission"));
}
/**
* 从User-Agent字符串中提取指定包的版本号
*
* @param userAgent User-Agent字符串
* @param packageName 包名,如 "@qiming-ai/qimingclaw"
* @return 版本号如果未找到则返回null
*/
private static String extractVersion(String userAgent, String packageName) {
// 转义包名中的特殊字符(如 @ 和 /
String escapedPackageName = Pattern.quote(packageName);
// 构建正则表达式:包名/版本号
String regex = escapedPackageName + "/([^\\s/]+)";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(userAgent);
if (matcher.find()) {
return matcher.group(1);
}
return null;
}
/**
* 从User-Agent字符串中提取 @qiming-ai/qimingclaw 的版本号
*
* @param userAgent User-Agent字符串
* @return 版本号如果未找到则返回null
*/
private static String extractQimingVersion(String userAgent) {
return extractVersion(userAgent, "@qiming-ai/qimingclaw");
}
/**
* 将版本号转换为整数,可用于版本对比
* 支持1-4位版本号统一格式每位版本号占2位数字不足补0
* 例如0.9.1 -> 0009011.2.3 -> 0102030.9.1.2 -> 00090102
*
* @param version 版本号字符串,如 "0.9.1"
* @return 整数,可直接用于版本大小对比
*/
private static int toVersionNumber(String version) {
if (version == null || version.isEmpty()) {
return 0;
}
// 按点号分割版本号
String[] parts = version.split("\\.");
int result = 0;
// 统一处理每部分占2位数字不足部分补0
for (int i = 0; i < 4; i++) {
result *= 100; // 每次左移2位
if (i < parts.length) {
result += Integer.parseInt(parts[i]);
}
}
return result;
}
}

View File

@@ -0,0 +1,122 @@
package com.xspaceagi.sandbox.ui.web.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
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.spec.annotation.RequireResource;
import com.xspaceagi.system.spec.common.RequestContext;
import com.xspaceagi.system.spec.dto.ReqResult;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import static com.xspaceagi.system.spec.enums.ResourceEnum.*;
/**
* 沙盒配置管理接口
*/
@Tag(name = "系统管理-沙盒配置相关接口")
@RestController
@RequestMapping("/api/system/sandbox/config")
public class SandboxConfigManageController {
@Resource
private SandboxConfigApplicationService sandboxConfigApplicationService;
@RequireResource(SANDBOX_CONFIG_QUERY)
@Operation(summary = "分页查询配置列表")
@PostMapping("/page")
public ReqResult<Page<SandboxConfigDto>> pageQuery(@RequestBody SandboxConfigQueryDto queryDto) {
return ReqResult.success(sandboxConfigApplicationService.pageQuery(queryDto));
}
@RequireResource(SANDBOX_CONFIG_QUERY)
@Operation(summary = "查询配置详情")
@GetMapping("/get/{id}")
public ReqResult<SandboxConfigDto> getById(
@Parameter(description = "配置ID") @PathVariable Long id) {
return ReqResult.success(sandboxConfigApplicationService.getById(id));
}
@Operation(summary = "沙箱连通性测试")
@GetMapping("/test/{id}")
public ReqResult<Void> testConnection(@Parameter(description = "配置ID") @PathVariable Long id) {
try {
sandboxConfigApplicationService.testConnection(id);
} catch (Exception e) {
return ReqResult.error("0007", e.getMessage());
}
return ReqResult.success();
}
@RequireResource(SANDBOX_CONFIG_QUERY)
@Operation(summary = "查询用户配置列表")
@GetMapping("/user/list")
public ReqResult<List<SandboxConfigDto>> listUserConfigsByType(
@Parameter(description = "用户ID为空时查询当前用户") @RequestParam(required = false) Long userId) {
return ReqResult.success(sandboxConfigApplicationService.listUserConfigsByType(userId));
}
@RequireResource(SANDBOX_CONFIG_QUERY)
@Operation(summary = "查询全局配置列表")
@GetMapping("/global/list")
public ReqResult<List<SandboxConfigDto>> listGlobalConfigsByType() {
return ReqResult.success(sandboxConfigApplicationService.listGlobalConfigsByType());
}
@RequireResource(SANDBOX_CONFIG_ADD)
@Operation(summary = "创建配置")
@PostMapping("/create")
public ReqResult<Void> create(@RequestBody SandboxConfigDto dto) {
sandboxConfigApplicationService.create(dto, false);
return ReqResult.success();
}
@RequireResource(SANDBOX_CONFIG_MODIFY)
@Operation(summary = "更新配置")
@PostMapping("/update")
public ReqResult<Void> update(@RequestBody SandboxConfigDto dto) {
sandboxConfigApplicationService.update(dto);
return ReqResult.success();
}
@RequireResource(SANDBOX_CONFIG_DELETE)
@Operation(summary = "删除配置")
@PostMapping("/delete/{id}")
public ReqResult<Void> delete(
@Parameter(description = "配置ID") @PathVariable Long id) {
sandboxConfigApplicationService.delete(id);
return ReqResult.success();
}
@RequireResource(SANDBOX_CONFIG_MODIFY)
@Operation(summary = "启用/禁用配置")
@PostMapping("/toggle/{id}")
public ReqResult<Void> toggle(
@Parameter(description = "配置ID") @PathVariable Long id) {
sandboxConfigApplicationService.toggle(id);
return ReqResult.success();
}
@RequireResource(SANDBOX_CONFIG_SAVE)
@Operation(summary = "更新沙箱全局配置")
@PostMapping("/global/update")
public ReqResult<Void> globalUpdate(@RequestBody SandboxGlobalConfigDto dto) {
sandboxConfigApplicationService.updateGlobalConfig(RequestContext.get().getTenantId(), dto);
return ReqResult.success();
}
@RequireResource(SANDBOX_CONFIG_QUERY)
@Operation(summary = "查询沙箱全局配置")
@PostMapping("/global")
public ReqResult<SandboxGlobalConfigDto> getGlobalConfig() {
SandboxGlobalConfigDto globalConfig = sandboxConfigApplicationService.getGlobalConfig(RequestContext.get().getTenantId());
return ReqResult.success(globalConfig);
}
}

View File

@@ -0,0 +1,13 @@
package com.xspaceagi.sandbox.ui.web.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
public class SandboxConfigCreateDto {
@Schema(description = "电脑名称", requiredMode = Schema.RequiredMode.REQUIRED)
private String name;
@Schema(description = "电脑描述")
private String description;
}

View File

@@ -0,0 +1,24 @@
package com.xspaceagi.sandbox.ui.web.dto;
import com.xspaceagi.sandbox.infra.dao.vo.SandboxConfigValue;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
public class SandboxRegDto {
@Schema(description = "用户名、邮箱或手机号码")
private String username;
@Schema(description = "密码")
private String password;
@Schema(description = "曾经保存的密钥,首次登录不需要")
private String savedKey;
@Schema(description = "设备ID")
private String deviceId;
@Schema(description = "终端配置信息")
private SandboxConfigValue sandboxConfigValue;
}

View File

@@ -0,0 +1,14 @@
package com.xspaceagi.sandbox.ui.web.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
public class SandboxTempLinkDto {
@Schema(description = "外部可访问的临时代理链接地址")
private String tempLink;
@Schema(description = "代理链接ID用于管理该链接比如删除时使用")
private Long id;
}

View File

@@ -0,0 +1,24 @@
package com.xspaceagi.sandbox.ui.web.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
import java.util.Map;
@Data
public class UserSandBoxSelectDto {
@Schema(description = "可选的沙盒列表")
private List<SelectDto> sandboxes;
@Schema(description = "已选择的沙盒, key为agentIdvalue为sandboxId")
private Map<String, String> agentSelected;
@Data
public static class SelectDto {
private String sandboxId;
private String name;
private String description;
}
}