From 26bcfbba8a56af19397dff3c4c876d63e269a503 Mon Sep 17 00:00:00 2001 From: zhang <823772544@qq.com> Date: Tue, 24 Feb 2026 16:07:18 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=95=B0=E6=8D=AE=E5=BA=93?= =?UTF-8?q?=E8=AF=BB=E5=8F=96=E5=B7=A5=E5=85=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/application-dev.yml | 18 ++-- .../ruoyi/agent/config/AgentMysqlConfig.java | 80 ++++++++--------- .../ruoyi/agent/domain/TableStructure.java | 3 +- .../agent/manager/TableSchemaInitializer.java | 6 +- .../agent/manager/TableSchemaManager.java | 41 ++++++--- .../ruoyi/agent/tool/ExecuteSqlQueryTool.java | 31 ++++--- .../ruoyi/agent/tool/QueryAllTablesTool.java | 85 ++++++++---------- .../agent/tool/QueryTableSchemaTool.java | 86 +++++++------------ .../impl/AbstractStreamingChatService.java | 75 +++++++++------- 9 files changed, 214 insertions(+), 211 deletions(-) diff --git a/ruoyi-admin/src/main/resources/application-dev.yml b/ruoyi-admin/src/main/resources/application-dev.yml index 8bcc34bb..91498093 100644 --- a/ruoyi-admin/src/main/resources/application-dev.yml +++ b/ruoyi-admin/src/main/resources/application-dev.yml @@ -58,9 +58,16 @@ spring: driverClassName: com.mysql.cj.jdbc.Driver # jdbc 所有参数配置参考 https://lionli.blog.csdn.net/article/details/122018562 # rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题) - url: jdbc:mysql://127.0.0.1:3306/mysql?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true + url: jdbc:mysql://127.0.0.1:3306/ruoyi_ai_agent?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true username: root - password: 123456 + password: Qzhang450000 + agent: + url: jdbc:mysql://127.0.0.1:3306/yunding?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true + # url: jdbc:mysql://localhost:3306/agent_db + username: root + password: Qzhang450000 + driverClassName: com.mysql.cj.jdbc.Driver + hikari: # 最大连接池数量 maxPoolSize: 20 @@ -77,11 +84,7 @@ spring: # 多久检查一次连接的活性 keepaliveTime: 30000 -agent: - mysql: - url: jdbc:mysql://localhost:3306/ruoyi-ai-agent?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true - username: root - password: root + --- # 上传文件地址 sys: @@ -265,3 +268,4 @@ justauth: client-secret: 1f7d08**********5b7**********29e redirect-uri: ${justauth.address}/social-callback?source=gitea +AGENT_ALLOWED_TABLES: "abtest_rule,abtest_project,agent_ban_log,agent_ban_logs,agent_install_sub_task,agent_install_sum_task,agent_install_task" \ No newline at end of file diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/config/AgentMysqlConfig.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/config/AgentMysqlConfig.java index b5d2f9e0..dfcc28b6 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/config/AgentMysqlConfig.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/config/AgentMysqlConfig.java @@ -1,44 +1,44 @@ -package org.ruoyi.agent.config; +// package org.ruoyi.agent.config; -import com.zaxxer.hikari.HikariConfig; -import com.zaxxer.hikari.HikariDataSource; -import javax.sql.DataSource; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; +// import com.zaxxer.hikari.HikariConfig; +// import com.zaxxer.hikari.HikariDataSource; +// import javax.sql.DataSource; +// import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +// import org.springframework.boot.context.properties.EnableConfigurationProperties; +// import org.springframework.context.annotation.Bean; +// import org.springframework.context.annotation.Configuration; -/** - * Agent MySQL 数据源配置 - * 为 Agent 配置独立的 MySQL 数据库连接池(HikariCP) - * - * 仅在 agent.mysql.enabled=true 时启用 - */ -@Configuration -@EnableConfigurationProperties(AgentMysqlProperties.class) -@ConditionalOnProperty(name = "agent.mysql.enabled", havingValue = "true") -public class AgentMysqlConfig { +// /** +// * Agent MySQL 数据源配置 +// * 为 Agent 配置独立的 MySQL 数据库连接池(HikariCP) +// * +// * 仅在 agent.mysql.enabled=true 时启用 +// */ +// @Configuration +// @EnableConfigurationProperties(AgentMysqlProperties.class) +// @ConditionalOnProperty(name = "agent.mysql.enabled", havingValue = "true") +// public class AgentMysqlConfig { - /** - * 创建 Agent 专用的数据源 - * 与项目主数据源隔离,独立管理 - * - * @param properties Agent MySQL 配置属性 - * @return HikariCP 数据源 - */ - @Bean("agentDataSource") - public DataSource agentDataSource(AgentMysqlProperties properties) { - HikariConfig config = new HikariConfig(); - config.setJdbcUrl(properties.getUrl()); - config.setUsername(properties.getUsername()); - config.setPassword(properties.getPassword()); - config.setDriverClassName("com.mysql.cj.jdbc.Driver"); - config.setMaximumPoolSize(properties.getMaxPoolSize()); - config.setMinimumIdle(properties.getMinIdle()); - config.setConnectionTimeout(30000); - config.setIdleTimeout(600000); - config.setMaxLifetime(1800000); +// /** +// * 创建 Agent 专用的数据源 +// * 与项目主数据源隔离,独立管理 +// * +// * @param properties Agent MySQL 配置属性 +// * @return HikariCP 数据源 +// */ +// @Bean("agentDataSource") +// public DataSource agentDataSource(AgentMysqlProperties properties) { +// HikariConfig config = new HikariConfig(); +// config.setJdbcUrl(properties.getUrl()); +// config.setUsername(properties.getUsername()); +// config.setPassword(properties.getPassword()); +// config.setDriverClassName("com.mysql.cj.jdbc.Driver"); +// config.setMaximumPoolSize(properties.getMaxPoolSize()); +// config.setMinimumIdle(properties.getMinIdle()); +// config.setConnectionTimeout(30000); +// config.setIdleTimeout(600000); +// config.setMaxLifetime(1800000); - return new HikariDataSource(config); - } -} +// return new HikariDataSource(config); +// } +// } diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/domain/TableStructure.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/domain/TableStructure.java index d1c3bca2..7ff66c21 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/domain/TableStructure.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/domain/TableStructure.java @@ -16,7 +16,8 @@ public class TableStructure { * 表名 */ private String tableName; - + + private String tableType; // 添加此字段:BASE TABLE 或 VIEW /** * 表注释/说明 */ diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/manager/TableSchemaInitializer.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/manager/TableSchemaInitializer.java index 14bd86e3..ce75074e 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/manager/TableSchemaInitializer.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/manager/TableSchemaInitializer.java @@ -1,19 +1,19 @@ package org.ruoyi.agent.manager; -import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component; +import lombok.extern.slf4j.Slf4j; + /** * 架构初始化器 * 在应用启动完成后自动初始化表结构缓存 */ @Slf4j @Component -@ConditionalOnProperty(name = "agent.mysql.enabled", havingValue = "true") +// @ConditionalOnProperty(name = "agent.mysql.enabled", havingValue = "true") public class TableSchemaInitializer { @Autowired(required = false) diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/manager/TableSchemaManager.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/manager/TableSchemaManager.java index 9bdab429..a02eb78d 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/manager/TableSchemaManager.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/manager/TableSchemaManager.java @@ -1,16 +1,30 @@ package org.ruoyi.agent.manager; -import lombok.extern.slf4j.Slf4j; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +import javax.sql.DataSource; + import org.ruoyi.agent.domain.ColumnInfo; import org.ruoyi.agent.domain.TableStructure; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; -import javax.sql.DataSource; -import java.sql.*; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.stream.Collectors; + +import com.baomidou.dynamic.datasource.annotation.DS; + +import lombok.extern.slf4j.Slf4j; /** * 表结构管理器 @@ -24,12 +38,15 @@ import java.util.stream.Collectors; */ @Slf4j @Component -@ConditionalOnProperty(name = "agent.mysql.enabled", havingValue = "true") +@DS("agent") public class TableSchemaManager { - + @Autowired(required = false) private DataSource agentDataSource; + @Value("${AGENT_ALLOWED_TABLES}") + private String allowedTables; + /** * 表结构缓存 (表名 -> 表结构) * 使用 ConcurrentHashMap 支持高并发访问 @@ -55,12 +72,12 @@ public class TableSchemaManager { if (initialized) { return; } - try { log.info("Initializing database schema cache..."); loadAllowedTableSchemas(); initialized = true; log.info("Schema cache initialized with {} tables", schemaCache.size()); + } catch (Exception e) { log.error("Failed to initialize schema cache", e); } @@ -103,6 +120,7 @@ public class TableSchemaManager { try (ResultSet tableRs = metaData.getTables(conn.getCatalog(), null, tableName, new String[]{"TABLE"})) { if (tableRs.next()) { table.setTableComment(tableRs.getString("REMARKS")); + table.setTableType(tableRs.getString("TABLE_TYPE")); } } @@ -183,7 +201,6 @@ public class TableSchemaManager { * 获取所有允许的表名 */ public List getAllowedTableNames() { - String allowedTables = System.getenv("AGENT_ALLOWED_TABLES"); if (allowedTables == null || allowedTables.trim().isEmpty()) { log.warn("AGENT_ALLOWED_TABLES not configured"); return new ArrayList<>(); @@ -224,7 +241,7 @@ public class TableSchemaManager { * 检查表是否在允许列表中 */ private boolean isTableAllowed(String tableName) { - String allowedTables = System.getenv("AGENT_ALLOWED_TABLES"); + // String allowedTables = System.getenv("AGENT_ALLOWED_TABLES"); if (allowedTables == null || allowedTables.trim().isEmpty()) { return false; } diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/tool/ExecuteSqlQueryTool.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/tool/ExecuteSqlQueryTool.java index 914fa409..2cc33a67 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/tool/ExecuteSqlQueryTool.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/tool/ExecuteSqlQueryTool.java @@ -1,12 +1,5 @@ package org.ruoyi.agent.tool; -import dev.langchain4j.agent.tool.Tool; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.stereotype.Component; - -import javax.sql.DataSource; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -16,17 +9,26 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import javax.sql.DataSource; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder; + +import dev.langchain4j.agent.tool.Tool; +import lombok.extern.slf4j.Slf4j; + /** * 执行 SQL 查询的 Tool * 执行指定的 SELECT SQL 查询并返回结果 */ @Slf4j @Component -@ConditionalOnProperty(name = "agent.mysql.enabled", havingValue = "true") public class ExecuteSqlQueryTool { @Autowired(required = false) - private DataSource agentDataSource; + private DataSource dataSource; /** * 执行 SELECT SQL 查询 @@ -37,6 +39,8 @@ public class ExecuteSqlQueryTool { */ @Tool("Execute a SELECT SQL query and return the results. Example: SELECT * FROM sys_user") public String executeSql(String sql) { + // 2. 手动推入数据源上下文 + DynamicDataSourceContextHolder.push("agent"); if (sql == null || sql.trim().isEmpty()) { return "Error: SQL query cannot be empty"; } @@ -48,11 +52,11 @@ public class ExecuteSqlQueryTool { } try { - if (agentDataSource == null) { + if (dataSource == null) { return "Error: Database datasource not configured"; } - try (Connection connection = agentDataSource.getConnection()) { + try (Connection connection = dataSource.getConnection()) { try (PreparedStatement preparedStatement = connection.prepareStatement(sql); ResultSet resultSet = preparedStatement.executeQuery()) { @@ -82,7 +86,12 @@ public class ExecuteSqlQueryTool { } } catch (Exception e) { log.error("Error executing SQL: {}", sql, e); + // 3. 必须在 finally 中清除上下文,防止污染其他请求 + DynamicDataSourceContextHolder.clear(); return "Error: " + e.getMessage(); + } finally { + // 3. 必须在 finally 中清除上下文,防止污染其他请求 + DynamicDataSourceContextHolder.clear(); } } diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/tool/QueryAllTablesTool.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/tool/QueryAllTablesTool.java index 9e490dc3..d9ed0666 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/tool/QueryAllTablesTool.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/tool/QueryAllTablesTool.java @@ -1,20 +1,14 @@ package org.ruoyi.agent.tool; -import com.zaxxer.hikari.HikariConfig; -import com.zaxxer.hikari.HikariDataSource; -import dev.langchain4j.agent.tool.Tool; -import lombok.extern.slf4j.Slf4j; -import org.ruoyi.agent.config.AgentMysqlProperties; +import java.util.List; + +import org.ruoyi.agent.domain.TableStructure; +import org.ruoyi.agent.manager.TableSchemaManager; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Component; -import javax.sql.DataSource; -import java.sql.Connection; -import java.sql.DatabaseMetaData; -import java.sql.ResultSet; -import java.util.ArrayList; -import java.util.List; +import dev.langchain4j.agent.tool.Tool; +import lombok.extern.slf4j.Slf4j; /** * 查询数据库所有表的 Tool @@ -22,12 +16,11 @@ import java.util.List; */ @Slf4j @Component -@ConditionalOnProperty(name = "agent.mysql.enabled", havingValue = "true") public class QueryAllTablesTool { - @Autowired(required = false) - private DataSource agentDataSource; - + + @Autowired + private TableSchemaManager tableSchemaManager; // 注入管理器 /** * 查询数据库中所有表 * 返回数据库中存在的所有表的列表 @@ -37,44 +30,36 @@ public class QueryAllTablesTool { @Tool("Query all tables in the database and return table names and basic information") public String queryAllTables() { try { - if (agentDataSource == null) { - return "Error: Database datasource not configured"; - } - - try (Connection connection = agentDataSource.getConnection()) { - DatabaseMetaData databaseMetaData = connection.getMetaData(); - ResultSet resultSet = databaseMetaData.getTables(null, null, null, new String[]{"TABLE"}); - - List tableNames = new ArrayList<>(); - List tableDetails = new ArrayList<>(); - - while (resultSet.next()) { - String tableName = resultSet.getString("TABLE_NAME"); - String tableComment = resultSet.getString("REMARKS"); - String tableType = resultSet.getString("TABLE_TYPE"); - - tableNames.add(tableName); - tableDetails.add(String.format("- %s (%s) - %s", - tableName, tableType, tableComment != null ? tableComment : "No comment")); + // 1. 从管理器获取所有允许的表结构信息(内部已包含初始化/缓存逻辑) + List tableSchemas = tableSchemaManager.getAllowedTableSchemas(); + + if (tableSchemas == null || tableSchemas.isEmpty()) { + return "No tables found in database or cache is empty."; } - resultSet.close(); - - if (tableNames.isEmpty()) { - return "No tables found in database"; - } - + + // 2. 格式化结果 StringBuilder result = new StringBuilder(); - result.append("Found ").append(tableNames.size()).append(" tables:\n"); - for (String detail : tableDetails) { - result.append(detail).append("\n"); + result.append("Found ").append(tableSchemas.size()).append(" tables in cache:\n"); + + for (TableStructure schema : tableSchemas) { + String tableName = schema.getTableName(); + String tableType = schema.getTableType() != null ? schema.getTableType() : "TABLE"; + String tableComment = schema.getTableComment(); + + result.append(String.format("- %s (%s) - %s\n", + tableName, + tableType, + tableComment != null ? tableComment : "No comment")); } - - log.info("Successfully queried {} tables", tableNames.size()); + + log.info("Successfully retrieved {} tables from schema cache", tableSchemas.size()); return result.toString(); + + } catch (Exception e) { + log.error("Error retrieving tables from cache", e); + return "Error: " + e.getMessage(); } - } catch (Exception e) { - log.error("Error querying all tables", e); - return "Error: " + e.getMessage(); - } + + } } diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/tool/QueryTableSchemaTool.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/tool/QueryTableSchemaTool.java index b7872322..33d06b27 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/tool/QueryTableSchemaTool.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/agent/tool/QueryTableSchemaTool.java @@ -1,83 +1,57 @@ package org.ruoyi.agent.tool; -import com.zaxxer.hikari.HikariConfig; -import com.zaxxer.hikari.HikariDataSource; -import dev.langchain4j.agent.tool.Tool; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.stereotype.Component; - -import javax.sql.DataSource; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; -/** - * 查询表建表详情的 Tool - * 根据表名查询该表的建表 SQL 语句 - */ -@Slf4j +import javax.sql.DataSource; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder; + +import dev.langchain4j.agent.tool.Tool; +import lombok.extern.slf4j.Slf4j; + @Component -@ConditionalOnProperty(name = "agent.mysql.enabled", havingValue = "true") +@Slf4j public class QueryTableSchemaTool { @Autowired(required = false) - private DataSource agentDataSource; + private DataSource dataSource; - /** - * 根据表名查询建表详情 - * 返回指定表的 CREATE TABLE 语句 - * - * @param tableName 表名 - * @return 包含建表 SQL 的结果 - */ @Tool("Query the CREATE TABLE statement (DDL) for a specific table by table name") public String queryTableSchema(String tableName) { + // 2. 手动推入数据源上下文 + DynamicDataSourceContextHolder.push("agent"); if (tableName == null || tableName.trim().isEmpty()) { return "Error: Table name cannot be empty"; } - // 验证表名有效性,防止 SQL 注入 - if (!isValidIdentifier(tableName)) { + if (!tableName.matches("^[a-zA-Z0-9_]+$")) { return "Error: Invalid table name format"; } - try { - if (agentDataSource == null) { - return "Error: Database datasource not configured"; + String sql = "SHOW CREATE TABLE `" + tableName + "`"; + + try (Connection connection = dataSource.getConnection(); + PreparedStatement ps = connection.prepareStatement(sql); + ResultSet rs = ps.executeQuery()) { + + if (rs.next()) { + return rs.getString("Create Table"); } - try (Connection connection = agentDataSource.getConnection()) { - String sql = "SHOW CREATE TABLE " + tableName; - PreparedStatement preparedStatement = connection.prepareStatement(sql); - ResultSet resultSet = preparedStatement.executeQuery(); + return "Table not found: " + tableName; - if (resultSet.next()) { - String createTableSql = resultSet.getString("Create Table"); - resultSet.close(); - preparedStatement.close(); - - log.info("Successfully queried schema for table: {}", tableName); - return "CREATE TABLE DDL for " + tableName + ":\n\n" + createTableSql; - } - - resultSet.close(); - preparedStatement.close(); - return "Error: Table not found or not accessible: " + tableName; - } } catch (Exception e) { - log.error("Error querying table schema for table: {}", tableName, e); + // 3. 必须在 finally 中清除上下文,防止污染其他请求 + DynamicDataSourceContextHolder.clear(); + log.error("Error querying table schema: {}", tableName, e); return "Error: " + e.getMessage(); + } finally { + // 3. 必须在 finally 中清除上下文,防止污染其他请求 + DynamicDataSourceContextHolder.clear(); } } - - /** - * 验证是否为有效的 SQL 标识符 - */ - private boolean isValidIdentifier(String identifier) { - if (identifier == null || identifier.isEmpty()) { - return false; - } - return identifier.matches("^[a-zA-Z0-9_\\.]+$"); - } } diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/AbstractStreamingChatService.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/AbstractStreamingChatService.java index 75894c9a..fa2b8f70 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/AbstractStreamingChatService.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/AbstractStreamingChatService.java @@ -3,6 +3,7 @@ package org.ruoyi.service.chat.impl; import dev.langchain4j.agentic.AgenticServices; import dev.langchain4j.agentic.supervisor.SupervisorAgent; import dev.langchain4j.agentic.supervisor.SupervisorResponseStrategy; +import dev.langchain4j.community.model.dashscope.QwenChatModel; import dev.langchain4j.data.message.ChatMessage; import dev.langchain4j.data.message.UserMessage; import dev.langchain4j.mcp.McpToolProvider; @@ -325,26 +326,26 @@ public abstract class AbstractStreamingChatService implements IChatService { protected String doAgent(String userMessage, ChatModelVo chatModelVo) { // 步骤1: 配置MCP传输层 - 连接到bing-cn-mcp服务器 // 该服务提供两个工具: bing_search (必应搜索) 和 crawl_webpage (网页抓取) - McpTransport transport = new StdioMcpTransport.Builder() - .command(List.of("C:\\Program Files\\nodejs\\npx.cmd", "-y", - "bing-cn-mcp" - )) - .logEvents(true) - .build(); + // McpTransport transport = new StdioMcpTransport.Builder() + // .command(List.of("C:\\Program Files\\nodejs\\npx.cmd", "-y", + // "bing-cn-mcp" + // )) + // .logEvents(true) + // .build(); - // 步骤2: 创建MCP客户端 - McpClient mcpClient = new DefaultMcpClient.Builder() - .transport(transport) - .build(); + // // 步骤2: 创建MCP客户端 + // McpClient mcpClient = new DefaultMcpClient.Builder() + // .transport(transport) + // .build(); - // 步骤3: 配置工具提供者 - ToolProvider toolProvider = McpToolProvider.builder() - .mcpClients(List.of(mcpClient)) - .build(); + // // 步骤3: 配置工具提供者 + // ToolProvider toolProvider = McpToolProvider.builder() + // .mcpClients(List.of(mcpClient)) + // .build(); McpTransport transport1 = new StdioMcpTransport.Builder() - .command(List.of("C:\\Program Files\\nodejs\\npx.cmd", "-y", + .command(List.of("npx", "-y", "mcp-echarts" )) .logEvents(true) @@ -361,40 +362,52 @@ public abstract class AbstractStreamingChatService implements IChatService { .build(); // 步骤4: 配置OpenAI模型 - OpenAiChatModel PLANNER_MODEL = OpenAiChatModel.builder() - .baseUrl(chatModelVo.getApiHost()) + // OpenAiChatModel PLANNER_MODEL = OpenAiChatModel.builder() + // .baseUrl(chatModelVo.getApiHost()) + // .apiKey(chatModelVo.getApiKey()) + // .modelName(chatModelVo.getModelName()) + // .build(); + + + QwenChatModel qwenChatModel = QwenChatModel.builder() + // .baseUrl(chatModelVo.getApiHost()) .apiKey(chatModelVo.getApiKey()) .modelName(chatModelVo.getModelName()) - .build(); - + .build(); + SqlAgent sqlAgent = AgenticServices.agentBuilder(SqlAgent.class) - .chatModel(PLANNER_MODEL) + .chatModel( + qwenChatModel) .tools( - new QueryAllTablesTool(), - new QueryTableSchemaTool(), - new ExecuteSqlQueryTool() + SpringUtils.getBean(QueryAllTablesTool.class), // 必须通过 getBean 获取 + SpringUtils.getBean(QueryTableSchemaTool.class), + SpringUtils.getBean(ExecuteSqlQueryTool.class) ) .build(); - WebSearchAgent searchAgent = AgenticServices.agentBuilder(WebSearchAgent.class) - .chatModel(PLANNER_MODEL) - .toolProvider(toolProvider) - .build(); + // WebSearchAgent searchAgent = AgenticServices.agentBuilder(WebSearchAgent.class) + // .chatModel(PLANNER_MODEL) + // .toolProvider(toolProvider) + // .build(); ChartGenerationAgent chartGenerationAgent = AgenticServices.agentBuilder(ChartGenerationAgent.class) - .chatModel(PLANNER_MODEL) + .chatModel( + qwenChatModel) .toolProvider(toolProvider1) .build(); - + String res = sqlAgent.getData(userMessage); + String res1 = chartGenerationAgent.generateChart(res); + System.out.println(res1); + System.out.println(res); SupervisorAgent supervisor = AgenticServices .supervisorBuilder() - .chatModel(PLANNER_MODEL) + .chatModel(qwenChatModel) .subAgents(sqlAgent, chartGenerationAgent) .responseStrategy(SupervisorResponseStrategy.LAST) .build(); String invoke = supervisor.invoke(userMessage); System.out.println(invoke); - return invoke; + return res1; } }