mirror of
https://gitcode.com/ageerle/ruoyi-ai.git
synced 2026-03-13 20:53:42 +08:00
修改数据库读取工具
This commit is contained in:
@@ -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"
|
||||
@@ -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);
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -16,7 +16,8 @@ public class TableStructure {
|
||||
* 表名
|
||||
*/
|
||||
private String tableName;
|
||||
|
||||
|
||||
private String tableType; // 添加此字段:BASE TABLE 或 VIEW
|
||||
/**
|
||||
* 表注释/说明
|
||||
*/
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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<String> 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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<String> tableNames = new ArrayList<>();
|
||||
List<String> 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<TableStructure> 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();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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_\\.]+$");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user