From dc40228ee7c2ed93974da03b2771d61588f31576 Mon Sep 17 00:00:00 2001 From: Jack Xu Date: Tue, 6 Jan 2026 16:10:33 +0800 Subject: [PATCH 1/8] refactor: unfiy the angentId to Long, and remove ResponseEntity in AgentController --- .mvn/wrapper/maven-wrapper.properties | 3 + CI/linter/license/.licenserc.yaml | 2 + .../dataagent/controller/AgentController.java | 301 +++++------ .../controller/AgentDatasourceController.java | 289 +++++----- .../controller/GlobalExceptionHandler.java | 26 + .../dto/schema/SemanticModelAddDTO.java | 68 +-- .../ai/dataagent/entity/AgentDatasource.java | 10 +- .../ai/dataagent/entity/SemanticModel.java | 5 +- .../exception/InternalServerException.java | 22 + .../exception/InvalidInputException.java | 31 ++ .../mapper/AgentDatasourceMapper.java | 90 ++-- .../agent/AgentStartupInitialization.java | 320 ++++++------ .../datasource/AgentDatasourceService.java | 32 +- .../service/datasource/DatasourceService.java | 3 +- .../impl/AgentDatasourceServiceImpl.java | 301 ++++++----- .../impl/DatasourceServiceImpl.java | 29 +- .../semantic/SemanticModelServiceImpl.java | 25 +- .../cloud/ai/dataagent/util/DatabaseUtil.java | 6 +- .../cloud/ai/dataagent/util/FluxUtil.java | 274 ++++++---- .../workflow/node/SqlExecuteNode.java | 10 +- .../workflow/node/TableRelationNode.java | 57 +- .../mapper/AgentDatasourceMapperTest.java | 471 +++++++++-------- .../ai/dataagent/mapper/MappersTest.java | 447 ++++++++-------- mvnw | 492 +++++++++--------- mvnw.cmd | 375 +++++++------ pom.xml | 5 +- 26 files changed, 1873 insertions(+), 1821 deletions(-) create mode 100644 .mvn/wrapper/maven-wrapper.properties create mode 100644 data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/controller/GlobalExceptionHandler.java create mode 100644 data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/exception/InternalServerException.java create mode 100644 data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/exception/InvalidInputException.java diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 000000000..8dea6c227 --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,3 @@ +wrapperVersion=3.3.4 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.12/apache-maven-3.9.12-bin.zip diff --git a/CI/linter/license/.licenserc.yaml b/CI/linter/license/.licenserc.yaml index ab33da992..0ef0ec505 100644 --- a/CI/linter/license/.licenserc.yaml +++ b/CI/linter/license/.licenserc.yaml @@ -82,4 +82,6 @@ header: - '**/*.properties' - 'CI/linter/codespell/.codespell.ignorewords' - 'CI/linter/codespell/.codespell.skip' + - 'mvnw' + - 'mvnw.cmd' comment: never diff --git a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/controller/AgentController.java b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/controller/AgentController.java index 84a3a36a8..50b133657 100644 --- a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/controller/AgentController.java +++ b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/controller/AgentController.java @@ -19,9 +19,11 @@ import com.alibaba.cloud.ai.dataagent.service.agent.AgentService; import com.alibaba.cloud.ai.dataagent.vo.ApiKeyResponse; import com.alibaba.cloud.ai.dataagent.vo.ApiResponse; +import java.util.List; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.http.ResponseEntity; +import org.apache.commons.lang3.StringUtils; +import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -32,12 +34,9 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.server.ResponseStatusException; -import java.util.List; - -/** - * Agent Management Controller - */ +/** Agent Management Controller */ @Slf4j @RestController @RequestMapping("/api/agent") @@ -45,174 +44,124 @@ @AllArgsConstructor public class AgentController { - private final AgentService agentService; - - /** - * Get agent list - */ - @GetMapping("/list") - public ResponseEntity> list(@RequestParam(value = "status", required = false) String status, - @RequestParam(value = "keyword", required = false) String keyword) { - List result; - if (keyword != null && !keyword.trim().isEmpty()) { - result = agentService.search(keyword); - } - else if (status != null && !status.trim().isEmpty()) { - result = agentService.findByStatus(status); - } - else { - result = agentService.findAll(); - } - return ResponseEntity.ok(result); - } - - /** - * Get agent details by ID - */ - @GetMapping("/{id}") - public ResponseEntity get(@PathVariable(value = "id") Long id) { - Agent agent = agentService.findById(id); - if (agent == null) { - return ResponseEntity.notFound().build(); - } - return ResponseEntity.ok(agent); - } - - /** - * Create agent - */ - @PostMapping - public ResponseEntity create(@RequestBody Agent agent) { - // Set default status - if (agent.getStatus() == null || agent.getStatus().trim().isEmpty()) { - agent.setStatus("draft"); - } - Agent saved = agentService.save(agent); - return ResponseEntity.ok(saved); - } - - /** - * Update agent - */ - @PutMapping("/{id}") - public ResponseEntity update(@PathVariable(value = "id") Long id, @RequestBody Agent agent) { - if (agentService.findById(id) == null) { - return ResponseEntity.notFound().build(); - } - agent.setId(id); - Agent updated = agentService.save(agent); - return ResponseEntity.ok(updated); - } - - /** - * Delete agent - */ - @DeleteMapping("/{id}") - public ResponseEntity delete(@PathVariable(value = "id") Long id) { - if (agentService.findById(id) == null) { - return ResponseEntity.notFound().build(); - } - agentService.deleteById(id); - return ResponseEntity.ok().build(); - } - - /** - * Publish agent - */ - @PostMapping("/{id}/publish") - public ResponseEntity publish(@PathVariable(value = "id") Long id) { - Agent agent = agentService.findById(id); - if (agent == null) { - return ResponseEntity.notFound().build(); - } - agent.setStatus("published"); - Agent updated = agentService.save(agent); - return ResponseEntity.ok(updated); - } - - /** - * Offline agent - */ - @PostMapping("/{id}/offline") - public ResponseEntity offline(@PathVariable(value = "id") Long id) { - Agent agent = agentService.findById(id); - if (agent == null) { - return ResponseEntity.notFound().build(); - } - agent.setStatus("offline"); - Agent updated = agentService.save(agent); - return ResponseEntity.ok(updated); - } - - /** - * Get masked API Key status - */ - @GetMapping("/{id}/api-key") - public ResponseEntity> getApiKey(@PathVariable("id") Long id) { - Agent agent = agentService.findById(id); - if (agent == null) { - return ResponseEntity.notFound().build(); - } - String masked = agentService.getApiKeyMasked(id); - return ResponseEntity.ok(buildApiKeyResponse(masked, agent.getApiKeyEnabled(), "获取 API Key 成功")); - } - - /** - * Generate API Key - */ - @PostMapping("/{id}/api-key/generate") - public ResponseEntity> generateApiKey(@PathVariable("id") Long id) { - Agent existing = agentService.findById(id); - if (existing == null) { - return ResponseEntity.notFound().build(); - } - Agent agent = agentService.generateApiKey(id); - return ResponseEntity.ok(buildApiKeyResponse(agent.getApiKey(), agent.getApiKeyEnabled(), "生成 API Key 成功")); - } - - /** - * Reset API Key - */ - @PostMapping("/{id}/api-key/reset") - public ResponseEntity> resetApiKey(@PathVariable("id") Long id) { - Agent existing = agentService.findById(id); - if (existing == null) { - return ResponseEntity.notFound().build(); - } - Agent agent = agentService.resetApiKey(id); - return ResponseEntity.ok(buildApiKeyResponse(agent.getApiKey(), agent.getApiKeyEnabled(), "重置 API Key 成功")); - } - - /** - * Delete API Key - */ - @DeleteMapping("/{id}/api-key") - public ResponseEntity> deleteApiKey(@PathVariable("id") Long id) { - Agent existing = agentService.findById(id); - if (existing == null) { - return ResponseEntity.notFound().build(); - } - Agent agent = agentService.deleteApiKey(id); - return ResponseEntity.ok(buildApiKeyResponse(agent.getApiKey(), agent.getApiKeyEnabled(), "删除 API Key 成功")); - } - - /** - * Toggle API Key enable flag - */ - @PostMapping("/{id}/api-key/enable") - public ResponseEntity> toggleApiKey(@PathVariable("id") Long id, - @RequestParam("enabled") boolean enabled) { - Agent existing = agentService.findById(id); - if (existing == null) { - return ResponseEntity.notFound().build(); - } - Agent agent = agentService.toggleApiKey(id, enabled); - return ResponseEntity.ok(buildApiKeyResponse(agent.getApiKey() == null ? null : "****", - agent.getApiKeyEnabled(), "更新 API Key 状态成功")); - } - - private ApiResponse buildApiKeyResponse(String apiKey, Integer apiKeyEnabled, String message) { - return ApiResponse.success(message, new ApiKeyResponse(apiKey, apiKeyEnabled)); - } - + private final AgentService agentService; + + /** Get agent list */ + @GetMapping("/list") + public List list( + @RequestParam(value = "status", required = false) String status, + @RequestParam(value = "keyword", required = false) String keyword) { + List result; + if (StringUtils.isNotBlank(keyword)) { + result = agentService.search(keyword); + } else if (StringUtils.isNotBlank(status)) { + result = agentService.findByStatus(status); + } else { + result = agentService.findAll(); + } + return result; + } + + /** Get agent details by ID */ + @GetMapping("/{id}") + public Agent get(@PathVariable Long id) { + return checkAgentExists(id); + } + + /** Create agent */ + @PostMapping + public Agent create(@RequestBody Agent agent) { + // Set default status + if (StringUtils.isBlank(agent.getStatus())) { + agent.setStatus("draft"); + } + return agentService.save(agent); + } + + /** Update agent */ + @PutMapping("/{id}") + public Agent update(@PathVariable Long id, @RequestBody Agent agent) { + checkAgentExists(id); + agent.setId(id); + return agentService.save(agent); + } + + /** Delete agent */ + @DeleteMapping("/{id}") + public void delete(@PathVariable Long id) { + checkAgentExists(id); + agentService.deleteById(id); + } + + /** Publish agent */ + @PostMapping("/{id}/publish") + public Agent publish(@PathVariable Long id) { + Agent agent = checkAgentExists(id); + agent.setStatus("published"); + return agentService.save(agent); + } + + /** Offline agent */ + @PostMapping("/{id}/offline") + public Agent offline(@PathVariable Long id) { + Agent agent = checkAgentExists(id); + agent.setStatus("offline"); + return agentService.save(agent); + } + + /** Get masked API Key status */ + @GetMapping("/{id}/api-key") + public ApiResponse getApiKey(@PathVariable Long id) { + Agent agent = checkAgentExists(id); + String masked = agentService.getApiKeyMasked(id); + return buildApiKeyResponse(masked, agent.getApiKeyEnabled(), "获取 API Key 成功"); + } + + /** Generate API Key */ + @PostMapping("/{id}/api-key/generate") + public ApiResponse generateApiKey(@PathVariable Long id) { + checkAgentExists(id); + Agent agent = agentService.generateApiKey(id); + return buildApiKeyResponse(agent.getApiKey(), agent.getApiKeyEnabled(), "生成 API Key 成功"); + } + + /** Reset API Key */ + @PostMapping("/{id}/api-key/reset") + public ApiResponse resetApiKey(@PathVariable Long id) { + checkAgentExists(id); + Agent agent = agentService.resetApiKey(id); + return buildApiKeyResponse(agent.getApiKey(), agent.getApiKeyEnabled(), "重置 API Key 成功"); + } + + /** Delete API Key */ + @DeleteMapping("/{id}/api-key") + public ApiResponse deleteApiKey(@PathVariable Long id) { + checkAgentExists(id); + Agent agent = agentService.deleteApiKey(id); + return buildApiKeyResponse(agent.getApiKey(), agent.getApiKeyEnabled(), "删除 API Key 成功"); + } + + /** Toggle API Key enable flag */ + @PostMapping("/{id}/api-key/enable") + public ApiResponse toggleApiKey( + @PathVariable Long id, @RequestParam("enabled") boolean enabled) { + checkAgentExists(id); + Agent agent = agentService.toggleApiKey(id, enabled); + return buildApiKeyResponse( + agent.getApiKey() == null ? null : "****", agent.getApiKeyEnabled(), "更新 API Key 状态成功"); + } + + private Agent checkAgentExists(Long id) { + Agent agent = agentService.findById(id); + if (agent == null) { + throw new ResponseStatusException( + HttpStatus.NOT_FOUND, "agent with id: %d not found".formatted(id)); + } + return agent; + } + + private ApiResponse buildApiKeyResponse( + String apiKey, Integer apiKeyEnabled, String message) { + return ApiResponse.success(message, new ApiKeyResponse(apiKey, apiKeyEnabled)); + } } diff --git a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/controller/AgentDatasourceController.java b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/controller/AgentDatasourceController.java index 11cdffdd8..5eaa82eb1 100644 --- a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/controller/AgentDatasourceController.java +++ b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/controller/AgentDatasourceController.java @@ -18,20 +18,20 @@ import com.alibaba.cloud.ai.dataagent.dto.datasource.ToggleDatasourceDTO; import com.alibaba.cloud.ai.dataagent.dto.datasource.UpdateDatasourceTablesDTO; import com.alibaba.cloud.ai.dataagent.entity.AgentDatasource; +import com.alibaba.cloud.ai.dataagent.exception.InternalServerException; +import com.alibaba.cloud.ai.dataagent.exception.InvalidInputException; import com.alibaba.cloud.ai.dataagent.service.datasource.AgentDatasourceService; import com.alibaba.cloud.ai.dataagent.vo.ApiResponse; +import java.util.List; +import java.util.Optional; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import java.util.List; -import java.util.Optional; - /** - * Agent Schema Initialization Controller Handles agent's database Schema initialization - * to vector storage + * Agent Schema Initialization Controller Handles agent's database Schema initialization to vector + * storage */ @Slf4j @RestController @@ -40,149 +40,136 @@ @AllArgsConstructor public class AgentDatasourceController { - private final AgentDatasourceService agentDatasourceService; - - /** - * Initialize agent's database Schema to vector storage Corresponds to the "Initialize - * Information Source" function on the frontend - */ - @PostMapping("/init") - public ResponseEntity initSchema(@PathVariable(value = "agentId") Long agentId) { - // 防止前端恶意请求,dto数据应该在后端获取 - try { - AgentDatasource agentDatasource = agentDatasourceService.getCurrentAgentDatasource(agentId.intValue()); - log.info("Initializing schema for agent: {}", agentId); - - // Extract data source ID and table list from request - Integer datasourceId = agentDatasource.getDatasourceId(); - List tables = Optional.ofNullable(agentDatasource.getSelectTables()).orElse(List.of()); - - // Validate request parameters - if (datasourceId == null) { - return ResponseEntity.badRequest().body(ApiResponse.error("数据源ID不能为空")); - } - - if (tables.isEmpty()) { - return ResponseEntity.badRequest().body(ApiResponse.error("表列表不能为空")); - } - - // Execute Schema initialization - Boolean result = agentDatasourceService.initializeSchemaForAgentWithDatasource(agentId, datasourceId, - tables); - - if (result) { - log.info("Successfully initialized schema for agent: {}, tables: {}", agentId, tables.size()); - return ResponseEntity.ok(ApiResponse.success("Schema初始化成功")); - } - else { - return ResponseEntity.internalServerError().body(ApiResponse.error("Schema初始化失败")); - } - } - catch (Exception e) { - log.error("Failed to initialize schema for agent: {}", agentId, e); - return ResponseEntity.internalServerError().body(ApiResponse.error("Schema初始化失败:" + e.getMessage())); - } - } - - /** - * Get list of data sources configured for agent - */ - @GetMapping - public ResponseEntity getAgentDatasource(@PathVariable(value = "agentId") Long agentId) { - try { - log.info("Getting datasources for agent: {}", agentId); - List datasources = agentDatasourceService.getAgentDatasource(Math.toIntExact(agentId)); - log.info("Successfully retrieved {} datasources for agent: {}", datasources.size(), agentId); - return ResponseEntity.ok(ApiResponse.success("操作成功", datasources)); - } - catch (Exception e) { - log.error("Failed to get datasources for agent: {}", agentId, e); - return ResponseEntity.badRequest().body(ApiResponse.error("获取数据源失败:" + e.getMessage(), List.of())); - } - } - - @GetMapping("/active") - public ResponseEntity getActiveAgentDatasource(@PathVariable(value = "agentId") Long agentId) { - try { - log.info("Getting active datasource for agent: {}", agentId); - AgentDatasource datasource = agentDatasourceService.getCurrentAgentDatasource(Math.toIntExact(agentId)); - return ResponseEntity.ok(ApiResponse.success("操作成功", datasource)); - } - catch (Exception e) { - log.error("Failed to get active datasource for agent: {}", agentId, e); - return ResponseEntity.badRequest().body(ApiResponse.error("获取数据源失败:" + e.getMessage())); - } - } - - /** - * Add data source for agent - */ - @PostMapping("/{datasourceId}") - public ResponseEntity addDatasourceToAgent(@PathVariable(value = "agentId") Integer agentId, - @PathVariable(value = "datasourceId") Integer datasourceId) { - try { - if (datasourceId == null) { - return ResponseEntity.badRequest().body(ApiResponse.error("数据源ID不能为空")); - } - - AgentDatasource agentDatasource = agentDatasourceService.addDatasourceToAgent(agentId, datasourceId); - return ResponseEntity.ok(ApiResponse.success("数据源添加成功", agentDatasource)); - } - catch (Exception e) { - return ResponseEntity.badRequest().body(ApiResponse.error("添加失败:" + e.getMessage())); - } - } - - // 更新选择的数据表 - @PostMapping("/tables") - public ResponseEntity updateDatasourceTables(@PathVariable("agentId") String agentId, - @RequestBody @Validated UpdateDatasourceTablesDTO dto) { - try { - dto.setTables(Optional.ofNullable(dto.getTables()).orElse(List.of())); - agentDatasourceService.updateDatasourceTables(Integer.parseInt(agentId), dto.getDatasourceId(), - dto.getTables()); - return ResponseEntity.ok(ApiResponse.success("更新成功")); - } - catch (Exception e) { - log.error("Error: ", e); - return ResponseEntity.badRequest().body(ApiResponse.error("更新失败:" + e.getMessage())); - } - } - - /** - * Remove data source association from agent - */ - @DeleteMapping("/{datasourceId}") - public ResponseEntity removeDatasourceFromAgent(@PathVariable("agentId") Integer agentId, - @PathVariable("datasourceId") Integer datasourceId) { - try { - agentDatasourceService.removeDatasourceFromAgent(agentId, datasourceId); - return ResponseEntity.ok(ApiResponse.success("数据源已移除")); - } - catch (Exception e) { - return ResponseEntity.badRequest().body(ApiResponse.error("移除失败:" + e.getMessage())); - } - } - - /** - * 启用/禁用智能体的数据源 - */ - @PutMapping("/toggle") - public ResponseEntity toggleDatasourceForAgent(@PathVariable("agentId") Integer agentId, - @RequestBody ToggleDatasourceDTO dto) { - try { - Boolean isActive = dto.getIsActive(); - Integer datasourceId = dto.getDatasourceId(); - if (isActive == null || datasourceId == null) { - return ResponseEntity.badRequest().body(ApiResponse.error("激活状态不能为空")); - } - AgentDatasource agentDatasource = agentDatasourceService.toggleDatasourceForAgent(agentId, - dto.getDatasourceId(), isActive); - return ResponseEntity.ok(ApiResponse.success(isActive ? "数据源已启用" : "数据源已禁用", agentDatasource)); - } - catch (Exception e) { - return ResponseEntity.badRequest().body(ApiResponse.error("操作失败:" + e.getMessage())); - } - } - + private final AgentDatasourceService agentDatasourceService; + + /** + * Initialize agent's database Schema to vector storage Corresponds to the "Initialize Information + * Source" function on the frontend + */ + @PostMapping("/init") + public ApiResponse initSchema(@PathVariable Long agentId) { + // 防止前端恶意请求,dto数据应该在后端获取 + try { + AgentDatasource agentDatasource = agentDatasourceService.getCurrentAgentDatasource(agentId); + log.info("Initializing schema for agent: {}", agentId); + + // Extract data source ID and table list from request + Integer datasourceId = agentDatasource.getDatasourceId(); + List tables = + Optional.ofNullable(agentDatasource.getSelectTables()).orElse(List.of()); + + // Validate request parameters + if (datasourceId == null) { + throw new InvalidInputException("数据源ID不能为空"); + } + + if (tables.isEmpty()) { + throw new InvalidInputException("表列表不能为空"); + } + + // Execute Schema initialization + Boolean result = + agentDatasourceService.initializeSchemaForAgentWithDatasource( + agentId, datasourceId, tables); + + if (result) { + log.info( + "Successfully initialized schema for agent: {}, tables: {}", agentId, tables.size()); + return ApiResponse.success("Schema初始化成功"); + } else { + throw new InternalServerException("Schema初始化失败"); + } + } catch (Exception e) { + log.error("Failed to initialize schema for agent: {}", agentId, e); + throw new InternalServerException("Schema初始化失败:%s".formatted(e.getMessage())); + } + } + + /** Get list of data sources configured for agent */ + @GetMapping + public ApiResponse getAgentDatasource(@PathVariable Long agentId) { + try { + log.info("Getting datasources for agent: {}", agentId); + List datasources = agentDatasourceService.getAgentDatasource(agentId); + log.info("Successfully retrieved {} datasources for agent: {}", datasources.size(), agentId); + return ApiResponse.success("操作成功", datasources); + } catch (Exception e) { + log.error("Failed to get datasources for agent: {}", agentId, e); + throw new InvalidInputException("获取数据源失败:%s".formatted(e.getMessage()), List.of()); + } + } + + @GetMapping("/active") + public ApiResponse getActiveAgentDatasource(@PathVariable Long agentId) { + try { + log.info("Getting active datasource for agent: {}", agentId); + AgentDatasource datasource = agentDatasourceService.getCurrentAgentDatasource(agentId); + return ApiResponse.success("操作成功", datasource); + } catch (Exception e) { + log.error("Failed to get active datasource for agent: {}", agentId, e); + throw new InvalidInputException("获取数据源失败:%s".formatted(e.getMessage()), List.of()); + } + } + + /** Add data source for agent */ + @PostMapping("/{datasourceId}") + public ApiResponse addDatasourceToAgent( + @PathVariable Long agentId, @PathVariable Integer datasourceId) { + try { + if (datasourceId == null) { + throw new InvalidInputException("数据源ID不能为空"); + } + + AgentDatasource agentDatasource = + agentDatasourceService.addDatasourceToAgent(agentId, datasourceId); + return ApiResponse.success("数据源添加成功", agentDatasource); + } catch (Exception e) { + throw new InternalServerException("数据源添加失败:%s".formatted(e.getMessage())); + } + } + + // 更新选择的数据表 + @PostMapping("/tables") + public ApiResponse updateDatasourceTables( + @PathVariable Long agentId, @RequestBody @Validated UpdateDatasourceTablesDTO dto) { + try { + dto.setTables(Optional.ofNullable(dto.getTables()).orElse(List.of())); + agentDatasourceService.updateDatasourceTables( + agentId, dto.getDatasourceId(), dto.getTables()); + return ApiResponse.success("更新成功"); + } catch (Exception e) { + log.error("Error: ", e); + throw new InternalServerException("更新失败:%s".formatted(e.getMessage())); + } + } + + /** Remove data source association from agent */ + @DeleteMapping("/{datasourceId}") + public ApiResponse removeDatasourceFromAgent( + @PathVariable Long agentId, @PathVariable Integer datasourceId) { + try { + agentDatasourceService.removeDatasourceFromAgent(agentId, datasourceId); + return ApiResponse.success("数据源已移除"); + } catch (Exception e) { + throw new InternalServerException("移除失败:%s".formatted(e.getMessage())); + } + } + + /** 启用/禁用智能体的数据源 */ + @PutMapping("/toggle") + public ApiResponse toggleDatasourceForAgent( + @PathVariable Long agentId, @RequestBody ToggleDatasourceDTO dto) { + try { + Boolean isActive = dto.getIsActive(); + Integer datasourceId = dto.getDatasourceId(); + if (isActive == null || datasourceId == null) { + throw new InvalidInputException("激活状态不能为空"); + } + AgentDatasource agentDatasource = + agentDatasourceService.toggleDatasourceForAgent(agentId, dto.getDatasourceId(), isActive); + return ApiResponse.success(isActive ? "数据源已启用" : "数据源已禁用", agentDatasource); + } catch (Exception e) { + throw new InternalServerException("操作失败:%s".formatted(e.getMessage())); + } + } } diff --git a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/controller/GlobalExceptionHandler.java b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/controller/GlobalExceptionHandler.java new file mode 100644 index 000000000..0b6988523 --- /dev/null +++ b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/controller/GlobalExceptionHandler.java @@ -0,0 +1,26 @@ +package com.alibaba.cloud.ai.dataagent.controller; + +import com.alibaba.cloud.ai.dataagent.exception.InternalServerException; +import com.alibaba.cloud.ai.dataagent.exception.InvalidInputException; +import com.alibaba.cloud.ai.dataagent.vo.ApiResponse; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; + +@RestControllerAdvice +public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { + + @ExceptionHandler(InvalidInputException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ApiResponse handleInvalidInputException(InvalidInputException e) { + return ApiResponse.error(e.getMessage(), e.getData()); + } + + @ExceptionHandler(InternalServerException.class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public ApiResponse handleInvalidInputException(InternalServerException e) { + return ApiResponse.error(e.getMessage()); + } +} diff --git a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/dto/schema/SemanticModelAddDTO.java b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/dto/schema/SemanticModelAddDTO.java index bb82c9468..0a5f8cc39 100644 --- a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/dto/schema/SemanticModelAddDTO.java +++ b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/dto/schema/SemanticModelAddDTO.java @@ -15,66 +15,46 @@ */ package com.alibaba.cloud.ai.dataagent.dto.schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; - -/** - * 添加语义模型的DTO类 - */ +/** 添加语义模型的DTO类 */ @Data @Builder @NoArgsConstructor @AllArgsConstructor public class SemanticModelAddDTO { - /** - * 关联的智能体ID - */ - @NotNull(message = "智能体ID不能为空") - private Integer agentId; - - /** - * 关联的表名 - */ - @NotBlank(message = "表名不能为空") - private String tableName; + /** 关联的智能体ID */ + @NotNull(message = "智能体ID不能为空") + private Long agentId; - /** - * 数据库中的物理字段名 (例如: csat_score) - */ - @NotBlank(message = "数据库字段名不能为空") - private String columnName; + /** 关联的表名 */ + @NotBlank(message = "表名不能为空") + private String tableName; - /** - * 业务名/别名 (例如: 客户满意度分数) - */ - @NotBlank(message = "业务名称不能为空") - private String businessName; + /** 数据库中的物理字段名 (例如: csat_score) */ + @NotBlank(message = "数据库字段名不能为空") + private String columnName; - /** - * 业务名的同义词 (例如: 满意度,客户评分) - */ - private String synonyms; + /** 业务名/别名 (例如: 客户满意度分数) */ + @NotBlank(message = "业务名称不能为空") + private String businessName; - /** - * 业务描述 (用于向LLM解释字段的业务含义) - */ - private String businessDescription; + /** 业务名的同义词 (例如: 满意度,客户评分) */ + private String synonyms; - /** - * 数据库中的物理字段的原始注释 - */ - private String columnComment; + /** 业务描述 (用于向LLM解释字段的业务含义) */ + private String businessDescription; - /** - * 物理数据类型 (例如: int, varchar(20)) - */ - @NotBlank(message = "数据类型不能为空") - private String dataType; + /** 数据库中的物理字段的原始注释 */ + private String columnComment; + /** 物理数据类型 (例如: int, varchar(20)) */ + @NotBlank(message = "数据类型不能为空") + private String dataType; } diff --git a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/entity/AgentDatasource.java b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/entity/AgentDatasource.java index fa02956dc..fdd7ae0b5 100644 --- a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/entity/AgentDatasource.java +++ b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/entity/AgentDatasource.java @@ -16,14 +16,12 @@ package com.alibaba.cloud.ai.dataagent.entity; import com.fasterxml.jackson.annotation.JsonFormat; -import org.springframework.format.annotation.DateTimeFormat; - import java.time.LocalDateTime; import java.util.List; - +import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; -import lombok.AllArgsConstructor; +import org.springframework.format.annotation.DateTimeFormat; @Data @NoArgsConstructor @@ -32,7 +30,7 @@ public class AgentDatasource { private Integer id; - private Integer agentId; + private Long agentId; private Integer datasourceId; @@ -52,7 +50,7 @@ public class AgentDatasource { // 当前数据源选中的表 private List selectTables; - public AgentDatasource(Integer agentId, Integer datasourceId) { + public AgentDatasource(Long agentId, Integer datasourceId) { this.agentId = agentId; this.datasourceId = datasourceId; this.isActive = 1; diff --git a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/entity/SemanticModel.java b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/entity/SemanticModel.java index a3842441d..298819c23 100644 --- a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/entity/SemanticModel.java +++ b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/entity/SemanticModel.java @@ -16,13 +16,12 @@ package com.alibaba.cloud.ai.dataagent.entity; import com.fasterxml.jackson.annotation.JsonFormat; +import java.time.LocalDateTime; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -import java.time.LocalDateTime; - /** * Semantic Model Configuration Entity Class */ @@ -40,7 +39,7 @@ public class SemanticModel { /** * 关联的智能体ID */ - private Integer agentId; + private Long agentId; /** * 关联的数据源ID diff --git a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/exception/InternalServerException.java b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/exception/InternalServerException.java new file mode 100644 index 000000000..bda0fa33d --- /dev/null +++ b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/exception/InternalServerException.java @@ -0,0 +1,22 @@ +/* + * Copyright 2024-2026 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.cloud.ai.dataagent.exception; + +public class InternalServerException extends RuntimeException { + public InternalServerException(String message) { + super(message); + } +} diff --git a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/exception/InvalidInputException.java b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/exception/InvalidInputException.java new file mode 100644 index 000000000..7e4d95c60 --- /dev/null +++ b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/exception/InvalidInputException.java @@ -0,0 +1,31 @@ +/* + * Copyright 2024-2026 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.cloud.ai.dataagent.exception; + +import lombok.Getter; + +public class InvalidInputException extends RuntimeException { + @Getter private Object data; + + public InvalidInputException(String message) { + super(message); + } + + public InvalidInputException(String message, Object data) { + super(message); + this.data = data; + } +} diff --git a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/mapper/AgentDatasourceMapper.java b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/mapper/AgentDatasourceMapper.java index 55657fd45..aa77b84fe 100644 --- a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/mapper/AgentDatasourceMapper.java +++ b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/mapper/AgentDatasourceMapper.java @@ -16,6 +16,7 @@ package com.alibaba.cloud.ai.dataagent.mapper; import com.alibaba.cloud.ai.dataagent.entity.AgentDatasource; +import java.util.List; import org.apache.ibatis.annotations.Delete; import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Mapper; @@ -23,62 +24,59 @@ import org.apache.ibatis.annotations.Select; import org.apache.ibatis.annotations.Update; -import java.util.List; - @Mapper public interface AgentDatasourceMapper { - /** - * Query associated data sources by agent ID (including data source details) - */ - @Select("SELECT ad.*, d.name, d.type, d.host, d.port, d.database_name, " - + "d.connection_url, d.username, d.password, d.status, d.test_status, d.description " - + "FROM agent_datasource ad " + "LEFT JOIN datasource d ON ad.datasource_id = d.id " - + "WHERE ad.agent_id = #{agentId} " + "ORDER BY ad.create_time DESC") - List selectByAgentIdWithDatasource(@Param("agentId") Integer agentId); - - /** - * Query associated data sources by agent ID - */ - @Select("SELECT * FROM agent_datasource WHERE agent_id = #{agentId} ORDER BY create_time DESC") - List selectByAgentId(@Param("agentId") Integer agentId); + /** Query associated data sources by agent ID (including data source details) */ + @Select( + "SELECT ad.*, d.name, d.type, d.host, d.port, d.database_name, " + + "d.connection_url, d.username, d.password, d.status, d.test_status, d.description " + + "FROM agent_datasource ad " + + "LEFT JOIN datasource d ON ad.datasource_id = d.id " + + "WHERE ad.agent_id = #{agentId} " + + "ORDER BY ad.create_time DESC") + List selectByAgentIdWithDatasource(@Param("agentId") Long agentId); - /** - * Query association by agent ID and data source ID - */ - @Select("SELECT * FROM agent_datasource WHERE agent_id = #{agentId} AND datasource_id = #{datasourceId}") - AgentDatasource selectByAgentIdAndDatasourceId(@Param("agentId") Integer agentId, - @Param("datasourceId") Integer datasourceId); + /** Query associated data sources by agent ID */ + @Select("SELECT * FROM agent_datasource WHERE agent_id = #{agentId} ORDER BY create_time DESC") + List selectByAgentId(@Param("agentId") Long agentId); - /** - * Disable all data sources for an agent - */ - @Update("UPDATE agent_datasource SET is_active = 0 WHERE agent_id = #{agentId}") - int disableAllByAgentId(@Param("agentId") Integer agentId); + /** Query association by agent ID and data source ID */ + @Select( + "SELECT * FROM agent_datasource WHERE agent_id = #{agentId} AND datasource_id = #{datasourceId}") + AgentDatasource selectByAgentIdAndDatasourceId( + @Param("agentId") Long agentId, @Param("datasourceId") Integer datasourceId); - /** - * Count the number of enabled data sources for an agent (excluding the specified data - * source) - */ - @Select("SELECT COUNT(*) FROM agent_datasource WHERE agent_id = #{agentId} AND is_active = 1 AND datasource_id != #{excludeDatasourceId}") - int countActiveByAgentIdExcluding(@Param("agentId") Integer agentId, - @Param("excludeDatasourceId") Integer excludeDatasourceId); + /** Disable all data sources for an agent */ + @Update("UPDATE agent_datasource SET is_active = 0 WHERE agent_id = #{agentId}") + int disableAllByAgentId(@Param("agentId") Long agentId); - @Delete("DELETE FROM agent_datasource WHERE datasource_id = #{datasourceId}") - int deleteAllByDatasourceId(@Param("datasourceId") Integer datasourceId); + /** Count the number of enabled data sources for an agent (excluding the specified data source) */ + @Select( + "SELECT COUNT(*) FROM agent_datasource WHERE agent_id = #{agentId} AND is_active = 1 AND datasource_id != #{excludeDatasourceId}") + int countActiveByAgentIdExcluding( + @Param("agentId") Long agentId, @Param("excludeDatasourceId") Integer excludeDatasourceId); - @Insert("INSERT INTO agent_datasource (agent_id, datasource_id, is_active) VALUES (#{agentId}, #{datasourceId}, 1)") - int createNewRelationEnabled(@Param("agentId") Integer agentId, @Param("datasourceId") Integer datasourceId); + @Delete("DELETE FROM agent_datasource WHERE datasource_id = #{datasourceId}") + int deleteAllByDatasourceId(@Param("datasourceId") Integer datasourceId); - @Update("UPDATE agent_datasource SET is_active = #{isActive} WHERE agent_id = #{agentId} AND datasource_id = #{datasourceId}") - int updateRelation(@Param("agentId") Integer agentId, @Param("datasourceId") Integer datasourceId, - @Param("isActive") Integer isActive); + @Insert( + "INSERT INTO agent_datasource (agent_id, datasource_id, is_active) VALUES (#{agentId}, #{datasourceId}, 1)") + int createNewRelationEnabled( + @Param("agentId") Long agentId, @Param("datasourceId") Integer datasourceId); - default int enableRelation(Integer agentId, Integer datasourceId) { - return updateRelation(agentId, datasourceId, 1); - } + @Update( + "UPDATE agent_datasource SET is_active = #{isActive} WHERE agent_id = #{agentId} AND datasource_id = #{datasourceId}") + int updateRelation( + @Param("agentId") Long agentId, + @Param("datasourceId") Integer datasourceId, + @Param("isActive") Integer isActive); - @Delete("DELETE FROM agent_datasource WHERE agent_id = #{agentId} AND datasource_id = #{datasourceId}") - int removeRelation(@Param("agentId") Integer agentId, @Param("datasourceId") Integer datasourceId); + default int enableRelation(Long agentId, Integer datasourceId) { + return updateRelation(agentId, datasourceId, 1); + } + @Delete( + "DELETE FROM agent_datasource WHERE agent_id = #{agentId} AND datasource_id = #{datasourceId}") + int removeRelation(@Param("agentId") Long agentId, @Param("datasourceId") Integer datasourceId); } diff --git a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/service/agent/AgentStartupInitialization.java b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/service/agent/AgentStartupInitialization.java index ec56c7523..eba6f38f8 100644 --- a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/service/agent/AgentStartupInitialization.java +++ b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/service/agent/AgentStartupInitialization.java @@ -19,6 +19,9 @@ import com.alibaba.cloud.ai.dataagent.entity.AgentDatasource; import com.alibaba.cloud.ai.dataagent.service.datasource.AgentDatasourceService; import com.alibaba.cloud.ai.dataagent.service.vectorstore.AgentVectorStoreService; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.DisposableBean; @@ -26,164 +29,171 @@ import org.springframework.boot.ApplicationRunner; import org.springframework.stereotype.Service; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutorService; - @Slf4j @Service @RequiredArgsConstructor public class AgentStartupInitialization implements ApplicationRunner, DisposableBean { - private final AgentService agentService; - - private final AgentVectorStoreService agentVectorStoreService; - - private final AgentDatasourceService agentDatasourceService; - - private final ExecutorService executorService; - - @Override - public void run(ApplicationArguments args) { - log.info("Starting automatic initialization of published agents..."); - - try { - // 因为异步可以让初始化过程在后台运行,不会阻塞Spring启动主线程,提高启动速度和响应性;即使初始化很耗时也不会影响主程序正常启动。 - CompletableFuture.runAsync(this::initializePublishedAgents, executorService).exceptionally(throwable -> { - log.error("Error during agent initialization: {}", throwable.getMessage()); - return null; - }); - - } - catch (Exception e) { - log.error("Failed to start agent initialization process", e); - } - } - - /** - * Initialize all published agents - */ - private void initializePublishedAgents() { - try { - List publishedAgents = agentService.findByStatus("published"); - - if (publishedAgents.isEmpty()) { - log.info("No published agents found, skipping initialization"); - return; - } - - log.info("Found {} published agents, starting initialization...", publishedAgents.size()); - - int successCount = 0; - int failureCount = 0; - - for (Agent agent : publishedAgents) { - try { - boolean initialized = initializeAgentDataSource(agent); - if (initialized) { - successCount++; - log.info("Successfully initialized agent: {} (ID: {})", agent.getName(), agent.getId()); - } - else { - failureCount++; - log.warn("Failed to initialize agent: {} (ID: {}) - no active datasource or tables", - agent.getName(), agent.getId()); - } - } - catch (Exception e) { - failureCount++; - log.error("Error initializing agent: {} (ID: {}, reason: {})", agent.getName(), agent.getId(), - e.getMessage()); - } - - try { - Thread.sleep(1000); - } - catch (InterruptedException e) { - Thread.currentThread().interrupt(); - break; - } - } - - log.info("Agent initialization completed. Success: {}, Failed: {}, Total: {}", successCount, failureCount, - publishedAgents.size()); - - } - catch (Exception e) { - log.error("Error during published agents initialization", e); - } - } - - /** - * Initialize the data source for a single agent - * @param agent The agent - * @return Whether the initialization was successful - */ - private boolean initializeAgentDataSource(Agent agent) { - try { - Long agentId = agent.getId(); - - boolean hasData = isAlreadyInitialized(agentId); - - if (hasData) { - log.info("Agent {} already has vector data , skipping initialization", agentId); - return true; - } - - AgentDatasource activeDatasource = agentDatasourceService - .getCurrentAgentDatasource(Math.toIntExact(agentId)); - - Integer datasourceId = activeDatasource.getDatasourceId(); - - List tables = activeDatasource.getSelectTables(); - - if (tables.isEmpty()) { - log.warn("Datasource {} has no tables available for agent {}", datasourceId, agentId); - return false; - } - - log.info("Initializing agent {} with datasource {} and {} tables", agentId, datasourceId, tables.size()); - - Boolean result = agentDatasourceService.initializeSchemaForAgentWithDatasource(agentId, datasourceId, - tables); - - if (result) { - log.info("Successfully initialized datasource for agent {} with {} tables", agentId, tables.size()); - return true; - } - else { - log.error("Failed to initialize datasource for agent {}", agentId); - return false; - } - - } - catch (Exception e) { - log.error("Error initializing datasource for agent {}, reason: {}", agent.getId(), e.getMessage()); - return false; - } - } - - private boolean isAlreadyInitialized(Long agentId) { - try { - String agentIdStr = String.valueOf(agentId); - return agentVectorStoreService.hasDocuments(agentIdStr); - } - catch (Exception e) { - log.error("Failed to check initialization status for agent: {}, assuming not initialized", agentId, e); - return false; - } - } - - /** - * Clean up resources when the application shuts down. Implement the destroy method of - * the DisposableBean interface - */ - @Override - public void destroy() { - if (!executorService.isShutdown()) { - log.info("Shutting down agent initialization executor service"); - executorService.shutdown(); - } - } - + private final AgentService agentService; + + private final AgentVectorStoreService agentVectorStoreService; + + private final AgentDatasourceService agentDatasourceService; + + private final ExecutorService executorService; + + @Override + public void run(ApplicationArguments args) { + log.info("Starting automatic initialization of published agents..."); + + try { + // 因为异步可以让初始化过程在后台运行,不会阻塞Spring启动主线程,提高启动速度和响应性;即使初始化很耗时也不会影响主程序正常启动。 + CompletableFuture.runAsync(this::initializePublishedAgents, executorService) + .exceptionally( + throwable -> { + log.error("Error during agent initialization: {}", throwable.getMessage()); + return null; + }); + + } catch (Exception e) { + log.error("Failed to start agent initialization process", e); + } + } + + /** Initialize all published agents */ + private void initializePublishedAgents() { + try { + List publishedAgents = agentService.findByStatus("published"); + + if (publishedAgents.isEmpty()) { + log.info("No published agents found, skipping initialization"); + return; + } + + log.info("Found {} published agents, starting initialization...", publishedAgents.size()); + + int successCount = 0; + int failureCount = 0; + + for (Agent agent : publishedAgents) { + try { + boolean initialized = initializeAgentDataSource(agent); + if (initialized) { + successCount++; + log.info("Successfully initialized agent: {} (ID: {})", agent.getName(), agent.getId()); + } else { + failureCount++; + log.warn( + "Failed to initialize agent: {} (ID: {}) - no active datasource or tables", + agent.getName(), + agent.getId()); + } + } catch (Exception e) { + failureCount++; + log.error( + "Error initializing agent: {} (ID: {}, reason: {})", + agent.getName(), + agent.getId(), + e.getMessage()); + } + + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } + } + + log.info( + "Agent initialization completed. Success: {}, Failed: {}, Total: {}", + successCount, + failureCount, + publishedAgents.size()); + + } catch (Exception e) { + log.error("Error during published agents initialization", e); + } + } + + /** + * Initialize the data source for a single agent + * + * @param agent The agent + * @return Whether the initialization was successful + */ + private boolean initializeAgentDataSource(Agent agent) { + try { + Long agentId = agent.getId(); + + boolean hasData = isAlreadyInitialized(agentId); + + if (hasData) { + log.info("Agent {} already has vector data , skipping initialization", agentId); + return true; + } + + AgentDatasource activeDatasource = agentDatasourceService.getCurrentAgentDatasource(agentId); + + Integer datasourceId = activeDatasource.getDatasourceId(); + + List tables = activeDatasource.getSelectTables(); + + if (tables.isEmpty()) { + log.warn("Datasource {} has no tables available for agent {}", datasourceId, agentId); + return false; + } + + log.info( + "Initializing agent {} with datasource {} and {} tables", + agentId, + datasourceId, + tables.size()); + + Boolean result = + agentDatasourceService.initializeSchemaForAgentWithDatasource( + agentId, datasourceId, tables); + + if (result) { + log.info( + "Successfully initialized datasource for agent {} with {} tables", + agentId, + tables.size()); + return true; + } else { + log.error("Failed to initialize datasource for agent {}", agentId); + return false; + } + + } catch (Exception e) { + log.error( + "Error initializing datasource for agent {}, reason: {}", agent.getId(), e.getMessage()); + return false; + } + } + + private boolean isAlreadyInitialized(Long agentId) { + try { + String agentIdStr = String.valueOf(agentId); + return agentVectorStoreService.hasDocuments(agentIdStr); + } catch (Exception e) { + log.error( + "Failed to check initialization status for agent: {}, assuming not initialized", + agentId, + e); + return false; + } + } + + /** + * Clean up resources when the application shuts down. Implement the destroy method of the + * DisposableBean interface + */ + @Override + public void destroy() { + if (!executorService.isShutdown()) { + log.info("Shutting down agent initialization executor service"); + executorService.shutdown(); + } + } } diff --git a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/service/datasource/AgentDatasourceService.java b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/service/datasource/AgentDatasourceService.java index 6cbf7363f..8bf70b88f 100644 --- a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/service/datasource/AgentDatasourceService.java +++ b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/service/datasource/AgentDatasourceService.java @@ -16,31 +16,29 @@ package com.alibaba.cloud.ai.dataagent.service.datasource; import com.alibaba.cloud.ai.dataagent.entity.AgentDatasource; - import java.util.List; public interface AgentDatasourceService { - /** - * Initialize agent's database schema using datasource - */ - Boolean initializeSchemaForAgentWithDatasource(Long agentId, Integer datasourceId, List tables); - - List getAgentDatasource(Integer agentId); + /** Initialize agent's database schema using datasource */ + Boolean initializeSchemaForAgentWithDatasource( + Long agentId, Integer datasourceId, List tables); - default AgentDatasource getCurrentAgentDatasource(Integer agentId) { - return getAgentDatasource(agentId).stream() - .filter(a -> a.getIsActive() != 0) - .findFirst() - .orElseThrow(() -> new IllegalStateException("Agent " + agentId + " has no active datasource")); - } + List getAgentDatasource(Long agentId); - AgentDatasource addDatasourceToAgent(Integer agentId, Integer datasourceId); + default AgentDatasource getCurrentAgentDatasource(Long agentId) { + return getAgentDatasource(agentId).stream() + .filter(a -> a.getIsActive() != 0) + .findFirst() + .orElseThrow( + () -> new IllegalStateException("Agent " + agentId + " has no active datasource")); + } - void removeDatasourceFromAgent(Integer agentId, Integer datasourceId); + AgentDatasource addDatasourceToAgent(Long agentId, Integer datasourceId); - AgentDatasource toggleDatasourceForAgent(Integer agentId, Integer datasourceId, Boolean isActive); + void removeDatasourceFromAgent(Long agentId, Integer datasourceId); - void updateDatasourceTables(Integer agentId, Integer datasourceId, List tables); + AgentDatasource toggleDatasourceForAgent(Long agentId, Integer datasourceId, Boolean isActive); + void updateDatasourceTables(Long agentId, Integer datasourceId, List tables); } diff --git a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/service/datasource/DatasourceService.java b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/service/datasource/DatasourceService.java index c923e6e12..6af2dd570 100644 --- a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/service/datasource/DatasourceService.java +++ b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/service/datasource/DatasourceService.java @@ -19,7 +19,6 @@ import com.alibaba.cloud.ai.dataagent.entity.AgentDatasource; import com.alibaba.cloud.ai.dataagent.entity.Datasource; import com.alibaba.cloud.ai.dataagent.entity.LogicalRelation; - import java.util.List; public interface DatasourceService { @@ -74,7 +73,7 @@ public interface DatasourceService { */ // 应该使用 AgentDatasourceService 中的方法 @Deprecated - List getAgentDatasource(Integer agentId); + List getAgentDatasource(Long agentId); List getDatasourceTables(Integer datasourceId) throws Exception; diff --git a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/service/datasource/impl/AgentDatasourceServiceImpl.java b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/service/datasource/impl/AgentDatasourceServiceImpl.java index 75f4fd7b7..4c39a2d95 100644 --- a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/service/datasource/impl/AgentDatasourceServiceImpl.java +++ b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/service/datasource/impl/AgentDatasourceServiceImpl.java @@ -24,159 +24,172 @@ import com.alibaba.cloud.ai.dataagent.service.datasource.AgentDatasourceService; import com.alibaba.cloud.ai.dataagent.service.datasource.DatasourceService; import com.alibaba.cloud.ai.dataagent.service.schema.SchemaService; +import java.util.List; +import java.util.Optional; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.Assert; -import java.util.List; -import java.util.Optional; - @Slf4j @Service @AllArgsConstructor public class AgentDatasourceServiceImpl implements AgentDatasourceService { - private final DatasourceService datasourceService; - - private final SchemaService schemaService; - - private final AgentDatasourceMapper agentDatasourceMapper; - - private final AgentDatasourceTablesMapper tablesMapper; - - @Override - public Boolean initializeSchemaForAgentWithDatasource(Long agentId, Integer datasourceId, List tables) { - Assert.notNull(agentId, "Agent ID cannot be null"); - Assert.notNull(datasourceId, "Datasource ID cannot be null"); - Assert.notEmpty(tables, "Tables cannot be empty"); - try { - String agentIdStr = String.valueOf(agentId); - log.info("Initializing schema for agent: {} with datasource: {}, tables: {}", agentIdStr, datasourceId, - tables); - - // Get data source information - Datasource datasource = datasourceService.getDatasourceById(datasourceId); - if (datasource == null) { - throw new RuntimeException("Datasource not found with id: " + datasourceId); - } - - // Create database configuration - DbConfigBO dbConfig = datasourceService.getDbConfig(datasource); - - // Create SchemaInitRequest - SchemaInitRequest schemaInitRequest = new SchemaInitRequest(); - schemaInitRequest.setDbConfig(dbConfig); - schemaInitRequest.setTables(tables); - - log.info("Created SchemaInitRequest for agent: {}, dbConfig: {}, tables: {}", agentIdStr, dbConfig, tables); - - // Call the original initialization method - return schemaService.schema(agentIdStr, schemaInitRequest); - - } - catch (Exception e) { - log.error("Failed to initialize schema for agent: {} with datasource: {}", agentId, datasourceId, e); - throw new RuntimeException("Failed to initialize schema for agent " + agentId + ": " + e.getMessage(), e); - } - } - - @Override - public List getAgentDatasource(Integer agentId) { - Assert.notNull(agentId, "Agent ID cannot be null"); - List adentDatasources = agentDatasourceMapper.selectByAgentIdWithDatasource(agentId); - - // Manually fill in the data source information (since MyBatis Plus does not - // directly support complex join query result mapping) - for (AgentDatasource agentDatasource : adentDatasources) { - if (agentDatasource.getDatasourceId() != null) { - Datasource datasource = datasourceService.getDatasourceById(agentDatasource.getDatasourceId()); - agentDatasource.setDatasource(datasource); - } - // 获取选中的数据表 - int id = agentDatasource.getId(); - List tables = tablesMapper.getAgentDatasourceTables(id); - agentDatasource.setSelectTables(Optional.ofNullable(tables).orElse(List.of())); - } - - return adentDatasources; - } - - @Override - @Transactional - public AgentDatasource addDatasourceToAgent(Integer agentId, Integer datasourceId) { - // First, disable other data sources for this agent (an agent can only have one - // enabled data source) - agentDatasourceMapper.disableAllByAgentId(agentId); - - // Check if an association already exists - AgentDatasource existing = agentDatasourceMapper.selectByAgentIdAndDatasourceId(agentId, datasourceId); - - AgentDatasource result; - if (existing != null) { - // If it exists, activate the association - agentDatasourceMapper.enableRelation(agentId, datasourceId); - - // 删除已有的表 - tablesMapper.removeAllTables(existing.getId()); - - // Query and return the updated association - result = agentDatasourceMapper.selectByAgentIdAndDatasourceId(agentId, datasourceId); - } - else { - // If it does not exist, create a new association - AgentDatasource agentDatasource = new AgentDatasource(agentId, datasourceId); - agentDatasource.setIsActive(1); - agentDatasourceMapper.createNewRelationEnabled(agentId, datasourceId); - result = agentDatasource; - } - result.setSelectTables(List.of()); - return result; - } - - @Override - public void removeDatasourceFromAgent(Integer agentId, Integer datasourceId) { - agentDatasourceMapper.removeRelation(agentId, datasourceId); - } - - @Override - public AgentDatasource toggleDatasourceForAgent(Integer agentId, Integer datasourceId, Boolean isActive) { - // If enabling data source, first check if there are other enabled data sources - if (isActive) { - int activeCount = agentDatasourceMapper.countActiveByAgentIdExcluding(agentId, datasourceId); - if (activeCount > 0) { - throw new RuntimeException("同一智能体下只能启用一个数据源,请先禁用其他数据源后再启用此数据源"); - } - } - - // Update data source status - int updated = agentDatasourceMapper.updateRelation(agentId, datasourceId, isActive ? 1 : 0); - - if (updated == 0) { - throw new RuntimeException("未找到相关的数据源关联记录"); - } - - // Return the updated association record - return agentDatasourceMapper.selectByAgentIdAndDatasourceId(agentId, datasourceId); - } - - @Override - @Transactional - public void updateDatasourceTables(Integer agentId, Integer datasourceId, List tables) { - if (agentId == null || datasourceId == null || tables == null) { - throw new IllegalArgumentException("参数不能为空"); - } - AgentDatasource datasource = agentDatasourceMapper.selectByAgentIdAndDatasourceId(agentId, datasourceId); - if (datasource == null) { - throw new IllegalArgumentException("未找到对应的数据源关联记录"); - } - if (tables.isEmpty()) { - tablesMapper.removeAllTables(datasource.getId()); - } - else { - tablesMapper.updateAgentDatasourceTables(datasource.getId(), tables); - } - } - + private final DatasourceService datasourceService; + + private final SchemaService schemaService; + + private final AgentDatasourceMapper agentDatasourceMapper; + + private final AgentDatasourceTablesMapper tablesMapper; + + @Override + public Boolean initializeSchemaForAgentWithDatasource( + Long agentId, Integer datasourceId, List tables) { + Assert.notNull(agentId, "Agent ID cannot be null"); + Assert.notNull(datasourceId, "Datasource ID cannot be null"); + Assert.notEmpty(tables, "Tables cannot be empty"); + try { + String agentIdStr = String.valueOf(agentId); + log.info( + "Initializing schema for agent: {} with datasource: {}, tables: {}", + agentIdStr, + datasourceId, + tables); + + // Get data source information + Datasource datasource = datasourceService.getDatasourceById(datasourceId); + if (datasource == null) { + throw new RuntimeException("Datasource not found with id: " + datasourceId); + } + + // Create database configuration + DbConfigBO dbConfig = datasourceService.getDbConfig(datasource); + + // Create SchemaInitRequest + SchemaInitRequest schemaInitRequest = new SchemaInitRequest(); + schemaInitRequest.setDbConfig(dbConfig); + schemaInitRequest.setTables(tables); + + log.info( + "Created SchemaInitRequest for agent: {}, dbConfig: {}, tables: {}", + agentIdStr, + dbConfig, + tables); + + // Call the original initialization method + return schemaService.schema(agentIdStr, schemaInitRequest); + + } catch (Exception e) { + log.error( + "Failed to initialize schema for agent: {} with datasource: {}", + agentId, + datasourceId, + e); + throw new RuntimeException( + "Failed to initialize schema for agent " + agentId + ": " + e.getMessage(), e); + } + } + + @Override + public List getAgentDatasource(Long agentId) { + Assert.notNull(agentId, "Agent ID cannot be null"); + List adentDatasources = + agentDatasourceMapper.selectByAgentIdWithDatasource(agentId); + + // Manually fill in the data source information (since MyBatis Plus does not + // directly support complex join query result mapping) + for (AgentDatasource agentDatasource : adentDatasources) { + if (agentDatasource.getDatasourceId() != null) { + Datasource datasource = + datasourceService.getDatasourceById(agentDatasource.getDatasourceId()); + agentDatasource.setDatasource(datasource); + } + // 获取选中的数据表 + int id = agentDatasource.getId(); + List tables = tablesMapper.getAgentDatasourceTables(id); + agentDatasource.setSelectTables(Optional.ofNullable(tables).orElse(List.of())); + } + + return adentDatasources; + } + + @Override + @Transactional + public AgentDatasource addDatasourceToAgent(Long agentId, Integer datasourceId) { + // First, disable other data sources for this agent (an agent can only have one + // enabled data source) + agentDatasourceMapper.disableAllByAgentId(agentId); + + // Check if an association already exists + AgentDatasource existing = + agentDatasourceMapper.selectByAgentIdAndDatasourceId(agentId, datasourceId); + + AgentDatasource result; + if (existing != null) { + // If it exists, activate the association + agentDatasourceMapper.enableRelation(agentId, datasourceId); + + // 删除已有的表 + tablesMapper.removeAllTables(existing.getId()); + + // Query and return the updated association + result = agentDatasourceMapper.selectByAgentIdAndDatasourceId(agentId, datasourceId); + } else { + // If it does not exist, create a new association + AgentDatasource agentDatasource = new AgentDatasource(agentId, datasourceId); + agentDatasource.setIsActive(1); + agentDatasourceMapper.createNewRelationEnabled(agentId, datasourceId); + result = agentDatasource; + } + result.setSelectTables(List.of()); + return result; + } + + @Override + public void removeDatasourceFromAgent(Long agentId, Integer datasourceId) { + agentDatasourceMapper.removeRelation(agentId, datasourceId); + } + + @Override + public AgentDatasource toggleDatasourceForAgent( + Long agentId, Integer datasourceId, Boolean isActive) { + // If enabling data source, first check if there are other enabled data sources + if (isActive) { + int activeCount = agentDatasourceMapper.countActiveByAgentIdExcluding(agentId, datasourceId); + if (activeCount > 0) { + throw new RuntimeException("同一智能体下只能启用一个数据源,请先禁用其他数据源后再启用此数据源"); + } + } + + // Update data source status + int updated = agentDatasourceMapper.updateRelation(agentId, datasourceId, isActive ? 1 : 0); + + if (updated == 0) { + throw new RuntimeException("未找到相关的数据源关联记录"); + } + + // Return the updated association record + return agentDatasourceMapper.selectByAgentIdAndDatasourceId(agentId, datasourceId); + } + + @Override + @Transactional + public void updateDatasourceTables(Long agentId, Integer datasourceId, List tables) { + if (agentId == null || datasourceId == null || tables == null) { + throw new IllegalArgumentException("参数不能为空"); + } + AgentDatasource datasource = + agentDatasourceMapper.selectByAgentIdAndDatasourceId(agentId, datasourceId); + if (datasource == null) { + throw new IllegalArgumentException("未找到对应的数据源关联记录"); + } + if (tables.isEmpty()) { + tablesMapper.removeAllTables(datasource.getId()); + } else { + tablesMapper.updateAgentDatasourceTables(datasource.getId(), tables); + } + } } diff --git a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/service/datasource/impl/DatasourceServiceImpl.java b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/service/datasource/impl/DatasourceServiceImpl.java index 1e8d887de..513b0291d 100644 --- a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/service/datasource/impl/DatasourceServiceImpl.java +++ b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/service/datasource/impl/DatasourceServiceImpl.java @@ -15,30 +15,24 @@ */ package com.alibaba.cloud.ai.dataagent.service.datasource.impl; -import com.alibaba.cloud.ai.dataagent.connector.accessor.Accessor; -import com.alibaba.cloud.ai.dataagent.connector.accessor.AccessorFactory; +import com.alibaba.cloud.ai.dataagent.bo.DbConfigBO; import com.alibaba.cloud.ai.dataagent.bo.schema.ColumnInfoBO; -import com.alibaba.cloud.ai.dataagent.connector.DbQueryParameter; import com.alibaba.cloud.ai.dataagent.bo.schema.TableInfoBO; +import com.alibaba.cloud.ai.dataagent.connector.DbQueryParameter; +import com.alibaba.cloud.ai.dataagent.connector.accessor.Accessor; +import com.alibaba.cloud.ai.dataagent.connector.accessor.AccessorFactory; import com.alibaba.cloud.ai.dataagent.connector.pool.DBConnectionPool; import com.alibaba.cloud.ai.dataagent.connector.pool.DBConnectionPoolFactory; -import com.alibaba.cloud.ai.dataagent.bo.DbConfigBO; -import com.alibaba.cloud.ai.dataagent.entity.Datasource; import com.alibaba.cloud.ai.dataagent.entity.AgentDatasource; +import com.alibaba.cloud.ai.dataagent.entity.Datasource; import com.alibaba.cloud.ai.dataagent.entity.LogicalRelation; import com.alibaba.cloud.ai.dataagent.enums.ErrorCodeEnum; -import com.alibaba.cloud.ai.dataagent.service.datasource.handler.DatasourceTypeHandler; -import com.alibaba.cloud.ai.dataagent.service.datasource.handler.registry.DatasourceTypeHandlerRegistry; -import com.alibaba.cloud.ai.dataagent.mapper.DatasourceMapper; import com.alibaba.cloud.ai.dataagent.mapper.AgentDatasourceMapper; +import com.alibaba.cloud.ai.dataagent.mapper.DatasourceMapper; import com.alibaba.cloud.ai.dataagent.mapper.LogicalRelationMapper; import com.alibaba.cloud.ai.dataagent.service.datasource.DatasourceService; -import lombok.AllArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - +import com.alibaba.cloud.ai.dataagent.service.datasource.handler.DatasourceTypeHandler; +import com.alibaba.cloud.ai.dataagent.service.datasource.handler.registry.DatasourceTypeHandlerRegistry; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -46,6 +40,11 @@ import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; // todo: 检查Mapper的返回值,判断是否执行成功(或者对Mapper进行AOP) @Slf4j @@ -185,7 +184,7 @@ private boolean realConnectionTest(Datasource datasource) { @Override @Deprecated - public List getAgentDatasource(Integer agentId) { + public List getAgentDatasource(Long agentId) { List adentDatasources = agentDatasourceMapper.selectByAgentIdWithDatasource(agentId); // Manually fill in the data source information (since MyBatis Plus does not diff --git a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/service/semantic/SemanticModelServiceImpl.java b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/service/semantic/SemanticModelServiceImpl.java index fd9843d5a..41101383c 100644 --- a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/service/semantic/SemanticModelServiceImpl.java +++ b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/service/semantic/SemanticModelServiceImpl.java @@ -23,13 +23,12 @@ import com.alibaba.cloud.ai.dataagent.mapper.AgentDatasourceMapper; import com.alibaba.cloud.ai.dataagent.mapper.SemanticModelMapper; import com.alibaba.cloud.ai.dataagent.vo.BatchImportResult; +import java.util.List; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; -import java.util.List; - @Service @AllArgsConstructor @Slf4j @@ -75,7 +74,7 @@ public void addSemanticModel(SemanticModel semanticModel) { @Override public boolean addSemanticModel(SemanticModelAddDTO dto) { // 根据agentId查询关联的datasourceId - Integer datasourceId = findDatasourceIdByAgentId(dto.getAgentId().longValue()); + Integer datasourceId = findDatasourceIdByAgentId(dto.getAgentId()); // 转换DTO为Entity SemanticModel semanticModel = SemanticModel.builder() @@ -97,11 +96,9 @@ public boolean addSemanticModel(SemanticModelAddDTO dto) { return true; } - /** - * 根据agentId查找关联的datasourceId 如果有多个数据源,返回第一个启用的数据源 - */ + /** 根据agentId查找关联的datasourceId 如果有多个数据源,返回第一个启用的数据源 */ private Integer findDatasourceIdByAgentId(Long agentId) { - List agentDatasources = agentDatasourceMapper.selectByAgentId(agentId.intValue()); + List agentDatasources = agentDatasourceMapper.selectByAgentId(agentId); if (agentDatasources.isEmpty()) { throw new RuntimeException("No datasource found for Agent ID " + agentId); @@ -143,12 +140,6 @@ public void deleteSemanticModel(Long id) { semanticModelMapper.deleteById(id); } - @Override - public void updateSemanticModel(Long id, SemanticModel semanticModel) { - semanticModel.setId(id); - semanticModelMapper.updateById(semanticModel); - } - @Override public BatchImportResult batchImport(SemanticModelBatchImportDTO dto) { BatchImportResult result = BatchImportResult.builder() @@ -191,7 +182,7 @@ public BatchImportResult batchImport(SemanticModelBatchImportDTO dto) { else { // 插入新记录 SemanticModel newModel = SemanticModel.builder() - .agentId(dto.getAgentId().intValue()) + .agentId(dto.getAgentId()) .datasourceId(datasourceId) .tableName(item.getTableName()) .columnName(item.getColumnName()) @@ -252,4 +243,10 @@ public BatchImportResult importFromExcel(MultipartFile file, Long agentId) { } } + @Override + public void updateSemanticModel(Long id, SemanticModel semanticModel) { + semanticModel.setId(id); + semanticModelMapper.updateById(semanticModel); + } + } diff --git a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/util/DatabaseUtil.java b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/util/DatabaseUtil.java index ed186d2a7..98481ea36 100644 --- a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/util/DatabaseUtil.java +++ b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/util/DatabaseUtil.java @@ -15,9 +15,9 @@ */ package com.alibaba.cloud.ai.dataagent.util; +import com.alibaba.cloud.ai.dataagent.bo.DbConfigBO; import com.alibaba.cloud.ai.dataagent.connector.accessor.Accessor; import com.alibaba.cloud.ai.dataagent.connector.accessor.AccessorFactory; -import com.alibaba.cloud.ai.dataagent.bo.DbConfigBO; import com.alibaba.cloud.ai.dataagent.entity.AgentDatasource; import com.alibaba.cloud.ai.dataagent.service.datasource.AgentDatasourceService; import com.alibaba.cloud.ai.dataagent.service.datasource.DatasourceService; @@ -39,7 +39,7 @@ public class DatabaseUtil { private final DatasourceService datasourceService; - public DbConfigBO getAgentDbConfig(Integer agentId) { + public DbConfigBO getAgentDbConfig(Long agentId) { log.info("Getting datasource config for agent: {}", agentId); // Get the enabled data source for the agent @@ -52,7 +52,7 @@ public DbConfigBO getAgentDbConfig(Integer agentId) { return dbConfig; } - public Accessor getAgentAccessor(Integer agentId) { + public Accessor getAgentAccessor(Long agentId) { DbConfigBO dbConfig = getAgentDbConfig(agentId); return accessorFactory.getAccessorByDbConfig(dbConfig); } diff --git a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/util/FluxUtil.java b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/util/FluxUtil.java index 8f46cdc97..36f9a06e4 100644 --- a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/util/FluxUtil.java +++ b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/util/FluxUtil.java @@ -20,127 +20,175 @@ import com.alibaba.cloud.ai.graph.action.NodeAction; import com.alibaba.cloud.ai.graph.streaming.OutputType; import com.alibaba.cloud.ai.graph.streaming.StreamingOutput; +import java.util.Map; +import java.util.function.Function; +import java.util.function.Supplier; import org.springframework.ai.chat.model.ChatResponse; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import java.util.Map; -import java.util.function.Supplier; -import java.util.function.Function; - /** * @author vlsmb * @since 2025/10/22 */ public final class FluxUtil { - private FluxUtil() { - - } - - /** - * 级联两个具有前后关系的Flux - * @param originFlux 第一个Flux - * @param nextFluxFunc 根据第一个Flux的聚合结果生成第二个Flux - * @param aggregator 聚合第一个Flux的所有数据 - * @param preFlux 在第一个Flux前添加的信息Flux - * @param middleFlux 在第一个Flux和第二个Flux之间添加的信息Flux - * @param endFlux 在第二个Flux后添加的信息Flux - */ - public static Flux cascadeFlux(Flux originFlux, Function> nextFluxFunc, - Function, Mono> aggregator, Flux preFlux, Flux middleFlux, Flux endFlux) { - // 缓存原始流避免重复执行 - Flux cachedOrigin = originFlux.cache(); - - // 聚合结果 - Mono aggregatedResult = aggregator.apply(cachedOrigin).cache(); - - // 构建完整流 - Flux secondFlux = aggregatedResult.flatMapMany(nextFluxFunc); - - return preFlux.concatWith(cachedOrigin).concatWith(middleFlux).concatWith(secondFlux).concatWith(endFlux); - } - - /** - * 级联两个具有前后关系的Flux - * @param originFlux 第一个Flux - * @param nextFluxFunc 根据第一个Flux的聚合结果生成第二个Flux - * @param aggregator 聚合第一个Flux的所有数据 - */ - public static Flux cascadeFlux(Flux originFlux, Function> nextFluxFunc, - Function, Mono> aggregator) { - return cascadeFlux(originFlux, nextFluxFunc, aggregator, Flux.empty(), Flux.empty(), Flux.empty()); - } - - /** - * Quickly create streaming generator with start and end messages - * @param nodeClass node class - * @param state state - * @param startMessage start message - * @param completionMessage completion message - * @param resultMapper result mapping function - * @param sourceFlux source data stream - * @return Flux instance - */ - public static Flux> createStreamingGeneratorWithMessages( - Class nodeClass, OverAllState state, String startMessage, String completionMessage, - Function> resultMapper, Flux sourceFlux) { - String nodeName = nodeClass.getSimpleName(); - - // Used to collect actual processing results - final StringBuilder collectedResult = new StringBuilder(); - - // wrapperFlux - Flux startFlux = (startMessage == null ? Flux.empty() - : Flux.just(ChatResponseUtil.createResponse(startMessage))); - Flux wrapperFlux = startFlux.concatWith(sourceFlux.doOnNext(chatResponse -> { - String text = ChatResponseUtil.getText(chatResponse); - collectedResult.append(text); - })); - if (completionMessage != null) { - wrapperFlux = wrapperFlux.concatWith(Flux.just(ChatResponseUtil.createResponse(completionMessage))); - } - return toStreamingResponseFlux(nodeName, state, wrapperFlux, - () -> resultMapper.apply(collectedResult.toString())); - } - - public static Flux> createStreamingGeneratorWithMessages( - Class nodeClass, OverAllState state, - Function> resultMapper, Flux sourceFlux) { - return createStreamingGeneratorWithMessages(nodeClass, state, null, null, resultMapper, sourceFlux); - } - - /** - * create streaming generator with start and end flux - * @param nodeClass node class - * @param state state - * @param sourceFlux source data stream - * @param preFlux preFlux - * @param sufFlux sufFlux - * @param sourceMapper result of sourceFlux mapping function - * @return Flux instance - */ - public static Flux> createStreamingGenerator(Class nodeClass, - OverAllState state, Flux sourceFlux, Flux preFlux, Flux sufFlux, - Function> sourceMapper) { - String nodeName = nodeClass.getSimpleName(); - // Used to collect actual processing results - final StringBuilder collectedResult = new StringBuilder(); - sourceFlux = sourceFlux.doOnNext(r -> collectedResult.append(ChatResponseUtil.getText(r))); - return toStreamingResponseFlux(nodeName, state, Flux.concat(preFlux, sourceFlux, sufFlux), - () -> sourceMapper.apply(collectedResult.toString())); - } - - private static Flux> toStreamingResponseFlux(String nodeName, OverAllState state, - Flux sourceFlux, Supplier> resultSupplier) { - Flux> streamingFlux = sourceFlux - .filter(response -> response != null && response.getResult() != null - && response.getResult().getOutput() != null) - .map(response -> GraphResponse.of(new StreamingOutput<>(response.getResult().getOutput(), response, - nodeName, "", state, OutputType.from(true, nodeName)))); - - return streamingFlux.concatWith(Mono.fromSupplier(() -> GraphResponse.done(resultSupplier.get()))) - .onErrorResume(error -> Flux.just(GraphResponse.error(error))); - } - + private FluxUtil() {} + + /** + * 级联两个具有前后关系的Flux + * + * @param originFlux 第一个Flux + * @param nextFluxFunc 根据第一个Flux的聚合结果生成第二个Flux + * @param aggregator 聚合第一个Flux的所有数据 + * @param preFlux 在第一个Flux前添加的信息Flux + * @param middleFlux 在第一个Flux和第二个Flux之间添加的信息Flux + * @param endFlux 在第二个Flux后添加的信息Flux + */ + public static Flux cascadeFlux( + Flux originFlux, + Function> nextFluxFunc, + Function, Mono> aggregator, + Flux preFlux, + Flux middleFlux, + Flux endFlux) { + // 缓存原始流避免重复执行 + Flux cachedOrigin = originFlux.cache(); + + // 聚合结果 + Mono aggregatedResult = aggregator.apply(cachedOrigin).cache(); + + // 构建完整流 + Flux secondFlux = aggregatedResult.flatMapMany(nextFluxFunc); + + return preFlux + .concatWith(cachedOrigin) + .concatWith(middleFlux) + .concatWith(secondFlux) + .concatWith(endFlux); + } + + /** + * 级联两个具有前后关系的Flux + * + * @param originFlux 第一个Flux + * @param nextFluxFunc 根据第一个Flux的聚合结果生成第二个Flux + * @param aggregator 聚合第一个Flux的所有数据 + */ + public static Flux cascadeFlux( + Flux originFlux, + Function> nextFluxFunc, + Function, Mono> aggregator) { + return cascadeFlux( + originFlux, nextFluxFunc, aggregator, Flux.empty(), Flux.empty(), Flux.empty()); + } + + /** + * Quickly create streaming generator with start and end messages + * + * @param nodeClass node class + * @param state state + * @param startMessage start message + * @param completionMessage completion message + * @param resultMapper result mapping function + * @param sourceFlux source data stream + * @return Flux instance + */ + public static Flux> createStreamingGeneratorWithMessages( + Class nodeClass, + OverAllState state, + String startMessage, + String completionMessage, + Function> resultMapper, + Flux sourceFlux) { + String nodeName = nodeClass.getSimpleName(); + + // Used to collect actual processing results + final StringBuilder collectedResult = new StringBuilder(); + + // wrapperFlux + Flux startFlux = + (startMessage == null + ? Flux.empty() + : Flux.just(ChatResponseUtil.createResponse(startMessage))); + Flux wrapperFlux = + startFlux.concatWith( + sourceFlux.doOnNext( + chatResponse -> { + String text = ChatResponseUtil.getText(chatResponse); + collectedResult.append(text); + })); + if (completionMessage != null) { + wrapperFlux = + wrapperFlux.concatWith(Flux.just(ChatResponseUtil.createResponse(completionMessage))); + } + return toStreamingResponseFlux( + nodeName, state, wrapperFlux, () -> resultMapper.apply(collectedResult.toString())); + } + + public static Flux> createStreamingGeneratorWithMessages( + Class nodeClass, + OverAllState state, + Function> resultMapper, + Flux sourceFlux) { + return createStreamingGeneratorWithMessages( + nodeClass, state, null, null, resultMapper, sourceFlux); + } + + /** + * create streaming generator with start and end flux + * + * @param nodeClass node class + * @param state state + * @param sourceFlux source data stream + * @param preFlux preFlux + * @param sufFlux sufFlux + * @param sourceMapper result of sourceFlux mapping function + * @return Flux instance + */ + public static Flux> createStreamingGenerator( + Class nodeClass, + OverAllState state, + Flux sourceFlux, + Flux preFlux, + Flux sufFlux, + Function> sourceMapper) { + String nodeName = nodeClass.getSimpleName(); + // Used to collect actual processing results + final StringBuilder collectedResult = new StringBuilder(); + sourceFlux = sourceFlux.doOnNext(r -> collectedResult.append(ChatResponseUtil.getText(r))); + return toStreamingResponseFlux( + nodeName, + state, + Flux.concat(preFlux, sourceFlux, sufFlux), + () -> sourceMapper.apply(collectedResult.toString())); + } + + private static Flux> toStreamingResponseFlux( + String nodeName, + OverAllState state, + Flux sourceFlux, + Supplier> resultSupplier) { + Flux> streamingFlux = + sourceFlux + .filter( + response -> + response != null + && response.getResult() != null + && response.getResult().getOutput() != null) + .map( + response -> + GraphResponse.of( + new StreamingOutput<>( + response.getResult().getOutput(), + response, + nodeName, + "", + state, + OutputType.from(true, nodeName)))); + + return streamingFlux + .concatWith(Mono.fromSupplier(() -> GraphResponse.done(resultSupplier.get()))) + .onErrorResume(error -> Flux.just(GraphResponse.error(error))); + } } diff --git a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/workflow/node/SqlExecuteNode.java b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/workflow/node/SqlExecuteNode.java index 58f1ae157..66821d4d5 100644 --- a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/workflow/node/SqlExecuteNode.java +++ b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/workflow/node/SqlExecuteNode.java @@ -53,6 +53,7 @@ import java.util.Map; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.stereotype.Component; import reactor.core.publisher.Flux; @@ -60,6 +61,7 @@ /** * SQL execution node that executes SQL queries against the database. * + *

* This node is responsible for: - Executing SQL queries generated by previous nodes - * Handling query results and errors - Providing streaming feedback to users during * execution - Managing step-by-step result accumulation @@ -93,11 +95,11 @@ public Map apply(OverAllState state) throws Exception { // Get the agent ID from the state String agentIdStr = StateUtil.getStringValue(state, Constant.AGENT_ID); - if (agentIdStr == null || agentIdStr.trim().isEmpty()) { + if (StringUtils.isBlank(agentIdStr)) { throw new IllegalStateException("Agent ID cannot be empty."); } - Integer agentId = Integer.valueOf(agentIdStr); + Long agentId = Long.valueOf(agentIdStr); // Dynamically get the data source configuration for an agent DbConfigBO dbConfig = databaseUtil.getAgentDbConfig(agentId); @@ -108,6 +110,7 @@ public Map apply(OverAllState state) throws Exception { /** * Executes the SQL query against the database and handles the results. * + *

* This method follows the business-logic-first pattern: 1. Execute the actual SQL * query immediately 2. Process and store the results 3. Create streaming output for * user experience only @@ -120,7 +123,7 @@ public Map apply(OverAllState state) throws Exception { */ @SuppressWarnings("unchecked") private Map executeSqlQuery(OverAllState state, Integer currentStep, String sqlQuery, - DbConfigBO dbConfig, Integer agentId) { + DbConfigBO dbConfig, Long agentId) { // Execute business logic first - actual SQL execution DbQueryParameter dbQueryParameter = new DbQueryParameter(); dbQueryParameter.setSql(sqlQuery); @@ -192,7 +195,6 @@ private Map executeSqlQuery(OverAllState state, Integer currentS // result Flux> generator = FluxUtil.createStreamingGeneratorWithMessages(this.getClass(), state, v -> result, displayFlux); - return Map.of(SQL_EXECUTE_NODE_OUTPUT, generator); } diff --git a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/workflow/node/TableRelationNode.java b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/workflow/node/TableRelationNode.java index b80c77707..4d657d7de 100644 --- a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/workflow/node/TableRelationNode.java +++ b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/workflow/node/TableRelationNode.java @@ -15,6 +15,18 @@ */ package com.alibaba.cloud.ai.dataagent.workflow.node; +import static com.alibaba.cloud.ai.dataagent.constant.Constant.AGENT_ID; +import static com.alibaba.cloud.ai.dataagent.constant.Constant.COLUMN_DOCUMENTS__FOR_SCHEMA_OUTPUT; +import static com.alibaba.cloud.ai.dataagent.constant.Constant.DB_DIALECT_TYPE; +import static com.alibaba.cloud.ai.dataagent.constant.Constant.EVIDENCE; +import static com.alibaba.cloud.ai.dataagent.constant.Constant.GENEGRATED_SEMANTIC_MODEL_PROMPT; +import static com.alibaba.cloud.ai.dataagent.constant.Constant.SQL_GENERATE_SCHEMA_MISSING_ADVICE; +import static com.alibaba.cloud.ai.dataagent.constant.Constant.TABLE_DOCUMENTS_FOR_SCHEMA_OUTPUT; +import static com.alibaba.cloud.ai.dataagent.constant.Constant.TABLE_RELATION_EXCEPTION_OUTPUT; +import static com.alibaba.cloud.ai.dataagent.constant.Constant.TABLE_RELATION_OUTPUT; +import static com.alibaba.cloud.ai.dataagent.constant.Constant.TABLE_RELATION_RETRY_COUNT; +import static com.alibaba.cloud.ai.dataagent.prompt.PromptHelper.buildSemanticModelPrompt; + import com.alibaba.cloud.ai.dataagent.bo.DbConfigBO; import com.alibaba.cloud.ai.dataagent.dto.schema.SchemaDTO; import com.alibaba.cloud.ai.dataagent.dto.schema.TableDTO; @@ -24,17 +36,25 @@ import com.alibaba.cloud.ai.dataagent.enums.TextType; import com.alibaba.cloud.ai.dataagent.service.datasource.AgentDatasourceService; import com.alibaba.cloud.ai.dataagent.service.datasource.DatasourceService; -import com.alibaba.cloud.ai.dataagent.util.DatabaseUtil; -import com.alibaba.cloud.ai.graph.GraphResponse; -import com.alibaba.cloud.ai.graph.OverAllState; -import com.alibaba.cloud.ai.graph.action.NodeAction; -import com.alibaba.cloud.ai.graph.streaming.StreamingOutput; import com.alibaba.cloud.ai.dataagent.service.nl2sql.Nl2SqlService; import com.alibaba.cloud.ai.dataagent.service.schema.SchemaService; import com.alibaba.cloud.ai.dataagent.service.semantic.SemanticModelService; import com.alibaba.cloud.ai.dataagent.util.ChatResponseUtil; +import com.alibaba.cloud.ai.dataagent.util.DatabaseUtil; import com.alibaba.cloud.ai.dataagent.util.FluxUtil; import com.alibaba.cloud.ai.dataagent.util.StateUtil; +import com.alibaba.cloud.ai.graph.GraphResponse; +import com.alibaba.cloud.ai.graph.OverAllState; +import com.alibaba.cloud.ai.graph.action.NodeAction; +import com.alibaba.cloud.ai.graph.streaming.StreamingOutput; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; +import java.util.stream.Collectors; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.ai.chat.model.ChatResponse; @@ -42,17 +62,11 @@ import org.springframework.stereotype.Component; import reactor.core.publisher.Flux; -import java.util.*; -import java.util.function.Consumer; -import java.util.stream.Collectors; - -import static com.alibaba.cloud.ai.dataagent.constant.Constant.*; -import static com.alibaba.cloud.ai.dataagent.prompt.PromptHelper.buildSemanticModelPrompt; - /** * Table relationship inference node that automatically completes complex structures like * JOINs and foreign keys. * + *

* This node is responsible for: - Inferring relationships between tables and fields - * Building initial schema from documents - Processing schema selection based on input and * evidence - Handling schema advice for missing information @@ -88,9 +102,9 @@ public Map apply(OverAllState state) throws Exception { String agentIdStr = StateUtil.getStringValue(state, AGENT_ID); // Execute business logic first - get final result immediately - DbConfigBO agentDbConfig = databaseUtil.getAgentDbConfig(Integer.valueOf(agentIdStr)); + DbConfigBO agentDbConfig = databaseUtil.getAgentDbConfig(Long.valueOf(agentIdStr)); - List logicalForeignKeys = getLogicalForeignKeys(Integer.valueOf(agentIdStr), tableDocuments); + List logicalForeignKeys = getLogicalForeignKeys(Long.valueOf(agentIdStr), tableDocuments); log.info("Found {} logical foreign keys for agent: {}", logicalForeignKeys.size(), agentIdStr); SchemaDTO initialSchema = buildInitialSchema(agentIdStr, columnDocuments, tableDocuments, agentDbConfig, @@ -142,12 +156,9 @@ public Map apply(OverAllState state) throws Exception { // subsequent nodes return Map.of(TABLE_RELATION_OUTPUT, generator, DB_DIALECT_TYPE, agentDbConfig.getDialectType(), TABLE_RELATION_RETRY_COUNT, 0, TABLE_RELATION_EXCEPTION_OUTPUT, ""); - } - /** - * Builds initial schema from column and table documents. - */ + /** Builds initial schema from column and table documents. */ private SchemaDTO buildInitialSchema(String agentId, List columnDocuments, List tableDocuments, DbConfigBO agentDbConfig, List logicalForeignKeys) { SchemaDTO schemaDTO = new SchemaDTO(); @@ -174,9 +185,7 @@ private SchemaDTO buildInitialSchema(String agentId, List columnDocume return schemaDTO; } - /** - * Processes schema selection based on input, evidence, and optional advice. - */ + /** Processes schema selection based on input, evidence, and optional advice. */ private Flux processSchemaSelection(SchemaDTO schemaDTO, String input, String evidence, OverAllState state, DbConfigBO agentDbConfig, Consumer dtoConsumer) { String schemaAdvice = StateUtil.getStringValue(state, SQL_GENERATE_SCHEMA_MISSING_ADVICE, null); @@ -199,10 +208,8 @@ private Flux processSchemaSelection(SchemaDTO schemaDTO, String in ChatResponseUtil.createResponse("\n\n选择数据表完成。"))); } - /** - * 获取逻辑外键信息,并过滤只保留与当前召回表相关的外键 - */ - private List getLogicalForeignKeys(Integer agentId, List tableDocuments) { + /** 获取逻辑外键信息,并过滤只保留与当前召回表相关的外键 */ + private List getLogicalForeignKeys(Long agentId, List tableDocuments) { try { // 获取当前 agent 激活的数据源 AgentDatasource agentDatasource = agentDatasourceService.getCurrentAgentDatasource(agentId); diff --git a/data-agent-management/src/test/java/com/alibaba/cloud/ai/dataagent/mapper/AgentDatasourceMapperTest.java b/data-agent-management/src/test/java/com/alibaba/cloud/ai/dataagent/mapper/AgentDatasourceMapperTest.java index 8de72af79..1f9c407ca 100644 --- a/data-agent-management/src/test/java/com/alibaba/cloud/ai/dataagent/mapper/AgentDatasourceMapperTest.java +++ b/data-agent-management/src/test/java/com/alibaba/cloud/ai/dataagent/mapper/AgentDatasourceMapperTest.java @@ -15,9 +15,16 @@ */ package com.alibaba.cloud.ai.dataagent.mapper; +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertNull; + import com.alibaba.cloud.ai.dataagent.entity.AgentDatasource; import com.alibaba.cloud.ai.dataagent.entity.Datasource; import com.alibaba.cloud.ai.dataagent.service.MySqlContainerConfiguration; +import java.sql.Statement; +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mybatis.spring.boot.test.autoconfigure.MybatisTest; @@ -29,241 +36,243 @@ import org.springframework.jdbc.support.KeyHolder; import org.springframework.test.context.TestPropertySource; -import java.sql.Statement; -import java.time.LocalDateTime; -import java.util.List; -import java.util.UUID; - -import static org.junit.jupiter.api.Assertions.*; -import static org.junit.jupiter.api.Assertions.assertNull; - @MybatisTest @TestPropertySource( - properties = { "spring.sql.init.mode=never", "mybatis.configuration.map-underscore-to-camel-case=true" }) + properties = { + "spring.sql.init.mode=never", + "mybatis.configuration.map-underscore-to-camel-case=true" + }) @ImportTestcontainers(MySqlContainerConfiguration.class) @ImportAutoConfiguration(MySqlContainerConfiguration.class) public class AgentDatasourceMapperTest { - @Autowired - private DatasourceMapper datasourceMapper; - - @Autowired - private AgentDatasourceMapper agentDatasourceMapper; - - @Autowired - private JdbcTemplate jdbcTemplate; - - private Integer agentId; - - @BeforeEach - public void setUpAgent() { - this.agentId = createTestAgentAndGetId(); - } - - // ==================== AgentDatasourceMapper Tests ==================== - - @Test - public void testAgentDatasourceCreateNewRelationEnabled() { - // Given - Integer agentId = this.agentId; - Integer datasourceId = createTestDatasourceAndGetId(); - - // When - int insertResult = agentDatasourceMapper.createNewRelationEnabled(agentId, datasourceId); - AgentDatasource relation = agentDatasourceMapper.selectByAgentIdAndDatasourceId(agentId, datasourceId); - - // Then - assertEquals(1, insertResult); - assertNotNull(relation); - assertEquals(agentId, relation.getAgentId()); - assertEquals(datasourceId, relation.getDatasourceId()); - assertEquals(Integer.valueOf(1), relation.getIsActive()); - } - - @Test - public void testAgentDatasourceSelectByAgentId() { - // Given - Integer agentId = this.agentId; - Integer datasourceId1 = createTestDatasourceAndGetId(); - Integer datasourceId2 = createTestDatasourceAndGetId(); - - agentDatasourceMapper.createNewRelationEnabled(agentId, datasourceId1); - agentDatasourceMapper.createNewRelationEnabled(agentId, datasourceId2); - - // When - List relations = agentDatasourceMapper.selectByAgentId(agentId); - - // Then - assertEquals(2, relations.size()); - assertTrue(relations.stream().allMatch(r -> agentId.equals(r.getAgentId()))); - assertTrue(relations.stream().anyMatch(r -> datasourceId1.equals(r.getDatasourceId()))); - assertTrue(relations.stream().anyMatch(r -> datasourceId2.equals(r.getDatasourceId()))); - } - - @Test - public void testAgentDatasourceSelectByAgentIdAndDatasourceId() { - // Given - Integer agentId = this.agentId; - Integer datasourceId = createTestDatasourceAndGetId(); - agentDatasourceMapper.createNewRelationEnabled(agentId, datasourceId); - - // When - AgentDatasource relation = agentDatasourceMapper.selectByAgentIdAndDatasourceId(agentId, datasourceId); - - // Then - assertNotNull(relation); - assertEquals(agentId, relation.getAgentId()); - assertEquals(datasourceId, relation.getDatasourceId()); - } - - @Test - public void testAgentDatasourceUpdateRelation() { - // Given - Integer agentId = this.agentId; - Integer datasourceId = createTestDatasourceAndGetId(); - agentDatasourceMapper.createNewRelationEnabled(agentId, datasourceId); - - // When - 禁用关联 - int disableResult = agentDatasourceMapper.updateRelation(agentId, datasourceId, 0); - AgentDatasource disabledRelation = agentDatasourceMapper.selectByAgentIdAndDatasourceId(agentId, datasourceId); - - // Then - assertEquals(1, disableResult); - assertEquals(Integer.valueOf(0), disabledRelation.getIsActive()); - - // When - 启用关联 - int enableResult = agentDatasourceMapper.enableRelation(agentId, datasourceId); - AgentDatasource enabledRelation = agentDatasourceMapper.selectByAgentIdAndDatasourceId(agentId, datasourceId); - - // Then - assertEquals(1, enableResult); - assertEquals(Integer.valueOf(1), enabledRelation.getIsActive()); - } - - @Test - public void testAgentDatasourceDisableAllByAgentId() { - // Given - Integer agentId = this.agentId; - Integer datasourceId1 = createTestDatasourceAndGetId(); - Integer datasourceId2 = createTestDatasourceAndGetId(); - - agentDatasourceMapper.createNewRelationEnabled(agentId, datasourceId1); - agentDatasourceMapper.createNewRelationEnabled(agentId, datasourceId2); - - // When - int disableResult = agentDatasourceMapper.disableAllByAgentId(agentId); - - // Then - assertEquals(2, disableResult); - - List relations = agentDatasourceMapper.selectByAgentId(agentId); - assertTrue(relations.stream().allMatch(r -> r.getIsActive() != null && r.getIsActive() == 0)); - } - - @Test - public void testAgentDatasourceCountActiveByAgentIdExcluding() { - // Given - Integer agentId = this.agentId; - Integer datasourceId1 = createTestDatasourceAndGetId(); - Integer datasourceId2 = createTestDatasourceAndGetId(); - Integer datasourceId3 = createTestDatasourceAndGetId(); - - agentDatasourceMapper.createNewRelationEnabled(agentId, datasourceId1); - agentDatasourceMapper.createNewRelationEnabled(agentId, datasourceId2); - agentDatasourceMapper.createNewRelationEnabled(agentId, datasourceId3); - - // When - int count = agentDatasourceMapper.countActiveByAgentIdExcluding(agentId, datasourceId1); - - // Then - assertEquals(2, count); - } - - @Test - public void testAgentDatasourceRemoveRelation() { - // Given - Integer agentId = this.agentId; - Integer datasourceId = createTestDatasourceAndGetId(); - agentDatasourceMapper.createNewRelationEnabled(agentId, datasourceId); - - // When - int removeResult = agentDatasourceMapper.removeRelation(agentId, datasourceId); - AgentDatasource removed = agentDatasourceMapper.selectByAgentIdAndDatasourceId(agentId, datasourceId); - - // Then - assertEquals(1, removeResult); - assertNull(removed); - } - - @Test - public void testAgentDatasourceDeleteAllByDatasourceId() { - // Given - Integer agentId1 = createTestAgentAndGetId(); - Integer agentId2 = createTestAgentAndGetId(); - Integer datasourceId = createTestDatasourceAndGetId(); - - agentDatasourceMapper.createNewRelationEnabled(agentId1, datasourceId); - agentDatasourceMapper.createNewRelationEnabled(agentId2, datasourceId); - - // When - int deleteResult = agentDatasourceMapper.deleteAllByDatasourceId(datasourceId); - - // Then - assertEquals(2, deleteResult); - - AgentDatasource relation1 = agentDatasourceMapper.selectByAgentIdAndDatasourceId(agentId1, datasourceId); - AgentDatasource relation2 = agentDatasourceMapper.selectByAgentIdAndDatasourceId(agentId2, datasourceId); - assertNull(relation1); - assertNull(relation2); - } - - // ==================== Helper Methods ==================== - - private Datasource createTestDatasource() { - return Datasource.builder() - .name("测试数据源") - .type("mysql") - .host("localhost") - .port(3306) - .databaseName("test_db") - .username("test_user") - .password("test_password") - .status("active") - .testStatus("success") - .description("测试用数据源") - .creatorId(1L) - .createTime(LocalDateTime.now()) - .updateTime(LocalDateTime.now()) - .build(); - } - - private Integer createTestDatasourceAndGetId() { - Datasource datasource = createTestDatasource(); - datasourceMapper.insert(datasource); - return datasource.getId(); - } - - private Integer createTestAgentAndGetId() { - KeyHolder keyHolder = new GeneratedKeyHolder(); - jdbcTemplate.update(con -> { - var ps = con.prepareStatement( - "INSERT INTO agent (name, description, status, category) VALUES (?, ?, 'published', 'test')", - Statement.RETURN_GENERATED_KEYS); - ps.setString(1, "测试智能体" + UUID.randomUUID()); - ps.setString(2, "用于单元测试的临时智能体"); - return ps; - }, keyHolder); - Number key = keyHolder.getKey(); - Integer id = key == null ? null : key.intValue(); - // 立即校验插入是否可见且存在,避免外键引用失败 - if (id == null) { - throw new IllegalStateException("创建测试Agent失败:未获取到自增ID"); - } - Integer exists = jdbcTemplate.queryForObject("SELECT COUNT(1) FROM agent WHERE id = ?", Integer.class, id); - if (exists == null || exists == 0) { - throw new IllegalStateException("创建测试Agent失败:数据库中不存在id=" + id); - } - return id; - } - + @Autowired private DatasourceMapper datasourceMapper; + + @Autowired private AgentDatasourceMapper agentDatasourceMapper; + + @Autowired private JdbcTemplate jdbcTemplate; + + private Long agentId; + + @BeforeEach + public void setUpAgent() { + this.agentId = createTestAgentAndGetId(); + } + + // ==================== AgentDatasourceMapper Tests ==================== + + @Test + public void testAgentDatasourceCreateNewRelationEnabled() { + // Given + Long agentId = this.agentId; + Integer datasourceId = createTestDatasourceAndGetId(); + + // When + int insertResult = agentDatasourceMapper.createNewRelationEnabled(agentId, datasourceId); + AgentDatasource relation = + agentDatasourceMapper.selectByAgentIdAndDatasourceId(agentId, datasourceId); + + // Then + assertEquals(1, insertResult); + assertNotNull(relation); + assertEquals(agentId, relation.getAgentId()); + assertEquals(datasourceId, relation.getDatasourceId()); + assertEquals(Integer.valueOf(1), relation.getIsActive()); + } + + @Test + public void testAgentDatasourceSelectByAgentId() { + // Given + Long agentId = this.agentId; + Integer datasourceId1 = createTestDatasourceAndGetId(); + Integer datasourceId2 = createTestDatasourceAndGetId(); + + agentDatasourceMapper.createNewRelationEnabled(agentId, datasourceId1); + agentDatasourceMapper.createNewRelationEnabled(agentId, datasourceId2); + + // When + List relations = agentDatasourceMapper.selectByAgentId(agentId); + + // Then + assertEquals(2, relations.size()); + assertTrue(relations.stream().allMatch(r -> agentId.equals(r.getAgentId()))); + assertTrue(relations.stream().anyMatch(r -> datasourceId1.equals(r.getDatasourceId()))); + assertTrue(relations.stream().anyMatch(r -> datasourceId2.equals(r.getDatasourceId()))); + } + + @Test + public void testAgentDatasourceSelectByAgentIdAndDatasourceId() { + // Given + Long agentId = this.agentId; + Integer datasourceId = createTestDatasourceAndGetId(); + agentDatasourceMapper.createNewRelationEnabled(agentId, datasourceId); + + // When + AgentDatasource relation = + agentDatasourceMapper.selectByAgentIdAndDatasourceId(agentId, datasourceId); + + // Then + assertNotNull(relation); + assertEquals(agentId, relation.getAgentId()); + assertEquals(datasourceId, relation.getDatasourceId()); + } + + @Test + public void testAgentDatasourceUpdateRelation() { + // Given + Long agentId = this.agentId; + Integer datasourceId = createTestDatasourceAndGetId(); + agentDatasourceMapper.createNewRelationEnabled(agentId, datasourceId); + + // When - 禁用关联 + int disableResult = agentDatasourceMapper.updateRelation(agentId, datasourceId, 0); + AgentDatasource disabledRelation = + agentDatasourceMapper.selectByAgentIdAndDatasourceId(agentId, datasourceId); + + // Then + assertEquals(1, disableResult); + assertEquals(Integer.valueOf(0), disabledRelation.getIsActive()); + + // When - 启用关联 + int enableResult = agentDatasourceMapper.enableRelation(agentId, datasourceId); + AgentDatasource enabledRelation = + agentDatasourceMapper.selectByAgentIdAndDatasourceId(agentId, datasourceId); + + // Then + assertEquals(1, enableResult); + assertEquals(Integer.valueOf(1), enabledRelation.getIsActive()); + } + + @Test + public void testAgentDatasourceDisableAllByAgentId() { + // Given + Long agentId = this.agentId; + Integer datasourceId1 = createTestDatasourceAndGetId(); + Integer datasourceId2 = createTestDatasourceAndGetId(); + + agentDatasourceMapper.createNewRelationEnabled(agentId, datasourceId1); + agentDatasourceMapper.createNewRelationEnabled(agentId, datasourceId2); + + // When + int disableResult = agentDatasourceMapper.disableAllByAgentId(agentId); + + // Then + assertEquals(2, disableResult); + + List relations = agentDatasourceMapper.selectByAgentId(agentId); + assertTrue(relations.stream().allMatch(r -> r.getIsActive() != null && r.getIsActive() == 0)); + } + + @Test + public void testAgentDatasourceCountActiveByAgentIdExcluding() { + // Given + Long agentId = this.agentId; + Integer datasourceId1 = createTestDatasourceAndGetId(); + Integer datasourceId2 = createTestDatasourceAndGetId(); + Integer datasourceId3 = createTestDatasourceAndGetId(); + + agentDatasourceMapper.createNewRelationEnabled(agentId, datasourceId1); + agentDatasourceMapper.createNewRelationEnabled(agentId, datasourceId2); + agentDatasourceMapper.createNewRelationEnabled(agentId, datasourceId3); + + // When + int count = agentDatasourceMapper.countActiveByAgentIdExcluding(agentId, datasourceId1); + + // Then + assertEquals(2, count); + } + + @Test + public void testAgentDatasourceRemoveRelation() { + // Given + Long agentId = this.agentId; + Integer datasourceId = createTestDatasourceAndGetId(); + agentDatasourceMapper.createNewRelationEnabled(agentId, datasourceId); + + // When + int removeResult = agentDatasourceMapper.removeRelation(agentId, datasourceId); + AgentDatasource removed = + agentDatasourceMapper.selectByAgentIdAndDatasourceId(agentId, datasourceId); + + // Then + assertEquals(1, removeResult); + assertNull(removed); + } + + @Test + public void testAgentDatasourceDeleteAllByDatasourceId() { + // Given + Long agentId1 = createTestAgentAndGetId(); + Long agentId2 = createTestAgentAndGetId(); + Integer datasourceId = createTestDatasourceAndGetId(); + + agentDatasourceMapper.createNewRelationEnabled(agentId1, datasourceId); + agentDatasourceMapper.createNewRelationEnabled(agentId2, datasourceId); + + // When + int deleteResult = agentDatasourceMapper.deleteAllByDatasourceId(datasourceId); + + // Then + assertEquals(2, deleteResult); + + AgentDatasource relation1 = + agentDatasourceMapper.selectByAgentIdAndDatasourceId(agentId1, datasourceId); + AgentDatasource relation2 = + agentDatasourceMapper.selectByAgentIdAndDatasourceId(agentId2, datasourceId); + assertNull(relation1); + assertNull(relation2); + } + + // ==================== Helper Methods ==================== + + private Datasource createTestDatasource() { + return Datasource.builder() + .name("测试数据源") + .type("mysql") + .host("localhost") + .port(3306) + .databaseName("test_db") + .username("test_user") + .password("test_password") + .status("active") + .testStatus("success") + .description("测试用数据源") + .creatorId(1L) + .createTime(LocalDateTime.now()) + .updateTime(LocalDateTime.now()) + .build(); + } + + private Integer createTestDatasourceAndGetId() { + Datasource datasource = createTestDatasource(); + datasourceMapper.insert(datasource); + return datasource.getId(); + } + + private Long createTestAgentAndGetId() { + KeyHolder keyHolder = new GeneratedKeyHolder(); + jdbcTemplate.update( + con -> { + var ps = + con.prepareStatement( + "INSERT INTO agent (name, description, status, category) VALUES (?, ?, 'published', 'test')", + Statement.RETURN_GENERATED_KEYS); + ps.setString(1, "测试智能体" + UUID.randomUUID()); + ps.setString(2, "用于单元测试的临时智能体"); + return ps; + }, + keyHolder); + Number key = keyHolder.getKey(); + Long id = key == null ? null : key.longValue(); + // 立即校验插入是否可见且存在,避免外键引用失败 + if (id == null) { + throw new IllegalStateException("创建测试Agent失败:未获取到自增ID"); + } + Long exists = + jdbcTemplate.queryForObject("SELECT COUNT(1) FROM agent WHERE id = ?", Long.class, id); + if (exists == null || exists == 0) { + throw new IllegalStateException("创建测试Agent失败:数据库中不存在id=" + id); + } + return id; + } } diff --git a/data-agent-management/src/test/java/com/alibaba/cloud/ai/dataagent/mapper/MappersTest.java b/data-agent-management/src/test/java/com/alibaba/cloud/ai/dataagent/mapper/MappersTest.java index 486304767..a7be0499d 100644 --- a/data-agent-management/src/test/java/com/alibaba/cloud/ai/dataagent/mapper/MappersTest.java +++ b/data-agent-management/src/test/java/com/alibaba/cloud/ai/dataagent/mapper/MappersTest.java @@ -15,8 +15,10 @@ */ package com.alibaba.cloud.ai.dataagent.mapper; -import com.alibaba.cloud.ai.dataagent.service.MySqlContainerConfiguration; import com.alibaba.cloud.ai.dataagent.entity.*; +import com.alibaba.cloud.ai.dataagent.service.MySqlContainerConfiguration; +import java.time.LocalDateTime; +import java.util.List; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.mybatis.spring.boot.test.autoconfigure.MybatisTest; @@ -25,9 +27,6 @@ import org.springframework.boot.testcontainers.context.ImportTestcontainers; import org.springframework.test.context.TestPropertySource; -import java.time.LocalDateTime; -import java.util.List; - /** * Mappers 单元测试类 * @@ -35,231 +34,225 @@ * @since 2025/9/26 */ @MybatisTest -@TestPropertySource(properties = { "spring.sql.init.mode=never" }) +@TestPropertySource(properties = {"spring.sql.init.mode=never"}) @ImportTestcontainers(MySqlContainerConfiguration.class) @ImportAutoConfiguration(MySqlContainerConfiguration.class) public class MappersTest { - @Autowired - private AgentMapper agentMapper; - - @Autowired - private AgentKnowledgeMapper agentKnowledgeMapper; - - @Autowired - private AgentPresetQuestionMapper agentPresetQuestionMapper; - - @Autowired - private ChatSessionMapper chatSessionMapper; - - @Autowired - private ChatMessageMapper chatMessageMapper; - - @Autowired - private SemanticModelMapper semanticModelMapper; - - @Autowired - private BusinessKnowledgeMapper businessKnowledgeMapper; - - private Long createAgent(String name) { - Agent agent = Agent.builder() - .name(name) - .description("for fk") - .avatar("a") - .status("draft") - .prompt("p") - .category("c") - .adminId(1L) - .tags("t") - .createTime(LocalDateTime.now().withNano(0)) - .updateTime(LocalDateTime.now().withNano(0)) - .humanReviewEnabled(0) - .build(); - agentMapper.insert(agent); - return agent.getId(); - } - - @Test - public void testAgentMapper() { - Assertions.assertNotNull(agentMapper); - - agentMapper.findAll().stream().map(Agent::getId).forEach(id -> agentMapper.deleteById(id)); - - List all = agentMapper.findAll(); - Assertions.assertEquals(List.of(), all); - Agent agent = Agent.builder() - .name("test") - .description("test") - .avatar("test") - .status("test") - .prompt("test") - .category("test") - .adminId(1L) - .tags("test") - .createTime(LocalDateTime.now().withNano(0)) - .updateTime(LocalDateTime.now().withNano(0)) - .humanReviewEnabled(0) - .build(); - int insert = agentMapper.insert(agent); - Assertions.assertEquals(1, insert); - Agent findById = agentMapper.findById(agent.getId()); - Assertions.assertEquals(agent, findById); - List findByStatus = agentMapper.findByStatus("test"); - Assertions.assertEquals(List.of(agent), findByStatus); - List searchByKeyword = agentMapper.searchByKeyword("test"); - Assertions.assertEquals(List.of(agent), searchByKeyword); - agent.setName("test2"); - int update = agentMapper.updateById(agent); - Assertions.assertEquals(1, update); - agentMapper.deleteById(agent.getId()); - List allAfterDelete = agentMapper.findAll(); - Assertions.assertEquals(List.of(), allAfterDelete); - } - - @Test - public void testChatSessionAndMessageCrud() { - Long agentId = createAgent("session-holder"); - String sessionId = java.util.UUID.randomUUID().toString(); - // insert session - ChatSession session = new ChatSession(sessionId, agentId.intValue(), "tc_session", "active", 1L); - session.setCreateTime(LocalDateTime.now()); - session.setUpdateTime(LocalDateTime.now()); - int ins = chatSessionMapper.insert(session); - Assertions.assertEquals(1, ins); - - // insert message - ChatMessage msg = new ChatMessage(); - msg.setSessionId(sessionId); - msg.setRole("user"); - msg.setContent("hello tc"); - msg.setMessageType("text"); - int mins = chatMessageMapper.insert(msg); - Assertions.assertEquals(1, mins); - - // read - List list = chatMessageMapper.selectBySessionId(sessionId); - Assertions.assertEquals(1, list.size()); - int cnt = chatMessageMapper.countBySessionId(sessionId); - Assertions.assertEquals(1, cnt); - - // update session - chatSessionMapper.updateTitle(sessionId, "tc_session_updated", LocalDateTime.now()); - chatSessionMapper.updatePinStatus(sessionId, true, LocalDateTime.now()); - chatSessionMapper.updateSessionTime(sessionId, LocalDateTime.now()); - - // delete - int md = chatMessageMapper.deleteById(list.get(0).getId()); - Assertions.assertEquals(1, md); - int sd = chatSessionMapper.softDeleteById(sessionId, LocalDateTime.now()); - Assertions.assertEquals(1, sd); - - // cleanup agent - agentMapper.deleteById(agentId); - } - - @Test - public void testSemanticModelCrud() { - Long agentId = createAgent("semantic-holder"); - SemanticModel m = new SemanticModel(); - m.setAgentId(Math.toIntExact(agentId)); - m.setDatasourceId(1); // 添加数据源ID - m.setTableName("test_table"); // 添加表名 - m.setColumnName("origin_tc"); - m.setBusinessName("显示名"); - m.setSynonyms("别名A,别名B"); - m.setBusinessDescription("desc"); - m.setColumnComment("字段注释"); // 添加字段注释 - m.setDataType("VARCHAR"); - m.setStatus(1); - int ins = semanticModelMapper.insert(m); - Assertions.assertEquals(1, ins); - Assertions.assertNotNull(m.getId()); - - List query = semanticModelMapper.selectByAgentId(Long.valueOf(m.getAgentId())); - Assertions.assertTrue(query.stream().anyMatch(x -> x.getId().equals(m.getId()))); - - semanticModelMapper.disableById(m.getId()); - semanticModelMapper.enableById(m.getId()); - - m.setBusinessDescription("desc2"); - int upd = semanticModelMapper.updateById(m); - Assertions.assertEquals(1, upd); - - int del = semanticModelMapper.deleteById(m.getId()); - Assertions.assertEquals(1, del); - - agentMapper.deleteById(agentId); - } - - @Test - public void testAgentPresetQuestionCrud() { - // 先创建一个合法的 Agent 以满足外键约束 - Long agentId = createAgent("preset-holder"); - - // clean existing - agentPresetQuestionMapper.deleteByAgentId(agentId); - - AgentPresetQuestion q = new AgentPresetQuestion(); - q.setAgentId(agentId); - q.setQuestion("q1"); - q.setSortOrder(0); - q.setIsActive(true); - int ins = agentPresetQuestionMapper.insert(q); - Assertions.assertEquals(1, ins); - - List qs = agentPresetQuestionMapper.selectByAgentId(agentId); - Assertions.assertEquals(1, qs.size()); - - q.setQuestion("q1_updated"); - int upd = agentPresetQuestionMapper.update(q); - Assertions.assertEquals(1, upd); - - int del = agentPresetQuestionMapper.deleteById(qs.get(0).getId()); - Assertions.assertEquals(1, del); - - // 清理创建的 agent - agentMapper.deleteById(agentId); - } - - @Test - public void testBusinessKnowledgeMapperCrud() { - Long agentId = createAgent("bk-holder"); - // clean - businessKnowledgeMapper.selectAll().forEach(b -> businessKnowledgeMapper.deleteById(b.getId())); - - List before = businessKnowledgeMapper.selectAll(); - Assertions.assertEquals(List.of(), before); - - BusinessKnowledge k = new BusinessKnowledge(); - k.setBusinessTerm("term_ut"); - k.setDescription("desc_ut"); - k.setSynonyms("a,b"); - k.setAgentId(agentId); - try { - java.lang.reflect.Field field = BusinessKnowledge.class.getDeclaredField("isRecall"); - field.setAccessible(true); - field.set(k, 1); - } - catch (Exception e) { - throw new RuntimeException(e); - } - int ins = businessKnowledgeMapper.insert(k); - Assertions.assertEquals(1, ins); - Assertions.assertNotNull(k.getId()); - - List byDataset = businessKnowledgeMapper.selectByAgentId(agentId); - Assertions.assertEquals(1, byDataset.size()); - - List search = businessKnowledgeMapper.searchInAgent(agentId, "term_ut"); - Assertions.assertTrue(search.stream().anyMatch(x -> x.getId().equals(k.getId()))); - - k.setDescription("desc_ut_updated"); - int upd = businessKnowledgeMapper.updateById(k); - Assertions.assertEquals(1, upd); - - int del = businessKnowledgeMapper.deleteById(k.getId()); - Assertions.assertEquals(1, del); - agentMapper.deleteById(agentId); - } - + @Autowired private AgentMapper agentMapper; + + @Autowired private AgentKnowledgeMapper agentKnowledgeMapper; + + @Autowired private AgentPresetQuestionMapper agentPresetQuestionMapper; + + @Autowired private ChatSessionMapper chatSessionMapper; + + @Autowired private ChatMessageMapper chatMessageMapper; + + @Autowired private SemanticModelMapper semanticModelMapper; + + @Autowired private BusinessKnowledgeMapper businessKnowledgeMapper; + + private Long createAgent(String name) { + Agent agent = + Agent.builder() + .name(name) + .description("for fk") + .avatar("a") + .status("draft") + .prompt("p") + .category("c") + .adminId(1L) + .tags("t") + .createTime(LocalDateTime.now().withNano(0)) + .updateTime(LocalDateTime.now().withNano(0)) + .humanReviewEnabled(0) + .build(); + agentMapper.insert(agent); + return agent.getId(); + } + + @Test + public void testAgentMapper() { + Assertions.assertNotNull(agentMapper); + + agentMapper.findAll().stream().map(Agent::getId).forEach(id -> agentMapper.deleteById(id)); + + List all = agentMapper.findAll(); + Assertions.assertEquals(List.of(), all); + Agent agent = + Agent.builder() + .name("test") + .description("test") + .avatar("test") + .status("test") + .prompt("test") + .category("test") + .adminId(1L) + .tags("test") + .createTime(LocalDateTime.now().withNano(0)) + .updateTime(LocalDateTime.now().withNano(0)) + .humanReviewEnabled(0) + .build(); + int insert = agentMapper.insert(agent); + Assertions.assertEquals(1, insert); + Agent findById = agentMapper.findById(agent.getId()); + Assertions.assertEquals(agent, findById); + List findByStatus = agentMapper.findByStatus("test"); + Assertions.assertEquals(List.of(agent), findByStatus); + List searchByKeyword = agentMapper.searchByKeyword("test"); + Assertions.assertEquals(List.of(agent), searchByKeyword); + agent.setName("test2"); + int update = agentMapper.updateById(agent); + Assertions.assertEquals(1, update); + agentMapper.deleteById(agent.getId()); + List allAfterDelete = agentMapper.findAll(); + Assertions.assertEquals(List.of(), allAfterDelete); + } + + @Test + public void testChatSessionAndMessageCrud() { + Long agentId = createAgent("session-holder"); + String sessionId = java.util.UUID.randomUUID().toString(); + // insert session + ChatSession session = + new ChatSession(sessionId, agentId.intValue(), "tc_session", "active", 1L); + session.setCreateTime(LocalDateTime.now()); + session.setUpdateTime(LocalDateTime.now()); + int ins = chatSessionMapper.insert(session); + Assertions.assertEquals(1, ins); + + // insert message + ChatMessage msg = new ChatMessage(); + msg.setSessionId(sessionId); + msg.setRole("user"); + msg.setContent("hello tc"); + msg.setMessageType("text"); + int mins = chatMessageMapper.insert(msg); + Assertions.assertEquals(1, mins); + + // read + List list = chatMessageMapper.selectBySessionId(sessionId); + Assertions.assertEquals(1, list.size()); + int cnt = chatMessageMapper.countBySessionId(sessionId); + Assertions.assertEquals(1, cnt); + + // update session + chatSessionMapper.updateTitle(sessionId, "tc_session_updated", LocalDateTime.now()); + chatSessionMapper.updatePinStatus(sessionId, true, LocalDateTime.now()); + chatSessionMapper.updateSessionTime(sessionId, LocalDateTime.now()); + + // delete + int md = chatMessageMapper.deleteById(list.get(0).getId()); + Assertions.assertEquals(1, md); + int sd = chatSessionMapper.softDeleteById(sessionId, LocalDateTime.now()); + Assertions.assertEquals(1, sd); + + // cleanup agent + agentMapper.deleteById(agentId); + } + + @Test + public void testSemanticModelCrud() { + Long agentId = createAgent("semantic-holder"); + SemanticModel m = new SemanticModel(); + m.setAgentId(agentId); + m.setDatasourceId(1); // 添加数据源ID + m.setTableName("test_table"); // 添加表名 + m.setColumnName("origin_tc"); + m.setBusinessName("显示名"); + m.setSynonyms("别名A,别名B"); + m.setBusinessDescription("desc"); + m.setColumnComment("字段注释"); // 添加字段注释 + m.setDataType("VARCHAR"); + m.setStatus(1); + int ins = semanticModelMapper.insert(m); + Assertions.assertEquals(1, ins); + Assertions.assertNotNull(m.getId()); + + List query = semanticModelMapper.selectByAgentId(m.getAgentId()); + Assertions.assertTrue(query.stream().anyMatch(x -> x.getId().equals(m.getId()))); + + semanticModelMapper.disableById(m.getId()); + semanticModelMapper.enableById(m.getId()); + + m.setBusinessDescription("desc2"); + int upd = semanticModelMapper.updateById(m); + Assertions.assertEquals(1, upd); + + int del = semanticModelMapper.deleteById(m.getId()); + Assertions.assertEquals(1, del); + + agentMapper.deleteById(agentId); + } + + @Test + public void testAgentPresetQuestionCrud() { + // 先创建一个合法的 Agent 以满足外键约束 + Long agentId = createAgent("preset-holder"); + + // clean existing + agentPresetQuestionMapper.deleteByAgentId(agentId); + + AgentPresetQuestion q = new AgentPresetQuestion(); + q.setAgentId(agentId); + q.setQuestion("q1"); + q.setSortOrder(0); + q.setIsActive(true); + int ins = agentPresetQuestionMapper.insert(q); + Assertions.assertEquals(1, ins); + + List qs = agentPresetQuestionMapper.selectByAgentId(agentId); + Assertions.assertEquals(1, qs.size()); + + q.setQuestion("q1_updated"); + int upd = agentPresetQuestionMapper.update(q); + Assertions.assertEquals(1, upd); + + int del = agentPresetQuestionMapper.deleteById(qs.get(0).getId()); + Assertions.assertEquals(1, del); + + // 清理创建的 agent + agentMapper.deleteById(agentId); + } + + @Test + public void testBusinessKnowledgeMapperCrud() { + Long agentId = createAgent("bk-holder"); + // clean + businessKnowledgeMapper.selectAll().forEach(b -> businessKnowledgeMapper.deleteById(b.getId())); + + List before = businessKnowledgeMapper.selectAll(); + Assertions.assertEquals(List.of(), before); + + BusinessKnowledge k = new BusinessKnowledge(); + k.setBusinessTerm("term_ut"); + k.setDescription("desc_ut"); + k.setSynonyms("a,b"); + k.setAgentId(agentId); + try { + java.lang.reflect.Field field = BusinessKnowledge.class.getDeclaredField("isRecall"); + field.setAccessible(true); + field.set(k, 1); + } catch (Exception e) { + throw new RuntimeException(e); + } + int ins = businessKnowledgeMapper.insert(k); + Assertions.assertEquals(1, ins); + Assertions.assertNotNull(k.getId()); + + List byDataset = businessKnowledgeMapper.selectByAgentId(agentId); + Assertions.assertEquals(1, byDataset.size()); + + List search = businessKnowledgeMapper.searchInAgent(agentId, "term_ut"); + Assertions.assertTrue(search.stream().anyMatch(x -> x.getId().equals(k.getId()))); + + k.setDescription("desc_ut_updated"); + int upd = businessKnowledgeMapper.updateById(k); + Assertions.assertEquals(1, upd); + + int del = businessKnowledgeMapper.deleteById(k.getId()); + Assertions.assertEquals(1, del); + agentMapper.deleteById(agentId); + } } diff --git a/mvnw b/mvnw index d51242e7f..bd8896bf2 100755 --- a/mvnw +++ b/mvnw @@ -1,305 +1,295 @@ #!/bin/sh # ---------------------------------------------------------------------------- -# Copyright 2024-2025 the original author or authors. +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at # -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 # -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. # ---------------------------------------------------------------------------- # ---------------------------------------------------------------------------- -# Apache Maven Wrapper startup batch script, version 3.2.0 -# -# Required ENV vars: -# ------------------ -# JAVA_HOME - location of a JDK home dir +# Apache Maven Wrapper startup batch script, version 3.3.4 # # Optional ENV vars # ----------------- -# MAVEN_OPTS - parameters passed to the Java VM when running Maven -# e.g. to debug Maven itself, use -# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output # ---------------------------------------------------------------------------- -if [ -z "$MAVEN_SKIP_RC" ] ; then - - if [ -f /usr/local/etc/mavenrc ] ; then - . /usr/local/etc/mavenrc - fi - - if [ -f /etc/mavenrc ] ; then - . /etc/mavenrc - fi - - if [ -f "$HOME/.mavenrc" ] ; then - . "$HOME/.mavenrc" - fi - -fi +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x -# OS specific support. $var _must_ be set to either true or false. -cygwin=false; -darwin=false; -mingw=false +# OS specific support. +native_path() { printf %s\\n "$1"; } case "$(uname)" in - CYGWIN*) cygwin=true ;; - MINGW*) mingw=true;; - Darwin*) darwin=true - # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home - # See https://developer.apple.com/library/mac/qa/qa1170/_index.html - if [ -z "$JAVA_HOME" ]; then - if [ -x "/usr/libexec/java_home" ]; then - JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME - else - JAVA_HOME="/Library/Java/Home"; export JAVA_HOME - fi - fi - ;; +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; esac -if [ -z "$JAVA_HOME" ] ; then - if [ -r /etc/gentoo-release ] ; then - JAVA_HOME=$(java-config --jre-home) - fi -fi - -# For Cygwin, ensure paths are in UNIX format before anything is touched -if $cygwin ; then - [ -n "$JAVA_HOME" ] && - JAVA_HOME=$(cygpath --unix "$JAVA_HOME") - [ -n "$CLASSPATH" ] && - CLASSPATH=$(cygpath --path --unix "$CLASSPATH") -fi - -# For Mingw, ensure paths are in UNIX format before anything is touched -if $mingw ; then - [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] && - JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)" -fi - -if [ -z "$JAVA_HOME" ]; then - javaExecutable="$(which javac)" - if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then - # readlink(1) is not available as standard on Solaris 10. - readLink=$(which readlink) - if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then - if $darwin ; then - javaHome="$(dirname "\"$javaExecutable\"")" - javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac" - else - javaExecutable="$(readlink -f "\"$javaExecutable\"")" - fi - javaHome="$(dirname "\"$javaExecutable\"")" - javaHome=$(expr "$javaHome" : '\(.*\)/bin') - JAVA_HOME="$javaHome" - export JAVA_HOME - fi - fi -fi - -if [ -z "$JAVACMD" ] ; then - if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + if [ -n "${JAVA_HOME-}" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACCMD="$JAVA_HOME/jre/sh/javac" else JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi fi else - JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)" - fi -fi - -if [ ! -x "$JAVACMD" ] ; then - echo "Error: JAVA_HOME is not defined correctly." >&2 - echo " We cannot execute $JAVACMD" >&2 - exit 1 -fi + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : -if [ -z "$JAVA_HOME" ] ; then - echo "Warning: JAVA_HOME environment variable is not set." -fi - -# traverses directory structure from process work directory to filesystem root -# first directory with .mvn subdirectory is considered project base directory -find_maven_basedir() { - if [ -z "$1" ] - then - echo "Path not specified to find_maven_basedir" - return 1 + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi fi +} - basedir="$1" - wdir="$1" - while [ "$wdir" != '/' ] ; do - if [ -d "$wdir"/.mvn ] ; then - basedir=$wdir - break - fi - # workaround for JBEAP-8937 (on Solaris 10/Sparc) - if [ -d "${wdir}" ]; then - wdir=$(cd "$wdir/.." || exit 1; pwd) - fi - # end of workaround +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" done - printf '%s' "$(cd "$basedir" || exit 1; pwd)" + printf %x\\n $h } -# concatenates all lines of a file -concat_lines() { - if [ -f "$1" ]; then - # Remove \r in case we run on Windows within Git Bash - # and check out the repository with auto CRLF management - # enabled. Otherwise, we may read lines that are delimited with - # \r\n and produce $'-Xarg\r' rather than -Xarg due to word - # splitting rules. - tr -s '\r\n' ' ' < "$1" - fi +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 } -log() { - if [ "$MVNW_VERBOSE" = true ]; then - printf '%s\n' "$1" - fi +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +scriptDir="$(dirname "$0")" +scriptName="$(basename "$0")" + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"$scriptDir/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${scriptName#mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" } -BASE_DIR=$(find_maven_basedir "$(dirname "$0")") -if [ -z "$BASE_DIR" ]; then - exit 1; +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" fi -MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR -log "$MAVEN_PROJECTBASEDIR" +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac -########################################################################################## -# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central -# This allows using the maven wrapper in projects that prohibit checking in binary data. -########################################################################################## -wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" -if [ -r "$wrapperJarPath" ]; then - log "Found $wrapperJarPath" +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT else - log "Couldn't find $wrapperJarPath, downloading it ..." + die "cannot create temp dir" +fi - if [ -n "$MVNW_REPOURL" ]; then - wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" - else - wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" - fi - while IFS="=" read -r key value; do - # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) - safeValue=$(echo "$value" | tr -d '\r') - case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;; - esac - done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" - log "Downloading from: $wrapperUrl" - - if $cygwin; then - wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") - fi +mkdir -p -- "${MAVEN_HOME%/*}" - if command -v wget > /dev/null; then - log "Found wget ... using wget" - [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" - if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" - else - wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" - fi - elif command -v curl > /dev/null; then - log "Found curl ... using curl" - [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" - if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" - else - curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" - fi - else - log "Falling back to using Java to download" - javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" - javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" - # For Cygwin, switch paths to Windows format before running javac - if $cygwin; then - javaSource=$(cygpath --path --windows "$javaSource") - javaClass=$(cygpath --path --windows "$javaClass") - fi - if [ -e "$javaSource" ]; then - if [ ! -e "$javaClass" ]; then - log " - Compiling MavenWrapperDownloader.java ..." - ("$JAVA_HOME/bin/javac" "$javaSource") - fi - if [ -e "$javaClass" ]; then - log " - Running MavenWrapperDownloader.java ..." - ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" - fi - fi - fi +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" fi -########################################################################################## -# End of extension -########################################################################################## -# If specified, validate the SHA-256 sum of the Maven wrapper jar file -wrapperSha256Sum="" -while IFS="=" read -r key value; do - case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;; - esac -done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" -if [ -n "$wrapperSha256Sum" ]; then - wrapperSha256Result=false - if command -v sha256sum > /dev/null; then - if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then - wrapperSha256Result=true +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c - >/dev/null 2>&1; then + distributionSha256Result=true fi - elif command -v shasum > /dev/null; then - if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then - wrapperSha256Result=true + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true fi else - echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." - echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 exit 1 fi - if [ $wrapperSha256Result = false ]; then - echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 - echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 - echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 exit 1 fi fi -MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi + +# Find the actual extracted directory name (handles snapshots where filename != directory name) +actualDistributionDir="" -# For Cygwin, switch paths to Windows format before running java -if $cygwin; then - [ -n "$JAVA_HOME" ] && - JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") - [ -n "$CLASSPATH" ] && - CLASSPATH=$(cygpath --path --windows "$CLASSPATH") - [ -n "$MAVEN_PROJECTBASEDIR" ] && - MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") +# First try the expected directory name (for regular distributions) +if [ -d "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" ]; then + if [ -f "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD" ]; then + actualDistributionDir="$distributionUrlNameMain" + fi fi -# Provide a "standardized" way to retrieve the CLI args that will -# work with both Windows and non-Windows executions. -MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" -export MAVEN_CMD_LINE_ARGS +# If not found, search for any directory with the Maven executable (for snapshots) +if [ -z "$actualDistributionDir" ]; then + # enable globbing to iterate over items + set +f + for dir in "$TMP_DOWNLOAD_DIR"/*; do + if [ -d "$dir" ]; then + if [ -f "$dir/bin/$MVN_CMD" ]; then + actualDistributionDir="$(basename "$dir")" + break + fi + fi + done + set -f +fi + +if [ -z "$actualDistributionDir" ]; then + verbose "Contents of $TMP_DOWNLOAD_DIR:" + verbose "$(ls -la "$TMP_DOWNLOAD_DIR")" + die "Could not find Maven distribution directory in extracted archive" +fi -WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain +verbose "Found extracted Maven distribution directory: $actualDistributionDir" +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$actualDistributionDir" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" -# shellcheck disable=SC2086 # safe args -exec "$JAVACMD" \ - $MAVEN_OPTS \ - $MAVEN_DEBUG_OPTS \ - -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ - "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ - ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" +clean || : +exec_maven "$@" diff --git a/mvnw.cmd b/mvnw.cmd index 149977cfb..92450f932 100644 --- a/mvnw.cmd +++ b/mvnw.cmd @@ -1,200 +1,189 @@ -@rem Copyright 2024-2025 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. - +<# : batch portion @REM ---------------------------------------------------------------------------- -@REM Apache Maven Wrapper startup batch script, version 3.2.0 +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at @REM -@REM Required ENV vars: -@REM JAVA_HOME - location of a JDK home dir +@REM http://www.apache.org/licenses/LICENSE-2.0 @REM -@REM Optional ENV vars -@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands -@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending -@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven -@REM e.g. to debug Maven itself, use -@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. @REM ---------------------------------------------------------------------------- -@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' -@echo off -@REM set title of command window -title %0 -@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' -@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% - -@REM set %HOME% to equivalent of $HOME -if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") - -@REM Execute a user defined script before this one -if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre -@REM check for pre script, once with legacy .bat ending and once with .cmd ending -if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* -if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* -:skipRcPre - -@setlocal - -set ERROR_CODE=0 - -@REM To isolate internal variables from possible post scripts, we use another setlocal -@setlocal - -@REM ==== START VALIDATION ==== -if not "%JAVA_HOME%" == "" goto OkJHome - -echo. -echo Error: JAVA_HOME not found in your environment. >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -:OkJHome -if exist "%JAVA_HOME%\bin\java.exe" goto init - -echo. -echo Error: JAVA_HOME is set to an invalid directory. >&2 -echo JAVA_HOME = "%JAVA_HOME%" >&2 -echo Please set the JAVA_HOME variable in your environment to match the >&2 -echo location of your Java installation. >&2 -echo. -goto error - -@REM ==== END VALIDATION ==== - -:init - -@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". -@REM Fallback to current working directory if not found. - -set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% -IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir - -set EXEC_DIR=%CD% -set WDIR=%EXEC_DIR% -:findBaseDir -IF EXIST "%WDIR%"\.mvn goto baseDirFound -cd .. -IF "%WDIR%"=="%CD%" goto baseDirNotFound -set WDIR=%CD% -goto findBaseDir - -:baseDirFound -set MAVEN_PROJECTBASEDIR=%WDIR% -cd "%EXEC_DIR%" -goto endDetectBaseDir - -:baseDirNotFound -set MAVEN_PROJECTBASEDIR=%EXEC_DIR% -cd "%EXEC_DIR%" - -:endDetectBaseDir - -IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig - -@setlocal EnableExtensions EnableDelayedExpansion -for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a -@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% - -:endReadAdditionalConfig - -SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" -set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" -set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain - -set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" - -FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( - IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B -) - -@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central -@REM This allows using the maven wrapper in projects that prohibit checking in binary data. -if exist %WRAPPER_JAR% ( - if "%MVNW_VERBOSE%" == "true" ( - echo Found %WRAPPER_JAR% - ) -) else ( - if not "%MVNW_REPOURL%" == "" ( - SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" - ) - if "%MVNW_VERBOSE%" == "true" ( - echo Couldn't find %WRAPPER_JAR%, downloading it ... - echo Downloading from: %WRAPPER_URL% - ) - - powershell -Command "&{"^ - "$webclient = new-object System.Net.WebClient;"^ - "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ - "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ - "}"^ - "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ - "}" - if "%MVNW_VERBOSE%" == "true" ( - echo Finished downloading %WRAPPER_JAR% - ) -) -@REM End of extension +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.4 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- -@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file -SET WRAPPER_SHA_256_SUM="" -FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( - IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B -) -IF NOT %WRAPPER_SHA_256_SUM%=="" ( - powershell -Command "&{"^ - "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ - "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ - " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ - " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ - " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ - " exit 1;"^ - "}"^ - "}" - if ERRORLEVEL 1 goto error +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) ) - -@REM Provide a "standardized" way to retrieve the CLI args that will -@REM work with both Windows and non-Windows executions. -set MAVEN_CMD_LINE_ARGS=%* - -%MAVEN_JAVA_EXE% ^ - %JVM_CONFIG_MAVEN_PROPS% ^ - %MAVEN_OPTS% ^ - %MAVEN_DEBUG_OPTS% ^ - -classpath %WRAPPER_JAR% ^ - "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ - %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* -if ERRORLEVEL 1 goto error -goto end - -:error -set ERROR_CODE=1 - -:end -@endlocal & set ERROR_CODE=%ERROR_CODE% - -if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost -@REM check for post script, once with legacy .bat ending and once with .cmd ending -if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" -if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" -:skipRcPost - -@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' -if "%MAVEN_BATCH_PAUSE%"=="on" pause - -if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% - -cmd /C exit /B %ERROR_CODE% +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" ("%__MVNW_CMD__%" %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace "^.*$MVNW_REPO_PATTERN",'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' + +$MAVEN_M2_PATH = "$HOME/.m2" +if ($env:MAVEN_USER_HOME) { + $MAVEN_M2_PATH = "$env:MAVEN_USER_HOME" +} + +if (-not (Test-Path -Path $MAVEN_M2_PATH)) { + New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null +} + +$MAVEN_WRAPPER_DISTS = $null +if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) { + $MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists" +} else { + $MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists" +} + +$MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain" +$MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null + +# Find the actual extracted directory name (handles snapshots where filename != directory name) +$actualDistributionDir = "" + +# First try the expected directory name (for regular distributions) +$expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain" +$expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD" +if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) { + $actualDistributionDir = $distributionUrlNameMain +} + +# If not found, search for any directory with the Maven executable (for snapshots) +if (!$actualDistributionDir) { + Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object { + $testPath = Join-Path $_.FullName "bin/$MVN_CMD" + if (Test-Path -Path $testPath -PathType Leaf) { + $actualDistributionDir = $_.Name + } + } +} + +if (!$actualDistributionDir) { + Write-Error "Could not find Maven distribution directory in extracted archive" +} + +Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir" +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/pom.xml b/pom.xml index 9247f2e1b..66be44343 100644 --- a/pom.xml +++ b/pom.xml @@ -63,7 +63,7 @@ 4.37.0 2.3.232 8.1.3.140 - 1.19.3 + 1.21.4 8.18.0 @@ -292,6 +292,7 @@ +<<<<<<< HEAD origin/main @@ -314,6 +315,8 @@ (package|import) +======= +>>>>>>> b09f162 (refactor: unfiy the angentId to Long, and remove ResponseEntity in AgentController) From 3f06751874b18016fa8c392359e3cf61b1f2be84 Mon Sep 17 00:00:00 2001 From: Jack Xu Date: Wed, 7 Jan 2026 16:02:29 +0800 Subject: [PATCH 2/8] fix: globalExceptionHandler add miss license --- .../controller/GlobalExceptionHandler.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/controller/GlobalExceptionHandler.java b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/controller/GlobalExceptionHandler.java index 0b6988523..9832174bc 100644 --- a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/controller/GlobalExceptionHandler.java +++ b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/controller/GlobalExceptionHandler.java @@ -1,3 +1,18 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.alibaba.cloud.ai.dataagent.controller; import com.alibaba.cloud.ai.dataagent.exception.InternalServerException; From 10b4fb5f55ba2bfaca5a18003497adf87b8fbe12 Mon Sep 17 00:00:00 2001 From: Jack Xu Date: Wed, 7 Jan 2026 16:21:19 +0800 Subject: [PATCH 3/8] fix: fix java format --- .mvn/wrapper/maven-wrapper.properties | 3 - CI/linter/license/.licenserc.yaml | 2 - .../dataagent/controller/AgentController.java | 239 +++++---- .../controller/AgentDatasourceController.java | 269 +++++----- .../controller/GlobalExceptionHandler.java | 21 +- .../dto/schema/SemanticModelAddDTO.java | 43 +- .../exception/InternalServerException.java | 8 +- .../exception/InvalidInputException.java | 19 +- .../mapper/AgentDatasourceMapper.java | 79 ++- .../agent/AgentStartupInitialization.java | 310 ++++++----- .../datasource/AgentDatasourceService.java | 29 +- .../impl/AgentDatasourceServiceImpl.java | 296 +++++------ .../semantic/SemanticModelService.java | 42 +- .../semantic/SemanticModelServiceImpl.java | 5 +- .../cloud/ai/dataagent/util/FluxUtil.java | 266 ++++------ .../workflow/node/SqlExecuteNode.java | 32 +- .../mapper/AgentDatasourceMapperTest.java | 456 ++++++++-------- .../ai/dataagent/mapper/MappersTest.java | 440 ++++++++-------- mvnw | 492 +++++++++--------- mvnw.cmd | 375 ++++++------- 20 files changed, 1679 insertions(+), 1747 deletions(-) delete mode 100644 .mvn/wrapper/maven-wrapper.properties diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties deleted file mode 100644 index 8dea6c227..000000000 --- a/.mvn/wrapper/maven-wrapper.properties +++ /dev/null @@ -1,3 +0,0 @@ -wrapperVersion=3.3.4 -distributionType=only-script -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.12/apache-maven-3.9.12-bin.zip diff --git a/CI/linter/license/.licenserc.yaml b/CI/linter/license/.licenserc.yaml index 0ef0ec505..ab33da992 100644 --- a/CI/linter/license/.licenserc.yaml +++ b/CI/linter/license/.licenserc.yaml @@ -82,6 +82,4 @@ header: - '**/*.properties' - 'CI/linter/codespell/.codespell.ignorewords' - 'CI/linter/codespell/.codespell.skip' - - 'mvnw' - - 'mvnw.cmd' comment: never diff --git a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/controller/AgentController.java b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/controller/AgentController.java index 50b133657..01d8e46bb 100644 --- a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/controller/AgentController.java +++ b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/controller/AgentController.java @@ -44,124 +44,123 @@ @AllArgsConstructor public class AgentController { - private final AgentService agentService; - - /** Get agent list */ - @GetMapping("/list") - public List list( - @RequestParam(value = "status", required = false) String status, - @RequestParam(value = "keyword", required = false) String keyword) { - List result; - if (StringUtils.isNotBlank(keyword)) { - result = agentService.search(keyword); - } else if (StringUtils.isNotBlank(status)) { - result = agentService.findByStatus(status); - } else { - result = agentService.findAll(); - } - return result; - } - - /** Get agent details by ID */ - @GetMapping("/{id}") - public Agent get(@PathVariable Long id) { - return checkAgentExists(id); - } - - /** Create agent */ - @PostMapping - public Agent create(@RequestBody Agent agent) { - // Set default status - if (StringUtils.isBlank(agent.getStatus())) { - agent.setStatus("draft"); - } - return agentService.save(agent); - } - - /** Update agent */ - @PutMapping("/{id}") - public Agent update(@PathVariable Long id, @RequestBody Agent agent) { - checkAgentExists(id); - agent.setId(id); - return agentService.save(agent); - } - - /** Delete agent */ - @DeleteMapping("/{id}") - public void delete(@PathVariable Long id) { - checkAgentExists(id); - agentService.deleteById(id); - } - - /** Publish agent */ - @PostMapping("/{id}/publish") - public Agent publish(@PathVariable Long id) { - Agent agent = checkAgentExists(id); - agent.setStatus("published"); - return agentService.save(agent); - } - - /** Offline agent */ - @PostMapping("/{id}/offline") - public Agent offline(@PathVariable Long id) { - Agent agent = checkAgentExists(id); - agent.setStatus("offline"); - return agentService.save(agent); - } - - /** Get masked API Key status */ - @GetMapping("/{id}/api-key") - public ApiResponse getApiKey(@PathVariable Long id) { - Agent agent = checkAgentExists(id); - String masked = agentService.getApiKeyMasked(id); - return buildApiKeyResponse(masked, agent.getApiKeyEnabled(), "获取 API Key 成功"); - } - - /** Generate API Key */ - @PostMapping("/{id}/api-key/generate") - public ApiResponse generateApiKey(@PathVariable Long id) { - checkAgentExists(id); - Agent agent = agentService.generateApiKey(id); - return buildApiKeyResponse(agent.getApiKey(), agent.getApiKeyEnabled(), "生成 API Key 成功"); - } - - /** Reset API Key */ - @PostMapping("/{id}/api-key/reset") - public ApiResponse resetApiKey(@PathVariable Long id) { - checkAgentExists(id); - Agent agent = agentService.resetApiKey(id); - return buildApiKeyResponse(agent.getApiKey(), agent.getApiKeyEnabled(), "重置 API Key 成功"); - } - - /** Delete API Key */ - @DeleteMapping("/{id}/api-key") - public ApiResponse deleteApiKey(@PathVariable Long id) { - checkAgentExists(id); - Agent agent = agentService.deleteApiKey(id); - return buildApiKeyResponse(agent.getApiKey(), agent.getApiKeyEnabled(), "删除 API Key 成功"); - } - - /** Toggle API Key enable flag */ - @PostMapping("/{id}/api-key/enable") - public ApiResponse toggleApiKey( - @PathVariable Long id, @RequestParam("enabled") boolean enabled) { - checkAgentExists(id); - Agent agent = agentService.toggleApiKey(id, enabled); - return buildApiKeyResponse( - agent.getApiKey() == null ? null : "****", agent.getApiKeyEnabled(), "更新 API Key 状态成功"); - } - - private Agent checkAgentExists(Long id) { - Agent agent = agentService.findById(id); - if (agent == null) { - throw new ResponseStatusException( - HttpStatus.NOT_FOUND, "agent with id: %d not found".formatted(id)); - } - return agent; - } - - private ApiResponse buildApiKeyResponse( - String apiKey, Integer apiKeyEnabled, String message) { - return ApiResponse.success(message, new ApiKeyResponse(apiKey, apiKeyEnabled)); - } + private final AgentService agentService; + + /** Get agent list */ + @GetMapping("/list") + public List list(@RequestParam(value = "status", required = false) String status, + @RequestParam(value = "keyword", required = false) String keyword) { + List result; + if (StringUtils.isNotBlank(keyword)) { + result = agentService.search(keyword); + } + else if (StringUtils.isNotBlank(status)) { + result = agentService.findByStatus(status); + } + else { + result = agentService.findAll(); + } + return result; + } + + /** Get agent details by ID */ + @GetMapping("/{id}") + public Agent get(@PathVariable Long id) { + return checkAgentExists(id); + } + + /** Create agent */ + @PostMapping + public Agent create(@RequestBody Agent agent) { + // Set default status + if (StringUtils.isBlank(agent.getStatus())) { + agent.setStatus("draft"); + } + return agentService.save(agent); + } + + /** Update agent */ + @PutMapping("/{id}") + public Agent update(@PathVariable Long id, @RequestBody Agent agent) { + checkAgentExists(id); + agent.setId(id); + return agentService.save(agent); + } + + /** Delete agent */ + @DeleteMapping("/{id}") + public void delete(@PathVariable Long id) { + checkAgentExists(id); + agentService.deleteById(id); + } + + /** Publish agent */ + @PostMapping("/{id}/publish") + public Agent publish(@PathVariable Long id) { + Agent agent = checkAgentExists(id); + agent.setStatus("published"); + return agentService.save(agent); + } + + /** Offline agent */ + @PostMapping("/{id}/offline") + public Agent offline(@PathVariable Long id) { + Agent agent = checkAgentExists(id); + agent.setStatus("offline"); + return agentService.save(agent); + } + + /** Get masked API Key status */ + @GetMapping("/{id}/api-key") + public ApiResponse getApiKey(@PathVariable Long id) { + Agent agent = checkAgentExists(id); + String masked = agentService.getApiKeyMasked(id); + return buildApiKeyResponse(masked, agent.getApiKeyEnabled(), "获取 API Key 成功"); + } + + /** Generate API Key */ + @PostMapping("/{id}/api-key/generate") + public ApiResponse generateApiKey(@PathVariable Long id) { + checkAgentExists(id); + Agent agent = agentService.generateApiKey(id); + return buildApiKeyResponse(agent.getApiKey(), agent.getApiKeyEnabled(), "生成 API Key 成功"); + } + + /** Reset API Key */ + @PostMapping("/{id}/api-key/reset") + public ApiResponse resetApiKey(@PathVariable Long id) { + checkAgentExists(id); + Agent agent = agentService.resetApiKey(id); + return buildApiKeyResponse(agent.getApiKey(), agent.getApiKeyEnabled(), "重置 API Key 成功"); + } + + /** Delete API Key */ + @DeleteMapping("/{id}/api-key") + public ApiResponse deleteApiKey(@PathVariable Long id) { + checkAgentExists(id); + Agent agent = agentService.deleteApiKey(id); + return buildApiKeyResponse(agent.getApiKey(), agent.getApiKeyEnabled(), "删除 API Key 成功"); + } + + /** Toggle API Key enable flag */ + @PostMapping("/{id}/api-key/enable") + public ApiResponse toggleApiKey(@PathVariable Long id, @RequestParam("enabled") boolean enabled) { + checkAgentExists(id); + Agent agent = agentService.toggleApiKey(id, enabled); + return buildApiKeyResponse(agent.getApiKey() == null ? null : "****", agent.getApiKeyEnabled(), + "更新 API Key 状态成功"); + } + + private Agent checkAgentExists(Long id) { + Agent agent = agentService.findById(id); + if (agent == null) { + throw new ResponseStatusException(HttpStatus.NOT_FOUND, "agent with id: %d not found".formatted(id)); + } + return agent; + } + + private ApiResponse buildApiKeyResponse(String apiKey, Integer apiKeyEnabled, String message) { + return ApiResponse.success(message, new ApiKeyResponse(apiKey, apiKeyEnabled)); + } + } diff --git a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/controller/AgentDatasourceController.java b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/controller/AgentDatasourceController.java index 5eaa82eb1..427cb2c53 100644 --- a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/controller/AgentDatasourceController.java +++ b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/controller/AgentDatasourceController.java @@ -30,8 +30,8 @@ import org.springframework.web.bind.annotation.*; /** - * Agent Schema Initialization Controller Handles agent's database Schema initialization to vector - * storage + * Agent Schema Initialization Controller Handles agent's database Schema initialization + * to vector storage */ @Slf4j @RestController @@ -40,136 +40,137 @@ @AllArgsConstructor public class AgentDatasourceController { - private final AgentDatasourceService agentDatasourceService; - - /** - * Initialize agent's database Schema to vector storage Corresponds to the "Initialize Information - * Source" function on the frontend - */ - @PostMapping("/init") - public ApiResponse initSchema(@PathVariable Long agentId) { - // 防止前端恶意请求,dto数据应该在后端获取 - try { - AgentDatasource agentDatasource = agentDatasourceService.getCurrentAgentDatasource(agentId); - log.info("Initializing schema for agent: {}", agentId); - - // Extract data source ID and table list from request - Integer datasourceId = agentDatasource.getDatasourceId(); - List tables = - Optional.ofNullable(agentDatasource.getSelectTables()).orElse(List.of()); - - // Validate request parameters - if (datasourceId == null) { - throw new InvalidInputException("数据源ID不能为空"); - } - - if (tables.isEmpty()) { - throw new InvalidInputException("表列表不能为空"); - } - - // Execute Schema initialization - Boolean result = - agentDatasourceService.initializeSchemaForAgentWithDatasource( - agentId, datasourceId, tables); - - if (result) { - log.info( - "Successfully initialized schema for agent: {}, tables: {}", agentId, tables.size()); - return ApiResponse.success("Schema初始化成功"); - } else { - throw new InternalServerException("Schema初始化失败"); - } - } catch (Exception e) { - log.error("Failed to initialize schema for agent: {}", agentId, e); - throw new InternalServerException("Schema初始化失败:%s".formatted(e.getMessage())); - } - } - - /** Get list of data sources configured for agent */ - @GetMapping - public ApiResponse getAgentDatasource(@PathVariable Long agentId) { - try { - log.info("Getting datasources for agent: {}", agentId); - List datasources = agentDatasourceService.getAgentDatasource(agentId); - log.info("Successfully retrieved {} datasources for agent: {}", datasources.size(), agentId); - return ApiResponse.success("操作成功", datasources); - } catch (Exception e) { - log.error("Failed to get datasources for agent: {}", agentId, e); - throw new InvalidInputException("获取数据源失败:%s".formatted(e.getMessage()), List.of()); - } - } - - @GetMapping("/active") - public ApiResponse getActiveAgentDatasource(@PathVariable Long agentId) { - try { - log.info("Getting active datasource for agent: {}", agentId); - AgentDatasource datasource = agentDatasourceService.getCurrentAgentDatasource(agentId); - return ApiResponse.success("操作成功", datasource); - } catch (Exception e) { - log.error("Failed to get active datasource for agent: {}", agentId, e); - throw new InvalidInputException("获取数据源失败:%s".formatted(e.getMessage()), List.of()); - } - } - - /** Add data source for agent */ - @PostMapping("/{datasourceId}") - public ApiResponse addDatasourceToAgent( - @PathVariable Long agentId, @PathVariable Integer datasourceId) { - try { - if (datasourceId == null) { - throw new InvalidInputException("数据源ID不能为空"); - } - - AgentDatasource agentDatasource = - agentDatasourceService.addDatasourceToAgent(agentId, datasourceId); - return ApiResponse.success("数据源添加成功", agentDatasource); - } catch (Exception e) { - throw new InternalServerException("数据源添加失败:%s".formatted(e.getMessage())); - } - } - - // 更新选择的数据表 - @PostMapping("/tables") - public ApiResponse updateDatasourceTables( - @PathVariable Long agentId, @RequestBody @Validated UpdateDatasourceTablesDTO dto) { - try { - dto.setTables(Optional.ofNullable(dto.getTables()).orElse(List.of())); - agentDatasourceService.updateDatasourceTables( - agentId, dto.getDatasourceId(), dto.getTables()); - return ApiResponse.success("更新成功"); - } catch (Exception e) { - log.error("Error: ", e); - throw new InternalServerException("更新失败:%s".formatted(e.getMessage())); - } - } - - /** Remove data source association from agent */ - @DeleteMapping("/{datasourceId}") - public ApiResponse removeDatasourceFromAgent( - @PathVariable Long agentId, @PathVariable Integer datasourceId) { - try { - agentDatasourceService.removeDatasourceFromAgent(agentId, datasourceId); - return ApiResponse.success("数据源已移除"); - } catch (Exception e) { - throw new InternalServerException("移除失败:%s".formatted(e.getMessage())); - } - } - - /** 启用/禁用智能体的数据源 */ - @PutMapping("/toggle") - public ApiResponse toggleDatasourceForAgent( - @PathVariable Long agentId, @RequestBody ToggleDatasourceDTO dto) { - try { - Boolean isActive = dto.getIsActive(); - Integer datasourceId = dto.getDatasourceId(); - if (isActive == null || datasourceId == null) { - throw new InvalidInputException("激活状态不能为空"); - } - AgentDatasource agentDatasource = - agentDatasourceService.toggleDatasourceForAgent(agentId, dto.getDatasourceId(), isActive); - return ApiResponse.success(isActive ? "数据源已启用" : "数据源已禁用", agentDatasource); - } catch (Exception e) { - throw new InternalServerException("操作失败:%s".formatted(e.getMessage())); - } - } + private final AgentDatasourceService agentDatasourceService; + + /** + * Initialize agent's database Schema to vector storage Corresponds to the "Initialize + * Information Source" function on the frontend + */ + @PostMapping("/init") + public ApiResponse initSchema(@PathVariable Long agentId) { + // 防止前端恶意请求,dto数据应该在后端获取 + try { + AgentDatasource agentDatasource = agentDatasourceService.getCurrentAgentDatasource(agentId); + log.info("Initializing schema for agent: {}", agentId); + + // Extract data source ID and table list from request + Integer datasourceId = agentDatasource.getDatasourceId(); + List tables = Optional.ofNullable(agentDatasource.getSelectTables()).orElse(List.of()); + + // Validate request parameters + if (datasourceId == null) { + throw new InvalidInputException("数据源ID不能为空"); + } + + if (tables.isEmpty()) { + throw new InvalidInputException("表列表不能为空"); + } + + // Execute Schema initialization + Boolean result = agentDatasourceService.initializeSchemaForAgentWithDatasource(agentId, datasourceId, + tables); + + if (result) { + log.info("Successfully initialized schema for agent: {}, tables: {}", agentId, tables.size()); + return ApiResponse.success("Schema初始化成功"); + } + else { + throw new InternalServerException("Schema初始化失败"); + } + } + catch (Exception e) { + log.error("Failed to initialize schema for agent: {}", agentId, e); + throw new InternalServerException("Schema初始化失败:%s".formatted(e.getMessage())); + } + } + + /** Get list of data sources configured for agent */ + @GetMapping + public ApiResponse getAgentDatasource(@PathVariable Long agentId) { + try { + log.info("Getting datasources for agent: {}", agentId); + List datasources = agentDatasourceService.getAgentDatasource(agentId); + log.info("Successfully retrieved {} datasources for agent: {}", datasources.size(), agentId); + return ApiResponse.success("操作成功", datasources); + } + catch (Exception e) { + log.error("Failed to get datasources for agent: {}", agentId, e); + throw new InvalidInputException("获取数据源失败:%s".formatted(e.getMessage()), List.of()); + } + } + + @GetMapping("/active") + public ApiResponse getActiveAgentDatasource(@PathVariable Long agentId) { + try { + log.info("Getting active datasource for agent: {}", agentId); + AgentDatasource datasource = agentDatasourceService.getCurrentAgentDatasource(agentId); + return ApiResponse.success("操作成功", datasource); + } + catch (Exception e) { + log.error("Failed to get active datasource for agent: {}", agentId, e); + throw new InvalidInputException("获取数据源失败:%s".formatted(e.getMessage()), List.of()); + } + } + + /** Add data source for agent */ + @PostMapping("/{datasourceId}") + public ApiResponse addDatasourceToAgent(@PathVariable Long agentId, @PathVariable Integer datasourceId) { + try { + if (datasourceId == null) { + throw new InvalidInputException("数据源ID不能为空"); + } + + AgentDatasource agentDatasource = agentDatasourceService.addDatasourceToAgent(agentId, datasourceId); + return ApiResponse.success("数据源添加成功", agentDatasource); + } + catch (Exception e) { + throw new InternalServerException("数据源添加失败:%s".formatted(e.getMessage())); + } + } + + // 更新选择的数据表 + @PostMapping("/tables") + public ApiResponse updateDatasourceTables(@PathVariable Long agentId, + @RequestBody @Validated UpdateDatasourceTablesDTO dto) { + try { + dto.setTables(Optional.ofNullable(dto.getTables()).orElse(List.of())); + agentDatasourceService.updateDatasourceTables(agentId, dto.getDatasourceId(), dto.getTables()); + return ApiResponse.success("更新成功"); + } + catch (Exception e) { + log.error("Error: ", e); + throw new InternalServerException("更新失败:%s".formatted(e.getMessage())); + } + } + + /** Remove data source association from agent */ + @DeleteMapping("/{datasourceId}") + public ApiResponse removeDatasourceFromAgent(@PathVariable Long agentId, @PathVariable Integer datasourceId) { + try { + agentDatasourceService.removeDatasourceFromAgent(agentId, datasourceId); + return ApiResponse.success("数据源已移除"); + } + catch (Exception e) { + throw new InternalServerException("移除失败:%s".formatted(e.getMessage())); + } + } + + /** 启用/禁用智能体的数据源 */ + @PutMapping("/toggle") + public ApiResponse toggleDatasourceForAgent(@PathVariable Long agentId, @RequestBody ToggleDatasourceDTO dto) { + try { + Boolean isActive = dto.getIsActive(); + Integer datasourceId = dto.getDatasourceId(); + if (isActive == null || datasourceId == null) { + throw new InvalidInputException("激活状态不能为空"); + } + AgentDatasource agentDatasource = agentDatasourceService.toggleDatasourceForAgent(agentId, + dto.getDatasourceId(), isActive); + return ApiResponse.success(isActive ? "数据源已启用" : "数据源已禁用", agentDatasource); + } + catch (Exception e) { + throw new InternalServerException("操作失败:%s".formatted(e.getMessage())); + } + } + } diff --git a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/controller/GlobalExceptionHandler.java b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/controller/GlobalExceptionHandler.java index 9832174bc..a5e7c795d 100644 --- a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/controller/GlobalExceptionHandler.java +++ b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/controller/GlobalExceptionHandler.java @@ -27,15 +27,16 @@ @RestControllerAdvice public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { - @ExceptionHandler(InvalidInputException.class) - @ResponseStatus(HttpStatus.BAD_REQUEST) - public ApiResponse handleInvalidInputException(InvalidInputException e) { - return ApiResponse.error(e.getMessage(), e.getData()); - } + @ExceptionHandler(InvalidInputException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ApiResponse handleInvalidInputException(InvalidInputException e) { + return ApiResponse.error(e.getMessage(), e.getData()); + } + + @ExceptionHandler(InternalServerException.class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public ApiResponse handleInvalidInputException(InternalServerException e) { + return ApiResponse.error(e.getMessage()); + } - @ExceptionHandler(InternalServerException.class) - @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) - public ApiResponse handleInvalidInputException(InternalServerException e) { - return ApiResponse.error(e.getMessage()); - } } diff --git a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/dto/schema/SemanticModelAddDTO.java b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/dto/schema/SemanticModelAddDTO.java index 0a5f8cc39..44bfdfda9 100644 --- a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/dto/schema/SemanticModelAddDTO.java +++ b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/dto/schema/SemanticModelAddDTO.java @@ -29,32 +29,33 @@ @AllArgsConstructor public class SemanticModelAddDTO { - /** 关联的智能体ID */ - @NotNull(message = "智能体ID不能为空") - private Long agentId; + /** 关联的智能体ID */ + @NotNull(message = "智能体ID不能为空") + private Long agentId; - /** 关联的表名 */ - @NotBlank(message = "表名不能为空") - private String tableName; + /** 关联的表名 */ + @NotBlank(message = "表名不能为空") + private String tableName; - /** 数据库中的物理字段名 (例如: csat_score) */ - @NotBlank(message = "数据库字段名不能为空") - private String columnName; + /** 数据库中的物理字段名 (例如: csat_score) */ + @NotBlank(message = "数据库字段名不能为空") + private String columnName; - /** 业务名/别名 (例如: 客户满意度分数) */ - @NotBlank(message = "业务名称不能为空") - private String businessName; + /** 业务名/别名 (例如: 客户满意度分数) */ + @NotBlank(message = "业务名称不能为空") + private String businessName; - /** 业务名的同义词 (例如: 满意度,客户评分) */ - private String synonyms; + /** 业务名的同义词 (例如: 满意度,客户评分) */ + private String synonyms; - /** 业务描述 (用于向LLM解释字段的业务含义) */ - private String businessDescription; + /** 业务描述 (用于向LLM解释字段的业务含义) */ + private String businessDescription; - /** 数据库中的物理字段的原始注释 */ - private String columnComment; + /** 数据库中的物理字段的原始注释 */ + private String columnComment; + + /** 物理数据类型 (例如: int, varchar(20)) */ + @NotBlank(message = "数据类型不能为空") + private String dataType; - /** 物理数据类型 (例如: int, varchar(20)) */ - @NotBlank(message = "数据类型不能为空") - private String dataType; } diff --git a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/exception/InternalServerException.java b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/exception/InternalServerException.java index bda0fa33d..801096012 100644 --- a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/exception/InternalServerException.java +++ b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/exception/InternalServerException.java @@ -16,7 +16,9 @@ package com.alibaba.cloud.ai.dataagent.exception; public class InternalServerException extends RuntimeException { - public InternalServerException(String message) { - super(message); - } + + public InternalServerException(String message) { + super(message); + } + } diff --git a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/exception/InvalidInputException.java b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/exception/InvalidInputException.java index 7e4d95c60..de817fb13 100644 --- a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/exception/InvalidInputException.java +++ b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/exception/InvalidInputException.java @@ -18,14 +18,17 @@ import lombok.Getter; public class InvalidInputException extends RuntimeException { - @Getter private Object data; - public InvalidInputException(String message) { - super(message); - } + @Getter + private Object data; + + public InvalidInputException(String message) { + super(message); + } + + public InvalidInputException(String message, Object data) { + super(message); + this.data = data; + } - public InvalidInputException(String message, Object data) { - super(message); - this.data = data; - } } diff --git a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/mapper/AgentDatasourceMapper.java b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/mapper/AgentDatasourceMapper.java index aa77b84fe..1f6d9d6b7 100644 --- a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/mapper/AgentDatasourceMapper.java +++ b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/mapper/AgentDatasourceMapper.java @@ -27,56 +27,49 @@ @Mapper public interface AgentDatasourceMapper { - /** Query associated data sources by agent ID (including data source details) */ - @Select( - "SELECT ad.*, d.name, d.type, d.host, d.port, d.database_name, " - + "d.connection_url, d.username, d.password, d.status, d.test_status, d.description " - + "FROM agent_datasource ad " - + "LEFT JOIN datasource d ON ad.datasource_id = d.id " - + "WHERE ad.agent_id = #{agentId} " - + "ORDER BY ad.create_time DESC") - List selectByAgentIdWithDatasource(@Param("agentId") Long agentId); + /** Query associated data sources by agent ID (including data source details) */ + @Select("SELECT ad.*, d.name, d.type, d.host, d.port, d.database_name, " + + "d.connection_url, d.username, d.password, d.status, d.test_status, d.description " + + "FROM agent_datasource ad " + "LEFT JOIN datasource d ON ad.datasource_id = d.id " + + "WHERE ad.agent_id = #{agentId} " + "ORDER BY ad.create_time DESC") + List selectByAgentIdWithDatasource(@Param("agentId") Long agentId); - /** Query associated data sources by agent ID */ - @Select("SELECT * FROM agent_datasource WHERE agent_id = #{agentId} ORDER BY create_time DESC") - List selectByAgentId(@Param("agentId") Long agentId); + /** Query associated data sources by agent ID */ + @Select("SELECT * FROM agent_datasource WHERE agent_id = #{agentId} ORDER BY create_time DESC") + List selectByAgentId(@Param("agentId") Long agentId); - /** Query association by agent ID and data source ID */ - @Select( - "SELECT * FROM agent_datasource WHERE agent_id = #{agentId} AND datasource_id = #{datasourceId}") - AgentDatasource selectByAgentIdAndDatasourceId( - @Param("agentId") Long agentId, @Param("datasourceId") Integer datasourceId); + /** Query association by agent ID and data source ID */ + @Select("SELECT * FROM agent_datasource WHERE agent_id = #{agentId} AND datasource_id = #{datasourceId}") + AgentDatasource selectByAgentIdAndDatasourceId(@Param("agentId") Long agentId, + @Param("datasourceId") Integer datasourceId); - /** Disable all data sources for an agent */ - @Update("UPDATE agent_datasource SET is_active = 0 WHERE agent_id = #{agentId}") - int disableAllByAgentId(@Param("agentId") Long agentId); + /** Disable all data sources for an agent */ + @Update("UPDATE agent_datasource SET is_active = 0 WHERE agent_id = #{agentId}") + int disableAllByAgentId(@Param("agentId") Long agentId); - /** Count the number of enabled data sources for an agent (excluding the specified data source) */ - @Select( - "SELECT COUNT(*) FROM agent_datasource WHERE agent_id = #{agentId} AND is_active = 1 AND datasource_id != #{excludeDatasourceId}") - int countActiveByAgentIdExcluding( - @Param("agentId") Long agentId, @Param("excludeDatasourceId") Integer excludeDatasourceId); + /** + * Count the number of enabled data sources for an agent (excluding the specified data + * source) + */ + @Select("SELECT COUNT(*) FROM agent_datasource WHERE agent_id = #{agentId} AND is_active = 1 AND datasource_id != #{excludeDatasourceId}") + int countActiveByAgentIdExcluding(@Param("agentId") Long agentId, + @Param("excludeDatasourceId") Integer excludeDatasourceId); - @Delete("DELETE FROM agent_datasource WHERE datasource_id = #{datasourceId}") - int deleteAllByDatasourceId(@Param("datasourceId") Integer datasourceId); + @Delete("DELETE FROM agent_datasource WHERE datasource_id = #{datasourceId}") + int deleteAllByDatasourceId(@Param("datasourceId") Integer datasourceId); - @Insert( - "INSERT INTO agent_datasource (agent_id, datasource_id, is_active) VALUES (#{agentId}, #{datasourceId}, 1)") - int createNewRelationEnabled( - @Param("agentId") Long agentId, @Param("datasourceId") Integer datasourceId); + @Insert("INSERT INTO agent_datasource (agent_id, datasource_id, is_active) VALUES (#{agentId}, #{datasourceId}, 1)") + int createNewRelationEnabled(@Param("agentId") Long agentId, @Param("datasourceId") Integer datasourceId); - @Update( - "UPDATE agent_datasource SET is_active = #{isActive} WHERE agent_id = #{agentId} AND datasource_id = #{datasourceId}") - int updateRelation( - @Param("agentId") Long agentId, - @Param("datasourceId") Integer datasourceId, - @Param("isActive") Integer isActive); + @Update("UPDATE agent_datasource SET is_active = #{isActive} WHERE agent_id = #{agentId} AND datasource_id = #{datasourceId}") + int updateRelation(@Param("agentId") Long agentId, @Param("datasourceId") Integer datasourceId, + @Param("isActive") Integer isActive); - default int enableRelation(Long agentId, Integer datasourceId) { - return updateRelation(agentId, datasourceId, 1); - } + default int enableRelation(Long agentId, Integer datasourceId) { + return updateRelation(agentId, datasourceId, 1); + } + + @Delete("DELETE FROM agent_datasource WHERE agent_id = #{agentId} AND datasource_id = #{datasourceId}") + int removeRelation(@Param("agentId") Long agentId, @Param("datasourceId") Integer datasourceId); - @Delete( - "DELETE FROM agent_datasource WHERE agent_id = #{agentId} AND datasource_id = #{datasourceId}") - int removeRelation(@Param("agentId") Long agentId, @Param("datasourceId") Integer datasourceId); } diff --git a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/service/agent/AgentStartupInitialization.java b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/service/agent/AgentStartupInitialization.java index eba6f38f8..2df68800b 100644 --- a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/service/agent/AgentStartupInitialization.java +++ b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/service/agent/AgentStartupInitialization.java @@ -34,166 +34,152 @@ @RequiredArgsConstructor public class AgentStartupInitialization implements ApplicationRunner, DisposableBean { - private final AgentService agentService; - - private final AgentVectorStoreService agentVectorStoreService; - - private final AgentDatasourceService agentDatasourceService; - - private final ExecutorService executorService; - - @Override - public void run(ApplicationArguments args) { - log.info("Starting automatic initialization of published agents..."); - - try { - // 因为异步可以让初始化过程在后台运行,不会阻塞Spring启动主线程,提高启动速度和响应性;即使初始化很耗时也不会影响主程序正常启动。 - CompletableFuture.runAsync(this::initializePublishedAgents, executorService) - .exceptionally( - throwable -> { - log.error("Error during agent initialization: {}", throwable.getMessage()); - return null; - }); - - } catch (Exception e) { - log.error("Failed to start agent initialization process", e); - } - } - - /** Initialize all published agents */ - private void initializePublishedAgents() { - try { - List publishedAgents = agentService.findByStatus("published"); - - if (publishedAgents.isEmpty()) { - log.info("No published agents found, skipping initialization"); - return; - } - - log.info("Found {} published agents, starting initialization...", publishedAgents.size()); - - int successCount = 0; - int failureCount = 0; - - for (Agent agent : publishedAgents) { - try { - boolean initialized = initializeAgentDataSource(agent); - if (initialized) { - successCount++; - log.info("Successfully initialized agent: {} (ID: {})", agent.getName(), agent.getId()); - } else { - failureCount++; - log.warn( - "Failed to initialize agent: {} (ID: {}) - no active datasource or tables", - agent.getName(), - agent.getId()); - } - } catch (Exception e) { - failureCount++; - log.error( - "Error initializing agent: {} (ID: {}, reason: {})", - agent.getName(), - agent.getId(), - e.getMessage()); - } - - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - break; - } - } - - log.info( - "Agent initialization completed. Success: {}, Failed: {}, Total: {}", - successCount, - failureCount, - publishedAgents.size()); - - } catch (Exception e) { - log.error("Error during published agents initialization", e); - } - } - - /** - * Initialize the data source for a single agent - * - * @param agent The agent - * @return Whether the initialization was successful - */ - private boolean initializeAgentDataSource(Agent agent) { - try { - Long agentId = agent.getId(); - - boolean hasData = isAlreadyInitialized(agentId); - - if (hasData) { - log.info("Agent {} already has vector data , skipping initialization", agentId); - return true; - } - - AgentDatasource activeDatasource = agentDatasourceService.getCurrentAgentDatasource(agentId); - - Integer datasourceId = activeDatasource.getDatasourceId(); - - List tables = activeDatasource.getSelectTables(); - - if (tables.isEmpty()) { - log.warn("Datasource {} has no tables available for agent {}", datasourceId, agentId); - return false; - } - - log.info( - "Initializing agent {} with datasource {} and {} tables", - agentId, - datasourceId, - tables.size()); - - Boolean result = - agentDatasourceService.initializeSchemaForAgentWithDatasource( - agentId, datasourceId, tables); - - if (result) { - log.info( - "Successfully initialized datasource for agent {} with {} tables", - agentId, - tables.size()); - return true; - } else { - log.error("Failed to initialize datasource for agent {}", agentId); - return false; - } - - } catch (Exception e) { - log.error( - "Error initializing datasource for agent {}, reason: {}", agent.getId(), e.getMessage()); - return false; - } - } - - private boolean isAlreadyInitialized(Long agentId) { - try { - String agentIdStr = String.valueOf(agentId); - return agentVectorStoreService.hasDocuments(agentIdStr); - } catch (Exception e) { - log.error( - "Failed to check initialization status for agent: {}, assuming not initialized", - agentId, - e); - return false; - } - } - - /** - * Clean up resources when the application shuts down. Implement the destroy method of the - * DisposableBean interface - */ - @Override - public void destroy() { - if (!executorService.isShutdown()) { - log.info("Shutting down agent initialization executor service"); - executorService.shutdown(); - } - } + private final AgentService agentService; + + private final AgentVectorStoreService agentVectorStoreService; + + private final AgentDatasourceService agentDatasourceService; + + private final ExecutorService executorService; + + @Override + public void run(ApplicationArguments args) { + log.info("Starting automatic initialization of published agents..."); + + try { + // 因为异步可以让初始化过程在后台运行,不会阻塞Spring启动主线程,提高启动速度和响应性;即使初始化很耗时也不会影响主程序正常启动。 + CompletableFuture.runAsync(this::initializePublishedAgents, executorService).exceptionally(throwable -> { + log.error("Error during agent initialization: {}", throwable.getMessage()); + return null; + }); + + } + catch (Exception e) { + log.error("Failed to start agent initialization process", e); + } + } + + /** Initialize all published agents */ + private void initializePublishedAgents() { + try { + List publishedAgents = agentService.findByStatus("published"); + + if (publishedAgents.isEmpty()) { + log.info("No published agents found, skipping initialization"); + return; + } + + log.info("Found {} published agents, starting initialization...", publishedAgents.size()); + + int successCount = 0; + int failureCount = 0; + + for (Agent agent : publishedAgents) { + try { + boolean initialized = initializeAgentDataSource(agent); + if (initialized) { + successCount++; + log.info("Successfully initialized agent: {} (ID: {})", agent.getName(), agent.getId()); + } + else { + failureCount++; + log.warn("Failed to initialize agent: {} (ID: {}) - no active datasource or tables", + agent.getName(), agent.getId()); + } + } + catch (Exception e) { + failureCount++; + log.error("Error initializing agent: {} (ID: {}, reason: {})", agent.getName(), agent.getId(), + e.getMessage()); + } + + try { + Thread.sleep(1000); + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } + } + + log.info("Agent initialization completed. Success: {}, Failed: {}, Total: {}", successCount, failureCount, + publishedAgents.size()); + + } + catch (Exception e) { + log.error("Error during published agents initialization", e); + } + } + + /** + * Initialize the data source for a single agent + * @param agent The agent + * @return Whether the initialization was successful + */ + private boolean initializeAgentDataSource(Agent agent) { + try { + Long agentId = agent.getId(); + + boolean hasData = isAlreadyInitialized(agentId); + + if (hasData) { + log.info("Agent {} already has vector data , skipping initialization", agentId); + return true; + } + + AgentDatasource activeDatasource = agentDatasourceService.getCurrentAgentDatasource(agentId); + + Integer datasourceId = activeDatasource.getDatasourceId(); + + List tables = activeDatasource.getSelectTables(); + + if (tables.isEmpty()) { + log.warn("Datasource {} has no tables available for agent {}", datasourceId, agentId); + return false; + } + + log.info("Initializing agent {} with datasource {} and {} tables", agentId, datasourceId, tables.size()); + + Boolean result = agentDatasourceService.initializeSchemaForAgentWithDatasource(agentId, datasourceId, + tables); + + if (result) { + log.info("Successfully initialized datasource for agent {} with {} tables", agentId, tables.size()); + return true; + } + else { + log.error("Failed to initialize datasource for agent {}", agentId); + return false; + } + + } + catch (Exception e) { + log.error("Error initializing datasource for agent {}, reason: {}", agent.getId(), e.getMessage()); + return false; + } + } + + private boolean isAlreadyInitialized(Long agentId) { + try { + String agentIdStr = String.valueOf(agentId); + return agentVectorStoreService.hasDocuments(agentIdStr); + } + catch (Exception e) { + log.error("Failed to check initialization status for agent: {}, assuming not initialized", agentId, e); + return false; + } + } + + /** + * Clean up resources when the application shuts down. Implement the destroy method of + * the DisposableBean interface + */ + @Override + public void destroy() { + if (!executorService.isShutdown()) { + log.info("Shutting down agent initialization executor service"); + executorService.shutdown(); + } + } + } diff --git a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/service/datasource/AgentDatasourceService.java b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/service/datasource/AgentDatasourceService.java index 8bf70b88f..742928589 100644 --- a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/service/datasource/AgentDatasourceService.java +++ b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/service/datasource/AgentDatasourceService.java @@ -20,25 +20,24 @@ public interface AgentDatasourceService { - /** Initialize agent's database schema using datasource */ - Boolean initializeSchemaForAgentWithDatasource( - Long agentId, Integer datasourceId, List tables); + /** Initialize agent's database schema using datasource */ + Boolean initializeSchemaForAgentWithDatasource(Long agentId, Integer datasourceId, List tables); - List getAgentDatasource(Long agentId); + List getAgentDatasource(Long agentId); - default AgentDatasource getCurrentAgentDatasource(Long agentId) { - return getAgentDatasource(agentId).stream() - .filter(a -> a.getIsActive() != 0) - .findFirst() - .orElseThrow( - () -> new IllegalStateException("Agent " + agentId + " has no active datasource")); - } + default AgentDatasource getCurrentAgentDatasource(Long agentId) { + return getAgentDatasource(agentId).stream() + .filter(a -> a.getIsActive() != 0) + .findFirst() + .orElseThrow(() -> new IllegalStateException("Agent " + agentId + " has no active datasource")); + } - AgentDatasource addDatasourceToAgent(Long agentId, Integer datasourceId); + AgentDatasource addDatasourceToAgent(Long agentId, Integer datasourceId); - void removeDatasourceFromAgent(Long agentId, Integer datasourceId); + void removeDatasourceFromAgent(Long agentId, Integer datasourceId); - AgentDatasource toggleDatasourceForAgent(Long agentId, Integer datasourceId, Boolean isActive); + AgentDatasource toggleDatasourceForAgent(Long agentId, Integer datasourceId, Boolean isActive); + + void updateDatasourceTables(Long agentId, Integer datasourceId, List tables); - void updateDatasourceTables(Long agentId, Integer datasourceId, List tables); } diff --git a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/service/datasource/impl/AgentDatasourceServiceImpl.java b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/service/datasource/impl/AgentDatasourceServiceImpl.java index 4c39a2d95..c072bde55 100644 --- a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/service/datasource/impl/AgentDatasourceServiceImpl.java +++ b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/service/datasource/impl/AgentDatasourceServiceImpl.java @@ -37,159 +37,145 @@ @AllArgsConstructor public class AgentDatasourceServiceImpl implements AgentDatasourceService { - private final DatasourceService datasourceService; - - private final SchemaService schemaService; - - private final AgentDatasourceMapper agentDatasourceMapper; - - private final AgentDatasourceTablesMapper tablesMapper; - - @Override - public Boolean initializeSchemaForAgentWithDatasource( - Long agentId, Integer datasourceId, List tables) { - Assert.notNull(agentId, "Agent ID cannot be null"); - Assert.notNull(datasourceId, "Datasource ID cannot be null"); - Assert.notEmpty(tables, "Tables cannot be empty"); - try { - String agentIdStr = String.valueOf(agentId); - log.info( - "Initializing schema for agent: {} with datasource: {}, tables: {}", - agentIdStr, - datasourceId, - tables); - - // Get data source information - Datasource datasource = datasourceService.getDatasourceById(datasourceId); - if (datasource == null) { - throw new RuntimeException("Datasource not found with id: " + datasourceId); - } - - // Create database configuration - DbConfigBO dbConfig = datasourceService.getDbConfig(datasource); - - // Create SchemaInitRequest - SchemaInitRequest schemaInitRequest = new SchemaInitRequest(); - schemaInitRequest.setDbConfig(dbConfig); - schemaInitRequest.setTables(tables); - - log.info( - "Created SchemaInitRequest for agent: {}, dbConfig: {}, tables: {}", - agentIdStr, - dbConfig, - tables); - - // Call the original initialization method - return schemaService.schema(agentIdStr, schemaInitRequest); - - } catch (Exception e) { - log.error( - "Failed to initialize schema for agent: {} with datasource: {}", - agentId, - datasourceId, - e); - throw new RuntimeException( - "Failed to initialize schema for agent " + agentId + ": " + e.getMessage(), e); - } - } - - @Override - public List getAgentDatasource(Long agentId) { - Assert.notNull(agentId, "Agent ID cannot be null"); - List adentDatasources = - agentDatasourceMapper.selectByAgentIdWithDatasource(agentId); - - // Manually fill in the data source information (since MyBatis Plus does not - // directly support complex join query result mapping) - for (AgentDatasource agentDatasource : adentDatasources) { - if (agentDatasource.getDatasourceId() != null) { - Datasource datasource = - datasourceService.getDatasourceById(agentDatasource.getDatasourceId()); - agentDatasource.setDatasource(datasource); - } - // 获取选中的数据表 - int id = agentDatasource.getId(); - List tables = tablesMapper.getAgentDatasourceTables(id); - agentDatasource.setSelectTables(Optional.ofNullable(tables).orElse(List.of())); - } - - return adentDatasources; - } - - @Override - @Transactional - public AgentDatasource addDatasourceToAgent(Long agentId, Integer datasourceId) { - // First, disable other data sources for this agent (an agent can only have one - // enabled data source) - agentDatasourceMapper.disableAllByAgentId(agentId); - - // Check if an association already exists - AgentDatasource existing = - agentDatasourceMapper.selectByAgentIdAndDatasourceId(agentId, datasourceId); - - AgentDatasource result; - if (existing != null) { - // If it exists, activate the association - agentDatasourceMapper.enableRelation(agentId, datasourceId); - - // 删除已有的表 - tablesMapper.removeAllTables(existing.getId()); - - // Query and return the updated association - result = agentDatasourceMapper.selectByAgentIdAndDatasourceId(agentId, datasourceId); - } else { - // If it does not exist, create a new association - AgentDatasource agentDatasource = new AgentDatasource(agentId, datasourceId); - agentDatasource.setIsActive(1); - agentDatasourceMapper.createNewRelationEnabled(agentId, datasourceId); - result = agentDatasource; - } - result.setSelectTables(List.of()); - return result; - } - - @Override - public void removeDatasourceFromAgent(Long agentId, Integer datasourceId) { - agentDatasourceMapper.removeRelation(agentId, datasourceId); - } - - @Override - public AgentDatasource toggleDatasourceForAgent( - Long agentId, Integer datasourceId, Boolean isActive) { - // If enabling data source, first check if there are other enabled data sources - if (isActive) { - int activeCount = agentDatasourceMapper.countActiveByAgentIdExcluding(agentId, datasourceId); - if (activeCount > 0) { - throw new RuntimeException("同一智能体下只能启用一个数据源,请先禁用其他数据源后再启用此数据源"); - } - } - - // Update data source status - int updated = agentDatasourceMapper.updateRelation(agentId, datasourceId, isActive ? 1 : 0); - - if (updated == 0) { - throw new RuntimeException("未找到相关的数据源关联记录"); - } - - // Return the updated association record - return agentDatasourceMapper.selectByAgentIdAndDatasourceId(agentId, datasourceId); - } - - @Override - @Transactional - public void updateDatasourceTables(Long agentId, Integer datasourceId, List tables) { - if (agentId == null || datasourceId == null || tables == null) { - throw new IllegalArgumentException("参数不能为空"); - } - AgentDatasource datasource = - agentDatasourceMapper.selectByAgentIdAndDatasourceId(agentId, datasourceId); - if (datasource == null) { - throw new IllegalArgumentException("未找到对应的数据源关联记录"); - } - if (tables.isEmpty()) { - tablesMapper.removeAllTables(datasource.getId()); - } else { - tablesMapper.updateAgentDatasourceTables(datasource.getId(), tables); - } - } + private final DatasourceService datasourceService; + + private final SchemaService schemaService; + + private final AgentDatasourceMapper agentDatasourceMapper; + + private final AgentDatasourceTablesMapper tablesMapper; + + @Override + public Boolean initializeSchemaForAgentWithDatasource(Long agentId, Integer datasourceId, List tables) { + Assert.notNull(agentId, "Agent ID cannot be null"); + Assert.notNull(datasourceId, "Datasource ID cannot be null"); + Assert.notEmpty(tables, "Tables cannot be empty"); + try { + String agentIdStr = String.valueOf(agentId); + log.info("Initializing schema for agent: {} with datasource: {}, tables: {}", agentIdStr, datasourceId, + tables); + + // Get data source information + Datasource datasource = datasourceService.getDatasourceById(datasourceId); + if (datasource == null) { + throw new RuntimeException("Datasource not found with id: " + datasourceId); + } + + // Create database configuration + DbConfigBO dbConfig = datasourceService.getDbConfig(datasource); + + // Create SchemaInitRequest + SchemaInitRequest schemaInitRequest = new SchemaInitRequest(); + schemaInitRequest.setDbConfig(dbConfig); + schemaInitRequest.setTables(tables); + + log.info("Created SchemaInitRequest for agent: {}, dbConfig: {}, tables: {}", agentIdStr, dbConfig, tables); + + // Call the original initialization method + return schemaService.schema(agentIdStr, schemaInitRequest); + + } + catch (Exception e) { + log.error("Failed to initialize schema for agent: {} with datasource: {}", agentId, datasourceId, e); + throw new RuntimeException("Failed to initialize schema for agent " + agentId + ": " + e.getMessage(), e); + } + } + + @Override + public List getAgentDatasource(Long agentId) { + Assert.notNull(agentId, "Agent ID cannot be null"); + List adentDatasources = agentDatasourceMapper.selectByAgentIdWithDatasource(agentId); + + // Manually fill in the data source information (since MyBatis Plus does not + // directly support complex join query result mapping) + for (AgentDatasource agentDatasource : adentDatasources) { + if (agentDatasource.getDatasourceId() != null) { + Datasource datasource = datasourceService.getDatasourceById(agentDatasource.getDatasourceId()); + agentDatasource.setDatasource(datasource); + } + // 获取选中的数据表 + int id = agentDatasource.getId(); + List tables = tablesMapper.getAgentDatasourceTables(id); + agentDatasource.setSelectTables(Optional.ofNullable(tables).orElse(List.of())); + } + + return adentDatasources; + } + + @Override + @Transactional + public AgentDatasource addDatasourceToAgent(Long agentId, Integer datasourceId) { + // First, disable other data sources for this agent (an agent can only have one + // enabled data source) + agentDatasourceMapper.disableAllByAgentId(agentId); + + // Check if an association already exists + AgentDatasource existing = agentDatasourceMapper.selectByAgentIdAndDatasourceId(agentId, datasourceId); + + AgentDatasource result; + if (existing != null) { + // If it exists, activate the association + agentDatasourceMapper.enableRelation(agentId, datasourceId); + + // 删除已有的表 + tablesMapper.removeAllTables(existing.getId()); + + // Query and return the updated association + result = agentDatasourceMapper.selectByAgentIdAndDatasourceId(agentId, datasourceId); + } + else { + // If it does not exist, create a new association + AgentDatasource agentDatasource = new AgentDatasource(agentId, datasourceId); + agentDatasource.setIsActive(1); + agentDatasourceMapper.createNewRelationEnabled(agentId, datasourceId); + result = agentDatasource; + } + result.setSelectTables(List.of()); + return result; + } + + @Override + public void removeDatasourceFromAgent(Long agentId, Integer datasourceId) { + agentDatasourceMapper.removeRelation(agentId, datasourceId); + } + + @Override + public AgentDatasource toggleDatasourceForAgent(Long agentId, Integer datasourceId, Boolean isActive) { + // If enabling data source, first check if there are other enabled data sources + if (isActive) { + int activeCount = agentDatasourceMapper.countActiveByAgentIdExcluding(agentId, datasourceId); + if (activeCount > 0) { + throw new RuntimeException("同一智能体下只能启用一个数据源,请先禁用其他数据源后再启用此数据源"); + } + } + + // Update data source status + int updated = agentDatasourceMapper.updateRelation(agentId, datasourceId, isActive ? 1 : 0); + + if (updated == 0) { + throw new RuntimeException("未找到相关的数据源关联记录"); + } + + // Return the updated association record + return agentDatasourceMapper.selectByAgentIdAndDatasourceId(agentId, datasourceId); + } + + @Override + @Transactional + public void updateDatasourceTables(Long agentId, Integer datasourceId, List tables) { + if (agentId == null || datasourceId == null || tables == null) { + throw new IllegalArgumentException("参数不能为空"); + } + AgentDatasource datasource = agentDatasourceMapper.selectByAgentIdAndDatasourceId(agentId, datasourceId); + if (datasource == null) { + throw new IllegalArgumentException("未找到对应的数据源关联记录"); + } + if (tables.isEmpty()) { + tablesMapper.removeAllTables(datasource.getId()); + } + else { + tablesMapper.updateAgentDatasourceTables(datasource.getId(), tables); + } + } + } diff --git a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/service/semantic/SemanticModelService.java b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/service/semantic/SemanticModelService.java index 5261e5fc2..716574f9d 100644 --- a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/service/semantic/SemanticModelService.java +++ b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/service/semantic/SemanticModelService.java @@ -25,41 +25,41 @@ public interface SemanticModelService { - List getAll(); + List getAll(); - List getEnabledByAgentId(Long agentId); + List getEnabledByAgentId(Long agentId); - List getByAgentIdAndTableNames(Long agentId, List tableNames); + List getByAgentIdAndTableNames(Long agentId, List tableNames); - SemanticModel getById(Long id); + SemanticModel getById(Long id); - void addSemanticModel(SemanticModel semanticModel); + void addSemanticModel(SemanticModel semanticModel); - boolean addSemanticModel(SemanticModelAddDTO dto); + boolean addSemanticModel(SemanticModelAddDTO dto); - void enableSemanticModel(Long id); + void enableSemanticModel(Long id); - void disableSemanticModel(Long id); + void disableSemanticModel(Long id); - List getByAgentId(Long agentId); + List getByAgentId(Long agentId); - List search(String keyword); + List search(String keyword); - void deleteSemanticModel(Long id); + void deleteSemanticModel(Long id); - void updateSemanticModel(Long id, SemanticModel semanticModel); + void updateSemanticModel(Long id, SemanticModel semanticModel); - default void addSemanticModels(List semanticModels) { - semanticModels.forEach(this::addSemanticModel); - } + default void addSemanticModels(List semanticModels) { + semanticModels.forEach(this::addSemanticModel); + } - default void enableSemanticModels(List ids) { - ids.forEach(this::enableSemanticModel); - } + default void enableSemanticModels(List ids) { + ids.forEach(this::enableSemanticModel); + } - default void disableSemanticModels(List ids) { - ids.forEach(this::disableSemanticModel); - } + default void disableSemanticModels(List ids) { + ids.forEach(this::disableSemanticModel); + } default void deleteSemanticModels(List ids) { ids.forEach(this::deleteSemanticModel); diff --git a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/service/semantic/SemanticModelServiceImpl.java b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/service/semantic/SemanticModelServiceImpl.java index 41101383c..697d28d5e 100644 --- a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/service/semantic/SemanticModelServiceImpl.java +++ b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/service/semantic/SemanticModelServiceImpl.java @@ -31,12 +31,11 @@ @Service @AllArgsConstructor -@Slf4j public class SemanticModelServiceImpl implements SemanticModelService { - private final SemanticModelMapper semanticModelMapper; + private final SemanticModelMapper semanticModelMapper; - private final AgentDatasourceMapper agentDatasourceMapper; + private final AgentDatasourceMapper agentDatasourceMapper; private final SemanticModelExcelService excelService; diff --git a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/util/FluxUtil.java b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/util/FluxUtil.java index 36f9a06e4..3b4138ebb 100644 --- a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/util/FluxUtil.java +++ b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/util/FluxUtil.java @@ -33,162 +33,112 @@ */ public final class FluxUtil { - private FluxUtil() {} - - /** - * 级联两个具有前后关系的Flux - * - * @param originFlux 第一个Flux - * @param nextFluxFunc 根据第一个Flux的聚合结果生成第二个Flux - * @param aggregator 聚合第一个Flux的所有数据 - * @param preFlux 在第一个Flux前添加的信息Flux - * @param middleFlux 在第一个Flux和第二个Flux之间添加的信息Flux - * @param endFlux 在第二个Flux后添加的信息Flux - */ - public static Flux cascadeFlux( - Flux originFlux, - Function> nextFluxFunc, - Function, Mono> aggregator, - Flux preFlux, - Flux middleFlux, - Flux endFlux) { - // 缓存原始流避免重复执行 - Flux cachedOrigin = originFlux.cache(); - - // 聚合结果 - Mono aggregatedResult = aggregator.apply(cachedOrigin).cache(); - - // 构建完整流 - Flux secondFlux = aggregatedResult.flatMapMany(nextFluxFunc); - - return preFlux - .concatWith(cachedOrigin) - .concatWith(middleFlux) - .concatWith(secondFlux) - .concatWith(endFlux); - } - - /** - * 级联两个具有前后关系的Flux - * - * @param originFlux 第一个Flux - * @param nextFluxFunc 根据第一个Flux的聚合结果生成第二个Flux - * @param aggregator 聚合第一个Flux的所有数据 - */ - public static Flux cascadeFlux( - Flux originFlux, - Function> nextFluxFunc, - Function, Mono> aggregator) { - return cascadeFlux( - originFlux, nextFluxFunc, aggregator, Flux.empty(), Flux.empty(), Flux.empty()); - } - - /** - * Quickly create streaming generator with start and end messages - * - * @param nodeClass node class - * @param state state - * @param startMessage start message - * @param completionMessage completion message - * @param resultMapper result mapping function - * @param sourceFlux source data stream - * @return Flux instance - */ - public static Flux> createStreamingGeneratorWithMessages( - Class nodeClass, - OverAllState state, - String startMessage, - String completionMessage, - Function> resultMapper, - Flux sourceFlux) { - String nodeName = nodeClass.getSimpleName(); - - // Used to collect actual processing results - final StringBuilder collectedResult = new StringBuilder(); - - // wrapperFlux - Flux startFlux = - (startMessage == null - ? Flux.empty() - : Flux.just(ChatResponseUtil.createResponse(startMessage))); - Flux wrapperFlux = - startFlux.concatWith( - sourceFlux.doOnNext( - chatResponse -> { - String text = ChatResponseUtil.getText(chatResponse); - collectedResult.append(text); - })); - if (completionMessage != null) { - wrapperFlux = - wrapperFlux.concatWith(Flux.just(ChatResponseUtil.createResponse(completionMessage))); - } - return toStreamingResponseFlux( - nodeName, state, wrapperFlux, () -> resultMapper.apply(collectedResult.toString())); - } - - public static Flux> createStreamingGeneratorWithMessages( - Class nodeClass, - OverAllState state, - Function> resultMapper, - Flux sourceFlux) { - return createStreamingGeneratorWithMessages( - nodeClass, state, null, null, resultMapper, sourceFlux); - } - - /** - * create streaming generator with start and end flux - * - * @param nodeClass node class - * @param state state - * @param sourceFlux source data stream - * @param preFlux preFlux - * @param sufFlux sufFlux - * @param sourceMapper result of sourceFlux mapping function - * @return Flux instance - */ - public static Flux> createStreamingGenerator( - Class nodeClass, - OverAllState state, - Flux sourceFlux, - Flux preFlux, - Flux sufFlux, - Function> sourceMapper) { - String nodeName = nodeClass.getSimpleName(); - // Used to collect actual processing results - final StringBuilder collectedResult = new StringBuilder(); - sourceFlux = sourceFlux.doOnNext(r -> collectedResult.append(ChatResponseUtil.getText(r))); - return toStreamingResponseFlux( - nodeName, - state, - Flux.concat(preFlux, sourceFlux, sufFlux), - () -> sourceMapper.apply(collectedResult.toString())); - } - - private static Flux> toStreamingResponseFlux( - String nodeName, - OverAllState state, - Flux sourceFlux, - Supplier> resultSupplier) { - Flux> streamingFlux = - sourceFlux - .filter( - response -> - response != null - && response.getResult() != null - && response.getResult().getOutput() != null) - .map( - response -> - GraphResponse.of( - new StreamingOutput<>( - response.getResult().getOutput(), - response, - nodeName, - "", - state, - OutputType.from(true, nodeName)))); - - return streamingFlux - .concatWith(Mono.fromSupplier(() -> GraphResponse.done(resultSupplier.get()))) - .onErrorResume(error -> Flux.just(GraphResponse.error(error))); - } + private FluxUtil() { + } + + /** + * 级联两个具有前后关系的Flux + * @param originFlux 第一个Flux + * @param nextFluxFunc 根据第一个Flux的聚合结果生成第二个Flux + * @param aggregator 聚合第一个Flux的所有数据 + * @param preFlux 在第一个Flux前添加的信息Flux + * @param middleFlux 在第一个Flux和第二个Flux之间添加的信息Flux + * @param endFlux 在第二个Flux后添加的信息Flux + */ + public static Flux cascadeFlux(Flux originFlux, Function> nextFluxFunc, + Function, Mono> aggregator, Flux preFlux, Flux middleFlux, Flux endFlux) { + // 缓存原始流避免重复执行 + Flux cachedOrigin = originFlux.cache(); + + // 聚合结果 + Mono aggregatedResult = aggregator.apply(cachedOrigin).cache(); + + // 构建完整流 + Flux secondFlux = aggregatedResult.flatMapMany(nextFluxFunc); + + return preFlux.concatWith(cachedOrigin).concatWith(middleFlux).concatWith(secondFlux).concatWith(endFlux); + } + + /** + * 级联两个具有前后关系的Flux + * @param originFlux 第一个Flux + * @param nextFluxFunc 根据第一个Flux的聚合结果生成第二个Flux + * @param aggregator 聚合第一个Flux的所有数据 + */ + public static Flux cascadeFlux(Flux originFlux, Function> nextFluxFunc, + Function, Mono> aggregator) { + return cascadeFlux(originFlux, nextFluxFunc, aggregator, Flux.empty(), Flux.empty(), Flux.empty()); + } + + /** + * Quickly create streaming generator with start and end messages + * @param nodeClass node class + * @param state state + * @param startMessage start message + * @param completionMessage completion message + * @param resultMapper result mapping function + * @param sourceFlux source data stream + * @return Flux instance + */ + public static Flux> createStreamingGeneratorWithMessages( + Class nodeClass, OverAllState state, String startMessage, String completionMessage, + Function> resultMapper, Flux sourceFlux) { + String nodeName = nodeClass.getSimpleName(); + + // Used to collect actual processing results + final StringBuilder collectedResult = new StringBuilder(); + + // wrapperFlux + Flux startFlux = (startMessage == null ? Flux.empty() + : Flux.just(ChatResponseUtil.createResponse(startMessage))); + Flux wrapperFlux = startFlux.concatWith(sourceFlux.doOnNext(chatResponse -> { + String text = ChatResponseUtil.getText(chatResponse); + collectedResult.append(text); + })); + if (completionMessage != null) { + wrapperFlux = wrapperFlux.concatWith(Flux.just(ChatResponseUtil.createResponse(completionMessage))); + } + return toStreamingResponseFlux(nodeName, state, wrapperFlux, + () -> resultMapper.apply(collectedResult.toString())); + } + + public static Flux> createStreamingGeneratorWithMessages( + Class nodeClass, OverAllState state, + Function> resultMapper, Flux sourceFlux) { + return createStreamingGeneratorWithMessages(nodeClass, state, null, null, resultMapper, sourceFlux); + } + + /** + * create streaming generator with start and end flux + * @param nodeClass node class + * @param state state + * @param sourceFlux source data stream + * @param preFlux preFlux + * @param sufFlux sufFlux + * @param sourceMapper result of sourceFlux mapping function + * @return Flux instance + */ + public static Flux> createStreamingGenerator(Class nodeClass, + OverAllState state, Flux sourceFlux, Flux preFlux, Flux sufFlux, + Function> sourceMapper) { + String nodeName = nodeClass.getSimpleName(); + // Used to collect actual processing results + final StringBuilder collectedResult = new StringBuilder(); + sourceFlux = sourceFlux.doOnNext(r -> collectedResult.append(ChatResponseUtil.getText(r))); + return toStreamingResponseFlux(nodeName, state, Flux.concat(preFlux, sourceFlux, sufFlux), + () -> sourceMapper.apply(collectedResult.toString())); + } + + private static Flux> toStreamingResponseFlux(String nodeName, OverAllState state, + Flux sourceFlux, Supplier> resultSupplier) { + Flux> streamingFlux = sourceFlux + .filter(response -> response != null && response.getResult() != null + && response.getResult().getOutput() != null) + .map(response -> GraphResponse.of(new StreamingOutput<>(response.getResult().getOutput(), response, + nodeName, "", state, OutputType.from(true, nodeName)))); + + return streamingFlux.concatWith(Mono.fromSupplier(() -> GraphResponse.done(resultSupplier.get()))) + .onErrorResume(error -> Flux.just(GraphResponse.error(error))); + } + } diff --git a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/workflow/node/SqlExecuteNode.java b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/workflow/node/SqlExecuteNode.java index 66821d4d5..f78575246 100644 --- a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/workflow/node/SqlExecuteNode.java +++ b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/workflow/node/SqlExecuteNode.java @@ -73,9 +73,9 @@ @AllArgsConstructor public class SqlExecuteNode implements NodeAction { - private final DatabaseUtil databaseUtil; + private final DatabaseUtil databaseUtil; - private final Nl2SqlService nl2SqlService; + private final Nl2SqlService nl2SqlService; private final LlmService llmService; @@ -86,26 +86,26 @@ public class SqlExecuteNode implements NodeAction { @Override public Map apply(OverAllState state) throws Exception { - Integer currentStep = PlanProcessUtil.getCurrentStepNumber(state); + Integer currentStep = PlanProcessUtil.getCurrentStepNumber(state); - String sqlQuery = StateUtil.getStringValue(state, SQL_GENERATE_OUTPUT); - sqlQuery = nl2SqlService.sqlTrim(sqlQuery); + String sqlQuery = StateUtil.getStringValue(state, SQL_GENERATE_OUTPUT); + sqlQuery = nl2SqlService.sqlTrim(sqlQuery); - log.info("Executing SQL query: {}", sqlQuery); + log.info("Executing SQL query: {}", sqlQuery); - // Get the agent ID from the state - String agentIdStr = StateUtil.getStringValue(state, Constant.AGENT_ID); - if (StringUtils.isBlank(agentIdStr)) { - throw new IllegalStateException("Agent ID cannot be empty."); - } + // Get the agent ID from the state + String agentIdStr = StateUtil.getStringValue(state, Constant.AGENT_ID); + if (StringUtils.isBlank(agentIdStr)) { + throw new IllegalStateException("Agent ID cannot be empty."); + } - Long agentId = Long.valueOf(agentIdStr); + Long agentId = Long.valueOf(agentIdStr); - // Dynamically get the data source configuration for an agent - DbConfigBO dbConfig = databaseUtil.getAgentDbConfig(agentId); + // Dynamically get the data source configuration for an agent + DbConfigBO dbConfig = databaseUtil.getAgentDbConfig(agentId); - return executeSqlQuery(state, currentStep, sqlQuery, dbConfig, agentId); - } + return executeSqlQuery(state, currentStep, sqlQuery, dbConfig, agentId); + } /** * Executes the SQL query against the database and handles the results. diff --git a/data-agent-management/src/test/java/com/alibaba/cloud/ai/dataagent/mapper/AgentDatasourceMapperTest.java b/data-agent-management/src/test/java/com/alibaba/cloud/ai/dataagent/mapper/AgentDatasourceMapperTest.java index 1f9c407ca..df8b7460f 100644 --- a/data-agent-management/src/test/java/com/alibaba/cloud/ai/dataagent/mapper/AgentDatasourceMapperTest.java +++ b/data-agent-management/src/test/java/com/alibaba/cloud/ai/dataagent/mapper/AgentDatasourceMapperTest.java @@ -38,241 +38,231 @@ @MybatisTest @TestPropertySource( - properties = { - "spring.sql.init.mode=never", - "mybatis.configuration.map-underscore-to-camel-case=true" - }) + properties = { "spring.sql.init.mode=never", "mybatis.configuration.map-underscore-to-camel-case=true" }) @ImportTestcontainers(MySqlContainerConfiguration.class) @ImportAutoConfiguration(MySqlContainerConfiguration.class) public class AgentDatasourceMapperTest { - @Autowired private DatasourceMapper datasourceMapper; - - @Autowired private AgentDatasourceMapper agentDatasourceMapper; - - @Autowired private JdbcTemplate jdbcTemplate; - - private Long agentId; - - @BeforeEach - public void setUpAgent() { - this.agentId = createTestAgentAndGetId(); - } - - // ==================== AgentDatasourceMapper Tests ==================== - - @Test - public void testAgentDatasourceCreateNewRelationEnabled() { - // Given - Long agentId = this.agentId; - Integer datasourceId = createTestDatasourceAndGetId(); - - // When - int insertResult = agentDatasourceMapper.createNewRelationEnabled(agentId, datasourceId); - AgentDatasource relation = - agentDatasourceMapper.selectByAgentIdAndDatasourceId(agentId, datasourceId); - - // Then - assertEquals(1, insertResult); - assertNotNull(relation); - assertEquals(agentId, relation.getAgentId()); - assertEquals(datasourceId, relation.getDatasourceId()); - assertEquals(Integer.valueOf(1), relation.getIsActive()); - } - - @Test - public void testAgentDatasourceSelectByAgentId() { - // Given - Long agentId = this.agentId; - Integer datasourceId1 = createTestDatasourceAndGetId(); - Integer datasourceId2 = createTestDatasourceAndGetId(); - - agentDatasourceMapper.createNewRelationEnabled(agentId, datasourceId1); - agentDatasourceMapper.createNewRelationEnabled(agentId, datasourceId2); - - // When - List relations = agentDatasourceMapper.selectByAgentId(agentId); - - // Then - assertEquals(2, relations.size()); - assertTrue(relations.stream().allMatch(r -> agentId.equals(r.getAgentId()))); - assertTrue(relations.stream().anyMatch(r -> datasourceId1.equals(r.getDatasourceId()))); - assertTrue(relations.stream().anyMatch(r -> datasourceId2.equals(r.getDatasourceId()))); - } - - @Test - public void testAgentDatasourceSelectByAgentIdAndDatasourceId() { - // Given - Long agentId = this.agentId; - Integer datasourceId = createTestDatasourceAndGetId(); - agentDatasourceMapper.createNewRelationEnabled(agentId, datasourceId); - - // When - AgentDatasource relation = - agentDatasourceMapper.selectByAgentIdAndDatasourceId(agentId, datasourceId); - - // Then - assertNotNull(relation); - assertEquals(agentId, relation.getAgentId()); - assertEquals(datasourceId, relation.getDatasourceId()); - } - - @Test - public void testAgentDatasourceUpdateRelation() { - // Given - Long agentId = this.agentId; - Integer datasourceId = createTestDatasourceAndGetId(); - agentDatasourceMapper.createNewRelationEnabled(agentId, datasourceId); - - // When - 禁用关联 - int disableResult = agentDatasourceMapper.updateRelation(agentId, datasourceId, 0); - AgentDatasource disabledRelation = - agentDatasourceMapper.selectByAgentIdAndDatasourceId(agentId, datasourceId); - - // Then - assertEquals(1, disableResult); - assertEquals(Integer.valueOf(0), disabledRelation.getIsActive()); - - // When - 启用关联 - int enableResult = agentDatasourceMapper.enableRelation(agentId, datasourceId); - AgentDatasource enabledRelation = - agentDatasourceMapper.selectByAgentIdAndDatasourceId(agentId, datasourceId); - - // Then - assertEquals(1, enableResult); - assertEquals(Integer.valueOf(1), enabledRelation.getIsActive()); - } - - @Test - public void testAgentDatasourceDisableAllByAgentId() { - // Given - Long agentId = this.agentId; - Integer datasourceId1 = createTestDatasourceAndGetId(); - Integer datasourceId2 = createTestDatasourceAndGetId(); - - agentDatasourceMapper.createNewRelationEnabled(agentId, datasourceId1); - agentDatasourceMapper.createNewRelationEnabled(agentId, datasourceId2); - - // When - int disableResult = agentDatasourceMapper.disableAllByAgentId(agentId); - - // Then - assertEquals(2, disableResult); - - List relations = agentDatasourceMapper.selectByAgentId(agentId); - assertTrue(relations.stream().allMatch(r -> r.getIsActive() != null && r.getIsActive() == 0)); - } - - @Test - public void testAgentDatasourceCountActiveByAgentIdExcluding() { - // Given - Long agentId = this.agentId; - Integer datasourceId1 = createTestDatasourceAndGetId(); - Integer datasourceId2 = createTestDatasourceAndGetId(); - Integer datasourceId3 = createTestDatasourceAndGetId(); - - agentDatasourceMapper.createNewRelationEnabled(agentId, datasourceId1); - agentDatasourceMapper.createNewRelationEnabled(agentId, datasourceId2); - agentDatasourceMapper.createNewRelationEnabled(agentId, datasourceId3); - - // When - int count = agentDatasourceMapper.countActiveByAgentIdExcluding(agentId, datasourceId1); - - // Then - assertEquals(2, count); - } - - @Test - public void testAgentDatasourceRemoveRelation() { - // Given - Long agentId = this.agentId; - Integer datasourceId = createTestDatasourceAndGetId(); - agentDatasourceMapper.createNewRelationEnabled(agentId, datasourceId); - - // When - int removeResult = agentDatasourceMapper.removeRelation(agentId, datasourceId); - AgentDatasource removed = - agentDatasourceMapper.selectByAgentIdAndDatasourceId(agentId, datasourceId); - - // Then - assertEquals(1, removeResult); - assertNull(removed); - } - - @Test - public void testAgentDatasourceDeleteAllByDatasourceId() { - // Given - Long agentId1 = createTestAgentAndGetId(); - Long agentId2 = createTestAgentAndGetId(); - Integer datasourceId = createTestDatasourceAndGetId(); - - agentDatasourceMapper.createNewRelationEnabled(agentId1, datasourceId); - agentDatasourceMapper.createNewRelationEnabled(agentId2, datasourceId); - - // When - int deleteResult = agentDatasourceMapper.deleteAllByDatasourceId(datasourceId); - - // Then - assertEquals(2, deleteResult); - - AgentDatasource relation1 = - agentDatasourceMapper.selectByAgentIdAndDatasourceId(agentId1, datasourceId); - AgentDatasource relation2 = - agentDatasourceMapper.selectByAgentIdAndDatasourceId(agentId2, datasourceId); - assertNull(relation1); - assertNull(relation2); - } - - // ==================== Helper Methods ==================== - - private Datasource createTestDatasource() { - return Datasource.builder() - .name("测试数据源") - .type("mysql") - .host("localhost") - .port(3306) - .databaseName("test_db") - .username("test_user") - .password("test_password") - .status("active") - .testStatus("success") - .description("测试用数据源") - .creatorId(1L) - .createTime(LocalDateTime.now()) - .updateTime(LocalDateTime.now()) - .build(); - } - - private Integer createTestDatasourceAndGetId() { - Datasource datasource = createTestDatasource(); - datasourceMapper.insert(datasource); - return datasource.getId(); - } - - private Long createTestAgentAndGetId() { - KeyHolder keyHolder = new GeneratedKeyHolder(); - jdbcTemplate.update( - con -> { - var ps = - con.prepareStatement( - "INSERT INTO agent (name, description, status, category) VALUES (?, ?, 'published', 'test')", - Statement.RETURN_GENERATED_KEYS); - ps.setString(1, "测试智能体" + UUID.randomUUID()); - ps.setString(2, "用于单元测试的临时智能体"); - return ps; - }, - keyHolder); - Number key = keyHolder.getKey(); - Long id = key == null ? null : key.longValue(); - // 立即校验插入是否可见且存在,避免外键引用失败 - if (id == null) { - throw new IllegalStateException("创建测试Agent失败:未获取到自增ID"); - } - Long exists = - jdbcTemplate.queryForObject("SELECT COUNT(1) FROM agent WHERE id = ?", Long.class, id); - if (exists == null || exists == 0) { - throw new IllegalStateException("创建测试Agent失败:数据库中不存在id=" + id); - } - return id; - } + @Autowired + private DatasourceMapper datasourceMapper; + + @Autowired + private AgentDatasourceMapper agentDatasourceMapper; + + @Autowired + private JdbcTemplate jdbcTemplate; + + private Long agentId; + + @BeforeEach + public void setUpAgent() { + this.agentId = createTestAgentAndGetId(); + } + + // ==================== AgentDatasourceMapper Tests ==================== + + @Test + public void testAgentDatasourceCreateNewRelationEnabled() { + // Given + Long agentId = this.agentId; + Integer datasourceId = createTestDatasourceAndGetId(); + + // When + int insertResult = agentDatasourceMapper.createNewRelationEnabled(agentId, datasourceId); + AgentDatasource relation = agentDatasourceMapper.selectByAgentIdAndDatasourceId(agentId, datasourceId); + + // Then + assertEquals(1, insertResult); + assertNotNull(relation); + assertEquals(agentId, relation.getAgentId()); + assertEquals(datasourceId, relation.getDatasourceId()); + assertEquals(Integer.valueOf(1), relation.getIsActive()); + } + + @Test + public void testAgentDatasourceSelectByAgentId() { + // Given + Long agentId = this.agentId; + Integer datasourceId1 = createTestDatasourceAndGetId(); + Integer datasourceId2 = createTestDatasourceAndGetId(); + + agentDatasourceMapper.createNewRelationEnabled(agentId, datasourceId1); + agentDatasourceMapper.createNewRelationEnabled(agentId, datasourceId2); + + // When + List relations = agentDatasourceMapper.selectByAgentId(agentId); + + // Then + assertEquals(2, relations.size()); + assertTrue(relations.stream().allMatch(r -> agentId.equals(r.getAgentId()))); + assertTrue(relations.stream().anyMatch(r -> datasourceId1.equals(r.getDatasourceId()))); + assertTrue(relations.stream().anyMatch(r -> datasourceId2.equals(r.getDatasourceId()))); + } + + @Test + public void testAgentDatasourceSelectByAgentIdAndDatasourceId() { + // Given + Long agentId = this.agentId; + Integer datasourceId = createTestDatasourceAndGetId(); + agentDatasourceMapper.createNewRelationEnabled(agentId, datasourceId); + + // When + AgentDatasource relation = agentDatasourceMapper.selectByAgentIdAndDatasourceId(agentId, datasourceId); + + // Then + assertNotNull(relation); + assertEquals(agentId, relation.getAgentId()); + assertEquals(datasourceId, relation.getDatasourceId()); + } + + @Test + public void testAgentDatasourceUpdateRelation() { + // Given + Long agentId = this.agentId; + Integer datasourceId = createTestDatasourceAndGetId(); + agentDatasourceMapper.createNewRelationEnabled(agentId, datasourceId); + + // When - 禁用关联 + int disableResult = agentDatasourceMapper.updateRelation(agentId, datasourceId, 0); + AgentDatasource disabledRelation = agentDatasourceMapper.selectByAgentIdAndDatasourceId(agentId, datasourceId); + + // Then + assertEquals(1, disableResult); + assertEquals(Integer.valueOf(0), disabledRelation.getIsActive()); + + // When - 启用关联 + int enableResult = agentDatasourceMapper.enableRelation(agentId, datasourceId); + AgentDatasource enabledRelation = agentDatasourceMapper.selectByAgentIdAndDatasourceId(agentId, datasourceId); + + // Then + assertEquals(1, enableResult); + assertEquals(Integer.valueOf(1), enabledRelation.getIsActive()); + } + + @Test + public void testAgentDatasourceDisableAllByAgentId() { + // Given + Long agentId = this.agentId; + Integer datasourceId1 = createTestDatasourceAndGetId(); + Integer datasourceId2 = createTestDatasourceAndGetId(); + + agentDatasourceMapper.createNewRelationEnabled(agentId, datasourceId1); + agentDatasourceMapper.createNewRelationEnabled(agentId, datasourceId2); + + // When + int disableResult = agentDatasourceMapper.disableAllByAgentId(agentId); + + // Then + assertEquals(2, disableResult); + + List relations = agentDatasourceMapper.selectByAgentId(agentId); + assertTrue(relations.stream().allMatch(r -> r.getIsActive() != null && r.getIsActive() == 0)); + } + + @Test + public void testAgentDatasourceCountActiveByAgentIdExcluding() { + // Given + Long agentId = this.agentId; + Integer datasourceId1 = createTestDatasourceAndGetId(); + Integer datasourceId2 = createTestDatasourceAndGetId(); + Integer datasourceId3 = createTestDatasourceAndGetId(); + + agentDatasourceMapper.createNewRelationEnabled(agentId, datasourceId1); + agentDatasourceMapper.createNewRelationEnabled(agentId, datasourceId2); + agentDatasourceMapper.createNewRelationEnabled(agentId, datasourceId3); + + // When + int count = agentDatasourceMapper.countActiveByAgentIdExcluding(agentId, datasourceId1); + + // Then + assertEquals(2, count); + } + + @Test + public void testAgentDatasourceRemoveRelation() { + // Given + Long agentId = this.agentId; + Integer datasourceId = createTestDatasourceAndGetId(); + agentDatasourceMapper.createNewRelationEnabled(agentId, datasourceId); + + // When + int removeResult = agentDatasourceMapper.removeRelation(agentId, datasourceId); + AgentDatasource removed = agentDatasourceMapper.selectByAgentIdAndDatasourceId(agentId, datasourceId); + + // Then + assertEquals(1, removeResult); + assertNull(removed); + } + + @Test + public void testAgentDatasourceDeleteAllByDatasourceId() { + // Given + Long agentId1 = createTestAgentAndGetId(); + Long agentId2 = createTestAgentAndGetId(); + Integer datasourceId = createTestDatasourceAndGetId(); + + agentDatasourceMapper.createNewRelationEnabled(agentId1, datasourceId); + agentDatasourceMapper.createNewRelationEnabled(agentId2, datasourceId); + + // When + int deleteResult = agentDatasourceMapper.deleteAllByDatasourceId(datasourceId); + + // Then + assertEquals(2, deleteResult); + + AgentDatasource relation1 = agentDatasourceMapper.selectByAgentIdAndDatasourceId(agentId1, datasourceId); + AgentDatasource relation2 = agentDatasourceMapper.selectByAgentIdAndDatasourceId(agentId2, datasourceId); + assertNull(relation1); + assertNull(relation2); + } + + // ==================== Helper Methods ==================== + + private Datasource createTestDatasource() { + return Datasource.builder() + .name("测试数据源") + .type("mysql") + .host("localhost") + .port(3306) + .databaseName("test_db") + .username("test_user") + .password("test_password") + .status("active") + .testStatus("success") + .description("测试用数据源") + .creatorId(1L) + .createTime(LocalDateTime.now()) + .updateTime(LocalDateTime.now()) + .build(); + } + + private Integer createTestDatasourceAndGetId() { + Datasource datasource = createTestDatasource(); + datasourceMapper.insert(datasource); + return datasource.getId(); + } + + private Long createTestAgentAndGetId() { + KeyHolder keyHolder = new GeneratedKeyHolder(); + jdbcTemplate.update(con -> { + var ps = con.prepareStatement( + "INSERT INTO agent (name, description, status, category) VALUES (?, ?, 'published', 'test')", + Statement.RETURN_GENERATED_KEYS); + ps.setString(1, "测试智能体" + UUID.randomUUID()); + ps.setString(2, "用于单元测试的临时智能体"); + return ps; + }, keyHolder); + Number key = keyHolder.getKey(); + Long id = key == null ? null : key.longValue(); + // 立即校验插入是否可见且存在,避免外键引用失败 + if (id == null) { + throw new IllegalStateException("创建测试Agent失败:未获取到自增ID"); + } + Long exists = jdbcTemplate.queryForObject("SELECT COUNT(1) FROM agent WHERE id = ?", Long.class, id); + if (exists == null || exists == 0) { + throw new IllegalStateException("创建测试Agent失败:数据库中不存在id=" + id); + } + return id; + } + } diff --git a/data-agent-management/src/test/java/com/alibaba/cloud/ai/dataagent/mapper/MappersTest.java b/data-agent-management/src/test/java/com/alibaba/cloud/ai/dataagent/mapper/MappersTest.java index a7be0499d..bd152708a 100644 --- a/data-agent-management/src/test/java/com/alibaba/cloud/ai/dataagent/mapper/MappersTest.java +++ b/data-agent-management/src/test/java/com/alibaba/cloud/ai/dataagent/mapper/MappersTest.java @@ -34,225 +34,231 @@ * @since 2025/9/26 */ @MybatisTest -@TestPropertySource(properties = {"spring.sql.init.mode=never"}) +@TestPropertySource(properties = { "spring.sql.init.mode=never" }) @ImportTestcontainers(MySqlContainerConfiguration.class) @ImportAutoConfiguration(MySqlContainerConfiguration.class) public class MappersTest { - @Autowired private AgentMapper agentMapper; - - @Autowired private AgentKnowledgeMapper agentKnowledgeMapper; - - @Autowired private AgentPresetQuestionMapper agentPresetQuestionMapper; - - @Autowired private ChatSessionMapper chatSessionMapper; - - @Autowired private ChatMessageMapper chatMessageMapper; - - @Autowired private SemanticModelMapper semanticModelMapper; - - @Autowired private BusinessKnowledgeMapper businessKnowledgeMapper; - - private Long createAgent(String name) { - Agent agent = - Agent.builder() - .name(name) - .description("for fk") - .avatar("a") - .status("draft") - .prompt("p") - .category("c") - .adminId(1L) - .tags("t") - .createTime(LocalDateTime.now().withNano(0)) - .updateTime(LocalDateTime.now().withNano(0)) - .humanReviewEnabled(0) - .build(); - agentMapper.insert(agent); - return agent.getId(); - } - - @Test - public void testAgentMapper() { - Assertions.assertNotNull(agentMapper); - - agentMapper.findAll().stream().map(Agent::getId).forEach(id -> agentMapper.deleteById(id)); - - List all = agentMapper.findAll(); - Assertions.assertEquals(List.of(), all); - Agent agent = - Agent.builder() - .name("test") - .description("test") - .avatar("test") - .status("test") - .prompt("test") - .category("test") - .adminId(1L) - .tags("test") - .createTime(LocalDateTime.now().withNano(0)) - .updateTime(LocalDateTime.now().withNano(0)) - .humanReviewEnabled(0) - .build(); - int insert = agentMapper.insert(agent); - Assertions.assertEquals(1, insert); - Agent findById = agentMapper.findById(agent.getId()); - Assertions.assertEquals(agent, findById); - List findByStatus = agentMapper.findByStatus("test"); - Assertions.assertEquals(List.of(agent), findByStatus); - List searchByKeyword = agentMapper.searchByKeyword("test"); - Assertions.assertEquals(List.of(agent), searchByKeyword); - agent.setName("test2"); - int update = agentMapper.updateById(agent); - Assertions.assertEquals(1, update); - agentMapper.deleteById(agent.getId()); - List allAfterDelete = agentMapper.findAll(); - Assertions.assertEquals(List.of(), allAfterDelete); - } - - @Test - public void testChatSessionAndMessageCrud() { - Long agentId = createAgent("session-holder"); - String sessionId = java.util.UUID.randomUUID().toString(); - // insert session - ChatSession session = - new ChatSession(sessionId, agentId.intValue(), "tc_session", "active", 1L); - session.setCreateTime(LocalDateTime.now()); - session.setUpdateTime(LocalDateTime.now()); - int ins = chatSessionMapper.insert(session); - Assertions.assertEquals(1, ins); - - // insert message - ChatMessage msg = new ChatMessage(); - msg.setSessionId(sessionId); - msg.setRole("user"); - msg.setContent("hello tc"); - msg.setMessageType("text"); - int mins = chatMessageMapper.insert(msg); - Assertions.assertEquals(1, mins); - - // read - List list = chatMessageMapper.selectBySessionId(sessionId); - Assertions.assertEquals(1, list.size()); - int cnt = chatMessageMapper.countBySessionId(sessionId); - Assertions.assertEquals(1, cnt); - - // update session - chatSessionMapper.updateTitle(sessionId, "tc_session_updated", LocalDateTime.now()); - chatSessionMapper.updatePinStatus(sessionId, true, LocalDateTime.now()); - chatSessionMapper.updateSessionTime(sessionId, LocalDateTime.now()); - - // delete - int md = chatMessageMapper.deleteById(list.get(0).getId()); - Assertions.assertEquals(1, md); - int sd = chatSessionMapper.softDeleteById(sessionId, LocalDateTime.now()); - Assertions.assertEquals(1, sd); - - // cleanup agent - agentMapper.deleteById(agentId); - } - - @Test - public void testSemanticModelCrud() { - Long agentId = createAgent("semantic-holder"); - SemanticModel m = new SemanticModel(); - m.setAgentId(agentId); - m.setDatasourceId(1); // 添加数据源ID - m.setTableName("test_table"); // 添加表名 - m.setColumnName("origin_tc"); - m.setBusinessName("显示名"); - m.setSynonyms("别名A,别名B"); - m.setBusinessDescription("desc"); - m.setColumnComment("字段注释"); // 添加字段注释 - m.setDataType("VARCHAR"); - m.setStatus(1); - int ins = semanticModelMapper.insert(m); - Assertions.assertEquals(1, ins); - Assertions.assertNotNull(m.getId()); - - List query = semanticModelMapper.selectByAgentId(m.getAgentId()); - Assertions.assertTrue(query.stream().anyMatch(x -> x.getId().equals(m.getId()))); - - semanticModelMapper.disableById(m.getId()); - semanticModelMapper.enableById(m.getId()); - - m.setBusinessDescription("desc2"); - int upd = semanticModelMapper.updateById(m); - Assertions.assertEquals(1, upd); - - int del = semanticModelMapper.deleteById(m.getId()); - Assertions.assertEquals(1, del); - - agentMapper.deleteById(agentId); - } - - @Test - public void testAgentPresetQuestionCrud() { - // 先创建一个合法的 Agent 以满足外键约束 - Long agentId = createAgent("preset-holder"); - - // clean existing - agentPresetQuestionMapper.deleteByAgentId(agentId); - - AgentPresetQuestion q = new AgentPresetQuestion(); - q.setAgentId(agentId); - q.setQuestion("q1"); - q.setSortOrder(0); - q.setIsActive(true); - int ins = agentPresetQuestionMapper.insert(q); - Assertions.assertEquals(1, ins); - - List qs = agentPresetQuestionMapper.selectByAgentId(agentId); - Assertions.assertEquals(1, qs.size()); - - q.setQuestion("q1_updated"); - int upd = agentPresetQuestionMapper.update(q); - Assertions.assertEquals(1, upd); - - int del = agentPresetQuestionMapper.deleteById(qs.get(0).getId()); - Assertions.assertEquals(1, del); - - // 清理创建的 agent - agentMapper.deleteById(agentId); - } - - @Test - public void testBusinessKnowledgeMapperCrud() { - Long agentId = createAgent("bk-holder"); - // clean - businessKnowledgeMapper.selectAll().forEach(b -> businessKnowledgeMapper.deleteById(b.getId())); - - List before = businessKnowledgeMapper.selectAll(); - Assertions.assertEquals(List.of(), before); - - BusinessKnowledge k = new BusinessKnowledge(); - k.setBusinessTerm("term_ut"); - k.setDescription("desc_ut"); - k.setSynonyms("a,b"); - k.setAgentId(agentId); - try { - java.lang.reflect.Field field = BusinessKnowledge.class.getDeclaredField("isRecall"); - field.setAccessible(true); - field.set(k, 1); - } catch (Exception e) { - throw new RuntimeException(e); - } - int ins = businessKnowledgeMapper.insert(k); - Assertions.assertEquals(1, ins); - Assertions.assertNotNull(k.getId()); - - List byDataset = businessKnowledgeMapper.selectByAgentId(agentId); - Assertions.assertEquals(1, byDataset.size()); - - List search = businessKnowledgeMapper.searchInAgent(agentId, "term_ut"); - Assertions.assertTrue(search.stream().anyMatch(x -> x.getId().equals(k.getId()))); - - k.setDescription("desc_ut_updated"); - int upd = businessKnowledgeMapper.updateById(k); - Assertions.assertEquals(1, upd); - - int del = businessKnowledgeMapper.deleteById(k.getId()); - Assertions.assertEquals(1, del); - agentMapper.deleteById(agentId); - } + @Autowired + private AgentMapper agentMapper; + + @Autowired + private AgentKnowledgeMapper agentKnowledgeMapper; + + @Autowired + private AgentPresetQuestionMapper agentPresetQuestionMapper; + + @Autowired + private ChatSessionMapper chatSessionMapper; + + @Autowired + private ChatMessageMapper chatMessageMapper; + + @Autowired + private SemanticModelMapper semanticModelMapper; + + @Autowired + private BusinessKnowledgeMapper businessKnowledgeMapper; + + private Long createAgent(String name) { + Agent agent = Agent.builder() + .name(name) + .description("for fk") + .avatar("a") + .status("draft") + .prompt("p") + .category("c") + .adminId(1L) + .tags("t") + .createTime(LocalDateTime.now().withNano(0)) + .updateTime(LocalDateTime.now().withNano(0)) + .humanReviewEnabled(0) + .build(); + agentMapper.insert(agent); + return agent.getId(); + } + + @Test + public void testAgentMapper() { + Assertions.assertNotNull(agentMapper); + + agentMapper.findAll().stream().map(Agent::getId).forEach(id -> agentMapper.deleteById(id)); + + List all = agentMapper.findAll(); + Assertions.assertEquals(List.of(), all); + Agent agent = Agent.builder() + .name("test") + .description("test") + .avatar("test") + .status("test") + .prompt("test") + .category("test") + .adminId(1L) + .tags("test") + .createTime(LocalDateTime.now().withNano(0)) + .updateTime(LocalDateTime.now().withNano(0)) + .humanReviewEnabled(0) + .build(); + int insert = agentMapper.insert(agent); + Assertions.assertEquals(1, insert); + Agent findById = agentMapper.findById(agent.getId()); + Assertions.assertEquals(agent, findById); + List findByStatus = agentMapper.findByStatus("test"); + Assertions.assertEquals(List.of(agent), findByStatus); + List searchByKeyword = agentMapper.searchByKeyword("test"); + Assertions.assertEquals(List.of(agent), searchByKeyword); + agent.setName("test2"); + int update = agentMapper.updateById(agent); + Assertions.assertEquals(1, update); + agentMapper.deleteById(agent.getId()); + List allAfterDelete = agentMapper.findAll(); + Assertions.assertEquals(List.of(), allAfterDelete); + } + + @Test + public void testChatSessionAndMessageCrud() { + Long agentId = createAgent("session-holder"); + String sessionId = java.util.UUID.randomUUID().toString(); + // insert session + ChatSession session = new ChatSession(sessionId, agentId.intValue(), "tc_session", "active", 1L); + session.setCreateTime(LocalDateTime.now()); + session.setUpdateTime(LocalDateTime.now()); + int ins = chatSessionMapper.insert(session); + Assertions.assertEquals(1, ins); + + // insert message + ChatMessage msg = new ChatMessage(); + msg.setSessionId(sessionId); + msg.setRole("user"); + msg.setContent("hello tc"); + msg.setMessageType("text"); + int mins = chatMessageMapper.insert(msg); + Assertions.assertEquals(1, mins); + + // read + List list = chatMessageMapper.selectBySessionId(sessionId); + Assertions.assertEquals(1, list.size()); + int cnt = chatMessageMapper.countBySessionId(sessionId); + Assertions.assertEquals(1, cnt); + + // update session + chatSessionMapper.updateTitle(sessionId, "tc_session_updated", LocalDateTime.now()); + chatSessionMapper.updatePinStatus(sessionId, true, LocalDateTime.now()); + chatSessionMapper.updateSessionTime(sessionId, LocalDateTime.now()); + + // delete + int md = chatMessageMapper.deleteById(list.get(0).getId()); + Assertions.assertEquals(1, md); + int sd = chatSessionMapper.softDeleteById(sessionId, LocalDateTime.now()); + Assertions.assertEquals(1, sd); + + // cleanup agent + agentMapper.deleteById(agentId); + } + + @Test + public void testSemanticModelCrud() { + Long agentId = createAgent("semantic-holder"); + SemanticModel m = new SemanticModel(); + m.setAgentId(agentId); + m.setDatasourceId(1); // 添加数据源ID + m.setTableName("test_table"); // 添加表名 + m.setColumnName("origin_tc"); + m.setBusinessName("显示名"); + m.setSynonyms("别名A,别名B"); + m.setBusinessDescription("desc"); + m.setColumnComment("字段注释"); // 添加字段注释 + m.setDataType("VARCHAR"); + m.setStatus(1); + int ins = semanticModelMapper.insert(m); + Assertions.assertEquals(1, ins); + Assertions.assertNotNull(m.getId()); + + List query = semanticModelMapper.selectByAgentId(m.getAgentId()); + Assertions.assertTrue(query.stream().anyMatch(x -> x.getId().equals(m.getId()))); + + semanticModelMapper.disableById(m.getId()); + semanticModelMapper.enableById(m.getId()); + + m.setBusinessDescription("desc2"); + int upd = semanticModelMapper.updateById(m); + Assertions.assertEquals(1, upd); + + int del = semanticModelMapper.deleteById(m.getId()); + Assertions.assertEquals(1, del); + + agentMapper.deleteById(agentId); + } + + @Test + public void testAgentPresetQuestionCrud() { + // 先创建一个合法的 Agent 以满足外键约束 + Long agentId = createAgent("preset-holder"); + + // clean existing + agentPresetQuestionMapper.deleteByAgentId(agentId); + + AgentPresetQuestion q = new AgentPresetQuestion(); + q.setAgentId(agentId); + q.setQuestion("q1"); + q.setSortOrder(0); + q.setIsActive(true); + int ins = agentPresetQuestionMapper.insert(q); + Assertions.assertEquals(1, ins); + + List qs = agentPresetQuestionMapper.selectByAgentId(agentId); + Assertions.assertEquals(1, qs.size()); + + q.setQuestion("q1_updated"); + int upd = agentPresetQuestionMapper.update(q); + Assertions.assertEquals(1, upd); + + int del = agentPresetQuestionMapper.deleteById(qs.get(0).getId()); + Assertions.assertEquals(1, del); + + // 清理创建的 agent + agentMapper.deleteById(agentId); + } + + @Test + public void testBusinessKnowledgeMapperCrud() { + Long agentId = createAgent("bk-holder"); + // clean + businessKnowledgeMapper.selectAll().forEach(b -> businessKnowledgeMapper.deleteById(b.getId())); + + List before = businessKnowledgeMapper.selectAll(); + Assertions.assertEquals(List.of(), before); + + BusinessKnowledge k = new BusinessKnowledge(); + k.setBusinessTerm("term_ut"); + k.setDescription("desc_ut"); + k.setSynonyms("a,b"); + k.setAgentId(agentId); + try { + java.lang.reflect.Field field = BusinessKnowledge.class.getDeclaredField("isRecall"); + field.setAccessible(true); + field.set(k, 1); + } + catch (Exception e) { + throw new RuntimeException(e); + } + int ins = businessKnowledgeMapper.insert(k); + Assertions.assertEquals(1, ins); + Assertions.assertNotNull(k.getId()); + + List byDataset = businessKnowledgeMapper.selectByAgentId(agentId); + Assertions.assertEquals(1, byDataset.size()); + + List search = businessKnowledgeMapper.searchInAgent(agentId, "term_ut"); + Assertions.assertTrue(search.stream().anyMatch(x -> x.getId().equals(k.getId()))); + + k.setDescription("desc_ut_updated"); + int upd = businessKnowledgeMapper.updateById(k); + Assertions.assertEquals(1, upd); + + int del = businessKnowledgeMapper.deleteById(k.getId()); + Assertions.assertEquals(1, del); + agentMapper.deleteById(agentId); + } + } diff --git a/mvnw b/mvnw index bd8896bf2..d51242e7f 100755 --- a/mvnw +++ b/mvnw @@ -1,295 +1,305 @@ #!/bin/sh # ---------------------------------------------------------------------------- -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at +# Copyright 2024-2025 the original author or authors. # -# http://www.apache.org/licenses/LICENSE-2.0 +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at # -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. # ---------------------------------------------------------------------------- # ---------------------------------------------------------------------------- -# Apache Maven Wrapper startup batch script, version 3.3.4 +# Apache Maven Wrapper startup batch script, version 3.2.0 +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir # # Optional ENV vars # ----------------- -# JAVA_HOME - location of a JDK home dir, required when download maven via java source -# MVNW_REPOURL - repo url base for downloading maven distribution -# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven -# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files # ---------------------------------------------------------------------------- -set -euf -[ "${MVNW_VERBOSE-}" != debug ] || set -x +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /usr/local/etc/mavenrc ] ; then + . /usr/local/etc/mavenrc + fi + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi -# OS specific support. -native_path() { printf %s\\n "$1"; } +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false case "$(uname)" in -CYGWIN* | MINGW*) - [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" - native_path() { cygpath --path --windows "$1"; } - ;; + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME + else + JAVA_HOME="/Library/Java/Home"; export JAVA_HOME + fi + fi + ;; esac -# set JAVACMD and JAVACCMD -set_java_home() { - # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched - if [ -n "${JAVA_HOME-}" ]; then - if [ -x "$JAVA_HOME/jre/sh/java" ]; then +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=$(java-config --jre-home) + fi +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --unix "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --unix "$CLASSPATH") +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] && + JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="$(which javac)" + if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=$(which readlink) + if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then + if $darwin ; then + javaHome="$(dirname "\"$javaExecutable\"")" + javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac" + else + javaExecutable="$(readlink -f "\"$javaExecutable\"")" + fi + javaHome="$(dirname "\"$javaExecutable\"")" + javaHome=$(expr "$javaHome" : '\(.*\)/bin') + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" - JAVACCMD="$JAVA_HOME/jre/sh/javac" else JAVACMD="$JAVA_HOME/bin/java" - JAVACCMD="$JAVA_HOME/bin/javac" - - if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then - echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 - echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 - return 1 - fi fi else - JAVACMD="$( - 'set' +e - 'unset' -f command 2>/dev/null - 'command' -v java - )" || : - JAVACCMD="$( - 'set' +e - 'unset' -f command 2>/dev/null - 'command' -v javac - )" || : - - if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then - echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 - return 1 - fi + JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)" fi -} - -# hash string like Java String::hashCode -hash_string() { - str="${1:-}" h=0 - while [ -n "$str" ]; do - char="${str%"${str#?}"}" - h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) - str="${str#?}" - done - printf %x\\n $h -} - -verbose() { :; } -[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } +fi -die() { - printf %s\\n "$1" >&2 +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 exit 1 -} - -trim() { - # MWRAPPER-139: - # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. - # Needed for removing poorly interpreted newline sequences when running in more - # exotic environments such as mingw bash on Windows. - printf "%s" "${1}" | tr -d '[:space:]' -} +fi -scriptDir="$(dirname "$0")" -scriptName="$(basename "$0")" +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi -# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties -while IFS="=" read -r key value; do - case "${key-}" in - distributionUrl) distributionUrl=$(trim "${value-}") ;; - distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; - esac -done <"$scriptDir/.mvn/wrapper/maven-wrapper.properties" -[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi -case "${distributionUrl##*/}" in -maven-mvnd-*bin.*) - MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ - case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in - *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; - :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; - :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; - :Linux*x86_64*) distributionPlatform=linux-amd64 ;; - *) - echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 - distributionPlatform=linux-amd64 - ;; - esac - distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" - ;; -maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; -*) MVN_CMD="mvn${scriptName#mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; -esac + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=$(cd "$wdir/.." || exit 1; pwd) + fi + # end of workaround + done + printf '%s' "$(cd "$basedir" || exit 1; pwd)" +} -# apply MVNW_REPOURL and calculate MAVEN_HOME -# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ -[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" -distributionUrlName="${distributionUrl##*/}" -distributionUrlNameMain="${distributionUrlName%.*}" -distributionUrlNameMain="${distributionUrlNameMain%-bin}" -MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" -MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + # Remove \r in case we run on Windows within Git Bash + # and check out the repository with auto CRLF management + # enabled. Otherwise, we may read lines that are delimited with + # \r\n and produce $'-Xarg\r' rather than -Xarg due to word + # splitting rules. + tr -s '\r\n' ' ' < "$1" + fi +} -exec_maven() { - unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : - exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +log() { + if [ "$MVNW_VERBOSE" = true ]; then + printf '%s\n' "$1" + fi } -if [ -d "$MAVEN_HOME" ]; then - verbose "found existing MAVEN_HOME at $MAVEN_HOME" - exec_maven "$@" +BASE_DIR=$(find_maven_basedir "$(dirname "$0")") +if [ -z "$BASE_DIR" ]; then + exit 1; fi -case "${distributionUrl-}" in -*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; -*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; -esac +MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR +log "$MAVEN_PROJECTBASEDIR" -# prepare tmp dir -if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then - clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } - trap clean HUP INT TERM EXIT +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" +if [ -r "$wrapperJarPath" ]; then + log "Found $wrapperJarPath" else - die "cannot create temp dir" -fi - -mkdir -p -- "${MAVEN_HOME%/*}" - -# Download and Install Apache Maven -verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." -verbose "Downloading from: $distributionUrl" -verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" - -# select .zip or .tar.gz -if ! command -v unzip >/dev/null; then - distributionUrl="${distributionUrl%.zip}.tar.gz" - distributionUrlName="${distributionUrl##*/}" -fi - -# verbose opt -__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' -[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + log "Couldn't find $wrapperJarPath, downloading it ..." -# normalize http auth -case "${MVNW_PASSWORD:+has-password}" in -'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; -has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; -esac + if [ -n "$MVNW_REPOURL" ]; then + wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + else + wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + fi + while IFS="=" read -r key value; do + # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) + safeValue=$(echo "$value" | tr -d '\r') + case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;; + esac + done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" + log "Downloading from: $wrapperUrl" + + if $cygwin; then + wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") + fi -if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then - verbose "Found wget ... using wget" - wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" -elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then - verbose "Found curl ... using curl" - curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" -elif set_java_home; then - verbose "Falling back to use Java to download" - javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" - targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" - cat >"$javaSource" <<-END - public class Downloader extends java.net.Authenticator - { - protected java.net.PasswordAuthentication getPasswordAuthentication() - { - return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); - } - public static void main( String[] args ) throws Exception - { - setDefault( new Downloader() ); - java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); - } - } - END - # For Cygwin/MinGW, switch paths to Windows format before running javac and java - verbose " - Compiling Downloader.java ..." - "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" - verbose " - Running Downloader.java ..." - "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" + if command -v wget > /dev/null; then + log "Found wget ... using wget" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + else + wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + log "Found curl ... using curl" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + else + curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + fi + else + log "Falling back to using Java to download" + javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" + javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaSource=$(cygpath --path --windows "$javaSource") + javaClass=$(cygpath --path --windows "$javaClass") + fi + if [ -e "$javaSource" ]; then + if [ ! -e "$javaClass" ]; then + log " - Compiling MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/javac" "$javaSource") + fi + if [ -e "$javaClass" ]; then + log " - Running MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" + fi + fi + fi fi +########################################################################################## +# End of extension +########################################################################################## -# If specified, validate the SHA-256 sum of the Maven distribution zip file -if [ -n "${distributionSha256Sum-}" ]; then - distributionSha256Result=false - if [ "$MVN_CMD" = mvnd.sh ]; then - echo "Checksum validation is not supported for maven-mvnd." >&2 - echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 - exit 1 - elif command -v sha256sum >/dev/null; then - if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c - >/dev/null 2>&1; then - distributionSha256Result=true +# If specified, validate the SHA-256 sum of the Maven wrapper jar file +wrapperSha256Sum="" +while IFS="=" read -r key value; do + case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;; + esac +done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" +if [ -n "$wrapperSha256Sum" ]; then + wrapperSha256Result=false + if command -v sha256sum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then + wrapperSha256Result=true fi - elif command -v shasum >/dev/null; then - if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then - distributionSha256Result=true + elif command -v shasum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then + wrapperSha256Result=true fi else - echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 - echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." + echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." exit 1 fi - if [ $distributionSha256Result = false ]; then - echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 - echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + if [ $wrapperSha256Result = false ]; then + echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 + echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 + echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 exit 1 fi fi -# unzip and move -if command -v unzip >/dev/null; then - unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" -else - tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" -fi - -# Find the actual extracted directory name (handles snapshots where filename != directory name) -actualDistributionDir="" +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" -# First try the expected directory name (for regular distributions) -if [ -d "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" ]; then - if [ -f "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD" ]; then - actualDistributionDir="$distributionUrlNameMain" - fi +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --windows "$CLASSPATH") + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") fi -# If not found, search for any directory with the Maven executable (for snapshots) -if [ -z "$actualDistributionDir" ]; then - # enable globbing to iterate over items - set +f - for dir in "$TMP_DOWNLOAD_DIR"/*; do - if [ -d "$dir" ]; then - if [ -f "$dir/bin/$MVN_CMD" ]; then - actualDistributionDir="$(basename "$dir")" - break - fi - fi - done - set -f -fi - -if [ -z "$actualDistributionDir" ]; then - verbose "Contents of $TMP_DOWNLOAD_DIR:" - verbose "$(ls -la "$TMP_DOWNLOAD_DIR")" - die "Could not find Maven distribution directory in extracted archive" -fi +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" +export MAVEN_CMD_LINE_ARGS -verbose "Found extracted Maven distribution directory: $actualDistributionDir" -printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url" -mv -- "$TMP_DOWNLOAD_DIR/$actualDistributionDir" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain -clean || : -exec_maven "$@" +# shellcheck disable=SC2086 # safe args +exec "$JAVACMD" \ + $MAVEN_OPTS \ + $MAVEN_DEBUG_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/mvnw.cmd b/mvnw.cmd index 92450f932..149977cfb 100644 --- a/mvnw.cmd +++ b/mvnw.cmd @@ -1,189 +1,200 @@ -<# : batch portion -@REM ---------------------------------------------------------------------------- -@REM Licensed to the Apache Software Foundation (ASF) under one -@REM or more contributor license agreements. See the NOTICE file -@REM distributed with this work for additional information -@REM regarding copyright ownership. The ASF licenses this file -@REM to you under the Apache License, Version 2.0 (the -@REM "License"); you may not use this file except in compliance -@REM with the License. You may obtain a copy of the License at -@REM -@REM http://www.apache.org/licenses/LICENSE-2.0 -@REM -@REM Unless required by applicable law or agreed to in writing, -@REM software distributed under the License is distributed on an -@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -@REM KIND, either express or implied. See the License for the -@REM specific language governing permissions and limitations -@REM under the License. -@REM ---------------------------------------------------------------------------- +@rem Copyright 2024-2025 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. @REM ---------------------------------------------------------------------------- -@REM Apache Maven Wrapper startup batch script, version 3.3.4 +@REM Apache Maven Wrapper startup batch script, version 3.2.0 +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir @REM @REM Optional ENV vars -@REM MVNW_REPOURL - repo url base for downloading maven distribution -@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven -@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files @REM ---------------------------------------------------------------------------- -@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) -@SET __MVNW_CMD__= -@SET __MVNW_ERROR__= -@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% -@SET PSModulePath= -@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( - IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* +if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %WRAPPER_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file +SET WRAPPER_SHA_256_SUM="" +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B +) +IF NOT %WRAPPER_SHA_256_SUM%=="" ( + powershell -Command "&{"^ + "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ + "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ + " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ + " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ + " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ + " exit 1;"^ + "}"^ + "}" + if ERRORLEVEL 1 goto error ) -@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% -@SET __MVNW_PSMODULEP_SAVE= -@SET __MVNW_ARG0_NAME__= -@SET MVNW_USERNAME= -@SET MVNW_PASSWORD= -@IF NOT "%__MVNW_CMD__%"=="" ("%__MVNW_CMD__%" %*) -@echo Cannot start maven from wrapper >&2 && exit /b 1 -@GOTO :EOF -: end batch / begin powershell #> - -$ErrorActionPreference = "Stop" -if ($env:MVNW_VERBOSE -eq "true") { - $VerbosePreference = "Continue" -} - -# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties -$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl -if (!$distributionUrl) { - Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" -} - -switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { - "maven-mvnd-*" { - $USE_MVND = $true - $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" - $MVN_CMD = "mvnd.cmd" - break - } - default { - $USE_MVND = $false - $MVN_CMD = $script -replace '^mvnw','mvn' - break - } -} - -# apply MVNW_REPOURL and calculate MAVEN_HOME -# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ -if ($env:MVNW_REPOURL) { - $MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { "/org/apache/maven/" } else { "/maven/mvnd/" } - $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace "^.*$MVNW_REPO_PATTERN",'')" -} -$distributionUrlName = $distributionUrl -replace '^.*/','' -$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' - -$MAVEN_M2_PATH = "$HOME/.m2" -if ($env:MAVEN_USER_HOME) { - $MAVEN_M2_PATH = "$env:MAVEN_USER_HOME" -} - -if (-not (Test-Path -Path $MAVEN_M2_PATH)) { - New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null -} - -$MAVEN_WRAPPER_DISTS = $null -if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) { - $MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists" -} else { - $MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists" -} - -$MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain" -$MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' -$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" - -if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { - Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" - Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" - exit $? -} - -if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { - Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" -} - -# prepare tmp dir -$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile -$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" -$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null -trap { - if ($TMP_DOWNLOAD_DIR.Exists) { - try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } - catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } - } -} - -New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null - -# Download and Install Apache Maven -Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." -Write-Verbose "Downloading from: $distributionUrl" -Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" - -$webclient = New-Object System.Net.WebClient -if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { - $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) -} -[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 -$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null - -# If specified, validate the SHA-256 sum of the Maven distribution zip file -$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum -if ($distributionSha256Sum) { - if ($USE_MVND) { - Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." - } - Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash - if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { - Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." - } -} - -# unzip and move -Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null - -# Find the actual extracted directory name (handles snapshots where filename != directory name) -$actualDistributionDir = "" - -# First try the expected directory name (for regular distributions) -$expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain" -$expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD" -if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) { - $actualDistributionDir = $distributionUrlNameMain -} - -# If not found, search for any directory with the Maven executable (for snapshots) -if (!$actualDistributionDir) { - Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object { - $testPath = Join-Path $_.FullName "bin/$MVN_CMD" - if (Test-Path -Path $testPath -PathType Leaf) { - $actualDistributionDir = $_.Name - } - } -} - -if (!$actualDistributionDir) { - Write-Error "Could not find Maven distribution directory in extracted archive" -} - -Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir" -Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -NewName $MAVEN_HOME_NAME | Out-Null -try { - Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null -} catch { - if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { - Write-Error "fail to move MAVEN_HOME" - } -} finally { - try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } - catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } -} - -Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% ^ + %JVM_CONFIG_MAVEN_PROPS% ^ + %MAVEN_OPTS% ^ + %MAVEN_DEBUG_OPTS% ^ + -classpath %WRAPPER_JAR% ^ + "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ + %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" +if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%"=="on" pause + +if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% + +cmd /C exit /B %ERROR_CODE% From ef2c297efa2fbeaa42ad40becb4cfed322da6bf2 Mon Sep 17 00:00:00 2001 From: Jack Xu Date: Mon, 12 Jan 2026 10:07:57 +0800 Subject: [PATCH 4/8] fix: fix java format --- .../ai/dataagent/service/semantic/SemanticModelServiceImpl.java | 1 - 1 file changed, 1 deletion(-) diff --git a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/service/semantic/SemanticModelServiceImpl.java b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/service/semantic/SemanticModelServiceImpl.java index 697d28d5e..344607155 100644 --- a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/service/semantic/SemanticModelServiceImpl.java +++ b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/service/semantic/SemanticModelServiceImpl.java @@ -25,7 +25,6 @@ import com.alibaba.cloud.ai.dataagent.vo.BatchImportResult; import java.util.List; import lombok.AllArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; From 2d8afdba676fb94b4b671291b4121a2b96c490d1 Mon Sep 17 00:00:00 2001 From: Jack Xu Date: Mon, 12 Jan 2026 10:35:56 +0800 Subject: [PATCH 5/8] fix: fix java format --- .../semantic/SemanticModelService.java | 42 +++++++++---------- .../semantic/SemanticModelServiceImpl.java | 4 +- .../workflow/node/SqlExecuteNode.java | 32 +++++++------- 3 files changed, 39 insertions(+), 39 deletions(-) diff --git a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/service/semantic/SemanticModelService.java b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/service/semantic/SemanticModelService.java index 716574f9d..5261e5fc2 100644 --- a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/service/semantic/SemanticModelService.java +++ b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/service/semantic/SemanticModelService.java @@ -25,41 +25,41 @@ public interface SemanticModelService { - List getAll(); + List getAll(); - List getEnabledByAgentId(Long agentId); + List getEnabledByAgentId(Long agentId); - List getByAgentIdAndTableNames(Long agentId, List tableNames); + List getByAgentIdAndTableNames(Long agentId, List tableNames); - SemanticModel getById(Long id); + SemanticModel getById(Long id); - void addSemanticModel(SemanticModel semanticModel); + void addSemanticModel(SemanticModel semanticModel); - boolean addSemanticModel(SemanticModelAddDTO dto); + boolean addSemanticModel(SemanticModelAddDTO dto); - void enableSemanticModel(Long id); + void enableSemanticModel(Long id); - void disableSemanticModel(Long id); + void disableSemanticModel(Long id); - List getByAgentId(Long agentId); + List getByAgentId(Long agentId); - List search(String keyword); + List search(String keyword); - void deleteSemanticModel(Long id); + void deleteSemanticModel(Long id); - void updateSemanticModel(Long id, SemanticModel semanticModel); + void updateSemanticModel(Long id, SemanticModel semanticModel); - default void addSemanticModels(List semanticModels) { - semanticModels.forEach(this::addSemanticModel); - } + default void addSemanticModels(List semanticModels) { + semanticModels.forEach(this::addSemanticModel); + } - default void enableSemanticModels(List ids) { - ids.forEach(this::enableSemanticModel); - } + default void enableSemanticModels(List ids) { + ids.forEach(this::enableSemanticModel); + } - default void disableSemanticModels(List ids) { - ids.forEach(this::disableSemanticModel); - } + default void disableSemanticModels(List ids) { + ids.forEach(this::disableSemanticModel); + } default void deleteSemanticModels(List ids) { ids.forEach(this::deleteSemanticModel); diff --git a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/service/semantic/SemanticModelServiceImpl.java b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/service/semantic/SemanticModelServiceImpl.java index 344607155..60b0d1eba 100644 --- a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/service/semantic/SemanticModelServiceImpl.java +++ b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/service/semantic/SemanticModelServiceImpl.java @@ -32,9 +32,9 @@ @AllArgsConstructor public class SemanticModelServiceImpl implements SemanticModelService { - private final SemanticModelMapper semanticModelMapper; + private final SemanticModelMapper semanticModelMapper; - private final AgentDatasourceMapper agentDatasourceMapper; + private final AgentDatasourceMapper agentDatasourceMapper; private final SemanticModelExcelService excelService; diff --git a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/workflow/node/SqlExecuteNode.java b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/workflow/node/SqlExecuteNode.java index f78575246..66821d4d5 100644 --- a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/workflow/node/SqlExecuteNode.java +++ b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/workflow/node/SqlExecuteNode.java @@ -73,9 +73,9 @@ @AllArgsConstructor public class SqlExecuteNode implements NodeAction { - private final DatabaseUtil databaseUtil; + private final DatabaseUtil databaseUtil; - private final Nl2SqlService nl2SqlService; + private final Nl2SqlService nl2SqlService; private final LlmService llmService; @@ -86,26 +86,26 @@ public class SqlExecuteNode implements NodeAction { @Override public Map apply(OverAllState state) throws Exception { - Integer currentStep = PlanProcessUtil.getCurrentStepNumber(state); + Integer currentStep = PlanProcessUtil.getCurrentStepNumber(state); - String sqlQuery = StateUtil.getStringValue(state, SQL_GENERATE_OUTPUT); - sqlQuery = nl2SqlService.sqlTrim(sqlQuery); + String sqlQuery = StateUtil.getStringValue(state, SQL_GENERATE_OUTPUT); + sqlQuery = nl2SqlService.sqlTrim(sqlQuery); - log.info("Executing SQL query: {}", sqlQuery); + log.info("Executing SQL query: {}", sqlQuery); - // Get the agent ID from the state - String agentIdStr = StateUtil.getStringValue(state, Constant.AGENT_ID); - if (StringUtils.isBlank(agentIdStr)) { - throw new IllegalStateException("Agent ID cannot be empty."); - } + // Get the agent ID from the state + String agentIdStr = StateUtil.getStringValue(state, Constant.AGENT_ID); + if (StringUtils.isBlank(agentIdStr)) { + throw new IllegalStateException("Agent ID cannot be empty."); + } - Long agentId = Long.valueOf(agentIdStr); + Long agentId = Long.valueOf(agentIdStr); - // Dynamically get the data source configuration for an agent - DbConfigBO dbConfig = databaseUtil.getAgentDbConfig(agentId); + // Dynamically get the data source configuration for an agent + DbConfigBO dbConfig = databaseUtil.getAgentDbConfig(agentId); - return executeSqlQuery(state, currentStep, sqlQuery, dbConfig, agentId); - } + return executeSqlQuery(state, currentStep, sqlQuery, dbConfig, agentId); + } /** * Executes the SQL query against the database and handles the results. From d02153b1488e02360736e0d68c26a4a24d519297 Mon Sep 17 00:00:00 2001 From: Jack Xu Date: Mon, 12 Jan 2026 10:40:53 +0800 Subject: [PATCH 6/8] fix: fix miss slf4j annotation --- .../dataagent/service/semantic/SemanticModelServiceImpl.java | 2 ++ pom.xml | 3 --- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/service/semantic/SemanticModelServiceImpl.java b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/service/semantic/SemanticModelServiceImpl.java index 60b0d1eba..41101383c 100644 --- a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/service/semantic/SemanticModelServiceImpl.java +++ b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/service/semantic/SemanticModelServiceImpl.java @@ -25,11 +25,13 @@ import com.alibaba.cloud.ai.dataagent.vo.BatchImportResult; import java.util.List; import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; @Service @AllArgsConstructor +@Slf4j public class SemanticModelServiceImpl implements SemanticModelService { private final SemanticModelMapper semanticModelMapper; diff --git a/pom.xml b/pom.xml index 66be44343..758ede4a7 100644 --- a/pom.xml +++ b/pom.xml @@ -292,7 +292,6 @@ -<<<<<<< HEAD origin/main @@ -315,8 +314,6 @@ (package|import) -======= ->>>>>>> b09f162 (refactor: unfiy the angentId to Long, and remove ResponseEntity in AgentController) From 5a5a37758ed8a976c05e0f302641d4fbf92f81a4 Mon Sep 17 00:00:00 2001 From: Jack Xu Date: Thu, 15 Jan 2026 08:59:36 +0800 Subject: [PATCH 7/8] style: license update --- .../cloud/ai/dataagent/controller/GlobalExceptionHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/controller/GlobalExceptionHandler.java b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/controller/GlobalExceptionHandler.java index a5e7c795d..0a236a609 100644 --- a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/controller/GlobalExceptionHandler.java +++ b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/controller/GlobalExceptionHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2024-2025 the original author or authors. + * Copyright 2024-2026 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From ef89dbe9e07a4c5f2202a0402281519e0aa0dd71 Mon Sep 17 00:00:00 2001 From: VLSMB <2047857654@qq.com> Date: Thu, 22 Jan 2026 00:27:38 +0800 Subject: [PATCH 8/8] fix: add generic type parameter --- .../controller/AgentDatasourceController.java | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/controller/AgentDatasourceController.java b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/controller/AgentDatasourceController.java index 427cb2c53..f9962af1e 100644 --- a/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/controller/AgentDatasourceController.java +++ b/data-agent-management/src/main/java/com/alibaba/cloud/ai/dataagent/controller/AgentDatasourceController.java @@ -47,7 +47,7 @@ public class AgentDatasourceController { * Information Source" function on the frontend */ @PostMapping("/init") - public ApiResponse initSchema(@PathVariable Long agentId) { + public ApiResponse initSchema(@PathVariable Long agentId) { // 防止前端恶意请求,dto数据应该在后端获取 try { AgentDatasource agentDatasource = agentDatasourceService.getCurrentAgentDatasource(agentId); @@ -86,7 +86,7 @@ public ApiResponse initSchema(@PathVariable Long agentId) { /** Get list of data sources configured for agent */ @GetMapping - public ApiResponse getAgentDatasource(@PathVariable Long agentId) { + public ApiResponse> getAgentDatasource(@PathVariable Long agentId) { try { log.info("Getting datasources for agent: {}", agentId); List datasources = agentDatasourceService.getAgentDatasource(agentId); @@ -100,7 +100,7 @@ public ApiResponse getAgentDatasource(@PathVariable Long agentId) { } @GetMapping("/active") - public ApiResponse getActiveAgentDatasource(@PathVariable Long agentId) { + public ApiResponse getActiveAgentDatasource(@PathVariable Long agentId) { try { log.info("Getting active datasource for agent: {}", agentId); AgentDatasource datasource = agentDatasourceService.getCurrentAgentDatasource(agentId); @@ -114,7 +114,8 @@ public ApiResponse getActiveAgentDatasource(@PathVariable Long agentId) { /** Add data source for agent */ @PostMapping("/{datasourceId}") - public ApiResponse addDatasourceToAgent(@PathVariable Long agentId, @PathVariable Integer datasourceId) { + public ApiResponse addDatasourceToAgent(@PathVariable Long agentId, + @PathVariable Integer datasourceId) { try { if (datasourceId == null) { throw new InvalidInputException("数据源ID不能为空"); @@ -130,7 +131,7 @@ public ApiResponse addDatasourceToAgent(@PathVariable Long agentId, @PathVariabl // 更新选择的数据表 @PostMapping("/tables") - public ApiResponse updateDatasourceTables(@PathVariable Long agentId, + public ApiResponse updateDatasourceTables(@PathVariable Long agentId, @RequestBody @Validated UpdateDatasourceTablesDTO dto) { try { dto.setTables(Optional.ofNullable(dto.getTables()).orElse(List.of())); @@ -145,7 +146,7 @@ public ApiResponse updateDatasourceTables(@PathVariable Long agentId, /** Remove data source association from agent */ @DeleteMapping("/{datasourceId}") - public ApiResponse removeDatasourceFromAgent(@PathVariable Long agentId, @PathVariable Integer datasourceId) { + public ApiResponse removeDatasourceFromAgent(@PathVariable Long agentId, @PathVariable Integer datasourceId) { try { agentDatasourceService.removeDatasourceFromAgent(agentId, datasourceId); return ApiResponse.success("数据源已移除"); @@ -157,7 +158,8 @@ public ApiResponse removeDatasourceFromAgent(@PathVariable Long agentId, @PathVa /** 启用/禁用智能体的数据源 */ @PutMapping("/toggle") - public ApiResponse toggleDatasourceForAgent(@PathVariable Long agentId, @RequestBody ToggleDatasourceDTO dto) { + public ApiResponse toggleDatasourceForAgent(@PathVariable Long agentId, + @RequestBody ToggleDatasourceDTO dto) { try { Boolean isActive = dto.getIsActive(); Integer datasourceId = dto.getDatasourceId();