Initial commit: HZHub project setup with RuoYi-AI base

This commit is contained in:
2026-03-26 09:47:46 +00:00
commit 3584e491cc
5005 changed files with 318595 additions and 0 deletions

View File

@@ -0,0 +1,336 @@
# MCP工具管理模块 - API接口文档
## 概述
本文档描述了MCP工具管理模块的REST API接口供前端开发人员参考。
## 基础信息
- **Base URL**: `/api/mcp`
- **认证方式**: Bearer Token (SaToken)
- **响应格式**: JSON
---
## 1. MCP工具管理
### 1.1 查询工具列表(分页)
**接口**: `GET /tool/list`
**权限**: `mcp:tool:list`
**请求参数**:
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| name | String | 否 | 工具名称(模糊查询) |
| description | String | 否 | 工具描述(模糊查询) |
| type | String | 否 | 工具类型LOCAL/REMOTE/BUILTIN |
| status | String | 否 | 状态0-启用, 1-禁用 |
| pageNum | Integer | 是 | 页码默认1 |
| pageSize | Integer | 是 | 每页数量默认10 |
**响应示例**:
```json
{
"rows": [
{
"id": 1,
"name": "ReadFileTool",
"description": "读取文件内容工具",
"type": "BUILTIN",
"status": "0",
"configJson": null,
"createTime": "2026-03-08 10:00:00",
"updateTime": "2026-03-08 10:00:00"
}
],
"total": 1
}
```
### 1.2 查询工具列表(不分页)
**接口**: `GET /tool/all`
**权限**: `mcp:tool:list`
**请求参数**:
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| keyword | String | 否 | 关键词 |
| type | String | 否 | 工具类型 |
| status | String | 否 | 状态 |
**响应示例**:
```json
{
"tools": [
{
"id": 1,
"name": "ReadFileTool",
"description": "读取文件内容工具",
"type": "BUILTIN",
"status": "0"
}
],
"total": 1
}
```
### 1.3 获取工具详情
**接口**: `GET /tool/{id}`
**权限**: `mcp:tool:query`
**路径参数**:
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| id | Long | 是 | 工具ID |
### 1.4 新增工具
**接口**: `POST /tool`
**权限**: `mcp:tool:add`
**请求体**:
```json
{
"name": "MyMcpTool",
"description": "我的MCP工具",
"type": "REMOTE",
"status": "0",
"configJson": "{\"baseUrl\": \"http://localhost:8080/mcp\"}"
}
```
### 1.5 修改工具
**接口**: `PUT /tool`
**权限**: `mcp:tool:edit`
**请求体**: 同新增工具
### 1.6 删除工具
**接口**: `DELETE /tool/{ids}`
**权限**: `mcp:tool:remove`
**路径参数**:
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| ids | String | 是 | 工具ID多个用逗号分隔 |
### 1.7 更新工具状态
**接口**: `PUT /tool/{id}/status`
**权限**: `mcp:tool:edit`
**路径参数**:
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| id | Long | 是 | 工具ID |
**请求参数**:
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| status | String | 是 | 状态0-启用, 1-禁用 |
### 1.8 测试工具连接
**接口**: `POST /tool/{id}/test`
**权限**: `mcp:tool:query`
**响应示例**:
```json
{
"success": true,
"message": "连接测试成功",
"toolCount": 5,
"tools": ["tool1", "tool2", "tool3", "tool4", "tool5"]
}
```
---
## 2. MCP市场管理
### 2.1 查询市场列表
**接口**: `GET /market/list`
**权限**: `mcp:market:list`
**请求参数**:
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| name | String | 否 | 市场名称 |
| description | String | 否 | 市场描述 |
| status | String | 否 | 状态 |
| pageNum | Integer | 是 | 页码 |
| pageSize | Integer | 是 | 每页数量 |
### 2.2 获取市场工具列表
**接口**: `GET /market/{marketId}/tools`
**权限**: `mcp:market:query`
**路径参数**:
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| marketId | Long | 是 | 市场ID |
**请求参数**:
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| page | Integer | 否 | 页码默认1 |
| size | Integer | 否 | 每页数量默认10 |
### 2.3 刷新市场工具
**接口**: `POST /market/{marketId}/refresh`
**权限**: `mcp:market:edit`
**响应示例**:
```json
{
"success": true,
"message": "刷新成功",
"addedCount": 3,
"updatedCount": 5
}
```
### 2.4 加载工具到本地
**接口**: `POST /market/tool/{toolId}/load`
**权限**: `mcp:market:edit`
**路径参数**:
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| toolId | Long | 是 | 市场工具ID |
### 2.5 批量加载工具
**接口**: `POST /market/tools/batchLoad`
**权限**: `mcp:market:edit`
**请求体**:
```json
{
"toolIds": [1, 2, 3]
}
```
---
## 3. 工具调用日志
### 3.1 查询调用日志
**接口**: `GET /tool/callLog`
**权限**: `mcp:tool:query`
**请求参数**:
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| toolId | Long | 否 | 工具ID |
| sessionId | Long | 否 | 会话ID |
| startDate | Date | 否 | 开始日期 |
| endDate | Date | 否 | 结束日期 |
| pageNum | Integer | 是 | 页码 |
| pageSize | Integer | 是 | 每页数量 |
### 3.2 获取工具统计
**接口**: `GET /tool/{toolId}/metrics`
**权限**: `mcp:tool:query`
**响应示例**:
```json
{
"toolId": 1,
"toolName": "ReadFileTool",
"today": {
"callCount": 100,
"successCount": 95,
"failureCount": 5,
"avgDurationMs": 150,
"successRate": 95.0
},
"week": {
"callCount": 500,
"successCount": 475,
"failureCount": 25,
"avgDurationMs": 160,
"successRate": 95.0
}
}
```
---
## 4. 状态码说明
| 状态码 | 说明 |
|--------|------|
| 200 | 请求成功 |
| 401 | 未认证 |
| 403 | 无权限 |
| 404 | 资源不存在 |
| 500 | 服务器错误 |
---
## 5. 前端页面需求
### 5.1 MCP工具管理页面 (`/mcp/tool`)
**功能**:
- 工具列表展示(分页)
- 工具搜索和筛选
- 新增/编辑/删除工具
- 工具状态切换
- 工具连接测试
**表格列**:
- 工具名称
- 工具描述
- 工具类型(标签显示)
- 状态(开关)
- 创建时间
- 操作(编辑、删除、测试)
### 5.2 MCP市场管理页面 (`/mcp/market`)
**功能**:
- 市场列表展示
- 市场工具浏览
- 刷新市场工具
- 加载工具到本地
### 5.3 工具调用日志页面 (`/mcp/log`)
**功能**:
- 调用日志列表
- 按工具/日期筛选
- 成功率统计
- 响应时间统计
**图表**:
- 每日调用次数趋势图
- 工具调用成功率饼图
- 平均响应时间柱状图

View File

@@ -0,0 +1,152 @@
# 数据库操作智能体实现总结
## 概述
基于 LangChain4j 的 **Pure agentic AI** 模式,完成了一个智能数据库查询系统。该系统能够根据用户的自然语言问题,自动分析数据库结构、生成查询计划并执行相应的数据库操作。
## 架构设计
### 1. 整体框架
```
用户请求 → SupervisorAgent(协调器) → SqlAgent(数据库专家) → 数据库工具 → 数据库
结果处理与响应
```
### 2. 核心组件
#### A. SqlAgent (数据库查询专家)
- **文件**: `org.ruoyi.agent.SqlAgent`
- **职责**: 根据用户的自然语言问题,调用相应的工具查询数据库
- **使用的工具**:
- `QueryAllTablesTool`: 查询所有表名和注释
- `QueryTableSchemaTool`: 查询表的DDLCREATE TABLE语句
- `ExecuteSqlQueryTool`: 执行SELECT查询
```java
public interface SqlAgent {
@SystemMessage("...") // 详细的系统提示
@UserMessage("请回答以下问题:{{query}}")
@Agent("一个智能数据库查询助手...")
String getData(@V("query") String query);
}
```
#### B. SupervisorAgent (总体协调器)
-`OpenAIServiceImpl.doAgent()` 中创建
- 作用:协调 SqlAgent 的执行,管理任务流程
- 响应策略:`SUMMARY` - 返回所有操作的摘要
```java
SupervisorAgent supervisor = AgenticServices
.supervisorBuilder()
.chatModel(PLANNER_MODEL)
.subAgents(sqlAgent)
.responseStrategy(SupervisorResponseStrategy.SUMMARY)
.build();
```
#### C. 数据库工具 (Tools)
##### 1. QueryAllTablesTool
```java
@Tool("Query all tables in the database and return table names and basic information")
public String queryAllTables()
```
- 返回数据库中所有表的名称和注释
- 使用注入的 `agentDataSource` DataSource
##### 2. QueryTableSchemaTool
```java
@Tool("Query the CREATE TABLE statement (DDL) for a specific table by table name")
public String queryTableSchema(String tableName)
```
- 返回指定表的建表SQL语句
- 包含SQL注入防护表名有效性验证
##### 3. ExecuteSqlQueryTool
```java
@Tool("Execute a SELECT SQL query and return the results. Example: SELECT * FROM sys_user")
public String executeSql(String sql)
```
- 执行SELECT查询安全性考虑不允许执行其他操作
- 格式化查询结果最多显示前20行
### 3. 配置体系
#### AgentMysqlProperties
配置文件前缀:`agent.mysql`
```yaml
agent:
mysql:
enabled: true
url: jdbc:mysql://localhost:3306/your_database
username: your_username
password: your_password
max-pool-size: 10
min-idle: 2
```
#### AgentMysqlConfig
- 创建独立的 DataSource Bean (`agentDataSource`)
- 使用 HikariCP 连接池管理
- 与项目主数据源隔离
#### TableSchemaManager
- 在应用启动时初始化表结构缓存
- 使用 `ConcurrentHashMap` 存储结构信息
- 支持按需刷新单个表的结构
## 工作流程示例
### 用户查询: "数据库有哪些表?"
```
1. SupervisorAgent 接收请求
2. SupervisorAgent 分析请求,决定调用 SqlAgent
3. SqlAgent 理解需求,调用 QueryAllTablesTool
4. QueryAllTablesTool 连接数据库,获取所有表
5. 结果返回给 SqlAgent
6. SqlAgent 格式化结果
7. SupervisorAgent 生成最终摘要
8. 结果通过流式处理器返回给用户
```
### 用户查询: "查询 sys_user 表中有多少条记录"
```
1. SqlAgent 接收请求
2. SqlAgent 分析需求,可能先调用 QueryTableSchemaTool 了解表结构
3. 然后调用 ExecuteSqlQueryTool 执行 "SELECT COUNT(*) FROM sys_user"
4. 获取查询结果并返回
```
## Agentic AI 特性
### 自适应决策
- Agent 能根据上下文和之前的结果决定下一步操作
- 不是预定义的固定流程,而是动态适应
### 例子
当询问"查询部门表的字段信息时"
- SqlAgent 可能先调用 `QueryTableSchemaTool` 获取建表SQL
- 如果发现需要具体的数据示例,会继续调用 `ExecuteSqlQueryTool`
- 整个决策过程由 LLM 驱动,非硬编码
## 安全考虑
1. **数据源隔离**: Agent 使用独立的数据源agentDataSource与主应用隔离
2. **SQL验证**: 只允许执行 SELECT 查询
3. **表名验证**: 表名必须通过正则表达式验证防止SQL注入
4. **权限限制**: 可通过 AGENT_ALLOWED_TABLES 环境变量限制可访问的表
5. **凭证管理**: 数据库凭证通过配置文件管理,不硬编码

View File

@@ -0,0 +1,151 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-modules</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ruoyi-chat</artifactId>
<description>
聊天模块 - API定义和服务实现
</description>
<dependencies>
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-common-chat</artifactId>
</dependency>
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-common-sse</artifactId>
</dependency>
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-common-sensitive</artifactId>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai</artifactId>
<version>${langchain4j.version}</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-ollama</artifactId>
<version>${langchain4j.version}</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-agentic</artifactId>
<version>${langchain4j.community.version}</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-community-dashscope</artifactId>
<version>${langchain4j.community.version}</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-community-zhipu-ai</artifactId>
<version>${langchain4j.community.version}</version>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</dependency>
<!--文档解析-->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-document-parser-apache-tika</artifactId>
<version>${langchain4j.community.version}</version>
<exclusions>
<exclusion>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- LangChain4j Milvus Embedding Store -->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-milvus</artifactId>
<version>${langchain4j.community.version}</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-weaviate</artifactId>
<version>${langchain4j.community.version}</version>
</dependency>
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-mcp</artifactId>
<version>${langchain4j.community.version}</version>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>weaviate</artifactId>
<version>${weaviate.version}</version>
</dependency>
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-common-idempotent</artifactId>
</dependency>
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-common-log</artifactId>
</dependency>
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-common-web</artifactId>
</dependency>
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-common-doc</artifactId>
</dependency>
<dependency>
<groupId>org.neo4j.driver</groupId>
<artifactId>neo4j-java-driver</artifactId>
</dependency>
<dependency>
<groupId>io.github.imfangs</groupId>
<artifactId>dify-java-client</artifactId>
<version>${dify.version}</version>
</dependency>
<!-- HikariCP 数据库连接池 -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</dependency>
<!-- MySQL JDBC 驱动 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,24 @@
package org.ruoyi.agent;
import dev.langchain4j.agentic.Agent;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;
public interface ChartGenerationAgent extends Agent {
@SystemMessage("""
You are a chart generation specialist. Your only task is to generate Apache ECharts
chart configurations. Respond with ONLY the ECharts configuration in ```echarts markdown
code block format. Do not include any explanations, descriptions, or other content.
""")
@UserMessage("""
Generate an Apache ECharts chart configuration for: {{query}}
Response format: ```echarts
{valid JSON ECharts configuration}
```
""")
@Agent("Generate Apache ECharts chart configurations only.")
String generateChart(@V("query") String query);
}

View File

@@ -0,0 +1,89 @@
package org.ruoyi.agent;
import dev.langchain4j.agentic.Agent;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;
/**
* Text2SQL and Echarts Chart Generation Agent
* An intelligent assistant that converts natural language queries into SQL,
* executes database queries, and generates Echarts visualizations.
*/
public interface EchartsAgent {
@SystemMessage("""
You are a data visualization assistant that generates Echarts chart configurations.
CRITICAL OUTPUT REQUIREMENTS:
- Return Echarts JSON wrapped in markdown code block
- Use this exact format: ```json\n{...}\n```
- The JSON inside must be valid Echarts configuration
- Frontend expects markdown format for proper parsing
Your workflow:
1. Use MCP tools to query the database and get data
2. The MCP tool returns data in this structure:
{"data": [{"dict_type": "value1", "count": 10}, {"dict_type": "value2", "count": 20}, ...]}
3. Transform this data into Echarts configuration
4. Return ONLY the Echarts JSON
Data transformation rules:
- Extract array elements into xAxis categories and series data
- For the example above: xAxis.data = ["value1", "value2"], series.data = [10, 20]
- Choose chart type based on request: bar (default), line, pie, etc.
Expected output format (bar chart example):
```json
{
"title": {
"text": "Dict Type Distribution",
"left": "center"
},
"tooltip": {
"trigger": "axis"
},
"xAxis": {
"type": "category",
"data": ["type1", "type2", "type3"]
},
"yAxis": {
"type": "value"
},
"series": [
{
"name": "数量",
"type": "bar",
"data": [10, 20, 15]
}
]
}
```
For pie charts:
{
"title": {"text": "Chart Title", "left": "center"},
"tooltip": {"trigger": "item"},
"series": [{
"type": "pie",
"radius": "50%",
"data": [
{"value": 10, "name": "Category1"},
{"value": 20, "name": "Category2"}
]
}]
}
REMEMBER:
- Always wrap JSON in ```json and ``` markers
- Use proper formatting with indentation
- This is the expected format for frontend parsing
""")
@UserMessage("""
Generate an Echarts chart for: {{query}}
IMPORTANT: Return the Echarts configuration JSON wrapped in markdown code block (```json...```).
""")
@Agent("Data visualization assistant that returns Echarts JSON configurations for frontend rendering")
String search(@V("query") String query);
}

View File

@@ -0,0 +1,39 @@
package org.ruoyi.agent;
import dev.langchain4j.agentic.Agent;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;
/**
* SQL Database Agent
* A database query assistant that answers natural language questions by querying the database
* and returning relevant data and analysis results.
*
*/
public interface SqlAgent extends Agent {
@SystemMessage("""
This agent is designed for MySQL 5.7
You are an intelligent database query assistant. Your responsibility is to:
1. Query all tables in the database to understand the database structure
2. Understand the user's natural language question
3. Query the database to get the information needed
4. Provide accurate and helpful answers
Available tools:
- queryAllTables: Query all tables in the database
- queryTableSchema: Query the table structure and CREATE SQL for a specified table
- executeSql: Execute a SELECT SQL query and return results
CRITICAL REQUIREMENT - MUST FOLLOW:
- You MUST ALWAYS use queryAllTables first to query all tables in the database before executing any SQL queries
- Only after understanding the database schema can you construct and execute appropriate SQL queries
- This is mandatory and applies to all queries without exception
""")
@UserMessage("""
Answer the following question: {{query}}
""")
@Agent("Intelligent database query assistant that MUST check database tables first, then query table structures and execute SQL queries")
String getData(@V("query") String query);
}

View File

@@ -0,0 +1,38 @@
package org.ruoyi.agent;
import dev.langchain4j.agentic.Agent;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;
/**
* Web Search Agent
* A web search assistant that answers natural language questions by searching the internet
* and returning relevant information from web pages.
*/
public interface WebSearchAgent extends Agent {
@SystemMessage("""
You are a web search assistant. Answer questions by searching and retrieving web content.
Available tools:
1. bing_search: Search the internet with keywords
- query (required): search keywords
- count (optional): number of results, default 10, max 50
- offset (optional): pagination offset, default 0
Returns: title, link, and summary for each result
2. crawl_webpage: Extract text content from a web page
- url (required): web page URL
Returns: cleaned page title and main content
Instructions:
- Always cite sources in your answers
- Only use the two tools listed above
""")
@UserMessage("""
Answer the following question by searching the web: {{query}}
""")
@Agent("Web search assistant using Bing search and web scraping to find and retrieve information")
String search(@V("query") String query);
}

View File

@@ -0,0 +1,44 @@
// 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;
// /**
// * 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);
// return new HikariDataSource(config);
// }
// }

View File

@@ -0,0 +1,53 @@
package org.ruoyi.agent.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* Agent MySQL 配置属性
* 前缀agent.mysql
*
* 配置示例:
* agent:
* mysql:
* enabled: true
* url: jdbc:mysql://localhost:3306/database
* username: user
* password: password
* max-pool-size: 10
* min-idle: 2
*/
@Data
@ConfigurationProperties(prefix = "agent.mysql")
public class AgentMysqlProperties {
/**
* 是否启用 Agent MySQL 查询功能
*/
private Boolean enabled = false;
/**
* 数据库 URL (jdbc:mysql://host:port/database)
*/
private String url;
/**
* 数据库用户名
*/
private String username;
/**
* 数据库密码
*/
private String password;
/**
* 数据库连接池最大连接数
*/
private Integer maxPoolSize = 10;
/**
* 数据库连接池最小空闲连接数
*/
private Integer minIdle = 2;
}

View File

@@ -0,0 +1,52 @@
package org.ruoyi.agent.domain;
import lombok.Data;
/**
* 数据库列(字段)信息
* 描述表中单个列的详细信息
*/
@Data
public class ColumnInfo {
/**
* 列名
*/
private String columnName;
/**
* 列数据类型
* 示例VARCHAR(100), INT, DECIMAL(10,2), DATE 等
*/
private String columnType;
/**
* 是否可为空
*/
private boolean nullable = true;
/**
* 默认值
*/
private String defaultValue;
/**
* 列注释/说明
*/
private String columnComment;
/**
* 是否是主键
*/
private boolean primaryKey = false;
/**
* 是否自增
*/
private boolean autoIncrement = false;
/**
* 是否有索引
*/
private boolean indexed = false;
}

View File

@@ -0,0 +1,51 @@
package org.ruoyi.agent.domain;
import lombok.Data;
/**
* 单个查询条件
* 用于 WHERE 子句中的条件
*
* 示例:
* Condition c = new Condition();
* c.setField("order_date");
* c.setOperator(">=");
* c.setValue("2024-01-01");
*/
@Data
public class Condition {
/**
* 字段名
* 仅允许:字母、数字、下划线
*/
private String field;
/**
* 操作符
* 支持:=, !=, <, >, <=, >=, LIKE, IN, BETWEEN, IS NULL, IS NOT NULL
*/
private String operator;
/**
* 条件值
* 类型可以是String, Number, Boolean 等
* 会自动转义防止 SQL 注入
*/
private Object value;
/**
* 构造函数
*/
public Condition() {
}
/**
* 带参数的构造函数
*/
public Condition(String field, String operator, Object value) {
this.field = field;
this.operator = operator;
this.value = value;
}
}

View File

@@ -0,0 +1,46 @@
package org.ruoyi.agent.domain;
import lombok.Data;
import java.util.List;
/**
* 数据库查询对象
* 用于构建 SELECT 查询条件
*/
@Data
public class Query {
/**
* 表名
*/
private String table;
/**
* 选择的字段列表
* 可以使用 "*" 表示所有字段
*/
private List<String> select;
/**
* WHERE 条件列表
* 多个条件之间用 AND 连接
*/
private List<Condition> where;
/**
* 返回结果数量限制
* 默认 100最大 1000
*/
private Integer limit = 100;
/**
* 获取安全的 LIMIT 值
* @return 限制数量,最多 1000
*/
public Integer getLimit() {
if (limit == null) {
limit = 100;
}
return Math.min(limit, 1000);
}
}

View File

@@ -0,0 +1,56 @@
package org.ruoyi.agent.domain;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* 数据库查询结果
* 返回 SELECT 查询的结果
*/
@Data
public class Result {
/**
* 是否成功
*/
private boolean success;
/**
* 错误消息或成功消息
*/
private String message;
/**
* 查询结果数据
* 每个 Map 代表一行key 是字段名value 是字段值
*/
private List<Map<String, Object>> data;
/**
* 创建成功结果
* @param data 查询数据
* @return Result 对象
*/
public static Result success(List<Map<String, Object>> data) {
Result result = new Result();
result.success = true;
result.data = data;
result.message = "Query successful";
return result;
}
/**
* 创建失败结果
* @param message 错误消息
* @return Result 对象
*/
public static Result error(String message) {
Result result = new Result();
result.success = false;
result.message = message;
result.data = new ArrayList<>();
return result;
}
}

View File

@@ -0,0 +1,54 @@
package org.ruoyi.agent.domain;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* 表结构查询结果
* 返回数据库的表结构信息
*/
@Data
public class SchemaResult {
/**
* 是否成功
*/
private boolean success;
/**
* 错误消息或成功消息
*/
private String message;
/**
* 表结构列表
*/
private List<TableStructure> tables;
/**
* 创建成功结果
* @param tables 表结构列表
* @return SchemaResult 对象
*/
public static SchemaResult success(List<TableStructure> tables) {
SchemaResult result = new SchemaResult();
result.success = true;
result.tables = tables != null ? tables : new ArrayList<>();
result.message = "Schema retrieved successfully";
return result;
}
/**
* 创建失败结果
* @param message 错误消息
* @return SchemaResult 对象
*/
public static SchemaResult error(String message) {
SchemaResult result = new SchemaResult();
result.success = false;
result.message = message;
result.tables = new ArrayList<>();
return result;
}
}

View File

@@ -0,0 +1,54 @@
package org.ruoyi.agent.domain;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* 表列表查询结果
* 返回允许查询的表的名称列表
*/
@Data
public class TableListResult {
/**
* 是否成功
*/
private boolean success;
/**
* 错误消息或成功消息
*/
private String message;
/**
* 表名列表
*/
private List<String> tables;
/**
* 创建成功结果
* @param tables 表名列表
* @return TableListResult 对象
*/
public static TableListResult success(List<String> tables) {
TableListResult result = new TableListResult();
result.success = true;
result.tables = tables != null ? tables : new ArrayList<>();
result.message = "Tables listed successfully";
return result;
}
/**
* 创建失败结果
* @param message 错误消息
* @return TableListResult 对象
*/
public static TableListResult error(String message) {
TableListResult result = new TableListResult();
result.success = false;
result.message = message;
result.tables = new ArrayList<>();
return result;
}
}

View File

@@ -0,0 +1,70 @@
package org.ruoyi.agent.domain;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* 数据库表结构信息
* 包含表的所有字段、类型、约束等信息
* Agent 使用此信息来理解数据库架构
*/
@Data
public class TableStructure {
/**
* 表名
*/
private String tableName;
private String tableType; // 添加此字段BASE TABLE 或 VIEW
/**
* 表注释/说明
*/
private String tableComment;
/**
* 字段列表
*/
private List<ColumnInfo> columns = new ArrayList<>();
/**
* 主键字段名
*/
private String primaryKey;
/**
* 获取字段总数
* @return 字段数量
*/
public int getColumnCount() {
return columns.size();
}
/**
* 获取格式化的表结构描述
* 用于 Agent 理解表结构
*
* @return 格式化的表结构描述
*/
public String getFormattedDescription() {
StringBuilder sb = new StringBuilder();
sb.append("表: ").append(tableName);
if (tableComment != null && !tableComment.isEmpty()) {
sb.append("(").append(tableComment).append(")");
}
sb.append("\n字段:\n");
for (ColumnInfo col : columns) {
sb.append(" - ").append(col.getColumnName())
.append(" (").append(col.getColumnType()).append(")");
if (!col.isNullable()) {
sb.append(" NOT NULL");
}
if (col.getColumnComment() != null && !col.getColumnComment().isEmpty()) {
sb.append(" // ").append(col.getColumnComment());
}
sb.append("\n");
}
return sb.toString();
}
}

View File

@@ -0,0 +1,31 @@
package org.ruoyi.agent.manager;
import org.springframework.beans.factory.annotation.Autowired;
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")
public class TableSchemaInitializer {
@Autowired(required = false)
private TableSchemaManager tableSchemaManager;
/**
* 应用启动完成后初始化
*/
@EventListener(ContextRefreshedEvent.class)
public void initializeOnStartup() {
if (tableSchemaManager != null) {
tableSchemaManager.initializeSchema();
}
}
}

View File

@@ -0,0 +1,257 @@
package org.ruoyi.agent.manager;
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.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import com.baomidou.dynamic.datasource.annotation.DS;
import lombok.extern.slf4j.Slf4j;
/**
* 表结构管理器
* 负责获取和缓存数据库表结构信息
*
* 特点:
* - 应用启动时自动初始化所有表结构
* - 使用内存缓存 (ConcurrentHashMap) 确保高性能
* - 支持按需刷新单个表的结构
* - 延迟初始化:失败时在首次查询时重试
*/
@Slf4j
@Component
@DS("agent")
public class TableSchemaManager {
@Autowired(required = false)
private DataSource agentDataSource;
@Value("${AGENT_ALLOWED_TABLES}")
private String allowedTables;
/**
* 表结构缓存 (表名 -> 表结构)
* 使用 ConcurrentHashMap 支持高并发访问
*/
private final Map<String, TableStructure> schemaCache = new ConcurrentHashMap<>();
/**
* 缓存初始化标志
*/
private volatile boolean initialized = false;
/**
* 初始化表结构缓存
* Spring 会自动在 Bean 创建后调用此方法
*/
public void initializeSchema() {
if (agentDataSource == null) {
log.warn("Agent datasource not configured, schema initialization skipped");
return;
}
synchronized (this) {
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);
}
}
}
/**
* 加载所有允许的表的结构信息
*/
private void loadAllowedTableSchemas() {
List<String> allowedTables = getAllowedTableNames();
for (String tableName : allowedTables) {
try {
TableStructure schema = loadTableSchema(tableName);
if (schema != null) {
schemaCache.put(tableName, schema);
log.debug("Loaded schema for table: {}", tableName);
}
} catch (Exception e) {
log.warn("Failed to load schema for table: {}", tableName, e);
}
}
}
/**
* 从数据库加载指定表的结构信息
*/
private TableStructure loadTableSchema(String tableName) throws SQLException {
if (!isValidIdentifier(tableName) || !isTableAllowed(tableName)) {
return null;
}
TableStructure table = new TableStructure();
table.setTableName(tableName);
try (Connection conn = agentDataSource.getConnection()) {
DatabaseMetaData metaData = conn.getMetaData();
// 获取表注释
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"));
}
}
// 获取列信息
List<ColumnInfo> columns = new ArrayList<>();
try (ResultSet colRs = metaData.getColumns(conn.getCatalog(), null, tableName, null)) {
while (colRs.next()) {
ColumnInfo col = new ColumnInfo();
col.setColumnName(colRs.getString("COLUMN_NAME"));
col.setColumnType(colRs.getString("TYPE_NAME"));
int columnSize = colRs.getInt("COLUMN_SIZE");
if (columnSize > 0 && !isNumericType(col.getColumnType())) {
col.setColumnType(col.getColumnType() + "(" + columnSize + ")");
}
col.setNullable("YES".equalsIgnoreCase(colRs.getString("IS_NULLABLE")));
col.setDefaultValue(colRs.getString("COLUMN_DEF"));
col.setColumnComment(colRs.getString("REMARKS"));
col.setAutoIncrement("YES".equalsIgnoreCase(colRs.getString("IS_AUTOINCREMENT")));
columns.add(col);
}
}
// 获取主键信息
try (ResultSet pkRs = metaData.getPrimaryKeys(conn.getCatalog(), null, tableName)) {
if (pkRs.next()) {
String pkName = pkRs.getString("COLUMN_NAME");
table.setPrimaryKey(pkName);
for (ColumnInfo col : columns) {
if (col.getColumnName().equals(pkName)) {
col.setPrimaryKey(true);
break;
}
}
}
}
// 获取索引信息
try (ResultSet indexRs = metaData.getIndexInfo(conn.getCatalog(), null, tableName, false, false)) {
Set<String> indexedColumns = new HashSet<>();
while (indexRs.next()) {
String colName = indexRs.getString("COLUMN_NAME");
if (colName != null) {
indexedColumns.add(colName);
}
}
for (ColumnInfo col : columns) {
if (indexedColumns.contains(col.getColumnName())) {
col.setIndexed(true);
}
}
}
table.setColumns(columns);
}
return table;
}
/**
* 获取所有允许的表的结构信息
*/
public List<TableStructure> getAllowedTableSchemas() {
if (!initialized) {
initializeSchema();
}
List<String> allowedTables = getAllowedTableNames();
return allowedTables.stream().map(schemaCache::get).filter(Objects::nonNull).collect(Collectors.toList());
}
/**
* 获取所有允许的表名
*/
public List<String> getAllowedTableNames() {
if (allowedTables == null || allowedTables.trim().isEmpty()) {
log.warn("AGENT_ALLOWED_TABLES not configured");
return new ArrayList<>();
}
return Arrays.stream(allowedTables.split(","))
.map(String::trim)
.filter(s -> !s.isEmpty())
.collect(Collectors.toList());
}
/**
* 刷新指定表的结构信息
*/
public TableStructure refreshTableSchema(String tableName) throws SQLException {
if (!isValidIdentifier(tableName) || !isTableAllowed(tableName)) {
return null;
}
TableStructure schema = loadTableSchema(tableName);
if (schema != null) {
schemaCache.put(tableName, schema);
}
return schema;
}
/**
* 验证是否为有效的 SQL 标识符
*/
private boolean isValidIdentifier(String identifier) {
if (identifier == null || identifier.isEmpty()) {
return false;
}
return identifier.matches("^[a-zA-Z0-9_\\.]+$");
}
/**
* 检查表是否在允许列表中
*/
private boolean isTableAllowed(String tableName) {
// String allowedTables = System.getenv("AGENT_ALLOWED_TABLES");
if (allowedTables == null || allowedTables.trim().isEmpty()) {
return false;
}
Set<String> tables = new HashSet<>(Arrays.asList(allowedTables.split(",")));
return tables.contains(tableName.trim());
}
/**
* 判断是否为数值类型
*/
private boolean isNumericType(String typeName) {
String upper = typeName.toUpperCase();
return upper.contains("INT") || upper.contains("FLOAT") ||
upper.contains("DOUBLE") || upper.contains("DECIMAL");
}
}

View File

@@ -0,0 +1,199 @@
package org.ruoyi.agent.tool;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.sql.DataSource;
import org.ruoyi.common.core.utils.SpringUtils;
import org.springframework.stereotype.Component;
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
import dev.langchain4j.agent.tool.Tool;
import lombok.extern.slf4j.Slf4j;
import org.ruoyi.mcp.service.core.BuiltinToolProvider;
/**
* 执行 SQL 查询的 Tool
* 执行指定的 SELECT SQL 查询并返回结果
*/
@Slf4j
@Component
public class ExecuteSqlQueryTool implements BuiltinToolProvider {
// 使用延迟初始化,避免在构造函数中调用 SpringUtils.getBean()
private DataSource getDataSource() {
return SpringUtils.getBean(DataSource.class);
}
/**
* 执行 SELECT SQL 查询
* 只允许执行 SELECT 查询,防止恶意的数据修改操作
*
* @param sql 要执行的 SELECT SQL 语句例如SELECT * FROM sys_user
* @return 包含查询结果的字符串
*/
@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";
}
// 只允许执行 SELECT 查询,防止恶意操作
String upperSql = sql.trim().toUpperCase();
if (!upperSql.startsWith("SELECT")) {
return "Error: Only SELECT queries are allowed for security reasons";
}
try {
DataSource dataSource = getDataSource();
if (dataSource == null) {
return "Error: Database datasource not configured";
}
try (Connection connection = dataSource.getConnection()) {
try (PreparedStatement preparedStatement = connection.prepareStatement(sql);
ResultSet resultSet = preparedStatement.executeQuery()) {
ResultSetMetaData metaData = resultSet.getMetaData();
List<Map<String, Object>> results = new ArrayList<>();
int columnCount = metaData.getColumnCount();
// 获取列名
List<String> columnNames = new ArrayList<>();
for (int i = 1; i <= columnCount; i++) {
columnNames.add(metaData.getColumnName(i));
}
// 获取数据行限制最多1000行以防止内存溢出
int maxRows = 1000;
while (resultSet.next() && results.size() < maxRows) {
Map<String, Object> row = new LinkedHashMap<>();
for (int i = 1; i <= columnCount; i++) {
row.put(columnNames.get(i - 1), resultSet.getObject(i));
}
results.add(row);
}
return formatResults(results, columnNames);
}
}
} catch (Exception e) {
log.error("Error executing SQL: {}", sql, e);
// 3. 必须在 finally 中清除上下文,防止污染其他请求
DynamicDataSourceContextHolder.clear();
return "Error: " + e.getMessage();
} finally {
// 3. 必须在 finally 中清除上下文,防止污染其他请求
DynamicDataSourceContextHolder.clear();
}
}
/**
* 格式化查询结果
* 返回清晰的表格格式,展示关键数据
*/
private String formatResults(List<Map<String, Object>> results, List<String> columnNames) {
if (results.isEmpty()) {
return "Query executed successfully, but no results returned";
}
StringBuilder result = new StringBuilder();
// 限制显示的列数和行数,避免输出过大
int displayCols = Math.min(columnNames.size(), 8); // 最多显示8列
int displayRows = Math.min(results.size(), 10); // 最多显示10行
List<String> displayColumns = columnNames.subList(0, displayCols);
// 构建表头
result.append("| ");
for (String col : displayColumns) {
result.append(formatColumnName(col)).append(" | ");
}
result.append("\n");
// 构建分隔线
result.append("|");
for (int i = 0; i < displayCols; i++) {
result.append(" --- |");
}
result.append("\n");
// 构建数据行
for (int i = 0; i < displayRows; i++) {
result.append("| ");
Map<String, Object> row = results.get(i);
for (String column : displayColumns) {
Object value = row.get(column);
String displayValue = formatValue(value);
result.append(displayValue).append(" | ");
}
result.append("\n");
}
// 统计信息
result.append("\n").append("Total: ").append(results.size()).append(" rows");
if (displayRows < results.size()) {
result.append(" (displayed ").append(displayRows).append(" rows)");
}
if (displayCols < columnNames.size()) {
result.append("\nColumns: ").append(displayCols).append(" / ").append(columnNames.size());
}
log.info("Successfully executed SQL query, returned {} rows", results.size());
return result.toString();
}
/**
* 格式化列名,使其更易读
*/
private String formatColumnName(String columnName) {
// 将下划线替换为空格,首字母大写
String formatted = columnName.replace("_", " ");
if (formatted.length() > 15) {
return formatted.substring(0, 12) + "...";
}
return formatted;
}
/**
* 格式化单个值,适合表格显示
*/
private String formatValue(Object value) {
if (value == null) {
return "-";
}
String str = value.toString();
// 限制列宽以保持表格整洁,长文本截断
if (str.length() > 20) {
return str.substring(0, 17) + "...";
}
return str;
}
@Override
public String getToolName() {
return "execute_sql_query";
}
@Override
public String getDisplayName() {
return "执行SQL查询";
}
@Override
public String getDescription() {
return "Execute a SELECT SQL query and return the results. Example: SELECT * FROM sys_user";
}
}

View File

@@ -0,0 +1,81 @@
package org.ruoyi.agent.tool;
import java.util.List;
import org.ruoyi.agent.domain.TableStructure;
import org.ruoyi.agent.manager.TableSchemaManager;
import org.ruoyi.common.core.utils.SpringUtils;
import org.springframework.stereotype.Component;
import dev.langchain4j.agent.tool.Tool;
import lombok.extern.slf4j.Slf4j;
import org.ruoyi.mcp.service.core.BuiltinToolProvider;
/**
* 查询数据库所有表的 Tool
* 获取指定数据库中所有表的列表
*/
@Slf4j
@Component
public class QueryAllTablesTool implements BuiltinToolProvider {
// 使用延迟初始化,避免在构造函数中调用 SpringUtils.getBean()
private TableSchemaManager getTableSchemaManager() {
return SpringUtils.getBean(TableSchemaManager.class);
}
/**
* 查询数据库中所有表
* 返回数据库中存在的所有表的列表
*
* @return 包含所有表信息的结果
*/
@Tool("Query all tables in the database and return table names and basic information")
public String queryAllTables() {
try {
// 1. 从管理器获取所有允许的表结构信息(内部已包含初始化/缓存逻辑)
List<TableStructure> tableSchemas = getTableSchemaManager().getAllowedTableSchemas();
if (tableSchemas == null || tableSchemas.isEmpty()) {
return "No tables found in database or cache is empty.";
}
// 2. 格式化结果
StringBuilder result = new StringBuilder();
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 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();
}
}
@Override
public String getToolName() {
return "query_all_tables";
}
@Override
public String getDisplayName() {
return "查询所有表";
}
@Override
public String getDescription() {
return "Query all tables in the database and return table names and basic information";
}
}

View File

@@ -0,0 +1,75 @@
package org.ruoyi.agent.tool;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import javax.sql.DataSource;
import org.ruoyi.common.core.utils.SpringUtils;
import org.springframework.stereotype.Component;
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
import dev.langchain4j.agent.tool.Tool;
import lombok.extern.slf4j.Slf4j;
import org.ruoyi.mcp.service.core.BuiltinToolProvider;
@Component
@Slf4j
public class QueryTableSchemaTool implements BuiltinToolProvider {
// 使用延迟初始化,避免在构造函数中调用 SpringUtils.getBean()
private DataSource getDataSource() {
return SpringUtils.getBean(DataSource.class);
}
@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";
}
if (!tableName.matches("^[a-zA-Z0-9_]+$")) {
return "Error: Invalid table name format";
}
String sql = "SHOW CREATE TABLE `" + tableName + "`";
try (Connection connection = getDataSource().getConnection();
PreparedStatement ps = connection.prepareStatement(sql);
ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
return rs.getString("Create Table");
}
return "Table not found: " + tableName;
} catch (Exception e) {
// 3. 必须在 finally 中清除上下文,防止污染其他请求
DynamicDataSourceContextHolder.clear();
log.error("Error querying table schema: {}", tableName, e);
return "Error: " + e.getMessage();
} finally {
// 3. 必须在 finally 中清除上下文,防止污染其他请求
DynamicDataSourceContextHolder.clear();
}
}
@Override
public String getToolName() {
return "query_table_schema";
}
@Override
public String getDisplayName() {
return "查询表结构";
}
@Override
public String getDescription() {
return "Query the CREATE TABLE statement (DDL) for a specific table by table name";
}
}

View File

@@ -0,0 +1,21 @@
package org.ruoyi.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Data
@Component
@ConfigurationProperties(prefix = "mcp.sse")
public class McpSseConfig {
/**
* mcp对外暴露的端点地址
*/
private String url;
/**
* 是否开启
*/
private boolean enabled;
}

View File

@@ -0,0 +1,62 @@
package org.ruoyi.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 向量库配置属性
*
* @author ageer
*/
@Data
@Component
@ConfigurationProperties(prefix = "vector-store")
public class VectorStoreProperties {
/**
* 向量库类型
*/
private String type;
/**
* Weaviate配置
*/
private Weaviate weaviate = new Weaviate();
/**
* Milvus配置
*/
private Milvus milvus = new Milvus();
@Data
public static class Weaviate {
/**
* 协议
*/
private String protocol;
/**
* 主机地址
*/
private String host;
/**
* 类名
*/
private String classname;
}
@Data
public static class Milvus {
/**
* 连接URL
*/
private String url;
/**
* 集合名称
*/
private String collectionname;
}
}

View File

@@ -0,0 +1,93 @@
package org.ruoyi.config.mcp;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.ruoyi.domain.entity.mcp.McpTool;
import org.ruoyi.enums.McpToolStatus;
import org.ruoyi.mapper.mcp.McpToolMapper;
import org.ruoyi.mcp.service.core.BuiltinToolDefinition;
import org.ruoyi.mcp.service.core.BuiltinToolRegistry;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
/**
* 系统工具初始化器
* 在应用启动时,将系统内置工具同步到数据库
* 这样可以统一管理所有工具,支持动态启用/禁用
*
* @author ruoyi team
*/
@Slf4j
@Component
@Order(999) // 确保在其他初始化器之后执行
@RequiredArgsConstructor
public class SystemToolInitializer implements ApplicationRunner {
private final McpToolMapper mcpToolMapper;
private final BuiltinToolRegistry builtinToolRegistry;
@Override
@Transactional
public void run(ApplicationArguments args) {
log.info("开始同步系统内置工具到数据库...");
int addedCount = 0;
int existingCount = 0;
for (BuiltinToolDefinition tool : builtinToolRegistry.getAllBuiltinTools()) {
try {
boolean added = syncBuiltinTool(tool);
if (added) {
addedCount++;
} else {
existingCount++;
}
} catch (Exception e) {
log.error("同步内置工具失败: {}", tool.name(), e);
}
}
log.info("系统内置工具同步完成: 新增 {} 个, 已存在 {} 个", addedCount, existingCount);
}
/**
* 同步单个内置工具到数据库
*
* @param tool 工具定义
* @return 是否新增true=新增, false=已存在)
*/
private boolean syncBuiltinTool(BuiltinToolDefinition tool) {
// 检查是否已存在
LambdaQueryWrapper<McpTool> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(McpTool::getName, tool.name())
.eq(McpTool::getType, BuiltinToolRegistry.TYPE_BUILTIN);
McpTool existing = mcpToolMapper.selectOne(wrapper);
if (existing != null) {
// 已存在,更新描述信息(保留状态不变)
if (!tool.description().equals(existing.getDescription())) {
existing.setDescription(tool.description());
mcpToolMapper.updateById(existing);
log.debug("更新内置工具描述: {}", tool.name());
}
return false;
}
// 新增
McpTool newTool = new McpTool();
newTool.setName(tool.name());
newTool.setDescription(tool.description());
newTool.setType(BuiltinToolRegistry.TYPE_BUILTIN);
newTool.setStatus(McpToolStatus.ENABLED.getValue()); // 默认启用
newTool.setConfigJson(null); // 内置工具不需要配置
mcpToolMapper.insert(newTool);
log.info("新增内置工具: {} ({})", tool.name(), tool.displayName());
return true;
}
}

View File

@@ -0,0 +1,96 @@
package org.ruoyi.constant;
public class FileTypeConstants {
public static final String TXT = "txt";
public static final String CSV = "csv";
public static final String MD = "md";
public static final String DOC = "doc";
public static final String DOCX = "docx";
public static final String PDF = "pdf";
public static final String XLS = "xls";
public static final String XLSX = "xlsx";
public static final String LOG = "log";
public static final String XML = "xml";
public static final String JAVA = "java";
public static final String HTML = "html";
public static final String HTM = "htm";
public static final String CSS = "css";
public static final String JS = "js";
public static final String PY = "py";
public static final String CPP = "cpp";
public static final String SQL = "sql";
public static final String PHP = "php";
public static final String RUBY = "ruby";
public static final String C = "c";
public static final String H = "h";
public static final String HPP = "hpp";
public static final String SWIFT = "swift";
public static final String TS = "ts";
public static final String RUST = "rs";
public static final String PERL = "perl";
public static final String SHELL = "shell";
public static final String BAT = "bat";
public static final String CMD = "cmd";
public static final String PROPERTIES = "properties";
public static final String INI = "ini";
public static final String YAML = "yaml";
public static final String YML = "yml";
public static boolean isTextFile(String type) {
if (type.equalsIgnoreCase(TXT) || type.equalsIgnoreCase(CSV) || type.equalsIgnoreCase(PROPERTIES)
|| type.equalsIgnoreCase(INI) || type.equalsIgnoreCase(YAML) || type.equalsIgnoreCase(YML)
|| type.equalsIgnoreCase(LOG) || type.equalsIgnoreCase(XML)) {
return true;
} else {
return false;
}
}
public static boolean isCodeFile(String type) {
if (type.equalsIgnoreCase(JAVA) || type.equalsIgnoreCase(HTML) || type.equalsIgnoreCase(HTM) || type.equalsIgnoreCase(JS) || type.equalsIgnoreCase(PY)
|| type.equalsIgnoreCase(CPP) || type.equalsIgnoreCase(SQL) || type.equalsIgnoreCase(PHP) || type.equalsIgnoreCase(RUBY)
|| type.equalsIgnoreCase(C) || type.equalsIgnoreCase(H) || type.equalsIgnoreCase(HPP) || type.equalsIgnoreCase(SWIFT)
|| type.equalsIgnoreCase(TS) || type.equalsIgnoreCase(RUST) || type.equalsIgnoreCase(PERL) || type.equalsIgnoreCase(SHELL)
|| type.equalsIgnoreCase(BAT) || type.equalsIgnoreCase(CMD) || type.equalsIgnoreCase(CSS)) {
return true;
} else {
return false;
}
}
public static boolean isMdFile(String type) {
if (type.equalsIgnoreCase(MD)) {
return true;
} else {
return false;
}
}
public static boolean isWord(String type) {
if (type.equalsIgnoreCase(DOC) || type.equalsIgnoreCase(DOCX)) {
return true;
} else {
return false;
}
}
public static boolean isPdf(String type) {
if (type.equalsIgnoreCase(PDF)) {
return true;
} else {
return false;
}
}
public static boolean isExcel(String type) {
if (type.equalsIgnoreCase(XLS) || type.equalsIgnoreCase(XLSX)) {
return true;
} else {
return false;
}
}
}

View File

@@ -0,0 +1,37 @@
package org.ruoyi.controller.chat;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.ruoyi.common.chat.domain.dto.request.ChatRequest;
import org.ruoyi.service.chat.impl.ChatServiceFacade;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
/**
* 聊天管理
*
* @author ageerle@163.com
* @date 2023-03-01
*/
@Controller
@Slf4j
@RequiredArgsConstructor
@RequestMapping("/chat")
public class ChatController {
private final ChatServiceFacade chatService;
/**
* 聊天接口
*/
@PostMapping("/send")
@ResponseBody
public SseEmitter sseChat(@RequestBody @Valid ChatRequest chatRequest) {
return chatService.sseChat(chatRequest);
}
}

View File

@@ -0,0 +1,105 @@
package org.ruoyi.controller.chat;
import java.util.List;
import lombok.RequiredArgsConstructor;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.*;
import cn.dev33.satoken.annotation.SaCheckPermission;
import org.ruoyi.common.chat.domain.bo.chat.ChatMessageBo;
import org.ruoyi.common.chat.domain.vo.chat.ChatMessageVo;
import org.ruoyi.service.chat.IChatMessageService;
import org.springframework.web.bind.annotation.*;
import org.springframework.validation.annotation.Validated;
import org.ruoyi.common.idempotent.annotation.RepeatSubmit;
import org.ruoyi.common.log.annotation.Log;
import org.ruoyi.common.web.core.BaseController;
import org.ruoyi.common.mybatis.core.page.PageQuery;
import org.ruoyi.common.core.domain.R;
import org.ruoyi.common.core.validate.AddGroup;
import org.ruoyi.common.core.validate.EditGroup;
import org.ruoyi.common.log.enums.BusinessType;
import org.ruoyi.common.excel.utils.ExcelUtil;
import org.ruoyi.common.mybatis.core.page.TableDataInfo;
/**
* 聊天消息
*
* @author ageerle
* @date 2025-12-14
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/system/message")
public class ChatMessageController extends BaseController {
private final IChatMessageService chatMessageService;
/**
* 查询聊天消息列表
*/
@SaCheckPermission("system:message:list")
@GetMapping("/list")
public TableDataInfo<ChatMessageVo> list(ChatMessageBo bo, PageQuery pageQuery) {
return chatMessageService.queryPageList(bo, pageQuery);
}
/**
* 导出聊天消息列表
*/
@SaCheckPermission("system:message:export")
@Log(title = "聊天消息", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(ChatMessageBo bo, HttpServletResponse response) {
List<ChatMessageVo> list = chatMessageService.queryList(bo);
ExcelUtil.exportExcel(list, "聊天消息", ChatMessageVo.class, response);
}
/**
* 获取聊天消息详细信息
*
* @param id 主键
*/
@SaCheckPermission("system:message:query")
@GetMapping("/{id}")
public R<ChatMessageVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long id) {
return R.ok(chatMessageService.queryById(id));
}
/**
* 新增聊天消息
*/
@SaCheckPermission("system:message:add")
@Log(title = "聊天消息", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody ChatMessageBo bo) {
return toAjax(chatMessageService.insertByBo(bo));
}
/**
* 修改聊天消息
*/
@SaCheckPermission("system:message:edit")
@Log(title = "聊天消息", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody ChatMessageBo bo) {
return toAjax(chatMessageService.updateByBo(bo));
}
/**
* 删除聊天消息
*
* @param ids 主键串
*/
@SaCheckPermission("system:message:remove")
@Log(title = "聊天消息", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ids) {
return toAjax(chatMessageService.deleteWithValidByIds(List.of(ids), true));
}
}

View File

@@ -0,0 +1,115 @@
package org.ruoyi.controller.chat;
import java.util.List;
import lombok.RequiredArgsConstructor;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.*;
import cn.dev33.satoken.annotation.SaCheckPermission;
import org.ruoyi.common.chat.service.chat.IChatModelService;
import org.ruoyi.common.chat.domain.bo.chat.ChatModelBo;
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
import org.ruoyi.enums.ModelType;
import org.springframework.web.bind.annotation.*;
import org.springframework.validation.annotation.Validated;
import org.ruoyi.common.idempotent.annotation.RepeatSubmit;
import org.ruoyi.common.log.annotation.Log;
import org.ruoyi.common.web.core.BaseController;
import org.ruoyi.common.mybatis.core.page.PageQuery;
import org.ruoyi.common.core.domain.R;
import org.ruoyi.common.core.validate.AddGroup;
import org.ruoyi.common.core.validate.EditGroup;
import org.ruoyi.common.log.enums.BusinessType;
import org.ruoyi.common.excel.utils.ExcelUtil;
import org.ruoyi.common.mybatis.core.page.TableDataInfo;
/**
* 模型管理
*
* @author ageerle
* @date 2025-12-14
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/system/model")
public class ChatModelController extends BaseController {
private final IChatModelService chatModelService;
/**
* 查询模型管理列表
*/
@SaCheckPermission("system:model:list")
@GetMapping("/list")
public TableDataInfo<ChatModelVo> list(ChatModelBo bo, PageQuery pageQuery) {
return chatModelService.queryPageList(bo, pageQuery);
}
/**
* 查询用户聊天模型列表
*/
@GetMapping("/modelList")
public R<List<ChatModelVo>> modelList(ChatModelBo bo) {
bo.setCategory(ModelType.CHAT.getKey());
return R.ok(chatModelService.queryList(bo));
}
/**
* 导出模型管理列表
*/
@SaCheckPermission("system:model:export")
@Log(title = "模型管理", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(ChatModelBo bo, HttpServletResponse response) {
List<ChatModelVo> list = chatModelService.queryList(bo);
ExcelUtil.exportExcel(list, "模型管理", ChatModelVo.class, response);
}
/**
* 获取模型管理详细信息
*
* @param id 主键
*/
@SaCheckPermission("system:model:query")
@GetMapping("/{id}")
public R<ChatModelVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long id) {
return R.ok(chatModelService.queryById(id));
}
/**
* 新增模型管理
*/
@SaCheckPermission("system:model:add")
@Log(title = "模型管理", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody ChatModelBo bo) {
return toAjax(chatModelService.insertByBo(bo));
}
/**
* 修改模型管理
*/
@SaCheckPermission("system:model:edit")
@Log(title = "模型管理", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody ChatModelBo bo) {
return toAjax(chatModelService.updateByBo(bo));
}
/**
* 删除模型管理
*
* @param ids 主键串
*/
@SaCheckPermission("system:model:remove")
@Log(title = "模型管理", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ids) {
return toAjax(chatModelService.deleteWithValidByIds(List.of(ids), true));
}
}

View File

@@ -0,0 +1,105 @@
package org.ruoyi.controller.chat;
import java.util.List;
import lombok.RequiredArgsConstructor;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.*;
import cn.dev33.satoken.annotation.SaCheckPermission;
import org.ruoyi.service.chat.IChatProviderService;
import org.springframework.web.bind.annotation.*;
import org.springframework.validation.annotation.Validated;
import org.ruoyi.common.idempotent.annotation.RepeatSubmit;
import org.ruoyi.common.log.annotation.Log;
import org.ruoyi.common.web.core.BaseController;
import org.ruoyi.common.mybatis.core.page.PageQuery;
import org.ruoyi.common.core.domain.R;
import org.ruoyi.common.core.validate.AddGroup;
import org.ruoyi.common.core.validate.EditGroup;
import org.ruoyi.common.log.enums.BusinessType;
import org.ruoyi.common.excel.utils.ExcelUtil;
import org.ruoyi.domain.vo.chat.ChatProviderVo;
import org.ruoyi.domain.bo.chat.ChatProviderBo;
import org.ruoyi.common.mybatis.core.page.TableDataInfo;
/**
* 厂商管理
*
* @author ageerle
* @date 2025-12-14
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/system/provider")
public class ChatProviderController extends BaseController {
private final IChatProviderService chatProviderService;
/**
* 查询厂商管理列表
*/
@SaCheckPermission("system:provider:list")
@GetMapping("/list")
public TableDataInfo<ChatProviderVo> list(ChatProviderBo bo, PageQuery pageQuery) {
return chatProviderService.queryPageList(bo, pageQuery);
}
/**
* 导出厂商管理列表
*/
@SaCheckPermission("system:provider:export")
@Log(title = "厂商管理", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(ChatProviderBo bo, HttpServletResponse response) {
List<ChatProviderVo> list = chatProviderService.queryList(bo);
ExcelUtil.exportExcel(list, "厂商管理", ChatProviderVo.class, response);
}
/**
* 获取厂商管理详细信息
*
* @param id 主键
*/
@SaCheckPermission("system:provider:query")
@GetMapping("/{id}")
public R<ChatProviderVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long id) {
return R.ok(chatProviderService.queryById(id));
}
/**
* 新增厂商管理
*/
@SaCheckPermission("system:provider:add")
@Log(title = "厂商管理", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody ChatProviderBo bo) {
return toAjax(chatProviderService.insertByBo(bo));
}
/**
* 修改厂商管理
*/
@SaCheckPermission("system:provider:edit")
@Log(title = "厂商管理", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody ChatProviderBo bo) {
return toAjax(chatProviderService.updateByBo(bo));
}
/**
* 删除厂商管理
*
* @param ids 主键串
*/
@SaCheckPermission("system:provider:remove")
@Log(title = "厂商管理", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ids) {
return toAjax(chatProviderService.deleteWithValidByIds(List.of(ids), true));
}
}

View File

@@ -0,0 +1,115 @@
package org.ruoyi.controller.chat;
import java.util.List;
import lombok.RequiredArgsConstructor;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.*;
import cn.dev33.satoken.annotation.SaCheckPermission;
import org.ruoyi.common.satoken.utils.LoginHelper;
import org.ruoyi.service.chat.IChatSessionService;
import org.springframework.web.bind.annotation.*;
import org.springframework.validation.annotation.Validated;
import org.ruoyi.common.idempotent.annotation.RepeatSubmit;
import org.ruoyi.common.log.annotation.Log;
import org.ruoyi.common.web.core.BaseController;
import org.ruoyi.common.mybatis.core.page.PageQuery;
import org.ruoyi.common.core.domain.R;
import org.ruoyi.common.core.validate.AddGroup;
import org.ruoyi.common.core.validate.EditGroup;
import org.ruoyi.common.log.enums.BusinessType;
import org.ruoyi.common.excel.utils.ExcelUtil;
import org.ruoyi.domain.vo.chat.ChatSessionVo;
import org.ruoyi.domain.bo.chat.ChatSessionBo;
import org.ruoyi.common.mybatis.core.page.TableDataInfo;
/**
* 会话管理
*
* @author ageerle
* @date 2025-12-30
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/system/session")
public class ChatSessionController extends BaseController {
private final IChatSessionService chatSessionService;
/**
* 查询会话管理列表
*/
@SaCheckPermission("system:session:list")
@GetMapping("/list")
public TableDataInfo<ChatSessionVo> list(ChatSessionBo bo, PageQuery pageQuery) {
if(!LoginHelper.isLogin()){
// 如果用户没有登录,返回空会话列表
return TableDataInfo.build();
}
// 默认查询当前用户会话
bo.setUserId(LoginHelper.getUserId());
return chatSessionService.queryPageList(bo, pageQuery);
}
/**
* 导出会话管理列表
*/
@SaCheckPermission("system:session:export")
@Log(title = "会话管理", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(ChatSessionBo bo, HttpServletResponse response) {
List<ChatSessionVo> list = chatSessionService.queryList(bo);
ExcelUtil.exportExcel(list, "会话管理", ChatSessionVo.class, response);
}
/**
* 获取会话管理详细信息
*
* @param id 主键
*/
@SaCheckPermission("system:session:query")
@GetMapping("/{id}")
public R<ChatSessionVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long id) {
return R.ok(chatSessionService.queryById(id));
}
/**
* 新增会话管理
*/
@SaCheckPermission("system:session:add")
@Log(title = "会话管理", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Long> add(@Validated(AddGroup.class) @RequestBody ChatSessionBo bo) {
bo.setUserId(LoginHelper.getUserId());
chatSessionService.insertByBo(bo);
// 返回会话id
return R.ok(bo.getId());
}
/**
* 修改会话管理
*/
@SaCheckPermission("system:session:edit")
@Log(title = "会话管理", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody ChatSessionBo bo) {
return toAjax(chatSessionService.updateByBo(bo));
}
/**
* 删除会话管理
*
* @param ids 主键串
*/
@SaCheckPermission("system:session:remove")
@Log(title = "会话管理", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ids) {
return toAjax(chatSessionService.deleteWithValidByIds(List.of(ids), true));
}
}

View File

@@ -0,0 +1,115 @@
package org.ruoyi.controller.knowledge;
import java.util.List;
import lombok.RequiredArgsConstructor;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.*;
import cn.dev33.satoken.annotation.SaCheckPermission;
import org.ruoyi.domain.bo.knowledge.KnowledgeAttachBo;
import org.ruoyi.domain.bo.knowledge.KnowledgeInfoUploadBo;
import org.ruoyi.domain.vo.knowledge.KnowledgeAttachVo;
import org.ruoyi.service.knowledge.IKnowledgeAttachService;
import org.springframework.web.bind.annotation.*;
import org.springframework.validation.annotation.Validated;
import org.ruoyi.common.idempotent.annotation.RepeatSubmit;
import org.ruoyi.common.log.annotation.Log;
import org.ruoyi.common.web.core.BaseController;
import org.ruoyi.common.mybatis.core.page.PageQuery;
import org.ruoyi.common.core.domain.R;
import org.ruoyi.common.core.validate.AddGroup;
import org.ruoyi.common.core.validate.EditGroup;
import org.ruoyi.common.log.enums.BusinessType;
import org.ruoyi.common.excel.utils.ExcelUtil;
import org.ruoyi.common.mybatis.core.page.TableDataInfo;
/**
* 知识库附件
*
* @author ageerle
* @date 2025-12-17
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/system/attach")
public class KnowledgeAttachController extends BaseController {
private final IKnowledgeAttachService knowledgeAttachService;
/**
* 查询知识库附件列表
*/
@SaCheckPermission("system:attach:list")
@GetMapping("/list")
public TableDataInfo<KnowledgeAttachVo> list(KnowledgeAttachBo bo, PageQuery pageQuery) {
return knowledgeAttachService.queryPageList(bo, pageQuery);
}
/**
* 导出知识库附件列表
*/
@SaCheckPermission("system:attach:export")
@Log(title = "知识库附件", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(KnowledgeAttachBo bo, HttpServletResponse response) {
List<KnowledgeAttachVo> list = knowledgeAttachService.queryList(bo);
ExcelUtil.exportExcel(list, "知识库附件", KnowledgeAttachVo.class, response);
}
/**
* 获取知识库附件详细信息
*
* @param id 主键
*/
@SaCheckPermission("system:attach:query")
@GetMapping("/{id}")
public R<KnowledgeAttachVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long id) {
return R.ok(knowledgeAttachService.queryById(id));
}
/**
* 新增知识库附件
*/
@SaCheckPermission("system:attach:add")
@Log(title = "知识库附件", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody KnowledgeAttachBo bo) {
return toAjax(knowledgeAttachService.insertByBo(bo));
}
/**
* 修改知识库附件
*/
@SaCheckPermission("system:attach:edit")
@Log(title = "知识库附件", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody KnowledgeAttachBo bo) {
return toAjax(knowledgeAttachService.updateByBo(bo));
}
/**
* 删除知识库附件
*
* @param ids 主键串
*/
@SaCheckPermission("system:attach:remove")
@Log(title = "知识库附件", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ids) {
return toAjax(knowledgeAttachService.deleteWithValidByIds(List.of(ids), true));
}
/**
* 上传知识库附件
*/
@PostMapping(value = "/upload")
public R<String> upload(KnowledgeInfoUploadBo bo){
knowledgeAttachService.upload(bo);
return R.ok("上传知识库附件成功!");
}
}

View File

@@ -0,0 +1,105 @@
package org.ruoyi.controller.knowledge;
import java.util.List;
import lombok.RequiredArgsConstructor;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.*;
import cn.dev33.satoken.annotation.SaCheckPermission;
import org.ruoyi.domain.bo.knowledge.KnowledgeFragmentBo;
import org.ruoyi.domain.vo.knowledge.KnowledgeFragmentVo;
import org.ruoyi.service.knowledge.IKnowledgeFragmentService;
import org.springframework.web.bind.annotation.*;
import org.springframework.validation.annotation.Validated;
import org.ruoyi.common.idempotent.annotation.RepeatSubmit;
import org.ruoyi.common.log.annotation.Log;
import org.ruoyi.common.web.core.BaseController;
import org.ruoyi.common.mybatis.core.page.PageQuery;
import org.ruoyi.common.core.domain.R;
import org.ruoyi.common.core.validate.AddGroup;
import org.ruoyi.common.core.validate.EditGroup;
import org.ruoyi.common.log.enums.BusinessType;
import org.ruoyi.common.excel.utils.ExcelUtil;
import org.ruoyi.common.mybatis.core.page.TableDataInfo;
/**
* 知识片段
*
* @author ageerle
* @date 2025-12-17
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/system/fragment")
public class KnowledgeFragmentController extends BaseController {
private final IKnowledgeFragmentService knowledgeFragmentService;
/**
* 查询知识片段列表
*/
@SaCheckPermission("system:fragment:list")
@GetMapping("/list")
public TableDataInfo<KnowledgeFragmentVo> list(KnowledgeFragmentBo bo, PageQuery pageQuery) {
return knowledgeFragmentService.queryPageList(bo, pageQuery);
}
/**
* 导出知识片段列表
*/
@SaCheckPermission("system:fragment:export")
@Log(title = "知识片段", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(KnowledgeFragmentBo bo, HttpServletResponse response) {
List<KnowledgeFragmentVo> list = knowledgeFragmentService.queryList(bo);
ExcelUtil.exportExcel(list, "知识片段", KnowledgeFragmentVo.class, response);
}
/**
* 获取知识片段详细信息
*
* @param id 主键
*/
@SaCheckPermission("system:fragment:query")
@GetMapping("/{id}")
public R<KnowledgeFragmentVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long id) {
return R.ok(knowledgeFragmentService.queryById(id));
}
/**
* 新增知识片段
*/
@SaCheckPermission("system:fragment:add")
@Log(title = "知识片段", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody KnowledgeFragmentBo bo) {
return toAjax(knowledgeFragmentService.insertByBo(bo));
}
/**
* 修改知识片段
*/
@SaCheckPermission("system:fragment:edit")
@Log(title = "知识片段", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody KnowledgeFragmentBo bo) {
return toAjax(knowledgeFragmentService.updateByBo(bo));
}
/**
* 删除知识片段
*
* @param ids 主键串
*/
@SaCheckPermission("system:fragment:remove")
@Log(title = "知识片段", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ids) {
return toAjax(knowledgeFragmentService.deleteWithValidByIds(List.of(ids), true));
}
}

View File

@@ -0,0 +1,105 @@
package org.ruoyi.controller.knowledge;
import java.util.List;
import lombok.RequiredArgsConstructor;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.*;
import cn.dev33.satoken.annotation.SaCheckPermission;
import org.ruoyi.domain.bo.knowledge.KnowledgeGraphInstanceBo;
import org.ruoyi.domain.vo.knowledge.KnowledgeGraphInstanceVo;
import org.ruoyi.service.knowledge.IKnowledgeGraphInstanceService;
import org.springframework.web.bind.annotation.*;
import org.springframework.validation.annotation.Validated;
import org.ruoyi.common.idempotent.annotation.RepeatSubmit;
import org.ruoyi.common.log.annotation.Log;
import org.ruoyi.common.web.core.BaseController;
import org.ruoyi.common.mybatis.core.page.PageQuery;
import org.ruoyi.common.core.domain.R;
import org.ruoyi.common.core.validate.AddGroup;
import org.ruoyi.common.core.validate.EditGroup;
import org.ruoyi.common.log.enums.BusinessType;
import org.ruoyi.common.excel.utils.ExcelUtil;
import org.ruoyi.common.mybatis.core.page.TableDataInfo;
/**
* 知识图谱实例
*
* @author ageerle
* @date 2025-12-17
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/system/graphInstance")
public class KnowledgeGraphInstanceController extends BaseController {
private final IKnowledgeGraphInstanceService knowledgeGraphInstanceService;
/**
* 查询知识图谱实例列表
*/
@SaCheckPermission("system:graphInstance:list")
@GetMapping("/list")
public TableDataInfo<KnowledgeGraphInstanceVo> list(KnowledgeGraphInstanceBo bo, PageQuery pageQuery) {
return knowledgeGraphInstanceService.queryPageList(bo, pageQuery);
}
/**
* 导出知识图谱实例列表
*/
@SaCheckPermission("system:graphInstance:export")
@Log(title = "知识图谱实例", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(KnowledgeGraphInstanceBo bo, HttpServletResponse response) {
List<KnowledgeGraphInstanceVo> list = knowledgeGraphInstanceService.queryList(bo);
ExcelUtil.exportExcel(list, "知识图谱实例", KnowledgeGraphInstanceVo.class, response);
}
/**
* 获取知识图谱实例详细信息
*
* @param id 主键
*/
@SaCheckPermission("system:graphInstance:query")
@GetMapping("/{id}")
public R<KnowledgeGraphInstanceVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long id) {
return R.ok(knowledgeGraphInstanceService.queryById(id));
}
/**
* 新增知识图谱实例
*/
@SaCheckPermission("system:graphInstance:add")
@Log(title = "知识图谱实例", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody KnowledgeGraphInstanceBo bo) {
return toAjax(knowledgeGraphInstanceService.insertByBo(bo));
}
/**
* 修改知识图谱实例
*/
@SaCheckPermission("system:graphInstance:edit")
@Log(title = "知识图谱实例", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody KnowledgeGraphInstanceBo bo) {
return toAjax(knowledgeGraphInstanceService.updateByBo(bo));
}
/**
* 删除知识图谱实例
*
* @param ids 主键串
*/
@SaCheckPermission("system:graphInstance:remove")
@Log(title = "知识图谱实例", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ids) {
return toAjax(knowledgeGraphInstanceService.deleteWithValidByIds(List.of(ids), true));
}
}

View File

@@ -0,0 +1,105 @@
package org.ruoyi.controller.knowledge;
import java.util.List;
import lombok.RequiredArgsConstructor;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.*;
import cn.dev33.satoken.annotation.SaCheckPermission;
import org.ruoyi.domain.bo.knowledge.KnowledgeGraphSegmentBo;
import org.ruoyi.domain.vo.knowledge.KnowledgeGraphSegmentVo;
import org.ruoyi.service.knowledge.IKnowledgeGraphSegmentService;
import org.springframework.web.bind.annotation.*;
import org.springframework.validation.annotation.Validated;
import org.ruoyi.common.idempotent.annotation.RepeatSubmit;
import org.ruoyi.common.log.annotation.Log;
import org.ruoyi.common.web.core.BaseController;
import org.ruoyi.common.mybatis.core.page.PageQuery;
import org.ruoyi.common.core.domain.R;
import org.ruoyi.common.core.validate.AddGroup;
import org.ruoyi.common.core.validate.EditGroup;
import org.ruoyi.common.log.enums.BusinessType;
import org.ruoyi.common.excel.utils.ExcelUtil;
import org.ruoyi.common.mybatis.core.page.TableDataInfo;
/**
* 知识图谱片段
*
* @author ageerle
* @date 2025-12-17
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/system/graphSegment")
public class KnowledgeGraphSegmentController extends BaseController {
private final IKnowledgeGraphSegmentService knowledgeGraphSegmentService;
/**
* 查询知识图谱片段列表
*/
@SaCheckPermission("system:graphSegment:list")
@GetMapping("/list")
public TableDataInfo<KnowledgeGraphSegmentVo> list(KnowledgeGraphSegmentBo bo, PageQuery pageQuery) {
return knowledgeGraphSegmentService.queryPageList(bo, pageQuery);
}
/**
* 导出知识图谱片段列表
*/
@SaCheckPermission("system:graphSegment:export")
@Log(title = "知识图谱片段", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(KnowledgeGraphSegmentBo bo, HttpServletResponse response) {
List<KnowledgeGraphSegmentVo> list = knowledgeGraphSegmentService.queryList(bo);
ExcelUtil.exportExcel(list, "知识图谱片段", KnowledgeGraphSegmentVo.class, response);
}
/**
* 获取知识图谱片段详细信息
*
* @param id 主键
*/
@SaCheckPermission("system:graphSegment:query")
@GetMapping("/{id}")
public R<KnowledgeGraphSegmentVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long id) {
return R.ok(knowledgeGraphSegmentService.queryById(id));
}
/**
* 新增知识图谱片段
*/
@SaCheckPermission("system:graphSegment:add")
@Log(title = "知识图谱片段", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody KnowledgeGraphSegmentBo bo) {
return toAjax(knowledgeGraphSegmentService.insertByBo(bo));
}
/**
* 修改知识图谱片段
*/
@SaCheckPermission("system:graphSegment:edit")
@Log(title = "知识图谱片段", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody KnowledgeGraphSegmentBo bo) {
return toAjax(knowledgeGraphSegmentService.updateByBo(bo));
}
/**
* 删除知识图谱片段
*
* @param ids 主键串
*/
@SaCheckPermission("system:graphSegment:remove")
@Log(title = "知识图谱片段", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ids) {
return toAjax(knowledgeGraphSegmentService.deleteWithValidByIds(List.of(ids), true));
}
}

View File

@@ -0,0 +1,107 @@
package org.ruoyi.controller.knowledge;
import java.util.List;
import lombok.RequiredArgsConstructor;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.*;
import cn.dev33.satoken.annotation.SaCheckPermission;
import org.ruoyi.common.satoken.utils.LoginHelper;
import org.ruoyi.domain.bo.knowledge.KnowledgeInfoBo;
import org.ruoyi.domain.vo.knowledge.KnowledgeInfoVo;
import org.ruoyi.service.knowledge.IKnowledgeInfoService;
import org.springframework.web.bind.annotation.*;
import org.springframework.validation.annotation.Validated;
import org.ruoyi.common.idempotent.annotation.RepeatSubmit;
import org.ruoyi.common.log.annotation.Log;
import org.ruoyi.common.web.core.BaseController;
import org.ruoyi.common.mybatis.core.page.PageQuery;
import org.ruoyi.common.core.domain.R;
import org.ruoyi.common.core.validate.AddGroup;
import org.ruoyi.common.core.validate.EditGroup;
import org.ruoyi.common.log.enums.BusinessType;
import org.ruoyi.common.excel.utils.ExcelUtil;
import org.ruoyi.common.mybatis.core.page.TableDataInfo;
/**
* 知识库
*
* @author ageerle
* @date 2025-12-17
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/system/info")
public class KnowledgeInfoController extends BaseController {
private final IKnowledgeInfoService knowledgeInfoService;
/**
* 查询知识库列表
*/
@SaCheckPermission("system:info:list")
@GetMapping("/list")
public TableDataInfo<KnowledgeInfoVo> list(KnowledgeInfoBo bo, PageQuery pageQuery) {
return knowledgeInfoService.queryPageList(bo, pageQuery);
}
/**
* 导出知识库列表
*/
@SaCheckPermission("system:info:export")
@Log(title = "知识库", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(KnowledgeInfoBo bo, HttpServletResponse response) {
List<KnowledgeInfoVo> list = knowledgeInfoService.queryList(bo);
ExcelUtil.exportExcel(list, "知识库", KnowledgeInfoVo.class, response);
}
/**
* 获取知识库详细信息
*
* @param id 主键
*/
@SaCheckPermission("system:info:query")
@GetMapping("/{id}")
public R<KnowledgeInfoVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long id) {
return R.ok(knowledgeInfoService.queryById(id));
}
/**
* 新增知识库
*/
@SaCheckPermission("system:info:add")
@Log(title = "知识库", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody KnowledgeInfoBo bo) {
bo.setUserId(LoginHelper.getUserId());
return toAjax(knowledgeInfoService.insertByBo(bo));
}
/**
* 修改知识库
*/
@SaCheckPermission("system:info:edit")
@Log(title = "知识库", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody KnowledgeInfoBo bo) {
return toAjax(knowledgeInfoService.updateByBo(bo));
}
/**
* 删除知识库
*
* @param ids 主键串
*/
@SaCheckPermission("system:info:remove")
@Log(title = "知识库", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] ids) {
return toAjax(knowledgeInfoService.deleteWithValidByIds(List.of(ids), true));
}
}

View File

@@ -0,0 +1,172 @@
package org.ruoyi.controller.mcp;
import cn.dev33.satoken.annotation.SaCheckPermission;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.ruoyi.common.core.domain.R;
import org.ruoyi.common.excel.utils.ExcelUtil;
import org.ruoyi.common.idempotent.annotation.RepeatSubmit;
import org.ruoyi.common.log.annotation.Log;
import org.ruoyi.common.log.enums.BusinessType;
import org.ruoyi.common.mybatis.core.page.PageQuery;
import org.ruoyi.common.mybatis.core.page.TableDataInfo;
import org.ruoyi.common.web.core.BaseController;
import org.ruoyi.domain.bo.mcp.McpMarketBo;
import org.ruoyi.domain.dto.mcp.McpMarketListResult;
import org.ruoyi.domain.dto.mcp.McpMarketRefreshResult;
import org.ruoyi.domain.dto.mcp.McpMarketToolListResult;
import org.ruoyi.domain.vo.mcp.McpMarketVo;
import org.ruoyi.service.mcp.IMcpMarketService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
/**
* MCP 市场管理 Controller
*
* @author ruoyi team
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/mcp/market")
public class McpMarketController extends BaseController {
private final IMcpMarketService mcpMarketService;
/**
* 查询市场列表
*/
@SaCheckPermission("mcp:market:list")
@GetMapping("/list")
public TableDataInfo<McpMarketVo> list(McpMarketBo bo, PageQuery pageQuery) {
return mcpMarketService.selectPageList(bo, pageQuery);
}
/**
* 查询市场列表(不分页)
*/
@SaCheckPermission("mcp:market:list")
@GetMapping("/all")
public McpMarketListResult listAll(
@RequestParam(required = false) String keyword,
@RequestParam(required = false) String status) {
return mcpMarketService.listMarkets(keyword, status);
}
/**
* 导出 MCP 市场列表
*/
@SaCheckPermission("mcp:market:export")
@Log(title = "MCP市场管理", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(McpMarketBo bo, HttpServletResponse response) {
List<McpMarketVo> list = mcpMarketService.queryList(bo);
ExcelUtil.exportExcel(list, "MCP市场", McpMarketVo.class, response);
}
/**
* 根据市场ID获取详细信息
*
* @param id 市场ID
*/
@SaCheckPermission("mcp:market:query")
@GetMapping("/{id}")
public R<McpMarketVo> getInfo(@PathVariable Long id) {
return R.ok(mcpMarketService.selectById(id));
}
/**
* 新增市场
*/
@SaCheckPermission("mcp:market:add")
@Log(title = "MCP市场管理", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping
public R<Void> add(@Validated @RequestBody McpMarketBo bo) {
mcpMarketService.insert(bo);
return R.ok();
}
/**
* 修改市场
*/
@SaCheckPermission("mcp:market:edit")
@Log(title = "MCP市场管理", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping
public R<Void> edit(@Validated @RequestBody McpMarketBo bo) {
mcpMarketService.update(bo);
return R.ok();
}
/**
* 删除市场
*
* @param ids 市场ID串
*/
@SaCheckPermission("mcp:market:remove")
@Log(title = "MCP市场管理", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public R<Void> remove(@PathVariable Long[] ids) {
mcpMarketService.deleteByIds(List.of(ids));
return R.ok();
}
/**
* 更新市场状态
*/
@SaCheckPermission("mcp:market:edit")
@Log(title = "MCP市场管理", businessType = BusinessType.UPDATE)
@PutMapping("/{id}/status")
public R<Void> updateStatus(@PathVariable Long id, @RequestParam String status) {
mcpMarketService.updateStatus(id, status);
return R.ok();
}
/**
* 获取市场工具列表(分页)
*/
@SaCheckPermission("mcp:market:query")
@GetMapping("/{marketId}/tools")
public McpMarketToolListResult getMarketTools(
@PathVariable Long marketId,
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size) {
return mcpMarketService.getMarketTools(marketId, page, size);
}
/**
* 刷新市场工具列表
*/
@SaCheckPermission("mcp:market:edit")
@Log(title = "MCP市场管理", businessType = BusinessType.UPDATE)
@PostMapping("/{marketId}/refresh")
public R<McpMarketRefreshResult> refreshMarketTools(@PathVariable Long marketId) {
return R.ok(mcpMarketService.refreshMarketTools(marketId));
}
/**
* 加载单个工具到本地
*/
@SaCheckPermission("mcp:market:add")
@Log(title = "MCP市场管理", businessType = BusinessType.INSERT)
@PostMapping("/tools/{toolId}/load")
public R<Void> loadToolToLocal(@PathVariable Long toolId) {
mcpMarketService.loadToolToLocal(toolId);
return R.ok();
}
/**
* 批量加载工具到本地
*/
@SaCheckPermission("mcp:market:add")
@Log(title = "MCP市场管理", businessType = BusinessType.INSERT)
@PostMapping("/tools/batch-load")
public R<Map<String, Object>> batchLoadTools(@RequestBody List<Long> toolIds) {
int successCount = mcpMarketService.batchLoadTools(toolIds);
return R.ok(Map.of("successCount", successCount));
}
}

View File

@@ -0,0 +1,136 @@
package org.ruoyi.controller.mcp;
import cn.dev33.satoken.annotation.SaCheckPermission;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.ruoyi.common.core.domain.R;
import org.ruoyi.common.excel.utils.ExcelUtil;
import org.ruoyi.common.idempotent.annotation.RepeatSubmit;
import org.ruoyi.common.log.annotation.Log;
import org.ruoyi.common.log.enums.BusinessType;
import org.ruoyi.common.mybatis.core.page.PageQuery;
import org.ruoyi.common.mybatis.core.page.TableDataInfo;
import org.ruoyi.common.web.core.BaseController;
import org.ruoyi.domain.bo.mcp.McpToolBo;
import org.ruoyi.domain.dto.mcp.McpToolListResult;
import org.ruoyi.domain.dto.mcp.McpToolTestResult;
import org.ruoyi.domain.vo.mcp.McpToolVo;
import org.ruoyi.service.mcp.IMcpToolService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* MCP 工具管理 Controller
*
* @author ruoyi team
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/mcp/tool")
public class McpToolController extends BaseController {
private final IMcpToolService mcpToolService;
/**
* 查询 MCP 工具列表
*/
@SaCheckPermission("mcp:tool:list")
@GetMapping("/list")
public TableDataInfo<McpToolVo> list(McpToolBo bo, PageQuery pageQuery) {
return mcpToolService.selectPageList(bo, pageQuery);
}
/**
* 查询 MCP 工具列表(不分页)
*/
@SaCheckPermission("mcp:tool:list")
@GetMapping("/all")
public McpToolListResult listAll(
@RequestParam(required = false) String keyword,
@RequestParam(required = false) String type,
@RequestParam(required = false) String status) {
return mcpToolService.listTools(keyword, type, status);
}
/**
* 导出 MCP 工具列表
*/
@SaCheckPermission("mcp:tool:export")
@Log(title = "MCP工具管理", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(McpToolBo bo, HttpServletResponse response) {
List<McpToolVo> list = mcpToolService.queryList(bo);
ExcelUtil.exportExcel(list, "MCP工具", McpToolVo.class, response);
}
/**
* 根据工具ID获取详细信息
*
* @param id 工具ID
*/
@SaCheckPermission("mcp:tool:query")
@GetMapping("/{id}")
public R<McpToolVo> getInfo(@PathVariable Long id) {
return R.ok(mcpToolService.selectById(id));
}
/**
* 新增 MCP 工具
*/
@SaCheckPermission("mcp:tool:add")
@Log(title = "MCP工具管理", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping
public R<Void> add(@Validated @RequestBody McpToolBo bo) {
mcpToolService.insert(bo);
return R.ok();
}
/**
* 修改 MCP 工具
*/
@SaCheckPermission("mcp:tool:edit")
@Log(title = "MCP工具管理", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping
public R<Void> edit(@Validated @RequestBody McpToolBo bo) {
mcpToolService.update(bo);
return R.ok();
}
/**
* 删除 MCP 工具
*
* @param ids 工具ID串
*/
@SaCheckPermission("mcp:tool:remove")
@Log(title = "MCP工具管理", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public R<Void> remove(@PathVariable Long[] ids) {
mcpToolService.deleteByIds(List.of(ids));
return R.ok();
}
/**
* 更新工具状态
*/
@SaCheckPermission("mcp:tool:edit")
@Log(title = "MCP工具管理", businessType = BusinessType.UPDATE)
@PutMapping("/{id}/status")
public R<Void> updateStatus(@PathVariable Long id, @RequestParam String status) {
mcpToolService.updateStatus(id, status);
return R.ok();
}
/**
* 测试工具连接
*/
@SaCheckPermission("mcp:tool:query")
@PostMapping("/{id}/test")
public R<McpToolTestResult> testTool(@PathVariable Long id) {
return R.ok(mcpToolService.testTool(id));
}
}

View File

@@ -0,0 +1,77 @@
package org.ruoyi.domain.bo.chat;
import org.ruoyi.common.core.validate.AddGroup;
import org.ruoyi.common.core.validate.EditGroup;
import org.ruoyi.domain.entity.chat.ChatProvider;
import org.ruoyi.common.mybatis.core.domain.BaseEntity;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
import jakarta.validation.constraints.*;
/**
* 厂商管理业务对象 chat_provider
*
* @author ageerle
* @date 2025-12-14
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = ChatProvider.class, reverseConvertGenerate = false)
public class ChatProviderBo extends BaseEntity {
/**
* 主键
*/
@NotNull(message = "主键不能为空", groups = { EditGroup.class })
private Long id;
/**
* 厂商名称
*/
@NotBlank(message = "厂商名称不能为空", groups = { AddGroup.class, EditGroup.class })
private String providerName;
/**
* 厂商编码
*/
@NotBlank(message = "厂商编码不能为空", groups = { AddGroup.class, EditGroup.class })
private String providerCode;
/**
* 厂商图标
*/
private String providerIcon;
/**
* 厂商描述
*/
private String providerDesc;
/**
* API地址
*/
private String apiHost;
/**
* 状态0正常 1停用
*/
private String status;
/**
* 排序
*/
private Long sortOrder;
/**
* 备注
*/
private String remark;
/**
* 更新IP
*/
private String updateIp;
}

View File

@@ -0,0 +1,54 @@
package org.ruoyi.domain.bo.chat;
import org.ruoyi.common.core.validate.EditGroup;
import org.ruoyi.domain.entity.chat.ChatSession;
import org.ruoyi.common.mybatis.core.domain.BaseEntity;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
import jakarta.validation.constraints.*;
/**
* 会话管理业务对象 chat_session
*
* @author ageerle
* @date 2025-12-30
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = ChatSession.class, reverseConvertGenerate = false)
public class ChatSessionBo extends BaseEntity {
/**
* 主键
*/
@NotNull(message = "主键不能为空", groups = { EditGroup.class })
private Long id;
/**
* 用户id
*/
private Long userId;
/**
* 会话标题
*/
private String sessionTitle;
/**
* 会话内容
*/
private String sessionContent;
/**
* 备注
*/
private String remark;
/**
* 会话ID
*/
private String conversationId;
}

View File

@@ -0,0 +1,63 @@
package org.ruoyi.domain.bo.knowledge;
import org.ruoyi.common.core.validate.AddGroup;
import org.ruoyi.common.core.validate.EditGroup;
import org.ruoyi.common.mybatis.core.domain.BaseEntity;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
import jakarta.validation.constraints.*;
import org.ruoyi.domain.entity.knowledge.KnowledgeAttach;
/**
* 知识库附件业务对象 knowledge_attach
*
* @author ageerle
* @date 2025-12-17
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = KnowledgeAttach.class, reverseConvertGenerate = false)
public class KnowledgeAttachBo extends BaseEntity {
/**
* 主键
*/
@NotNull(message = "主键不能为空", groups = { EditGroup.class })
private Long id;
/**
* 知识库ID
*/
@NotBlank(message = "知识库ID不能为空", groups = { AddGroup.class, EditGroup.class })
private Long knowledgeId;
/**
* 文档ID-用于关联文本块信息
*/
private String docId;
/**
* 附件名称
*/
private String name;
/**
* 附件类型
*/
@NotBlank(message = "附件类型不能为空", groups = { AddGroup.class, EditGroup.class })
private String type;
/**
* 对象存储ID
*/
private Long ossId;
/**
* 备注
*/
private String remark;
}

View File

@@ -0,0 +1,53 @@
package org.ruoyi.domain.bo.knowledge;
import org.ruoyi.common.core.validate.AddGroup;
import org.ruoyi.common.core.validate.EditGroup;
import org.ruoyi.common.mybatis.core.domain.BaseEntity;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
import jakarta.validation.constraints.*;
import org.ruoyi.domain.entity.knowledge.KnowledgeFragment;
/**
* 知识片段业务对象 knowledge_fragment
*
* @author ageerle
* @date 2025-12-17
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = KnowledgeFragment.class, reverseConvertGenerate = false)
public class KnowledgeFragmentBo extends BaseEntity {
/**
* 主键
*/
@NotNull(message = "主键不能为空", groups = { EditGroup.class })
private Long id;
/**
* 文档ID-用于关联文本块信息
*/
private String docId;
/**
* 片段索引下标
*/
@NotNull(message = "片段索引下标不能为空", groups = { AddGroup.class, EditGroup.class })
private Integer idx;
/**
* 文档内容
*/
@NotBlank(message = "文档内容不能为空", groups = { AddGroup.class, EditGroup.class })
private String content;
/**
* 备注
*/
private String remark;
}

View File

@@ -0,0 +1,93 @@
package org.ruoyi.domain.bo.knowledge;
import org.ruoyi.common.core.validate.AddGroup;
import org.ruoyi.common.core.validate.EditGroup;
import org.ruoyi.common.mybatis.core.domain.BaseEntity;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
import jakarta.validation.constraints.*;
import org.ruoyi.domain.entity.knowledge.KnowledgeGraphInstance;
/**
* 知识图谱实例业务对象 knowledge_graph_instance
*
* @author ageerle
* @date 2025-12-17
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = KnowledgeGraphInstance.class, reverseConvertGenerate = false)
public class KnowledgeGraphInstanceBo extends BaseEntity {
/**
* 主键
*/
@NotNull(message = "主键不能为空", groups = { EditGroup.class })
private Long id;
/**
* 图谱UUID
*/
@NotBlank(message = "图谱UUID不能为空", groups = { AddGroup.class, EditGroup.class })
private String graphUuid;
/**
* 关联knowledge_info.kid
*/
@NotBlank(message = "关联knowledge_info.kid不能为空", groups = { AddGroup.class, EditGroup.class })
private String knowledgeId;
/**
* 图谱名称
*/
@NotBlank(message = "图谱名称不能为空", groups = { AddGroup.class, EditGroup.class })
private String graphName;
/**
* 构建状态10构建中、20已完成、30失败
*/
private Long graphStatus;
/**
* 节点数量
*/
private Long nodeCount;
/**
* 关系数量
*/
private Long relationshipCount;
/**
* 图谱配置(JSON格式)
*/
private String config;
/**
* LLM模型名称
*/
private String modelName;
/**
* 实体类型(逗号分隔)
*/
private String entityTypes;
/**
* 关系类型(逗号分隔)
*/
private String relationTypes;
/**
* 错误信息
*/
private String errorMessage;
/**
* 备注
*/
private String remark;
}

View File

@@ -0,0 +1,102 @@
package org.ruoyi.domain.bo.knowledge;
import org.ruoyi.common.core.validate.AddGroup;
import org.ruoyi.common.core.validate.EditGroup;
import org.ruoyi.common.mybatis.core.domain.BaseEntity;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
import jakarta.validation.constraints.*;
import org.ruoyi.domain.entity.knowledge.KnowledgeGraphSegment;
/**
* 知识图谱片段业务对象 knowledge_graph_segment
*
* @author ageerle
* @date 2025-12-17
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = KnowledgeGraphSegment.class, reverseConvertGenerate = false)
public class KnowledgeGraphSegmentBo extends BaseEntity {
/**
* 主键ID
*/
@NotNull(message = "主键ID不能为空", groups = { EditGroup.class })
private Long id;
/**
* 片段UUID
*/
@NotBlank(message = "片段UUID不能为空", groups = { AddGroup.class, EditGroup.class })
private String uuid;
/**
* 知识库UUID
*/
@NotBlank(message = "知识库UUID不能为空", groups = { AddGroup.class, EditGroup.class })
private String kbUuid;
/**
* 知识库条目UUID
*/
private String kbItemUuid;
/**
* 文档UUID
*/
private String docUuid;
/**
* 片段文本内容
*/
private String segmentText;
/**
* 片段索引(第几个片段)
*/
private Long chunkIndex;
/**
* 总片段数
*/
private Long totalChunks;
/**
* 抽取状态0-待处理 1-处理中 2-已完成 3-失败
*/
private Long extractionStatus;
/**
* 抽取的实体数量
*/
private Long entityCount;
/**
* 抽取的关系数量
*/
private Long relationCount;
/**
* 消耗的token数
*/
private Long tokenUsed;
/**
* 错误信息
*/
private String errorMessage;
/**
* 用户ID
*/
private Long userId;
/**
* 备注
*/
private String remark;
}

View File

@@ -0,0 +1,86 @@
package org.ruoyi.domain.bo.knowledge;
import org.ruoyi.common.core.validate.AddGroup;
import org.ruoyi.common.core.validate.EditGroup;
import org.ruoyi.common.mybatis.core.domain.BaseEntity;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
import jakarta.validation.constraints.*;
import org.ruoyi.domain.entity.knowledge.KnowledgeInfo;
/**
* 知识库业务对象 knowledge_info
*
* @author ageerle
* @date 2025-12-17
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = KnowledgeInfo.class, reverseConvertGenerate = false)
public class KnowledgeInfoBo extends BaseEntity {
/**
* 主键
*/
@NotNull(message = "主键不能为空", groups = { EditGroup.class })
private Long id;
/**
* 用户ID
*/
private Long userId;
/**
* 知识库名称
*/
@NotBlank(message = "知识库名称不能为空", groups = { AddGroup.class, EditGroup.class })
private String name;
/**
* 是否公开知识库0 否 1是
*/
private Long share;
/**
* 知识库描述
*/
private String description;
/**
* 知识分隔符
*/
private String separator;
/**
* 重叠字符数
*/
private Long overlapChar;
/**
* 知识库中检索的条数
*/
private Long retrieveLimit;
/**
* 文本块大小
*/
private Long textBlockSize;
/**
* 向量库
*/
private String vectorModel;
/**
* 向量模型
*/
private String embeddingModel;
/**
* 备注
*/
private String remark;
}

View File

@@ -0,0 +1,25 @@
package org.ruoyi.domain.bo.knowledge;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.web.multipart.MultipartFile;
import java.util.Date;
/**
* 附件上传请求
*/
@Data
public class KnowledgeInfoUploadBo {
private Long knowledgeId;
private MultipartFile file;
/**
* 生效时间, 为空则立即生效
*/
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date effectiveTime;
}

View File

@@ -0,0 +1,55 @@
package org.ruoyi.domain.bo.mcp;
import io.github.linpeilie.annotations.AutoMapper;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.ruoyi.common.mybatis.core.domain.BaseEntity;
import org.ruoyi.domain.entity.mcp.McpMarket;
/**
* MCP 市场业务对象
*
* @author ruoyi team
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = McpMarket.class, reverseConvertGenerate = false)
public class McpMarketBo extends BaseEntity {
/**
* 市场ID
*/
private Long id;
/**
* 市场名称
*/
@NotBlank(message = "市场名称不能为空")
@Size(min = 0, max = 200, message = "市场名称不能超过{max}个字符")
private String name;
/**
* 市场 URL
*/
@NotBlank(message = "市场URL不能为空")
@Size(min = 0, max = 500, message = "市场URL不能超过{max}个字符")
private String url;
/**
* 市场描述
*/
private String description;
/**
* 认证配置JSON格式
*/
private String authConfig;
/**
* 状态ENABLED-启用, DISABLED-禁用
*/
private String status;
}

View File

@@ -0,0 +1,59 @@
package org.ruoyi.domain.bo.mcp;
import io.github.linpeilie.annotations.AutoMapper;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.ruoyi.common.mybatis.core.domain.BaseEntity;
import org.ruoyi.domain.entity.mcp.McpTool;
import java.io.Serial;
/**
* MCP 工具业务对象
*
* @author ruoyi team
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = McpTool.class, reverseConvertGenerate = false)
public class McpToolBo extends BaseEntity {
@Serial
private static final long serialVersionUID = 1L;
/**
* 工具ID
*/
private Long id;
/**
* 工具名称
*/
@NotBlank(message = "工具名称不能为空")
@Size(min = 0, max = 200, message = "工具名称不能超过{max}个字符")
private String name;
/**
* 工具描述
*/
private String description;
/**
* 工具类型LOCAL-本地, REMOTE-远程, BUILTIN-内置
*/
@NotBlank(message = "工具类型不能为空")
private String type;
/**
* 状态ENABLED-启用, DISABLED-禁用
*/
private String status;
/**
* 配置信息JSON格式
*/
private String configJson;
}

View File

@@ -0,0 +1,54 @@
package org.ruoyi.domain.bo.vector;
import lombok.Data;
/**
* 查询向量所需参数
*
* @author ageer
*/
@Data
public class QueryVectorBo {
/**
* 查询内容
*/
private String query;
/**
* 知识库kid
*/
private String kid;
/**
* 查询向量返回条数
*/
private Integer maxResults;
/**
* 向量库模型名称
*/
private String vectorModelName;
/**
* 向量化模型ID
*/
private Long embeddingModelId;
/**
* 向量化模型ID
*/
private String embeddingModelName;
/**
* 请求key
*/
private String apiKey;
/**
* 请求地址
*/
private String baseUrl;
}

View File

@@ -0,0 +1,60 @@
package org.ruoyi.domain.bo.vector;
import lombok.Data;
import java.util.List;
/**
* 保存向量所需参数
*
* @author ageer
*/
@Data
public class StoreEmbeddingBo {
/**
* 切分文本块列表
*/
private List<String> chunkList;
/**
* 知识库kid
*/
private String kid;
/**
* 文档id
*/
private String docId;
/**
* 知识块id列表
*/
private List<String> fids;
/**
* 向量库名称
*/
private String vectorStoreName;
/**
* 向量化模型id
*/
private Long embeddingModelId;
/**
* 向量化模型名称
*/
private String embeddingModelName;
/**
* 请求key
*/
private String apiKey;
/**
* 请求地址
*/
private String baseUrl;
}

View File

@@ -0,0 +1,45 @@
package org.ruoyi.domain.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
/**
* 抽取的实体
*
* @author ruoyi
* @date 2025-09-30
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ExtractedEntity implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 实体名称
*/
private String name;
/**
* 实体类型
*/
private String type;
/**
* 实体描述
*/
private String description;
/**
* 置信度0.0-1.0
*/
private Double confidence;
}

View File

@@ -0,0 +1,50 @@
package org.ruoyi.domain.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
/**
* 抽取的关系
*
* @author ruoyi
* @date 2025-09-30
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ExtractedRelation implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 源实体名称
*/
private String sourceEntity;
/**
* 目标实体名称
*/
private String targetEntity;
/**
* 关系描述
*/
private String description;
/**
* 关系强度0-10
*/
private Integer strength;
/**
* 置信度0.0-1.0
*/
private Double confidence;
}

View File

@@ -0,0 +1,57 @@
package org.ruoyi.domain.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
/**
* 图谱抽取结果
*
* @author ruoyi
* @date 2025-09-30
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class GraphExtractionResult implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 抽取的实体列表
*/
private List<ExtractedEntity> entities;
/**
* 抽取的关系列表
*/
private List<ExtractedRelation> relations;
/**
* 原始LLM响应
*/
private String rawResponse;
/**
* 消耗的token数
*/
private Integer tokenUsed;
/**
* 是否成功
*/
private Boolean success;
/**
* 错误信息
*/
private String errorMessage;
}

View File

@@ -0,0 +1,71 @@
package org.ruoyi.domain.dto;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import lombok.Builder;
import lombok.Data;
/**
* @Author: Robust_H
* @Date: 2025-09-30-下午2:13
* @Description: 多模态输入
*/
@Data
@Builder
public class MultiModalInput {
private String text;
private byte[] imageData;
private byte[] videoData;
private String imageMimeType;
private String videoMimeType;
private String[] multiImageUrls;
private String imageUrl;
private String videoUrl;
/**
* 检查是否有文本内容
*/
public boolean hasText() {
return StrUtil.isNotBlank(text);
}
/**
* 检查是否有图片内容
*/
public boolean hasImage() {
return ArrayUtil.isNotEmpty(imageData) || StrUtil.isNotBlank(imageUrl);
}
/**
* 检查是否有视频内容
*/
public boolean hasVideo() {
return ArrayUtil.isNotEmpty(videoData) || StrUtil.isNotBlank(videoUrl);
}
/**
* 检查是否有多图片
*/
public boolean hasMultiImages() {
return ArrayUtil.isNotEmpty(multiImageUrls);
}
/**
* 检查是否有任何内容
*/
public boolean hasAnyContent() {
return hasText() || hasImage() || hasVideo() || hasMultiImages();
}
/**
* 获取内容的数量
*/
public int getContentCount() {
int count = 0;
if (hasText()) count++;
if (hasImage()) count++;
if (hasVideo()) count++;
if (hasMultiImages()) count++;
return count;
}
}

View File

@@ -0,0 +1,44 @@
package org.ruoyi.domain.dto.mcp;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.ruoyi.domain.entity.mcp.McpMarket;
import java.util.List;
/**
* MCP 市场列表返回结果
*
* @author ruoyi team
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class McpMarketListResult {
/**
* 是否成功
*/
private boolean success;
/**
* 市场列表
*/
private List<McpMarket> data;
/**
* 总数
*/
private int total;
public static McpMarketListResult of(List<McpMarket> data) {
return McpMarketListResult.builder()
.success(true)
.data(data)
.total(data != null ? data.size() : 0)
.build();
}
}

View File

@@ -0,0 +1,38 @@
package org.ruoyi.domain.dto.mcp;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* MCP 市场工具刷新结果
*
* @author ruoyi team
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class McpMarketRefreshResult {
/**
* 是否成功
*/
private boolean success;
/**
* 消息
*/
private String message;
/**
* 新增工具数量
*/
private int addedCount;
/**
* 更新工具数量
*/
private int updatedCount;
}

View File

@@ -0,0 +1,63 @@
package org.ruoyi.domain.dto.mcp;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.ruoyi.domain.entity.mcp.McpMarketTool;
import java.util.List;
/**
* MCP 市场工具列表返回结果(分页)
*
* @author ruoyi team
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class McpMarketToolListResult {
/**
* 是否成功
*/
private boolean success;
/**
* 工具列表
*/
private List<McpMarketTool> data;
/**
* 总数
*/
private long total;
/**
* 当前页
*/
private int page;
/**
* 每页大小
*/
private int size;
/**
* 总页数
*/
private long pages;
public static McpMarketToolListResult of(List<McpMarketTool> data, long total, int page, int size) {
long pages = (total + size - 1) / size;
return McpMarketToolListResult.builder()
.success(true)
.data(data)
.total(total)
.page(page)
.size(size)
.pages(pages)
.build();
}
}

View File

@@ -0,0 +1,44 @@
package org.ruoyi.domain.dto.mcp;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.ruoyi.domain.entity.mcp.McpTool;
import java.util.List;
/**
* MCP 工具列表返回结果
*
* @author ruoyi team
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class McpToolListResult {
/**
* 是否成功
*/
private boolean success;
/**
* 工具列表
*/
private List<McpTool> data;
/**
* 总数
*/
private int total;
public static McpToolListResult of(List<McpTool> data) {
return McpToolListResult.builder()
.success(true)
.data(data)
.total(data != null ? data.size() : 0)
.build();
}
}

View File

@@ -0,0 +1,56 @@
package org.ruoyi.domain.dto.mcp;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* MCP 工具测试结果
*
* @author ruoyi team
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class McpToolTestResult {
/**
* 是否成功
*/
private boolean success;
/**
* 消息
*/
private String message;
/**
* 发现的工具数量
*/
private Integer toolCount;
/**
* 工具名称列表
*/
private List<String> tools;
public static McpToolTestResult success(String message, int toolCount, List<String> tools) {
return McpToolTestResult.builder()
.success(true)
.message(message)
.toolCount(toolCount)
.tools(tools)
.build();
}
public static McpToolTestResult fail(String message) {
return McpToolTestResult.builder()
.success(false)
.message(message)
.build();
}
}

View File

@@ -0,0 +1,45 @@
package org.ruoyi.domain.dto.request;
import lombok.Data;
import org.ruoyi.common.json.utils.JsonUtils;
import java.util.List;
import java.util.Map;
/**
* @Author: Robust_H
* @Date: 2025-10-1-上午10:00
* @Description: 阿里云多模态嵌入请求
*/
@Data
public class AliyunMultiModalEmbedRequest {
private String model;
private Input input;
/**
* 创建请求对象
*/
public static AliyunMultiModalEmbedRequest create(String modelName, List<Map<String, Object>> contents) {
AliyunMultiModalEmbedRequest request = new AliyunMultiModalEmbedRequest();
request.setModel(modelName);
Input input = new Input(contents);
request.setInput(input);
return request;
}
/**
* 转换为JSON字符串
*/
public String toJson() {
return JsonUtils.toJsonString(this);
}
/**
* 表示输入数据的记录类(Record)
* 该类用于封装一个包含多个映射关系列表的输入数据结构
*
* @param contents 包含多个Map的列表每个Map中存储String类型的键和Object类型的值
*/
public record Input(List<Map<String, Object>> contents) {
}
}

View File

@@ -0,0 +1,44 @@
package org.ruoyi.domain.dto.response;
import java.util.List;
/**
* 阿里云多模态嵌入 API 响应数据模型
*/
public record AliyunMultiModalEmbedResponse(
Output output, // 输出结果对象
String request_id, // 请求唯一标识
String code, // 错误码
String message, // 错误消息
Usage usage // 用量信息
) {
/**
* 输出对象,包含嵌入向量结果
*/
public record Output(
List<EmbeddingItem> embeddings // 嵌入向量列表
) {
}
/**
* 单个嵌入向量条目
*/
public record EmbeddingItem(
int index, // 输入内容的索引
List<Double> embedding, // 生成的 1024 维向量
String type // 输入的类型 (text/image/video/multi_images)
) {
}
/**
* 用量统计信息
*/
public record Usage(
int input_tokens, // 本次请求输入的 Token 数量
int image_tokens, // 本次请求输入的图像 Token 数量
int image_count, // 本次请求输入的图像数量
int duration // 本次请求输入的视频时长(秒)
) {
}
}

View File

@@ -0,0 +1,88 @@
package org.ruoyi.domain.entity.chat;
import org.ruoyi.common.tenant.core.TenantEntity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serial;
/**
* 厂商管理对象 chat_provider
*
* @author ageerle
* @date 2025-12-14
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("chat_provider")
public class ChatProvider extends TenantEntity {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@TableId(value = "id")
private Long id;
/**
* 厂商名称
*/
private String providerName;
/**
* 厂商编码
*/
private String providerCode;
/**
* 厂商图标
*/
private String providerIcon;
/**
* 厂商描述
*/
private String providerDesc;
/**
* API地址
*/
private String apiHost;
/**
* 状态0正常 1停用
*/
private String status;
/**
* 排序
*/
private Long sortOrder;
/**
* 备注
*/
private String remark;
/**
* 版本
*/
@Version
private Long version;
/**
* 删除标志0代表存在 1代表删除
*/
@TableLogic
private String delFlag;
/**
* 更新IP
*/
private String updateIp;
}

View File

@@ -0,0 +1,55 @@
package org.ruoyi.domain.entity.chat;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.ruoyi.common.mybatis.core.domain.BaseEntity;
import java.io.Serial;
/**
* 会话管理对象 chat_session
*
* @author ageerle
* @date 2025-12-30
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("chat_session")
public class ChatSession extends BaseEntity {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@TableId(value = "id")
private Long id;
/**
* 用户id
*/
private Long userId;
/**
* 会话标题
*/
private String sessionTitle;
/**
* 会话内容
*/
private String sessionContent;
/**
* 会话ID
*/
private String conversationId;
/**
* 备注
*/
private String remark;
}

View File

@@ -0,0 +1,61 @@
package org.ruoyi.domain.entity.knowledge;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.ruoyi.common.mybatis.core.domain.BaseEntity;
import java.io.Serial;
/**
* 知识库附件对象 knowledge_attach
*
* @author ageerle
* @date 2025-12-17
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("knowledge_attach")
public class KnowledgeAttach extends BaseEntity {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@TableId(value = "id")
private Long id;
/**
* 知识库ID
*/
private Long knowledgeId;
/**
* 文档ID-用于关联文本块信息
*/
private String docId;
/**
* 附件名称
*/
private String name;
/**
* 附件类型
*/
private String type;
/**
* 对象存储ID
*/
private Long ossId;
/**
* 备注
*/
private String remark;
}

View File

@@ -0,0 +1,51 @@
package org.ruoyi.domain.entity.knowledge;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.ruoyi.common.mybatis.core.domain.BaseEntity;
import java.io.Serial;
/**
* 知识片段对象 knowledge_fragment
*
* @author ageerle
* @date 2025-12-17
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("knowledge_fragment")
public class KnowledgeFragment extends BaseEntity {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@TableId(value = "id")
private Long id;
/**
* 文档ID-用于关联文本块信息
*/
private String docId;
/**
* 片段索引下标
*/
private Integer idx;
/**
* 文档内容
*/
private String content;
/**
* 备注
*/
private String remark;
}

View File

@@ -0,0 +1,97 @@
package org.ruoyi.domain.entity.knowledge;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.ruoyi.common.mybatis.core.domain.BaseEntity;
import java.io.Serial;
/**
* 知识图谱实例对象 knowledge_graph_instance
*
* @author ageerle
* @date 2025-12-17
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("knowledge_graph_instance")
public class KnowledgeGraphInstance extends BaseEntity {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@TableId(value = "id")
private Long id;
/**
* 图谱UUID
*/
private String graphUuid;
/**
* 关联knowledge_info.kid
*/
private String knowledgeId;
/**
* 图谱名称
*/
private String graphName;
/**
* 构建状态10构建中、20已完成、30失败
*/
private Long graphStatus;
/**
* 节点数量
*/
private Long nodeCount;
/**
* 关系数量
*/
private Long relationshipCount;
/**
* 图谱配置(JSON格式)
*/
private String config;
/**
* LLM模型名称
*/
private String modelName;
/**
* 实体类型(逗号分隔)
*/
private String entityTypes;
/**
* 关系类型(逗号分隔)
*/
private String relationTypes;
/**
* 错误信息
*/
private String errorMessage;
/**
* 备注
*/
private String remark;
/**
* 删除标志0代表存在 1代表删除
*/
@TableLogic
private String delFlag;
}

View File

@@ -0,0 +1,101 @@
package org.ruoyi.domain.entity.knowledge;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.ruoyi.common.mybatis.core.domain.BaseEntity;
import java.io.Serial;
/**
* 知识图谱片段对象 knowledge_graph_segment
*
* @author ageerle
* @date 2025-12-17
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("knowledge_graph_segment")
public class KnowledgeGraphSegment extends BaseEntity {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value = "id")
private Long id;
/**
* 片段UUID
*/
private String uuid;
/**
* 知识库UUID
*/
private String kbUuid;
/**
* 知识库条目UUID
*/
private String kbItemUuid;
/**
* 文档UUID
*/
private String docUuid;
/**
* 片段文本内容
*/
private String segmentText;
/**
* 片段索引(第几个片段)
*/
private Long chunkIndex;
/**
* 总片段数
*/
private Long totalChunks;
/**
* 抽取状态0-待处理 1-处理中 2-已完成 3-失败
*/
private Long extractionStatus;
/**
* 抽取的实体数量
*/
private Long entityCount;
/**
* 抽取的关系数量
*/
private Long relationCount;
/**
* 消耗的token数
*/
private Long tokenUsed;
/**
* 错误信息
*/
private String errorMessage;
/**
* 用户ID
*/
private Long userId;
/**
* 备注
*/
private String remark;
}

View File

@@ -0,0 +1,87 @@
package org.ruoyi.domain.entity.knowledge;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.ruoyi.common.mybatis.core.domain.BaseEntity;
import java.io.Serial;
/**
* 知识库对象 knowledge_info
*
* @author ageerle
* @date 2025-12-17
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("knowledge_info")
public class KnowledgeInfo extends BaseEntity {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@TableId(value = "id")
private Long id;
/**
* 用户ID
*/
private Long userId;
/**
* 知识库名称
*/
private String name;
/**
* 是否公开知识库0 否 1是
*/
private Long share;
/**
* 知识库描述
*/
private String description;
/**
* 知识分隔符
*/
@TableField(value = "`separator`")
private String separator;
/**
* 重叠字符数
*/
private Long overlapChar;
/**
* 知识库中检索的条数
*/
private Long retrieveLimit;
/**
* 文本块大小
*/
private Long textBlockSize;
/**
* 向量库
*/
private String vectorModel;
/**
* 向量模型
*/
private String embeddingModel;
/**
* 备注
*/
private String remark;
}

View File

@@ -0,0 +1,51 @@
package org.ruoyi.domain.entity.mcp;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.ruoyi.common.tenant.core.TenantEntity;
/**
* MCP 市场信息实体
*
* @author ruoyi team
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("mcp_market_info")
public class McpMarket extends TenantEntity {
/**
* 市场ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 市场名称
*/
private String name;
/**
* 市场 URL
*/
private String url;
/**
* 市场描述
*/
private String description;
/**
* 认证配置JSON格式
*/
private String authConfig;
/**
* 状态ENABLED-启用, DISABLED-禁用
*/
private String status;
}

View File

@@ -0,0 +1,61 @@
package org.ruoyi.domain.entity.mcp;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.ruoyi.common.mybatis.core.domain.BaseEntity;
/**
* MCP 市场工具关联实体
*
* @author ruoyi team
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("mcp_market_tool")
public class McpMarketTool extends BaseEntity {
/**
* ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 市场 ID
*/
private Long marketId;
/**
* 工具名称
*/
private String toolName;
/**
* 工具描述
*/
private String toolDescription;
/**
* 工具版本
*/
private String toolVersion;
/**
* 工具元数据JSON格式
*/
private String toolMetadata;
/**
* 是否已加载到本地
*/
private Boolean isLoaded;
/**
* 关联的本地工具 ID
*/
private Long localToolId;
}

View File

@@ -0,0 +1,55 @@
package org.ruoyi.domain.entity.mcp;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.ruoyi.common.tenant.core.TenantEntity;
/**
* MCP 工具信息实体
*
* @author ruoyi team
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("mcp_tool_info")
public class McpTool extends TenantEntity {
/**
* 工具ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 工具名称
*/
private String name;
/**
* 工具描述
*/
private String description;
/**
* 工具类型LOCAL-本地, REMOTE-远程, BUILTIN-内置
*/
private String type;
/**
* 状态ENABLED-启用, DISABLED-禁用
*/
private String status;
/**
* 配置信息JSON格式
* LOCAL: {"command": "npx", "args": ["-y", "@example/mcp-server"], "env": {...}}
* REMOTE: {"baseUrl": "http://localhost:8080/mcp"}
* BUILTIN: null
*/
private String configJson;
}

View File

@@ -0,0 +1,92 @@
package org.ruoyi.domain.vo.chat;
import org.ruoyi.domain.entity.chat.ChatProvider;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import org.ruoyi.common.excel.annotation.ExcelDictFormat;
import org.ruoyi.common.excel.convert.ExcelDictConvert;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 厂商管理视图对象 chat_provider
*
* @author ageerle
* @date 2025-12-14
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = ChatProvider.class)
public class ChatProviderVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@ExcelProperty(value = "主键")
private Long id;
/**
* 厂商名称
*/
@ExcelProperty(value = "厂商名称")
private String providerName;
/**
* 厂商编码
*/
@ExcelProperty(value = "厂商编码")
private String providerCode;
/**
* 厂商图标
*/
@ExcelProperty(value = "厂商图标")
private String providerIcon;
/**
* 厂商描述
*/
@ExcelProperty(value = "厂商描述")
private String providerDesc;
/**
* API地址
*/
@ExcelProperty(value = "API地址")
private String apiHost;
/**
* 状态0正常 1停用
*/
@ExcelProperty(value = "状态", converter = ExcelDictConvert.class)
@ExcelDictFormat(readConverterExp = "0=正常,1=停用")
private String status;
/**
* 排序
*/
@ExcelProperty(value = "排序")
private Long sortOrder;
/**
* 备注
*/
@ExcelProperty(value = "备注")
private String remark;
/**
* 更新IP
*/
@ExcelProperty(value = "更新IP")
private String updateIp;
}

View File

@@ -0,0 +1,64 @@
package org.ruoyi.domain.vo.chat;
import org.ruoyi.domain.entity.chat.ChatSession;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 会话管理视图对象 chat_session
*
* @author ageerle
* @date 2025-12-30
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = ChatSession.class)
public class ChatSessionVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@ExcelProperty(value = "主键")
private Long id;
/**
* 用户id
*/
@ExcelProperty(value = "用户id")
private Long userId;
/**
* 会话标题
*/
@ExcelProperty(value = "会话标题")
private String sessionTitle;
/**
* 会话内容
*/
@ExcelProperty(value = "会话内容")
private String sessionContent;
/**
* 备注
*/
@ExcelProperty(value = "备注")
private String remark;
/**
* 会话ID
*/
@ExcelProperty(value = "会话ID")
private String conversationId;
}

View File

@@ -0,0 +1,72 @@
package org.ruoyi.domain.vo.knowledge;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import org.ruoyi.domain.entity.knowledge.KnowledgeAttach;
import java.io.Serial;
import java.io.Serializable;
/**
* 知识库附件视图对象 knowledge_attach
*
* @author ageerle
* @date 2025-12-17
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = KnowledgeAttach.class)
public class KnowledgeAttachVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@ExcelProperty(value = "主键")
private Long id;
/**
* 知识库ID
*/
@ExcelProperty(value = "知识库ID")
private Long knowledgeId;
/**
* 文档ID-用于关联文本块信息
*/
@ExcelProperty(value = "文档ID")
private String docId;
/**
* 附件名称
*/
@ExcelProperty(value = "附件名称")
private String name;
/**
* 附件类型
*/
@ExcelProperty(value = "附件类型")
private String type;
/**
* 对象存储ID
*/
@ExcelProperty(value = "对象存储ID")
private Long ossId;
/**
* 备注
*/
@ExcelProperty(value = "备注")
private String remark;
}

View File

@@ -0,0 +1,57 @@
package org.ruoyi.domain.vo.knowledge;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import org.ruoyi.domain.entity.knowledge.KnowledgeFragment;
import java.io.Serial;
import java.io.Serializable;
/**
* 知识片段视图对象 knowledge_fragment
*
* @author ageerle
* @date 2025-12-17
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = KnowledgeFragment.class)
public class KnowledgeFragmentVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@ExcelProperty(value = "主键")
private Long id;
/**
* 文档ID-用于关联文本块信息
*/
private String docId;
/**
* 片段索引下标
*/
@ExcelProperty(value = "片段索引下标")
private Long idx;
/**
* 文档内容
*/
@ExcelProperty(value = "文档内容")
private String content;
/**
* 备注
*/
@ExcelProperty(value = "备注")
private String remark;
}

View File

@@ -0,0 +1,112 @@
package org.ruoyi.domain.vo.knowledge;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import org.ruoyi.common.excel.annotation.ExcelDictFormat;
import org.ruoyi.common.excel.convert.ExcelDictConvert;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import org.ruoyi.domain.entity.knowledge.KnowledgeGraphInstance;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 知识图谱实例视图对象 knowledge_graph_instance
*
* @author ageerle
* @date 2025-12-17
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = KnowledgeGraphInstance.class)
public class KnowledgeGraphInstanceVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@ExcelProperty(value = "主键")
private Long id;
/**
* 图谱UUID
*/
@ExcelProperty(value = "图谱UUID")
private String graphUuid;
/**
* 关联knowledge_info.kid
*/
@ExcelProperty(value = "关联knowledge_info.kid")
private String knowledgeId;
/**
* 图谱名称
*/
@ExcelProperty(value = "图谱名称")
private String graphName;
/**
* 构建状态10构建中、20已完成、30失败
*/
@ExcelProperty(value = "构建状态10构建中、20已完成、30失败")
private Long graphStatus;
/**
* 节点数量
*/
@ExcelProperty(value = "节点数量")
private Long nodeCount;
/**
* 关系数量
*/
@ExcelProperty(value = "关系数量")
private Long relationshipCount;
/**
* 图谱配置(JSON格式)
*/
@ExcelProperty(value = "图谱配置(JSON格式)")
private String config;
/**
* LLM模型名称
*/
@ExcelProperty(value = "LLM模型名称")
private String modelName;
/**
* 实体类型(逗号分隔)
*/
@ExcelProperty(value = "实体类型", converter = ExcelDictConvert.class)
@ExcelDictFormat(readConverterExp = "逗=号分隔")
private String entityTypes;
/**
* 关系类型(逗号分隔)
*/
@ExcelProperty(value = "关系类型", converter = ExcelDictConvert.class)
@ExcelDictFormat(readConverterExp = "逗=号分隔")
private String relationTypes;
/**
* 错误信息
*/
@ExcelProperty(value = "错误信息")
private String errorMessage;
/**
* 备注
*/
@ExcelProperty(value = "备注")
private String remark;
}

View File

@@ -0,0 +1,123 @@
package org.ruoyi.domain.vo.knowledge;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import org.ruoyi.common.excel.annotation.ExcelDictFormat;
import org.ruoyi.common.excel.convert.ExcelDictConvert;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import org.ruoyi.domain.entity.knowledge.KnowledgeGraphSegment;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 知识图谱片段视图对象 knowledge_graph_segment
*
* @author ageerle
* @date 2025-12-17
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = KnowledgeGraphSegment.class)
public class KnowledgeGraphSegmentVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@ExcelProperty(value = "主键ID")
private Long id;
/**
* 片段UUID
*/
@ExcelProperty(value = "片段UUID")
private String uuid;
/**
* 知识库UUID
*/
@ExcelProperty(value = "知识库UUID")
private String kbUuid;
/**
* 知识库条目UUID
*/
@ExcelProperty(value = "知识库条目UUID")
private String kbItemUuid;
/**
* 文档UUID
*/
@ExcelProperty(value = "文档UUID")
private String docUuid;
/**
* 片段文本内容
*/
@ExcelProperty(value = "片段文本内容")
private String segmentText;
/**
* 片段索引(第几个片段)
*/
@ExcelProperty(value = "片段索引", converter = ExcelDictConvert.class)
@ExcelDictFormat(readConverterExp = "第=几个片段")
private Long chunkIndex;
/**
* 总片段数
*/
@ExcelProperty(value = "总片段数")
private Long totalChunks;
/**
* 抽取状态0-待处理 1-处理中 2-已完成 3-失败
*/
@ExcelProperty(value = "抽取状态0-待处理 1-处理中 2-已完成 3-失败")
private Long extractionStatus;
/**
* 抽取的实体数量
*/
@ExcelProperty(value = "抽取的实体数量")
private Long entityCount;
/**
* 抽取的关系数量
*/
@ExcelProperty(value = "抽取的关系数量")
private Long relationCount;
/**
* 消耗的token数
*/
@ExcelProperty(value = "消耗的token数")
private Long tokenUsed;
/**
* 错误信息
*/
@ExcelProperty(value = "错误信息")
private String errorMessage;
/**
* 用户ID
*/
@ExcelProperty(value = "用户ID")
private Long userId;
/**
* 备注
*/
@ExcelProperty(value = "备注")
private String remark;
}

View File

@@ -0,0 +1,104 @@
package org.ruoyi.domain.vo.knowledge;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import org.ruoyi.common.excel.annotation.ExcelDictFormat;
import org.ruoyi.common.excel.convert.ExcelDictConvert;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import org.ruoyi.domain.entity.knowledge.KnowledgeInfo;
import java.io.Serial;
import java.io.Serializable;
/**
* 知识库视图对象 knowledge_info
*
* @author ageerle
* @date 2025-12-17
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = KnowledgeInfo.class)
public class KnowledgeInfoVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@ExcelProperty(value = "主键")
private Long id;
/**
* 用户ID
*/
@ExcelProperty(value = "用户ID")
private Long userId;
/**
* 知识库名称
*/
@ExcelProperty(value = "知识库名称")
private String name;
/**
* 是否公开知识库0 否 1是
*/
@ExcelProperty(value = "是否公开知识库", converter = ExcelDictConvert.class)
@ExcelDictFormat(readConverterExp = "0=,否=,1=是")
private Long share;
/**
* 知识库描述
*/
@ExcelProperty(value = "知识库描述")
private String description;
/**
* 知识分隔符
*/
@ExcelProperty(value = "知识分隔符")
private String separator;
/**
* 重叠字符数
*/
@ExcelProperty(value = "重叠字符数")
private Long overlapChar;
/**
* 知识库中检索的条数
*/
@ExcelProperty(value = "知识库中检索的条数")
private Integer retrieveLimit;
/**
* 文本块大小
*/
@ExcelProperty(value = "文本块大小")
private Long textBlockSize;
/**
* 向量库
*/
@ExcelProperty(value = "向量库")
private String vectorModel;
/**
* 向量模型
*/
@ExcelProperty(value = "向量模型")
private String embeddingModel;
/**
* 备注
*/
@ExcelProperty(value = "备注")
private String remark;
}

View File

@@ -0,0 +1,74 @@
package org.ruoyi.domain.vo.mcp;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import org.ruoyi.domain.entity.mcp.McpMarket;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* MCP 市场视图对象
*
* @author ruoyi team
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = McpMarket.class)
public class McpMarketVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 市场ID
*/
@ExcelProperty(value = "市场ID")
private Long id;
/**
* 市场名称
*/
@ExcelProperty(value = "市场名称")
private String name;
/**
* 市场 URL
*/
@ExcelProperty(value = "市场URL")
private String url;
/**
* 市场描述
*/
@ExcelProperty(value = "市场描述")
private String description;
/**
* 认证配置
*/
@ExcelProperty(value = "认证配置")
private String authConfig;
/**
* 状态
*/
@ExcelProperty(value = "状态")
private String status;
/**
* 创建时间
*/
@ExcelProperty(value = "创建时间")
private Date createTime;
/**
* 更新时间
*/
@ExcelProperty(value = "更新时间")
private Date updateTime;
}

View File

@@ -0,0 +1,70 @@
package org.ruoyi.domain.vo.mcp;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import org.ruoyi.domain.entity.mcp.McpTool;
import java.io.Serializable;
import java.util.Date;
/**
* MCP 工具视图对象
*
* @author ruoyi team
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = McpTool.class)
public class McpToolVo implements Serializable {
/**
* 工具ID
*/
@ExcelProperty(value = "工具ID")
private Long id;
/**
* 工具名称
*/
@ExcelProperty(value = "工具名称")
private String name;
/**
* 工具描述
*/
@ExcelProperty(value = "工具描述")
private String description;
/**
* 工具类型
*/
@ExcelProperty(value = "工具类型")
private String type;
/**
* 状态
*/
@ExcelProperty(value = "状态")
private String status;
/**
* 配置信息
*/
@ExcelProperty(value = "配置信息")
private String configJson;
/**
* 创建时间
*/
@ExcelProperty(value = "创建时间")
private Date createTime;
/**
* 更新时间
*/
@ExcelProperty(value = "更新时间")
private Date updateTime;
}

View File

@@ -0,0 +1,23 @@
package org.ruoyi.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 计费类型枚举
*
* @author ageerle@163.com
* @date 2025-12-17
*/
@Getter
@AllArgsConstructor
public enum BillingType {
TOKEN("1", "token计费"),
COUNT("2", "次数计费"),
;
private final String code;
private final String description;
}

View File

@@ -0,0 +1,27 @@
package org.ruoyi.enums;
import lombok.Getter;
/**
* 模型分类
*
* @author ageerle@163.com
* @date 2025-12-14
*/
@Getter
public enum ChatModeType {
OLLAMA("ollama", "ollama本地部署模型"),
ZHI_PU("zhipu", "智谱清言"),
DEEP_SEEK("deepseek", "深度求索"),
QIAN_WEN("qianwen", "通义千问"),
OPEN_AI("openai", "openai"),
PPIO("ppio", "ppio");
private final String code;
private final String description;
ChatModeType(String code, String description) {
this.code = code;
this.description = description;
}
}

View File

@@ -0,0 +1,24 @@
package org.ruoyi.enums;
import lombok.Getter;
/**
* 是否显示
*
* @author ageerle@163.com
* @date 2025-12-14
*/
@Getter
public enum DisplayType {
HIDDEN("1", "不显示"),
VISIBLE("0", "显示");
private final String code;
private final String description;
DisplayType(String code, String description) {
this.code = code;
this.description = description;
}
}

View File

@@ -0,0 +1,23 @@
package org.ruoyi.enums;
import lombok.Getter;
/**
* 文生图模型分类
*
* @author Zengxb
* @date 2026-02-14
*/
@Getter
public enum ImageModeType {
TONGYI_WANX("Tongyiwanx", "万相");
private final String code;
private final String description;
ImageModeType(String code, String description) {
this.code = code;
this.description = description;
}
}

View File

@@ -0,0 +1,47 @@
package org.ruoyi.enums;
import lombok.Getter;
/**
* MCP 工具状态枚举
*
* @author ruoyi team
*/
@Getter
public enum McpToolStatus {
/**
* 启用状态
*/
ENABLED("ENABLED", "启用"),
/**
* 禁用状态
*/
DISABLED("DISABLED", "禁用");
/**
* 状态值(存储到数据库)
*/
private final String value;
/**
* 状态描述
*/
private final String description;
McpToolStatus(String value, String description) {
this.value = value;
this.description = description;
}
/**
* 判断是否为启用状态
*
* @param value 状态值
* @return 是否启用
*/
public static boolean isEnabled(String value) {
return ENABLED.value.equals(value);
}
}

View File

@@ -0,0 +1,8 @@
package org.ruoyi.enums;
/**
* 模态类型
*/
public enum ModalityType {
TEXT, IMAGE, AUDIO, VIDEO, MULTI
}

View File

@@ -0,0 +1,77 @@
package org.ruoyi.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 模型分类枚举
*
* @author ageerle@163.com
* @date 2025-12-30
*/
@Getter
@AllArgsConstructor
public enum ModelType {
/**
* 聊天模型
*/
CHAT(0, "chat", "聊天模型"),
/**
* 图片识别模型
*/
IMAGE(1, "image", "图片识别模型"),
/**
* 知识库向量模型
*/
VECTOR(3, "vector", "知识库向量模型"),
/**
* 知识库内容重新排序模型
*/
RERANKER(4, "reranker", "知识库内容重新排序模型"),
/**
* 语音生成模型
*/
AUDIO(5, "audio", "语音生成模型"),
/**
* 语音转文本模型
*/
TEXT(6, "text", "语音转文本模型"),
/**
* 文生视频模型
*/
VIDEO(7, "video", "文生视频模型"),
/**
* 文生PPT模型
*/
PPT(8, "ppt", "文生PPT模型"),
/**
* 文生音乐模型
*/
MUSIC(9, "music", "文生音乐模型"),
;
/**
* 编码
*/
private final Integer code;
/**
* 标识
*/
private final String key;
/**
* 描述
*/
private final String description;
}

View File

@@ -0,0 +1,53 @@
package org.ruoyi.enums;
import lombok.Getter;
/**
* 构建任务状态枚举
*
* @author ruoyi
* @date 2025-09-30
*/
@Getter
public enum TaskStatusEnum {
/**
* 待执行
*/
PENDING(1, "待执行"),
/**
* 执行中
*/
RUNNING(2, "执行中"),
/**
* 成功
*/
SUCCESS(3, "成功"),
/**
* 失败
*/
FAILED(4, "失败");
private final Integer code;
private final String description;
TaskStatusEnum(Integer code, String description) {
this.code = code;
this.description = description;
}
/**
* 根据code获取枚举
*/
public static TaskStatusEnum getByCode(Integer code) {
for (TaskStatusEnum status : values()) {
if (status.getCode().equals(code)) {
return status;
}
}
return null;
}
}

View File

@@ -0,0 +1,48 @@
package org.ruoyi.enums;
import lombok.Getter;
/**
* 构建任务类型枚举
*
* @author ruoyi
* @date 2025-09-30
*/
@Getter
public enum TaskTypeEnum {
/**
* 全量构建
*/
FULL_BUILD(1, "全量构建"),
/**
* 增量更新
*/
INCREMENTAL_UPDATE(2, "增量更新"),
/**
* 重建
*/
REBUILD(3, "重建");
private final Integer code;
private final String description;
TaskTypeEnum(Integer code, String description) {
this.code = code;
this.description = description;
}
/**
* 根据code获取枚举
*/
public static TaskTypeEnum getByCode(Integer code) {
for (TaskTypeEnum type : values()) {
if (type.getCode().equals(code)) {
return type;
}
}
return null;
}
}

View File

@@ -0,0 +1,47 @@
package org.ruoyi.factory;
import org.ruoyi.common.chat.service.chat.IChatService;
import org.ruoyi.service.chat.AbstractChatService;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 聊天服务工厂类
*
* @author ageerle@163.com
* @date 2025-12-13
*/
@Component
public class ChatServiceFactory implements ApplicationContextAware {
private final Map<String, AbstractChatService> chatServiceMap = new ConcurrentHashMap<>();
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
// 初始化时收集所有IChatService的实现
Map<String, AbstractChatService> serviceMap = applicationContext.getBeansOfType(AbstractChatService.class);
for (AbstractChatService service : serviceMap.values()) {
if (service != null ) {
chatServiceMap.put(service.getProviderName(), service);
}
}
}
/**
* 获取原始服务(不包装代理)
*/
public AbstractChatService getOriginalService(String category) {
AbstractChatService service = chatServiceMap.get(category);
if (service == null) {
throw new IllegalArgumentException("不支持的模型类别: " + category);
}
return service;
}
}

View File

@@ -0,0 +1,118 @@
package org.ruoyi.factory;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.ruoyi.common.chat.service.chat.IChatModelService;
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
import org.ruoyi.service.embed.BaseEmbedModelService;
import org.ruoyi.service.embed.MultiModalEmbedModelService;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 嵌入模型工厂服务类
* 负责创建和管理各种嵌入模型实例
*/
@Service
@RequiredArgsConstructor
@Slf4j
public class EmbeddingModelFactory {
private final ApplicationContext applicationContext;
private final IChatModelService chatModelService;
// 模型缓存使用ConcurrentHashMap保证线程安全
private final Map<String, BaseEmbedModelService> modelCache = new ConcurrentHashMap<>();
/**
* 创建嵌入模型实例
* 如果模型已存在于缓存中,则直接返回;否则创建新的实例
*
* @param embeddingModelName 嵌入模型名称
*/
public BaseEmbedModelService createModel(String embeddingModelName) {
return modelCache.computeIfAbsent(embeddingModelName, name -> {
ChatModelVo modelConfig = chatModelService.selectModelByName(embeddingModelName);
if (modelConfig == null) {
throw new IllegalArgumentException("未找到模型配置name=" + name);
}
return createModelInstance(modelConfig.getProviderCode(), modelConfig);
});
}
/**
* 检查模型是否支持多模态
*
* @param embeddingModelName 嵌入模型名称
* @return boolean 如果模型支持多模态则返回true否则返回false
*/
public boolean isMultimodalModel(String embeddingModelName) {
return createModel(embeddingModelName) instanceof MultiModalEmbedModelService;
}
/**
* 创建多模态嵌入模型实例
*
* @param embeddingModelName 嵌入模型名称
* @return MultiModalEmbedModelService 多模态嵌入模型服务实例
* @throws IllegalArgumentException 当模型不支持多模态时抛出
*/
public MultiModalEmbedModelService createMultimodalModel(String embeddingModelName) {
BaseEmbedModelService model = createModel(embeddingModelName);
if (model instanceof MultiModalEmbedModelService) {
return (MultiModalEmbedModelService) model;
}
throw new IllegalArgumentException("该模型不支持多模态");
}
/**
* 刷新模型缓存
* 根据给定的嵌入模型ID从缓存中移除对应的模型
*
* @param embeddingModelId 嵌入模型的唯一标识ID
*/
public void refreshModel(Long embeddingModelId) {
// 从模型缓存中移除指定ID的模型
modelCache.remove(embeddingModelId);
}
/**
* 获取所有支持模型工厂的列表
*
* @return List<String> 支持的模型工厂名称列表
*/
public List<String> getSupportedFactories() {
return new ArrayList<>(applicationContext.getBeansOfType(BaseEmbedModelService.class)
.keySet());
}
/**
* 创建具体的模型实例
* 根据提供的工厂名称和配置信息创建并配置模型实例
*
* @param factory 工厂名称,用于标识模型类型
* @param config 模型配置信息
* @return BaseEmbedModelService 配置好的模型实例
* @throws IllegalArgumentException 当无法获取指定的模型实例时抛出
*/
private BaseEmbedModelService createModelInstance(String factory, ChatModelVo config) {
try {
// 从Spring上下文中获取模型实例
BaseEmbedModelService model = applicationContext.getBean(factory, BaseEmbedModelService.class);
// 配置模型参数
model.configure(config);
log.info("成功创建嵌入模型: factory={}, modelId={}", config.getProviderCode(), config.getId());
return model;
} catch (NoSuchBeanDefinitionException e) {
throw new IllegalArgumentException("获取不到嵌入模型: " + factory, e);
}
}
}

View File

@@ -0,0 +1,37 @@
package org.ruoyi.factory;
import lombok.AllArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.ruoyi.constant.FileTypeConstants;
import org.ruoyi.service.knowledge.ResourceLoader;
import org.ruoyi.service.knowledge.impl.loader.*;
import org.ruoyi.service.knowledge.impl.split.*;
import org.springframework.stereotype.Component;
@AllArgsConstructor
@Component
public class ResourceLoaderFactory {
private final CharacterTextSplitter characterTextSplitter;
private final CodeTextSplitter codeTextSplitter;
private final MarkdownTextSplitter markdownTextSplitter;
private final ExcelTextSplitter excelTextSplitter;
public ResourceLoader getLoaderByFileType(String fileType) {
fileType = StringUtils.removeStart(fileType, ".");
if (FileTypeConstants.isTextFile(fileType)) {
return new TextFileLoader(characterTextSplitter);
} else if (FileTypeConstants.isWord(fileType)) {
return new WordLoader(characterTextSplitter);
} else if (FileTypeConstants.isPdf(fileType)) {
return new PdfFileLoader(characterTextSplitter);
} else if (FileTypeConstants.isMdFile(fileType)) {
return new MarkDownFileLoader(markdownTextSplitter);
} else if (FileTypeConstants.isCodeFile(fileType)) {
return new CodeFileLoader(codeTextSplitter);
} else if (FileTypeConstants.isExcel(fileType)) {
return new ExcelFileLoader(excelTextSplitter);
} else {
return new TextFileLoader(characterTextSplitter);
}
}
}

View File

@@ -0,0 +1,58 @@
package org.ruoyi.factory;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.ruoyi.config.VectorStoreProperties;
import org.ruoyi.service.vector.VectorStoreService;
import org.ruoyi.service.vector.impl.MilvusVectorStoreStrategy;
import org.ruoyi.service.vector.impl.WeaviateVectorStoreStrategy;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
/**
* 向量库策略工厂
* 根据配置动态选择向量库实现
*
* @author Yzm
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class VectorStoreStrategyFactory {
private final VectorStoreProperties vectorStoreProperties;
private final WeaviateVectorStoreStrategy weaviateStrategy;
private final MilvusVectorStoreStrategy milvusStrategy;
private Map<String, VectorStoreService> strategies;
@PostConstruct
public void init() {
strategies = new HashMap<>();
strategies.put("weaviate", weaviateStrategy);
strategies.put("milvus", milvusStrategy);
log.info("向量库策略工厂初始化完成,支持的策略: {}", strategies.keySet());
}
/**
* 获取当前配置的向量库策略
*/
public VectorStoreService getStrategy() {
String vectorStoreType = vectorStoreProperties.getType();
if (vectorStoreType == null || vectorStoreType.trim().isEmpty()) {
vectorStoreType = "weaviate"; // 默认使用weaviate
}
VectorStoreService strategy = strategies.get(vectorStoreType.toLowerCase());
if (strategy == null) {
log.warn("未找到向量库策略: {}, 使用默认策略: weaviate", vectorStoreType);
strategy = strategies.get("weaviate");
}
log.debug("使用向量库策略: {}", vectorStoreType);
return strategy;
}
}

View File

@@ -0,0 +1,15 @@
package org.ruoyi.mapper.chat;
import org.ruoyi.common.chat.domain.vo.chat.ChatMessageVo;
import org.ruoyi.common.chat.entity.chat.ChatMessage;
import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus;
/**
* 聊天消息Mapper接口
*
* @author ageerle
* @date 2025-12-14
*/
public interface ChatMessageMapper extends BaseMapperPlus<ChatMessage, ChatMessageVo> {
}

View File

@@ -0,0 +1,15 @@
package org.ruoyi.mapper.chat;
import org.ruoyi.common.chat.entity.chat.ChatModel;
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus;
/**
* 模型管理Mapper接口
*
* @author ageerle
* @date 2025-12-14
*/
public interface ChatModelMapper extends BaseMapperPlus<ChatModel, ChatModelVo> {
}

View File

@@ -0,0 +1,15 @@
package org.ruoyi.mapper.chat;
import org.ruoyi.domain.entity.chat.ChatProvider;
import org.ruoyi.domain.vo.chat.ChatProviderVo;
import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus;
/**
* 厂商管理Mapper接口
*
* @author ageerle
* @date 2025-12-14
*/
public interface ChatProviderMapper extends BaseMapperPlus<ChatProvider, ChatProviderVo> {
}

View File

@@ -0,0 +1,15 @@
package org.ruoyi.mapper.chat;
import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus;
import org.ruoyi.domain.entity.chat.ChatSession;
import org.ruoyi.domain.vo.chat.ChatSessionVo;
/**
* 会话管理Mapper接口
*
* @author ageerle
* @date 2025-12-30
*/
public interface ChatSessionMapper extends BaseMapperPlus<ChatSession, ChatSessionVo> {
}

View File

@@ -0,0 +1,15 @@
package org.ruoyi.mapper.knowledge;
import org.ruoyi.domain.entity.knowledge.KnowledgeAttach;
import org.ruoyi.domain.vo.knowledge.KnowledgeAttachVo;
import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus;
/**
* 知识库附件Mapper接口
*
* @author ageerle
* @date 2025-12-17
*/
public interface KnowledgeAttachMapper extends BaseMapperPlus<KnowledgeAttach, KnowledgeAttachVo> {
}

View File

@@ -0,0 +1,15 @@
package org.ruoyi.mapper.knowledge;
import org.ruoyi.domain.entity.knowledge.KnowledgeFragment;
import org.ruoyi.domain.vo.knowledge.KnowledgeFragmentVo;
import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus;
/**
* 知识片段Mapper接口
*
* @author ageerle
* @date 2025-12-17
*/
public interface KnowledgeFragmentMapper extends BaseMapperPlus<KnowledgeFragment, KnowledgeFragmentVo> {
}

View File

@@ -0,0 +1,15 @@
package org.ruoyi.mapper.knowledge;
import org.ruoyi.domain.entity.knowledge.KnowledgeGraphInstance;
import org.ruoyi.domain.vo.knowledge.KnowledgeGraphInstanceVo;
import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus;
/**
* 知识图谱实例Mapper接口
*
* @author ageerle
* @date 2025-12-17
*/
public interface KnowledgeGraphInstanceMapper extends BaseMapperPlus<KnowledgeGraphInstance, KnowledgeGraphInstanceVo> {
}

View File

@@ -0,0 +1,15 @@
package org.ruoyi.mapper.knowledge;
import org.ruoyi.domain.entity.knowledge.KnowledgeGraphSegment;
import org.ruoyi.domain.vo.knowledge.KnowledgeGraphSegmentVo;
import org.ruoyi.common.mybatis.core.mapper.BaseMapperPlus;
/**
* 知识图谱片段Mapper接口
*
* @author ageerle
* @date 2025-12-17
*/
public interface KnowledgeGraphSegmentMapper extends BaseMapperPlus<KnowledgeGraphSegment, KnowledgeGraphSegmentVo> {
}

Some files were not shown because too many files have changed in this diff Show More