48 Commits

Author SHA1 Message Date
ageerle
3071bfd0f9 Merge pull request #282 from Anush008/main
docs: Docker Compose setup for Qdrant
2026-03-28 20:33:25 +08:00
Anush008
7bb938c145 docs: Docker Compose setup for Qdrant 2026-03-28 13:37:53 +05:30
ageerle
75b21d3633 Merge pull request #281 from Anush008/main
feat: Adds support for Qdrant vector search
2026-03-27 21:51:03 +08:00
Anush008
7ed9d8def4 chore: Rename METADATA_DOC_ID_KEY 2026-03-27 18:36:30 +05:30
Anush008
63ed7ddb02 feat: Adds support for Qdrant vector search 2026-03-27 18:31:05 +05:30
ageerle
11696a016d fix: 修复文件类型匹配和知识库切割配置问题
1. 修复 ResourceLoaderFactory 文件扩展名匹配问题
   - 去除扩展名前导点,确保 .pdf 能正确匹配 PDF 解析器
   - 修复 PDF/Word/Excel 等文件走错解析逻辑的问题

2. 优化文本切割器动态配置
   - CharacterTextSplitter 和 ExcelTextSplitter 支持从知识库读取配置
   - 根据 kid 查询 separator、textBlockSize、overlapChar
   - 查询失败时降级使用默认配置

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-25 18:51:01 +08:00
ageerle
1a10104751 docs: 更新README文档,同步中英文版本
- 移除优秀开源项目推荐章节
- 英文版添加Docker部署完整文档
- 英文版添加技术架构详细描述
- 英文版添加RAG排查手册链接
- 统一核心功能表格格式

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-21 13:02:46 +08:00
ageerle
f95cb17933 chore: 删除ruoyi-modules-api模块
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-21 12:31:09 +08:00
ageerle
0687b49542 Merge branch 'v3.0.0' into main
合并 v3.0.0 分支到 main,包含以下主要更新:
- 重构聊天模块架构,引入Handler模式
- 添加 Docker 部署支持
- 恢复 MCP 模块功能
- 工作流与大模型聊天对话整合
- 多项 bug 修复和文档更新

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-21 12:15:40 +08:00
ageerle
27ad00ac3a refactor: 抽离特殊聊天模式处理逻辑
- 将工作流、人机交互恢复、思考模式处理逻辑抽离为独立方法
- 新增 handleSpecialChatModes 方法统一处理特殊模式
- 新增 handleThinkingMode 方法专门处理思考模式
- 简化 sseChat 方法结构,提高代码可读性

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-20 10:38:54 +08:00
ageerle
c84d6247b0 refactor: 重构聊天模块架构
- 删除废弃的ChatMessageDTO、ChatContext、AbstractChatMessageService等类
- 迁移ChatServiceFactory和IChatMessageService到ruoyi-chat模块
- 重构ChatHandler体系,移除DefaultChatHandler和ChatContextBuilder
- 优化SSE消息处理,新增SseEventDto
- 简化各AI服务提供商实现类代码
- 优化工作流节点消息处理逻辑

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-20 01:20:41 +08:00
ageerle
f582f38570 docs: 整理Docker配置文件并更新文档
- 将Docker相关配置文件移动到docs/docker/ruoyi-ai/目录
- 更新README.md核心亮点表格格式
- 新增流程编排模块详细说明文档

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-19 15:22:47 +08:00
ageerle
13800dc389 Merge pull request #277 from XiaoEns/v3.0.0
fix: 修复知识库附件上传乱码问题
2026-03-17 22:07:32 +08:00
xiaoen
619d9b1e84 fix: 兼容旧代码,添加deepseek服务调用 2026-03-17 21:30:56 +08:00
xiaoen
556cc93f14 fix: 修复知识库附件上传乱码问题 2026-03-17 18:57:30 +08:00
ageerle
a50375616e Merge pull request #276 from LM20230311/v3.0.0
V3.0.0
2026-03-17 14:09:52 +08:00
LM20230311
e33447d023 Merge branch 'v3.0.0' of https://github.com/ageerle/ruoyi-ai into v3.0.0 2026-03-17 13:48:21 +08:00
LM20230311
7be277b3e6 Merge branch 'v3.0.0-deploy' into v3.0.0 2026-03-17 13:47:27 +08:00
LM20230311
5fa385e90b docs: add Docker deployment instructions and service port mappings to README; update sys_client table in MySQL script 2026-03-17 11:09:13 +08:00
LM20230311
0a78966737 chore: update UPSTREAM_HOST configuration in docker-compose-all.yaml to remove http:// prefix 2026-03-17 10:38:43 +08:00
LM20230311
0eb7f00867 chore: add environment variables for backend API configuration in docker-compose-all.yaml 2026-03-16 17:14:01 +08:00
LM20230311
00ce7f2d98 chore: update port mappings in docker-compose.yaml for MySQL, Weaviate, and backend services 2026-03-16 15:32:27 +08:00
ageerle
003c066361 chore(sql): 更新数据库脚本,移除chat_config表
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 23:57:55 +08:00
ageerle
a5e7c59fd4 refactor: 重构项目架构,优化向量服务
- 移除 Graph 知识图谱相关模块(Neo4j、GraphRAG等)
- 移除 demo、job、wechat 示例模块,简化项目结构
- 修复向量维度获取方式,改为从数据库配置读取
- 添加 gRPC BOM 依赖管理,解决 Milvus SDK 版本冲突
- 新增 PPIO 服务和 Embedding 提供者支持
- 清理冗余代码和未使用的依赖

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 23:39:30 +08:00
ageerle
accac603cf chore(sql): 更新数据库脚本v3版本
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 14:16:45 +08:00
ageerle
7c96c730e6 refactor(chat): 重构聊天服务架构,引入Handler模式
主要变更:
1. 移除ruoyi-ai-copilot模块
2. 重构docker配置目录结构,统一迁移至docs/docker/
3. 聊天服务引入Handler模式:
   - 新增ChatHandler接口及多种实现
   - DefaultChatHandler: 默认聊天处理
   - AgentChatHandler: Agent模式处理
   - WorkflowChatHandler: 工作流处理
   - ResumeChatHandler: 恢复会话处理
   - ChatContextBuilder: 上下文构建器
4. 简化AbstractStreamingChatService和ChatServiceFacade代码
5. 优化各Provider实现,统一代码风格

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 14:14:56 +08:00
LM20230311
a2dbdb30ff fix: remove unnecessary COPY command for ruoyi-modules-api in Dockerfile.backend 2026-03-11 09:57:47 +08:00
LM20230311
772e4af9bf Merge remote-tracking branch 'upstream/v3.0.0' into v3.0.0-deploy 2026-03-11 09:35:48 +08:00
ageerle
a8bd4b47a0 chore: 移除项目文档和技术交流链接
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 11:44:07 +08:00
ageerle
a59ddf6070 refactor: 重构Issue模板为通用格式
- 删除企业合作登记模板
- 新增漏洞报告模板
- 新增想法建议模板
- 新增自定义模板

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 11:39:06 +08:00
ageerle
797ecbb054 feat: 新增联系人/联系方式字段并添加填写示例
- 新增联系人/联系方式字段,支持登记后主动联系
- 在可复制模板中添加完整填写示例

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 11:30:15 +08:00
ageerle
b6b78afea9 refactor: 优化企业合作登记模板格式
- 改为评论登记模式,用户在Issue下评论填写
- 提供格式预览和可复制模板
- 新增公司Logo和项目Logo字段
- 移除联系方式模块,保护隐私

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 11:20:31 +08:00
ageerle
02240f3fd0 feat: 添加企业AI应用合作登记Issue模板
- 新增企业合作登记模板,用于收集企业AI应用需求
- 包含基本信息、AI应用需求、联系方式三个模块
- 预设筛选字段便于评估合作匹配度

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 11:07:25 +08:00
LM20230311
1600ab384e feat: 支持手动挂载数据卷; 2026-03-05 09:35:27 +08:00
LM20230311
418805a1ef feat: 源码docker部署完成; 2026-03-05 09:27:34 +08:00
LM20230311
4c4b52bca7 feat: 忽略data目录; 2026-03-05 09:07:10 +08:00
LM20230311
7245259bc4 fix: 忽略所有错误以确保SQL脚本继续执行 2026-03-04 17:57:08 +08:00
LM20230311
6690c8204c fix: 修复表数量异常问题; 2026-03-04 17:56:20 +08:00
LM20230311
d8fc597f85 fix: 解决mysql容器长时间建表导致失败问题; 2026-03-04 17:34:38 +08:00
LM20230311
5a716da5a6 feat: 添加docker部署文件; 2026-03-04 16:31:54 +08:00
ageerle
a916f14efc Merge pull request #268 from onestardao/main
docs: add structured RAG 16-problem troubleshooting guide and README entry
2026-03-03 10:40:32 +08:00
PSBigBig × MiniPS
523628ade6 docs: add structured RAG 16-problem troubleshooting guide and README entry 2026-03-02 19:01:59 +08:00
PSBigBig × MiniPS
2259a2f717 docs: add structured RAG 16-problem troubleshooting guide and README entry
This commit introduces a structured RAG troubleshooting guide based on a fixed 16-problem failure map.

Changes include:

1. Added a dedicated RAG troubleshooting document:
   - docs/troubleshooting/rag-failures.md
   - Includes full 16-problem map (No.1–No.16) with layer tags [IN]/[RE]/[ST]/[OP]
   - Adds symptom-based entry points
   - Adds layer-based diagnostics section
   - Adds issue reporting template referencing No.X labels

2. Updated README.md:
   - Added entry link under the "使用文档" section
   - Direct link to the new RAG troubleshooting guide
   - Keeps README structure and tone consistent

No functional changes.
Documentation only.
2026-03-02 18:51:36 +08:00
PSBigBig × MiniPS
8df37274da docs: add RAG answer troubleshooting guide 2026-03-02 17:44:59 +08:00
PSBigBig × MiniPS
393057ab24 docs: add RAG answer troubleshooting guide 2026-03-02 17:41:47 +08:00
ageerle
ee8c882b6f Merge pull request #256 from StevenJack666/main
修改AI工作流后端逻辑
2026-02-11 17:03:31 +08:00
zhang
69ec2a33a4 init 2026-02-09 17:47:18 +08:00
zhang
1cd8ae1cd9 人机交互节点逻辑修改 2026-02-09 17:43:29 +08:00
281 changed files with 3494 additions and 25151 deletions

1
.gitignore vendored
View File

@@ -44,6 +44,7 @@ nbdist/
*.log.gz *.log.gz
*.xml.versionsBackup *.xml.versionsBackup
*.swp *.swp
data/
!*/build/*.java !*/build/*.java
!*/build/*.html !*/build/*.html

169
README.md
View File

@@ -19,7 +19,7 @@
### 企业级AI助手平台 ### 企业级AI助手平台
*开箱即用的全栈AI平台支持多智能体协同、Supervisor模式编排、多种决策模型,提供先进的RAG技术和可视化流程编排能力* *开箱即用的全栈AI平台支持多智能体协同、Supervisor模式编排、多种决策模式、RAG技术和流程编排能力*
**[English](README_EN.md)** | **[📖 使用文档](https://doc.pandarobot.chat)** | **[English](README_EN.md)** | **[📖 使用文档](https://doc.pandarobot.chat)** |
**[🚀 在线体验](https://web.pandarobot.chat)** | **[🐛 问题反馈](https://github.com/ageerle/ruoyi-ai/issues)** | **[💡 功能建议](https://github.com/ageerle/ruoyi-ai/issues)** **[🚀 在线体验](https://web.pandarobot.chat)** | **[🐛 问题反馈](https://github.com/ageerle/ruoyi-ai/issues)** | **[💡 功能建议](https://github.com/ageerle/ruoyi-ai/issues)**
@@ -27,18 +27,15 @@
</div> </div>
## ✨ 核心亮点 ## ✨ 核心亮点
| 模块 | 现有能力 | 扩展方向 | | 模块 | 现有能力
|:---:|---|---| |:----------:|---
| **模型管理** | 多模型接入(OpenAI/DeepSeek/通义/智谱)、多模态理解、Coze/DIFY/FastGPT平台集成 | 自动模式、容错机制 | | **模型管理** | 多模型接入(OpenAI/DeepSeek/通义/智谱)、多模态理解、Coze/DIFY/FastGPT平台集成
| **知识** | 本地RAG + 向量库(Milvus/Weaviate) + 知识图谱 + 文档解析 +重排序 | 音频视频解析、知识出处 | | **知识管理** | 本地RAG + 向量库(Milvus/Weaviate/Qdrant) + 文档解析
| **工具管理** | Mcp协议集成、Skills能力 + 可扩展工具生态 | 工具插件市场、toolAgent自动加载工具 | | **工具管理** | Mcp协议集成、Skills能力 + 可扩展工具生态
| **流程编排** | 可视化工作流设计器、节点拖拽编排、SSE流式执行,目前已经支持模型调用,邮件发送,人工审核等节点 | 更多节点类型 | | **流程编排** | 可视化工作流设计器、节点拖拽编排、SSE流式执行,目前已经支持模型调用,邮件发送,人工审核等节点
| **多智能体** | 基于Langchain4j的Agent框架、Supervisor模式编排,支持多种决策模型 | 智能体可配置 | | **多智能体** | 基于Langchain4j的Agent框架、Supervisor模式编排,支持多种决策模型
| **AI编程** | 智能代码分析、项目脚手架生成、Copilot助手 | 代码生成优化 |
## 🚀 快速体验 ## 🚀 快速体验
@@ -66,17 +63,138 @@
### 核心框架 ### 核心框架
- **后端架构**Spring Boot 4.0 + Spring ai 2.0 + Langchain4j - **后端架构**Spring Boot 4.0 + Spring ai 2.0 + Langchain4j
- **数据存储**MySQL 8.0 + Redis + 向量数据库Milvus/Weaviate - **数据存储**MySQL 8.0 + Redis + 向量数据库Milvus/Weaviate/Qdrant
- **前端技术**Vue 3 + Vben Admin + element-plus-x - **前端技术**Vue 3 + Vben Admin + element-plus-x
- **安全认证**Sa-Token + JWT 双重保障 - **安全认证**Sa-Token + JWT 双重保障
- **文档处理**PDF、Word、Excel 解析,图像智能分析
- **实时通信**WebSocket 实时通信SSE 流式响应
- **系统监控**:完善的日志体系、性能监控、服务健康检查
## 🐳 Docker 部署
本项目提供两种 Docker 部署方式:
### 方式一:一键启动所有服务(推荐)
使用 `docker-compose-all.yaml` 可以一键启动所有服务(包括后端、管理端、用户端及依赖服务):
```bash
# 克隆仓库
git clone https://github.com/ageerle/ruoyi-ai.git
cd ruoyi-ai
# 启动所有服务(从镜像仓库拉取预构建镜像)
docker-compose -f docker-compose-all.yaml up -d
# 查看服务状态
docker-compose -f docker-compose-all.yaml ps
# 访问服务
# 管理端: http://localhost:25666 (admin / admin123)
# 用户端: http://localhost:25137
# 后端API: http://localhost:26039
```
### 方式二:分步部署(源码编译)
如果您需要从源码构建后端服务,请按照以下步骤操作:
#### 第一步:部署后端服务
```bash
# 进入后端项目目录
cd ruoyi-ai
# 启动后端服务(源码编译构建)
docker-compose up -d --build
# 等待后端服务启动完成
docker-compose logs -f backend
```
#### 第二步:部署管理端
```bash
# 进入管理端项目目录
cd ruoyi-admin
# 构建并启动管理端
docker-compose up -d --build
# 访问管理端
# 地址: http://localhost:5666
```
#### 第三步:部署用户端(可选)
```bash
# 进入用户端项目目录
cd ruoyi-web
# 构建并启动用户端
docker-compose up -d --build
# 访问用户端
# 地址: http://localhost:5137
```
### 服务端口说明
| 服务 | 一键启动端口 | 分步部署端口 | 说明 |
|------|-------------|-------------|------|
| 管理端 | 25666 | 5666 | 管理后台访问地址 |
| 用户端 | 25137 | 5137 | 用户前端访问地址 |
| 后端服务 | 26039 | 6039 | 后端 API 服务 |
| MySQL | 23306 | 23306 | 数据库服务 |
| Redis | 26379 | 6379 | 缓存服务 |
| Weaviate | 28080 | 28080 | 向量数据库 |
| MinIO API | 29000 | 9000 | 对象存储 API |
| MinIO Console | 29090 | 9090 | 对象存储控制台 |
### 镜像仓库
所有镜像托管在阿里云容器镜像服务:
```
crpi-31mraxd99y2gqdgr.cn-beijing.personal.cr.aliyuncs.com/ruoyi_ai
```
可用镜像:
- `mysql:v3` - MySQL 数据库(包含初始化 SQL
- `redis:6.2` - Redis 缓存
- `weaviate:1.30.0` - 向量数据库
- `minio:latest` - 对象存储
- `ruoyi-ai-backend:latest` - 后端服务
- `ruoyi-ai-admin:latest` - 管理端前端
- `ruoyi-ai-web:latest` - 用户端前端
### 常用命令
```bash
# 停止所有服务
docker-compose -f docker-compose-all.yaml down
# 查看服务日志
docker-compose -f docker-compose-all.yaml logs -f [服务名]
# 重启某个服务
docker-compose -f docker-compose-all.yaml restart [服务名]
```
## 📚 使用文档 ## 📚 使用文档
想要深入了解安装部署、功能配置和二次开发? 想要深入了解安装部署、功能配置和二次开发?
**👉 [完整使用文档](https://doc.pandarobot.chat)** **👉 [完整使用文档](https://doc.pandarobot.chat)**
遇到知识库或 RAG 回答异常问题?
**👉 [RAG 回答异常排查手册](docs/troubleshooting/rag-failures.md)**
---
## 🤝 参与贡献 ## 🤝 参与贡献
我们热烈欢迎社区贡献!无论您是资深开发者还是初学者,都可以为项目贡献力量 💪 我们热烈欢迎社区贡献!无论您是资深开发者还是初学者,都可以为项目贡献力量 💪
@@ -110,9 +228,6 @@
算力和模型 API 服务 算力和模型 API 服务
- [优云智算](https://www.compshare.cn/?ytag=GPU_YY-gh_ruoyi) - 万卡RTX40系GPU+海内外主流模型API服务秒级响应按量计费新客免费用。 - [优云智算](https://www.compshare.cn/?ytag=GPU_YY-gh_ruoyi) - 万卡RTX40系GPU+海内外主流模型API服务秒级响应按量计费新客免费用。
## 优秀开源项目及社区推荐
- [imaiwork](https://gitee.com/tsinghua-open/imaiwork) - AI手机开源版AI获客手机项目基于无障碍模式RPA比豆包AI手机更强大。
## 💬 社区交流 ## 💬 社区交流
<div align="center"> <div align="center">
@@ -136,30 +251,6 @@
</div> </div>
--- ---
## 📺 视频教程
<div align="center">
<table>
<tr>
<td align="center">
<img src="docs/image/dy.png" alt="微信二维码" width="200" height="200"><br>
<strong>打开抖音扫一扫</strong><br>
<em>获取免费视频教程</em>
</td>
<td align="center">
<img src="docs/image/bibi.png" alt="QQ群二维码" width="200" height="200"><br>
<strong>打开B站扫一扫</strong><br>
<em>获取免费视频教程</em>
</td>
</tr>
</table>
</div>
<div align="center"> <div align="center">
**[⭐ 点个Star支持一下](https://github.com/ageerle/ruoyi-ai)** • **[ Fork 开始贡献](https://github.com/ageerle/ruoyi-ai/fork)** • **[📚 English](README_EN.md)** • **[📖 查看完整文档](https://doc.pandarobot.chat)** **[⭐ 点个Star支持一下](https://github.com/ageerle/ruoyi-ai)** • **[ Fork 开始贡献](https://github.com/ageerle/ruoyi-ai/fork)** • **[📚 English](README_EN.md)** • **[📖 查看完整文档](https://doc.pandarobot.chat)**

View File

@@ -32,14 +32,13 @@
## ✨ Core Features ## ✨ Core Features
| Module | Current Capabilities | Extension Direction | | Module | Current Capabilities |
|:---:|---|---| |:---:|---|
| **Model Management** | Multi-model integration (OpenAI/DeepSeek/Tongyi/Zhipu), multi-modal understanding, Coze/DIFY/FastGPT platform integration | Auto mode, fault tolerance | | **Model Management** | Multi-model integration (OpenAI/DeepSeek/Tongyi/Zhipu), multi-modal understanding, Coze/DIFY/FastGPT platform integration |
| **Knowledge Base** | Local RAG + Vector DB (Milvus/Weaviate) + Knowledge Graph + Document parsing + Reranking | Audio/video parsing, knowledge source | | **Knowledge Base** | Local RAG + Vector DB (Milvus/Weaviate/Qdrant) + Document parsing |
| **Tool Management** | MCP protocol integration, Skills capability + Extensible tool ecosystem | Tool plugin marketplace, toolAgent auto-loading | | **Tool Management** | MCP protocol integration, Skills capability + Extensible tool ecosystem |
| **Workflow Orchestration** | Visual workflow designer, drag-and-drop node orchestration, SSE streaming execution, currently supports model (with RAG) calls, email sending, manual review nodes | More node types | | **Workflow Orchestration** | Visual workflow designer, drag-and-drop node orchestration, SSE streaming execution, currently supports model calls, email sending, manual review nodes |
| **Multi-Agent** | Agent framework based on Langchain4j, Supervisor mode orchestration, supports multiple decision models | Configurable agents | | **Multi-Agent** | Agent framework based on Langchain4j, Supervisor mode orchestration, supports multiple decision models |
| **AI Coding** | Intelligent code analysis, project scaffolding generation, Copilot assistant | Code generation optimization |
## 🚀 Quick Start ## 🚀 Quick Start
@@ -59,7 +58,7 @@
| 🛠️ Admin Panel | [ruoyi-admin](https://github.com/ageerle/ruoyi-admin) | [ruoyi-admin](https://gitee.com/ageerle/ruoyi-admin) | [ruoyi-admin](https://gitcode.com/ageerle/ruoyi-admin) | | 🛠️ Admin Panel | [ruoyi-admin](https://github.com/ageerle/ruoyi-admin) | [ruoyi-admin](https://gitee.com/ageerle/ruoyi-admin) | [ruoyi-admin](https://gitcode.com/ageerle/ruoyi-admin) |
### Partner Projects ### Partner Projects
| Project Name | GitHub Repository | Gitee Repository | Project Name | GitHub Repository | Gitee Repository |
|----------------|-------------------------------------------------------|------------------------------------------------------| |----------------|-------------------------------------------------------|------------------------------------------------------|
| element-plus-x | [element-plus-x](https://github.com/element-plus-x/Element-Plus-X) | [element-plus-x](https://gitee.com/he-jiayue/element-plus-x) | | element-plus-x | [element-plus-x](https://github.com/element-plus-x/Element-Plus-X) | [element-plus-x](https://gitee.com/he-jiayue/element-plus-x) |
@@ -67,17 +66,138 @@
### Core Framework ### Core Framework
- **Backend**: Spring Boot 4.0 + Spring AI 2.0 + Langchain4j - **Backend**: Spring Boot 4.0 + Spring AI 2.0 + Langchain4j
- **Data Storage**: MySQL 8.0 + Redis + Vector Databases (Milvus/Weaviate) - **Data Storage**: MySQL 8.0 + Redis + Vector Databases (Milvus/Weaviate/Qdrant)
- **Frontend**: Vue 3 + Vben Admin + element-plus-x - **Frontend**: Vue 3 + Vben Admin + element-plus-x
- **Security**: Sa-Token + JWT dual-layer security - **Security**: Sa-Token + JWT dual-layer security
- **Document Processing**: PDF, Word, Excel parsing, intelligent image analysis
- **Real-time Communication**: WebSocket real-time communication, SSE streaming response
- **System Monitoring**: Comprehensive logging system, performance monitoring, service health checks
## 🐳 Docker Deployment
This project provides two Docker deployment methods:
### Method 1: One-click Start All Services (Recommended)
Use `docker-compose-all.yaml` to start all services at once (including backend, admin panel, user frontend, and dependencies):
```bash
# Clone the repository
git clone https://github.com/ageerle/ruoyi-ai.git
cd ruoyi-ai
# Start all services (pull pre-built images from registry)
docker-compose -f docker-compose-all.yaml up -d
# Check service status
docker-compose -f docker-compose-all.yaml ps
# Access services
# Admin Panel: http://localhost:25666 (admin / admin123)
# User Frontend: http://localhost:25137
# Backend API: http://localhost:26039
```
### Method 2: Step-by-step Deployment (Source Build)
If you need to build backend services from source, follow these steps:
#### Step 1: Deploy Backend Service
```bash
# Enter backend project directory
cd ruoyi-ai
# Start backend service (build from source)
docker-compose up -d --build
# Wait for backend service to start
docker-compose logs -f backend
```
#### Step 2: Deploy Admin Panel
```bash
# Enter admin panel project directory
cd ruoyi-admin
# Build and start admin panel
docker-compose up -d --build
# Access admin panel
# URL: http://localhost:5666
```
#### Step 3: Deploy User Frontend (Optional)
```bash
# Enter user frontend project directory
cd ruoyi-web
# Build and start user frontend
docker-compose up -d --build
# Access user frontend
# URL: http://localhost:5137
```
### Service Ports
| Service | One-click Port | Step-by-step Port | Description |
|------|-------------|-------------|------|
| Admin Panel | 25666 | 5666 | Admin backend access |
| User Frontend | 25137 | 5137 | User frontend access |
| Backend Service | 26039 | 6039 | Backend API service |
| MySQL | 23306 | 23306 | Database service |
| Redis | 26379 | 6379 | Cache service |
| Weaviate | 28080 | 28080 | Vector database |
| MinIO API | 29000 | 9000 | Object storage API |
| MinIO Console | 29090 | 9090 | Object storage console |
### Image Registry
All images are hosted on Alibaba Cloud Container Registry:
```
crpi-31mraxd99y2gqdgr.cn-beijing.personal.cr.aliyuncs.com/ruoyi_ai
```
Available images:
- `mysql:v3` - MySQL database (includes initialization SQL)
- `redis:6.2` - Redis cache
- `weaviate:1.30.0` - Vector database
- `minio:latest` - Object storage
- `ruoyi-ai-backend:latest` - Backend service
- `ruoyi-ai-admin:latest` - Admin frontend
- `ruoyi-ai-web:latest` - User frontend
### Common Commands
```bash
# Stop all services
docker-compose -f docker-compose-all.yaml down
# View service logs
docker-compose -f docker-compose-all.yaml logs -f [service-name]
# Restart a service
docker-compose -f docker-compose-all.yaml restart [service-name]
```
## 📚 Documentation ## 📚 Documentation
Want to learn more about installation, deployment, configuration, and secondary development? Want to learn more about installation, deployment, configuration, and secondary development?
**👉 [Complete Documentation](https://doc.pandarobot.chat)** **👉 [Complete Documentation](https://doc.pandarobot.chat)**
Experiencing issues with knowledge base or RAG responses?
**👉 [RAG Troubleshooting Guide](docs/troubleshooting/rag-failures.md)**
---
## 🤝 Contributing ## 🤝 Contributing
We warmly welcome community contributions! Whether you are a seasoned developer or just getting started, you can contribute to the project 💪 We warmly welcome community contributions! Whether you are a seasoned developer or just getting started, you can contribute to the project 💪
@@ -109,14 +229,13 @@ Thanks to the following excellent open-source projects for their support:
- [PPIO Cloud](https://ppinfra.com/user/register?invited_by=P8QTUY&utm_source=github_ruoyi-ai) - Provides cost-effective GPU computing and model API services - [PPIO Cloud](https://ppinfra.com/user/register?invited_by=P8QTUY&utm_source=github_ruoyi-ai) - Provides cost-effective GPU computing and model API services
- [Youyun Intelligent Computing](https://www.compshare.cn/?ytag=GPU_YY-gh_ruoyi) - Thousands of RTX40 series GPUs + mainstream models API services, second-level response, pay-per-use, free for new customers. - [Youyun Intelligent Computing](https://www.compshare.cn/?ytag=GPU_YY-gh_ruoyi) - Thousands of RTX40 series GPUs + mainstream models API services, second-level response, pay-per-use, free for new customers.
## Outstanding Open-Source Projects and Community Recommendations
- [imaiwork](https://gitee.com/tsinghua-open/imaiwork) - Open-source AI phone, AI customer acquisition phone project, based on accessibility mode and RPA, more powerful than Doubao AI phone.
## 💬 Community Chat ## 💬 Community Chat
<div align="center"> <div align="center">
**[📱 Join Telegram Group](https://t.me/+LqooQAc5HxRmYmE1)** **[📱 Join Telegram Group](
https://t.me/+LqooQAc5HxRmYmE1)**
</div> </div>

View File

@@ -0,0 +1,28 @@
---
version: '3.8'
services:
minio:
image: minio/minio
container_name: minio
ports:
- "9000:9000"
- "9090:9090"
environment:
- MINIO_ACCESS_KEY=ruoyi
- MINIO_SECRET_KEY=ruoyi123
volumes:
- minio_data:/data
- minio_config:/root/.minio
command: server /data --console-address ":9090"
restart: always
networks:
- minio-net
networks:
minio-net:
driver: bridge
volumes:
minio_data:
minio_config:

View File

@@ -0,0 +1,65 @@
version: '3.8'
services:
neo4j:
image: neo4j:5.15.0
container_name: ruoyi-neo4j
restart: unless-stopped
ports:
# HTTP端口
- "7474:7474"
# HTTPS端口
- "7473:7473"
# Bolt端口
- "7687:7687"
environment:
# 初始密码设置(首次启动后需要修改)
- NEO4J_AUTH=neo4j/your_password
# 接受许可协议
- NEO4J_ACCEPT_LICENSE_AGREEMENT=yes
# 内存配置(根据服务器配置调整)
- NEO4J_dbms_memory_heap_initial__size=512m
- NEO4J_dbms_memory_heap_max__size=2g
- NEO4J_dbms_memory_pagecache_size=1g
# 事务日志配置
- NEO4J_dbms_tx__log_rotation_retention__policy=3 days
# 允许从任何主机连接
- NEO4J_dbms_default__listen__address=0.0.0.0
# 启用APOC插件
- NEO4J_dbms_security_procedures_unrestricted=apoc.*
- NEO4J_dbms_security_procedures_allowlist=apoc.*
# 日志级别
- NEO4J_dbms_logs_debug_level=INFO
volumes:
# 数据持久化
- neo4j_data:/data
# 日志持久化
- neo4j_logs:/logs
# 导入目录
- neo4j_import:/var/lib/neo4j/import
# 插件目录
- neo4j_plugins:/plugins
networks:
- ruoyi-network
healthcheck:
test: [ "CMD-SHELL", "wget --no-verbose --tries=1 --spider localhost:7474 || exit 1" ]
interval: 30s
timeout: 10s
retries: 5
start_period: 40s
volumes:
neo4j_data:
name: ruoyi-neo4j-data
neo4j_logs:
name: ruoyi-neo4j-logs
neo4j_import:
name: ruoyi-neo4j-import
neo4j_plugins:
name: ruoyi-neo4j-plugins
networks:
ruoyi-network:
name: ruoyi-network
driver: bridge

View File

@@ -0,0 +1,75 @@
version: '3.5'
services:
etcd:
container_name: milvus-etcd
image: quay.io/coreos/etcd:v3.5.18
environment:
- ETCD_AUTO_COMPACTION_MODE=revision
- ETCD_AUTO_COMPACTION_RETENTION=1000
- ETCD_QUOTA_BACKEND_BYTES=4294967296
- ETCD_SNAPSHOT_COUNT=50000
volumes:
- ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/etcd:/etcd
command: etcd -advertise-client-urls=http://etcd:2379 -listen-client-urls http://0.0.0.0:2379 --data-dir /etcd
healthcheck:
test: ["CMD", "etcdctl", "endpoint", "health"]
interval: 30s
timeout: 20s
retries: 3
minio:
container_name: milvus-minio
image: minio/minio:RELEASE.2023-03-20T20-16-18Z
environment:
MINIO_ACCESS_KEY: minioadmin
MINIO_SECRET_KEY: minioadmin
ports:
- "9001:9001"
- "9000:9000"
volumes:
- ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/minio:/minio_data
command: minio server /minio_data --console-address ":9001"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 30s
timeout: 20s
retries: 3
standalone:
container_name: milvus-standalone
image: milvusdb/milvus:v2.5.7
command: ["milvus", "run", "standalone"]
security_opt:
- seccomp:unconfined
environment:
ETCD_ENDPOINTS: etcd:2379
MINIO_ADDRESS: minio:9000
volumes:
- ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/milvus:/var/lib/milvus
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9091/healthz"]
interval: 30s
start_period: 90s
timeout: 20s
retries: 3
ports:
- "19530:19530"
- "9091:9091"
depends_on:
- "etcd"
- "minio"
attu:
container_name: attu
image: zilliz/attu:v2.5.7
environment:
MILVUS_URL: milvus-standalone:19530
ports:
- "19500:3000" # 外部端口19500可以自定义
depends_on:
- "standalone"
networks:
default:
name: milvus

View File

@@ -0,0 +1,12 @@
---
services:
qdrant:
image: qdrant/qdrant:latest
ports:
- 6333:6333
- 6334:6334
volumes:
- qdrant_data:/qdrant/storage
volumes:
qdrant_data:
...

View File

@@ -0,0 +1,36 @@
# RuoYi-AI 后端 Dockerfile
# 基于 Maven + OpenJDK 17
FROM maven:3.9-eclipse-temurin-17 AS builder
# 设置工作目录
WORKDIR /build
# 复制 pom.xml 和源码
COPY pom.xml .
COPY ruoyi-admin ./ruoyi-admin
COPY ruoyi-common ./ruoyi-common
COPY ruoyi-modules ./ruoyi-modules
COPY ruoyi-extend ./ruoyi-extend
# 构建项目 (使用 prod profile)
RUN mvn clean package -Pprod -DskipTests
# 最终运行镜像
FROM eclipse-temurin:17-jre-alpine
# 设置工作目录
WORKDIR /app
# 从构建阶段复制 jar 包
COPY --from=builder /build/ruoyi-admin/target/ruoyi-admin.jar ./ruoyi-admin.jar
# 创建日志目录
RUN mkdir -p /ruoyi/server/logs
# 暴露端口
EXPOSE 6039
# 启动命令
ENTRYPOINT ["java", "-jar", "ruoyi-admin.jar", "--spring.profiles.active=prod"]

View File

@@ -0,0 +1,21 @@
# 基于官方MySQL 8.0镜像构建自定义镜像
# 构建命令: docker build -t registry.cn-hangzhou.aliyuncs.com/ruoyi-ai/mysql:v3 -f Dockerfile.mysql .
FROM mysql:8.0.33
# 设置时区
ENV TZ=Asia/Shanghai
# 复制初始化脚本和SQL文件到镜像中
COPY docs/script/docker/mysql/init/init-db.sh /docker-entrypoint-initdb.d/init-db.sh
COPY docs/script/sql/ruoyi-ai-v3_mysql8.sql /docker-entrypoint-initdb.d/ruoyi-ai-v3_mysql8.sql
# 设置脚本可执行权限
RUN chmod +x /docker-entrypoint-initdb.d/init-db.sh
# MySQL启动参数
CMD ["--default-authentication-plugin=mysql_native_password", \
"--character-set-server=utf8mb4", \
"--collation-server=utf8mb4_general_ci", \
"--explicit_defaults_for_timestamp=true", \
"--lower_case_table_names=1", \
"--skip-ssl"]

View File

@@ -0,0 +1,180 @@
# RuoYi-AI 一键启动全部服务
# 使用方式: docker-compose up -d
#
# 包含服务:
# - MySQL 8.0 (数据库包含初始化SQL)
# - Redis 6.2 (缓存)
# - Weaviate (向量数据库)
# - MinIO (对象存储)
# - RuoYi-Backend (后端服务)
# - RuoYi-Admin (管理端前端)
# - RuoYi-Web (用户端前端)
#
# 镜像仓库地址: crpi-31mraxd99y2gqdgr.cn-beijing.personal.cr.aliyuncs.com/ruoyi_ai
version: '3.8'
services:
# ==================== MySQL 数据库 ====================
mysql:
# 阿里云镜像地址包含初始化SQL
image: crpi-31mraxd99y2gqdgr.cn-beijing.personal.cr.aliyuncs.com/ruoyi_ai/mysql:v3
container_name: ruoyi-ai-mysql
restart: always
ports:
- "23306:3306"
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: ruoyi-ai-agent
TZ: Asia/Shanghai
volumes:
- mysql-data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-proot"]
interval: 15s
timeout: 10s
retries: 10
start_period: 60s
networks:
- ruoyi-net
# ==================== Redis 缓存 ====================
redis:
image: crpi-31mraxd99y2gqdgr.cn-beijing.personal.cr.aliyuncs.com/ruoyi_ai/redis:6.2
container_name: ruoyi-ai-redis
restart: always
ports:
- "26379:6379"
volumes:
- redis-data:/data
command: redis-server --appendonly yes
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
networks:
- ruoyi-net
# ==================== Weaviate 向量数据库 ====================
weaviate:
image: crpi-31mraxd99y2gqdgr.cn-beijing.personal.cr.aliyuncs.com/ruoyi_ai/weaviate:1.30.0
container_name: ruoyi-ai-weaviate
restart: always
ports:
- "28080:8080"
environment:
QUERY_DEFAULTS_LIMIT: 25
AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED: true
PERSISTENCE_DATA_PATH: /var/lib/weaviate
DEFAULT_VECTORIZER_MODULE: none
ENABLE_MODULES: text2vec-cohere,text2vec-huggingface,text2vec-palm,text2vec-openai,generative-openai,generative-cohere,generative-palm,ref2vec-centroid,reranker-cohere,qna-openai
CLUSTER_HOSTNAME: node1
volumes:
- weaviate-data:/var/lib/weaviate
networks:
- ruoyi-net
# ==================== MinIO 对象存储 ====================
minio:
image: crpi-31mraxd99y2gqdgr.cn-beijing.personal.cr.aliyuncs.com/ruoyi_ai/minio:latest
container_name: ruoyi-ai-minio
restart: always
ports:
- "29000:9000"
- "29090:9090"
environment:
MINIO_ROOT_USER: ruoyi
MINIO_ROOT_PASSWORD: ruoyi123
volumes:
- minio-data:/data
command: server /data --console-address ":9090"
networks:
- ruoyi-net
# ==================== RuoYi-AI 后端服务 ====================
backend:
image: crpi-31mraxd99y2gqdgr.cn-beijing.personal.cr.aliyuncs.com/ruoyi_ai/ruoyi-ai-backend:latest
container_name: ruoyi-ai-backend
restart: always
ports:
- "26039:6039"
environment:
TZ: Asia/Shanghai
# MySQL 配置
SPRING_DATASOURCE_DYNAMIC_PRIMARY: master
SPRING_DATASOURCE_DYNAMIC_DATASOURCE_MASTER_DRIVERCLASSNAME: com.mysql.cj.jdbc.Driver
SPRING_DATASOURCE_DYNAMIC_DATASOURCE_MASTER_URL: jdbc:mysql://mysql:3306/ruoyi-ai-agent?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
SPRING_DATASOURCE_DYNAMIC_DATASOURCE_MASTER_USERNAME: root
SPRING_DATASOURCE_DYNAMIC_DATASOURCE_MASTER_PASSWORD: root
# Redis 配置
SPRING_DATA_REDIS_HOST: redis
SPRING_DATA_REDIS_PORT: 6379
SPRING_DATA_REDIS_DATABASE: 0
# 日志配置
LOGGING_LEVEL_ORG_RUOYI: info
LOGGING_LEVEL_ORG_SPRINGFRAMEWORK: warn
SYS_UPLOAD_PATH: /ruoyi/upload
volumes:
- logs-data:/ruoyi/server/logs
- upload-data:/ruoyi/upload
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_started
networks:
- ruoyi-net
# ==================== RuoYi-AI 管理端前端 ====================
admin-frontend:
image: crpi-31mraxd99y2gqdgr.cn-beijing.personal.cr.aliyuncs.com/ruoyi_ai/ruoyi-ai-admin:latest
container_name: ruoyi-ai-admin
restart: always
ports:
- "25666:5666"
environment:
# 后端 API 地址 - 运行时动态配置(无需重新构建镜像)
# nginx upstream 配置不需要 http:// 前缀,直接使用 host:port
UPSTREAM_HOST: backend:6039
# 资源限制 - 防止 CPU 和内存耗尽
deploy:
resources:
limits:
cpus: '2'
memory: 3G
reservations:
cpus: '1'
memory: 1G
depends_on:
- backend
networks:
- ruoyi-net
# ==================== RuoYi-AI 用户端前端 ====================
web-frontend:
image: crpi-31mraxd99y2gqdgr.cn-beijing.personal.cr.aliyuncs.com/ruoyi_ai/ruoyi-ai-web:latest
container_name: ruoyi-ai-web
restart: always
ports:
- "25137:5137"
environment:
UPSTREAM_URL: http://backend:6039
depends_on:
- backend
networks:
- ruoyi-net
# ==================== 网络配置 ====================
networks:
ruoyi-net:
driver: bridge
# ==================== 数据卷配置 ====================
volumes:
mysql-data:
redis-data:
weaviate-data:
minio-data:
logs-data:
upload-data:

View File

@@ -0,0 +1,144 @@
# RuoYi-AI 一键启动后端服务
# 使用方式: docker-compose up -d --build
#
# 包含服务:
# - MySQL 8.0 (数据库)
# - Redis 6.2 (缓存)
# - Weaviate (向量数据库)
# - MinIO (对象存储)
# - RuoYi-Backend (后端服务,源码编译)
services:
# MySQL 数据库
mysql:
image: mysql:8.0.33
container_name: ruoyi-ai-mysql
restart: always
ports:
- "23306:3306"
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: ruoyi-ai-agent
TZ: Asia/Shanghai
volumes:
- ./docs/script/docker/mysql/init/init-db.sh:/docker-entrypoint-initdb.d/init-db.sh:ro
- ./docs/script/sql/ruoyi-ai-v3_mysql8.sql:/docker-entrypoint-initdb.d/ruoyi-ai-v3_mysql8.sql:ro
- mysql-data:/var/lib/mysql
command:
--default-authentication-plugin=mysql_native_password
--character-set-server=utf8mb4
--collation-server=utf8mb4_general_ci
--explicit_defaults_for_timestamp=true
--lower_case_table_names=1
--skip-ssl
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-proot"]
interval: 15s
timeout: 10s
retries: 10
start_period: 60s
networks:
- ruoyi-net
# Redis 缓存
redis:
image: redis:6.2
container_name: ruoyi-ai-redis
restart: always
ports:
- "6379:6379"
volumes:
- redis-data:/data
command: redis-server --appendonly yes
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
networks:
- ruoyi-net
# Weaviate 向量数据库
weaviate:
image: semitechnologies/weaviate:1.30.0
container_name: ruoyi-ai-weaviate
restart: always
ports:
- "28080:8080"
environment:
QUERY_DEFAULTS_LIMIT: 25
AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED: true
PERSISTENCE_DATA_PATH: /var/lib/weaviate
DEFAULT_VECTORIZER_MODULE: none
ENABLE_MODULES: text2vec-cohere,text2vec-huggingface,text2vec-palm,text2vec-openai,generative-openai,generative-cohere,generative-palm,ref2vec-centroid,reranker-cohere,qna-openai
CLUSTER_HOSTNAME: node1
volumes:
- weaviate-data:/var/lib/weaviate
networks:
- ruoyi-net
# MinIO 对象存储
minio:
image: minio/minio
container_name: ruoyi-ai-minio
restart: always
ports:
- "9000:9000"
- "9090:9090"
environment:
MINIO_ROOT_USER: ruoyi
MINIO_ROOT_PASSWORD: ruoyi123
volumes:
- minio-data:/data
command: server /data --console-address ":9090"
networks:
- ruoyi-net
# RuoYi-AI 后端服务 (源码编译)
backend:
build:
context: .
dockerfile: Dockerfile.backend
container_name: ruoyi-ai-backend
restart: always
ports:
- "26039:6039"
environment:
TZ: Asia/Shanghai
# MySQL 配置
SPRING_DATASOURCE_DYNAMIC_PRIMARY: master
SPRING_DATASOURCE_DYNAMIC_DATASOURCE_MASTER_DRIVERCLASSNAME: com.mysql.cj.jdbc.Driver
SPRING_DATASOURCE_DYNAMIC_DATASOURCE_MASTER_URL: jdbc:mysql://mysql:3306/ruoyi-ai-agent?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
SPRING_DATASOURCE_DYNAMIC_DATASOURCE_MASTER_USERNAME: root
SPRING_DATASOURCE_DYNAMIC_DATASOURCE_MASTER_PASSWORD: root
# Redis 配置
SPRING_DATA_REDIS_HOST: redis
SPRING_DATA_REDIS_PORT: 6379
SPRING_DATA_REDIS_DATABASE: 0
# 日志配置
LOGGING_LEVEL_ORG_RUOYI: info
LOGGING_LEVEL_ORG_SPRINGFRAMEWORK: warn
SYS_UPLOAD_PATH: /ruoyi/upload # 新增:对应 sys.upload.path
volumes:
- logs-data:/ruoyi/server/logs
- upload-data:/ruoyi/upload
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_started
networks:
- ruoyi-net
networks:
ruoyi-net:
driver: bridge
# 数据卷 支持手动指定 空为默认值
volumes:
mysql-data:
redis-data:
weaviate-data:
minio-data:
logs-data:
upload-data:

View File

@@ -0,0 +1,25 @@
---
services:
weaviate:
command:
- --host
- 0.0.0.0
- --port
- '6038'
- --scheme
- http
image: semitechnologies/weaviate:1.19.7
ports:
- 6038:6038
- 50051:50051
volumes:
- weaviate_data:/var/lib/weaviate
environment:
QUERY_DEFAULTS_LIMIT: 25
AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED: 'true'
PERSISTENCE_DATA_PATH: '/var/lib/weaviate'
DEFAULT_VECTORIZER_MODULE: 'none'
CLUSTER_HOSTNAME: 'node1'
volumes:
weaviate_data:
...

View File

@@ -0,0 +1,10 @@
#!/bin/bash
# 数据库初始化脚本
# 使用 --force 参数确保即使出错也继续执行
echo "开始初始化数据库..."
# 使用 --force 参数忽略错误继续执行
mysql -uroot -proot ruoyi-ai-agent --force < /docker-entrypoint-initdb.d/ruoyi-ai-v3_mysql8.sql
echo "数据库初始化完成"

View File

@@ -1,161 +0,0 @@
worker_processes 1;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
# 可以根据业务并发量适当调高
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
# 高效传输文件
sendfile on;
# 长连接超时时间
keepalive_timeout 65;
# 单连接最大请求数,提高长连接复用率
keepalive_requests 100000;
# 限制body大小
client_max_body_size 100m;
client_header_buffer_size 32k;
client_body_buffer_size 512k;
# 开启静态资源压缩
gzip_static on;
# 连接数限制 (防御类配置) 10m 一般够用了,能存储上万 IP 的计数
limit_conn_zone $binary_remote_addr zone=perip:10m;
limit_conn_zone $server_name zone=perserver:10m;
# 隐藏 nginx 版本号,防止暴露版本信息
server_tokens off;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
upstream server {
ip_hash;
server 127.0.0.1:8080;
server 127.0.0.1:8081;
}
upstream monitor-admin {
server 127.0.0.1:9090;
}
upstream snailjob-server {
server 127.0.0.1:8800;
}
server {
listen 80;
server_name localhost;
# https配置参考 start
#listen 443 ssl;
# 证书直接存放 /docker/nginx/cert/ 目录下即可 更改证书名称即可 无需更改证书路径
#ssl on;
#ssl_certificate /etc/nginx/cert/xxx.local.crt; # /etc/nginx/cert/ 为docker映射路径 不允许更改
#ssl_certificate_key /etc/nginx/cert/xxx.local.key; # /etc/nginx/cert/ 为docker映射路径 不允许更改
#ssl_session_timeout 5m;
#ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
#ssl_protocols TLSv1.3 TLSv1.2 TLSv1.1 TLSv1;
#ssl_prefer_server_ciphers on;
# https配置参考 end
# 演示环境配置 拦截除 GET POST 之外的所有请求
# if ($request_method !~* GET|POST) {
# rewrite ^/(.*)$ /403;
# }
# location = /403 {
# default_type application/json;
# return 200 '{"msg":"演示模式,不允许操作","code":500}';
# }
# 限制外网访问内网 actuator 相关路径
location ~ ^(/[^/]*)?/actuator.*(/.*)?$ {
return 403;
}
location / {
root /usr/share/nginx/html; # docker映射路径 不允许更改
try_files $uri $uri/ /index.html;
index index.html index.htm;
}
location /prod-api/ {
# 设置客户端请求头中的 Host 信息(保持原始 Host
proxy_set_header Host $http_host;
# 获取客户端真实 IP
proxy_set_header X-Real-IP $remote_addr;
# 自定义头 REMOTE-HOST记录客户端 IP
proxy_set_header REMOTE-HOST $remote_addr;
# 获取完整的客户端 IP 链(经过多级代理时)
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# 设置后端响应超时时间(这里是 24 小时,适合长连接/SSE
proxy_read_timeout 86400s;
# SSE (Server-Sent Events) 与 WebSocket 支持参数
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# 禁用代理缓冲,数据直接传给客户端
proxy_buffering off;
# 禁用代理缓存
proxy_cache off;
# 按 IP 限制连接数(防 CC 攻击) 小型站10~20 就够 中型站50~100
limit_conn perip 20;
# 按 Server 限制总并发连接数 根据服务器的最大并发处理能力来定 太小会限制合法用户访问,太大会占满服务器资源
limit_conn perserver 500;
proxy_pass http://server/;
}
# https 会拦截内链所有的 http 请求 造成功能无法使用
# 解决方案1 将 admin 服务 也配置成 https
# 解决方案2 将菜单配置为外链访问 走独立页面 http 访问
location /admin/ {
# 设置客户端请求头中的 Host 信息(保持原始 Host
proxy_set_header Host $http_host;
# 获取客户端真实 IP
proxy_set_header X-Real-IP $remote_addr;
# 自定义头 REMOTE-HOST记录客户端 IP
proxy_set_header REMOTE-HOST $remote_addr;
# 获取完整的客户端 IP 链(经过多级代理时)
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# 禁用代理缓冲,数据直接传给客户端
proxy_buffering off;
# 禁用代理缓存
proxy_cache off;
proxy_pass http://monitor-admin/admin/;
}
location /snail-job/ {
# 设置客户端请求头中的 Host 信息(保持原始 Host
proxy_set_header Host $http_host;
# 获取客户端真实 IP
proxy_set_header X-Real-IP $remote_addr;
# 自定义头 REMOTE-HOST记录客户端 IP
proxy_set_header REMOTE-HOST $remote_addr;
# 获取完整的客户端 IP 链(经过多级代理时)
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# SSE (Server-Sent Events) 与 WebSocket 支持参数
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# 禁用代理缓冲,直接传输给客户端
proxy_buffering off;
# 禁用代理缓存
proxy_cache off;
proxy_pass http://snailjob-server/snail-job/;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}

View File

@@ -1,28 +0,0 @@
# redis 密码
requirepass ruoyi123
# key 监听器配置
# notify-keyspace-events Ex
# 配置持久化文件存储路径
dir /redis/data
# 配置rdb
# 15分钟内有至少1个key被更改则进行快照
save 900 1
# 5分钟内有至少10个key被更改则进行快照
save 300 10
# 1分钟内有至少10000个key被更改则进行快照
save 60 10000
# 开启压缩
rdbcompression yes
# rdb文件名 用默认的即可
dbfilename dump.rdb
# 开启aof
appendonly yes
# 文件名
appendfilename "appendonly.aof"
# 持久化策略,no:不同步,everysec:每秒一次,always:总是同步,速度比较慢
# appendfsync always
appendfsync everysec
# appendfsync no

View File

@@ -1 +0,0 @@
数据目录 请执行 `chmod 777 /docker/redis/data` 赋予读写权限 否则将无法写入数据

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,352 @@
<a id="top"></a>
# RAG 常见故障排查16 问题清单)
当知识库已经接入,系统也能正常回答,但结果仍然出现命中错误、引用旧内容、推理漂移、跨轮次失忆,或部署后表面可用但实际异常时,最常见的问题不是“模型不行”,而是**不同层的故障被混在一起处理**。
这份页面不重新发明一套新方法。
它直接使用一份固定的 **16 问题清单** 作为排查主轴,让你先把问题标到正确的 **No.X**,再决定下一步查哪里、改哪里,而不是一次性乱改检索、模型、切块、会话和部署配置。
这份清单的核心目的只有一个:
**先把问题放进正确的故障域,再做修复。**
快速导航:
[这页怎么用](#how-to-use) | [标签说明](#legend) | [常见症状入口](#symptoms) | [16 问题清单](#map16) | [按层排查](#by-layer) |
---
<a id="how-to-use"></a>
## 一、这页怎么用
这不是一篇“从头到尾照着做”的传统教程。
它更像一张固定的 RAG 故障地图,作用是先帮助你**判断故障属于哪一种类型**。
建议按下面顺序使用:
### 1. 先看现象,不要先改配置
先回答两个问题:
1. 你看到的故障,最像哪一种症状
2. 这个故障更像发生在输入检索层、推理层、状态层,还是部署层
在还没判断层级之前,不要先一起改这些东西:
- 检索条数
- 切块大小
- 会话配置
- 模型参数
- 部署顺序
- 依赖服务
如果先全部一起动,问题通常只会更难定位。
### 2. 先给问题打上 No.X 标签
这份页面最重要的动作,不是“立刻修好”,而是先做一件小事:
**给当前问题贴上最接近的 No.X。**
例如:
- 检索结果看起来相似,但其实答非所问,先看 `No.1``No.5`
- 切块是对的,但结论还是错,先看 `No.2`
- 系统回答很自信,但没有根据,先看 `No.4`
- 刚部署完就炸,先看 `No.14``No.16`
### 3. 一次只排一个故障域
同一个表面现象,背后可能是不同层的问题。
例如“答案不对”既可能是:
- `No.1` 检索漂移
- `No.2` 理解塌陷
- `No.4` 自信乱答
- `No.8` 根本看不到错误路径
所以这张表的用法不是“多选全改”,而是:
**先挑最接近的一项,优先验证这一项是否成立。**
[返回顶部](#top) | [下一节:标签说明](#legend)
---
<a id="legend"></a>
## 二、标签说明
这份 16 问题清单本身已经带有层级 / 标签结构。
这些标签不是装饰,而是用来帮助你快速判断故障发生在哪一层。
### 1. 层级标签
- `[IN]`:输入与检索
输入、切块、召回、语义匹配、可见性问题
- `[RE]`:推理与规划
理解、推理、归纳、逻辑链、抽象处理问题
- `[ST]`:状态与上下文
会话、记忆、上下文连续性、多代理状态问题
- `[OP]`:基础设施与部署
启动顺序、依赖就绪、部署锁死、预部署状态问题
### 2. `{OBS}` 标签
`{OBS}` 的项,通常都和“**你是否看得见问题是怎么坏掉的**”有关。
它们往往不是单纯回答错误,而是:
- 错误路径不可见
- 漂移过程不可见
- 状态熔化过程不可见
- 多代理覆盖过程不可见
所以一旦你发现“我知道结果错,但我根本看不到它是怎么错的”,通常就已经很接近 `{OBS}` 类问题了。
### 3. 为什么要保留这些标签
因为同样叫“答错了”,实际含义完全不同。
例如:
- `[IN]` 的答错,常常是**拿错材料**
- `[RE]` 的答错,常常是**拿对材料但理解错**
- `[ST]` 的答错,常常是**前文断掉、状态漂移**
- `[OP]` 的答错,常常是**系统根本没在完整状态下运行**
如果不先分层,就会掉进典型的 RAG 地狱:
表面在改答案,实际上在盲修。
[返回顶部](#top) | [下一节:常见症状入口](#symptoms)
---
<a id="symptoms"></a>
## 三、常见症状入口
如果你现在还不知道该从哪一项开始,就先从症状入口反查。
### 1. 检索返回了错误内容,或看起来相关但其实不回答问题
这类问题最常见的是:
“有命中,但命中的不是该用的内容。”
优先看:
- [No.1](#no1) `幻觉与切块漂移`
- [No.5](#no5) `语义 ≠ 向量嵌入`
- [No.8](#no8) `调试是一个黑箱`
### 2. 切块本身是对的,但最终答案还是错的
这类问题不是简单没检索到,而是后面那层坏了。
优先看:
- [No.2](#no2) `解释塌陷`
- [No.4](#no4) `虚张声势 / 过度自信`
- [No.6](#no6) `逻辑塌陷与恢复`
### 3. 多步任务一开始正常,后面越来越偏
这类问题通常不是单点错误,而是中途漂移或熔化。
优先看:
- [No.3](#no3) `长推理链`
- [No.6](#no6) `逻辑塌陷与恢复`
- [No.9](#no9) `熵塌陷`
### 4. 多轮对话后开始失忆,跨轮次接不上
这类问题一般已经进入状态层。
优先看:
- [No.7](#no7) `跨会话记忆断裂`
- [No.9](#no9) `熵塌陷`
- [No.13](#no13) `多代理混乱`
### 5. 遇到抽象、逻辑、规则、符号关系就崩
这类问题通常不是检索空,而是推理结构扛不住。
优先看:
- [No.11](#no11) `符号塌陷`
- [No.12](#no12) `哲学递归`
### 6. 你根本不知道错在哪一层,只知道结果不对
这类问题先不要乱调参数。
先解决“不可见”的问题。
优先看:
- [No.8](#no8) `调试是一个黑箱`
### 7. 刚部署完最容易炸,首轮调用异常,重启后偶尔恢复
这类问题通常不在答案逻辑,而在部署状态。
优先看:
- [No.14](#no14) `引导启动顺序`
- [No.15](#no15) `部署死锁`
- [No.16](#no16) `预部署塌陷`
[返回顶部](#top) | [下一节16 问题清单](#map16)
---
<a id="map16"></a>
## 四、16 问题清单(固定主表)
下面这 16 项按固定顺序使用。
不要先重组,不要先混类,先判断最接近哪一个 **No.X**
| # | 问题域(含层级/标签) | 会坏在哪里 |
|---|---|---|
| <a id="no1"></a> 1 | `[IN] 幻觉与切块漂移 {OBS}` | 检索返回错误/无关内容 |
| <a id="no2"></a> 2 | `[RE] 解释塌陷` | 切块是对的,逻辑是错的 |
| <a id="no3"></a> 3 | `[RE] 长推理链 {OBS}` | 在多步任务中逐步漂移 |
| <a id="no4"></a> 4 | `[RE] 虚张声势 / 过度自信` | 自信但没有根据的回答 |
| <a id="no5"></a> 5 | `[IN] 语义 ≠ 向量嵌入 {OBS}` | 余弦匹配 ≠ 真实语义 |
| <a id="no6"></a> 6 | `[RE] 逻辑塌陷与恢复 {OBS}` | 走入死胡同,需要受控重置 |
| <a id="no7"></a> 7 | `[ST] 跨会话记忆断裂` | 线索丢失,没有连续性 |
| <a id="no8"></a> 8 | `[IN] 调试是一个黑箱 {OBS}` | 看不到故障路径 |
| <a id="no9"></a> 9 | `[ST] 熵塌陷` | 注意力熔化,输出失去连贯性 |
| <a id="no10"></a> 10 | `[RE] 创造力冻结` | 平直、字面化输出 |
| <a id="no11"></a> 11 | `[RE] 符号塌陷` | 抽象/逻辑性提示词失效 |
| <a id="no12"></a> 12 | `[RE] 哲学递归` | 自我引用循环、悖论陷阱 |
| <a id="no13"></a> 13 | `[ST] 多代理混乱 {OBS}` | 代理互相覆盖或使逻辑错位 |
| <a id="no14"></a> 14 | `[OP] 引导启动顺序` | 依赖未就绪时服务先启动 |
| <a id="no15"></a> 15 | `[OP] 部署死锁` | 基础设施中的循环等待 |
| <a id="no16"></a> 16 | `[OP] 预部署塌陷 {OBS}` | 首次调用时版本错配 / 缺少密钥 |
这张表是主表。
如果你时间很少,只做一件事也行:
**先从这 16 项里选出最接近的一项。**
[返回顶部](#top) | [下一节:按层排查](#by-layer)
---
<a id="by-layer"></a>
## 五、按层排查:不要改错层
这一节不重写 16 项,只是告诉你:
当你已经选到某个 No.X 时,第一眼应该优先查哪一层。
### A. `[IN]` 层:先确认你拿到的是不是对的材料
对应编号:
- [No.1](#no1)
- [No.5](#no5)
- [No.8](#no8)
这层最常见的误判是:
“我以为系统理解错了,其实它一开始就拿错了东西。”
如果你命中了弱相关片段、表面相似文本、错误切块,后面推理再强也没用。
所以 `[IN]` 层优先看的是:
1. 原始召回内容到底是什么
2. 命中的片段是否只是“相似”,而不是“正确”
3. 你是否能看到检索过程,还是整个过程像黑箱
这层如果没先排好,后面的推理诊断通常会失真。
### B. `[RE]` 层:材料可能是对的,但系统用错了
对应编号:
- [No.2](#no2)
- [No.3](#no3)
- [No.4](#no4)
- [No.6](#no6)
- [No.10](#no10)
- [No.11](#no11)
- [No.12](#no12)
这层最常见的误判是:
“我以为是检索坏了,其实是后面理解、归纳、逻辑链坏了。”
例如:
- 切块是对的,但结论错了 → 常见是 `No.2`
- 多步任务中途开始偏 → 常见是 `No.3`
- 回答很笃定,但完全站不住 → 常见是 `No.4`
- 遇到抽象规则就崩 → 常见是 `No.11`
- 陷入循环解释 → 常见是 `No.12`
如果 `[IN]` 层已经基本没问题,答案还是不对,就应该优先回到 `[RE]` 层判断是哪一种塌陷。
### C. `[ST]` 层:单轮正常,不代表状态层正常
对应编号:
- [No.7](#no7)
- [No.9](#no9)
- [No.13](#no13)
这层最常见的误判是:
“单轮看起来还行,所以我以为系统没问题。”
其实很多 RAG 地狱不是单轮错误,而是:
- 多轮之后前文断掉
- 上下文越来越乱
- 多角色、多代理之间互相覆盖
如果你发现:
- 第一轮没事,后面越来越歪
- 切换角色后前面的约束消失
- 多个步骤之间状态彼此污染
那就不要再只盯着检索条数了,应该直接回到 `[ST]` 层看 `No.7 / No.9 / No.13`
### D. `[OP]` 层:别把部署问题误诊成回答问题
对应编号:
- [No.14](#no14)
- [No.15](#no15)
- [No.16](#no16)
这层最常见的误判是:
“答案不稳定,所以我先去调模型或检索。”
但如果系统根本没有在完整状态下启动,所有上层表现都会像鬼打墙。
尤其是这些情况:
- 依赖还没就绪,服务先起了 → `No.14`
- 多个组件互相等待,长期半可用 → `No.15`
- 首次调用就因为版本、密钥、环境没对齐而塌陷 → `No.16`
只要你看到“刚部署最容易出事”“首轮异常最严重”“重启后暂时恢复”,就要优先怀疑 `[OP]` 层,而不是先改提示词或参数。
[返回顶部](#top) |
---
<a id="issue-report"></a>
## 六、快速返回
[返回顶部](#top) | [这页怎么用](#how-to-use) | [标签说明](#legend) | [常见症状入口](#symptoms) | [16 问题清单](#map16) | [按层排查](#by-layer)

View File

@@ -0,0 +1,42 @@
## 接口信息
**接口路径**: `POST /resource/oss/upload`
**请求类型**: `multipart/form-data`
**权限要求**: `system:oss:upload`
**业务类型**: [INSERT]
### 接口描述
上传OSS对象存储接口用于将文件上传到对象存储服务。
### 请求参数
| 参数名 | 类型 | 必填 | 说明 |
| ---- | ------------- | ---- | ------ |
| file | MultipartFile | 是 | 要上传的文件 |
### 请求头
- `Content-Type`: `multipart/form-data`
### 返回值
返回 `R<SysOssUploadVo>` 类型,包含以下字段:
| 字段名 | 类型 | 说明 |
| -------- | ------ | ------- |
| url | String | 文件访问URL |
| fileName | String | 原始文件名 |
| ossId | String | 文件ID |
### 响应示例
```json
{
"code": 200,
"msg": "操作成功",
"data": {
"url": "fileid://xxx",
"fileName": "example.jpg",
"ossId": "123"
}
}
```
### 异常情况
- 当上传文件为空时,返回错误信息:"上传文件不能为空"

37
pom.xml
View File

@@ -59,6 +59,8 @@
<langgraph4j.version>1.5.3</langgraph4j.version> <langgraph4j.version>1.5.3</langgraph4j.version>
<weaviate.version>1.19.6</weaviate.version> <weaviate.version>1.19.6</weaviate.version>
<dify.version>1.0.7</dify.version> <dify.version>1.0.7</dify.version>
<!-- gRPC 版本 - 解决 Milvus SDK 依赖冲突 -->
<grpc.version>1.62.2</grpc.version>
<!-- Apache Commons Compress - 用于POI处理ZIP格式 --> <!-- Apache Commons Compress - 用于POI处理ZIP格式 -->
<commons-compress.version>1.27.1</commons-compress.version> <commons-compress.version>1.27.1</commons-compress.version>
@@ -129,6 +131,15 @@
<scope>import</scope> <scope>import</scope>
</dependency> </dependency>
<!-- gRPC BOM - 解决 Milvus SDK 依赖冲突,强制统一版本 -->
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-bom</artifactId>
<version>${grpc.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- hutool 的依赖配置--> <!-- hutool 的依赖配置-->
<dependency> <dependency>
<groupId>cn.hutool</groupId> <groupId>cn.hutool</groupId>
@@ -352,24 +363,12 @@
<version>${revision}</version> <version>${revision}</version>
</dependency> </dependency>
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-job</artifactId>
<version>${revision}</version>
</dependency>
<dependency> <dependency>
<groupId>org.ruoyi</groupId> <groupId>org.ruoyi</groupId>
<artifactId>ruoyi-generator</artifactId> <artifactId>ruoyi-generator</artifactId>
<version>${revision}</version> <version>${revision}</version>
</dependency> </dependency>
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-demo</artifactId>
<version>${revision}</version>
</dependency>
<dependency> <dependency>
<groupId>org.ruoyi</groupId> <groupId>org.ruoyi</groupId>
<artifactId>ruoyi-chat</artifactId> <artifactId>ruoyi-chat</artifactId>
@@ -383,13 +382,6 @@
<version>${revision}</version> <version>${revision}</version>
</dependency> </dependency>
<!-- 微信模块 -->
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-wechat</artifactId>
<version>${revision}</version>
</dependency>
<!-- AI流程编排模块 --> <!-- AI流程编排模块 -->
<dependency> <dependency>
<groupId>org.ruoyi</groupId> <groupId>org.ruoyi</groupId>
@@ -397,13 +389,6 @@
<version>${revision}</version> <version>${revision}</version>
</dependency> </dependency>
<!-- 企业微信SDK -->
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-cp</artifactId>
<version>${weixin-java-cp.version}</version>
</dependency>
<!-- Jackson XML --> <!-- Jackson XML -->
<dependency> <dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId> <groupId>com.fasterxml.jackson.dataformat</groupId>

View File

@@ -70,23 +70,12 @@
<artifactId>ruoyi-system</artifactId> <artifactId>ruoyi-system</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-job</artifactId>
</dependency>
<!-- 代码生成--> <!-- 代码生成-->
<dependency> <dependency>
<groupId>org.ruoyi</groupId> <groupId>org.ruoyi</groupId>
<artifactId>ruoyi-generator</artifactId> <artifactId>ruoyi-generator</artifactId>
</dependency> </dependency>
<!-- demo模块 -->
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-demo</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.ruoyi</groupId> <groupId>org.ruoyi</groupId>
<artifactId>ruoyi-chat</artifactId> <artifactId>ruoyi-chat</artifactId>
@@ -98,12 +87,6 @@
<artifactId>ruoyi-workflow</artifactId> <artifactId>ruoyi-workflow</artifactId>
</dependency> </dependency>
<!-- 微信模块 -->
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-wechat</artifactId>
</dependency>
<!-- AI流程编排模块 --> <!-- AI流程编排模块 -->
<dependency> <dependency>
<groupId>org.ruoyi</groupId> <groupId>org.ruoyi</groupId>

View File

@@ -80,10 +80,16 @@ public class AuthController {
// 授权类型和客户端id // 授权类型和客户端id
String clientId = loginBody.getClientId(); String clientId = loginBody.getClientId();
String grantType = loginBody.getGrantType(); String grantType = loginBody.getGrantType();
log.info("登录请求 - clientId: {}, grantType: {}", clientId, grantType);
SysClientVo client = clientService.queryByClientId(clientId); SysClientVo client = clientService.queryByClientId(clientId);
log.info("查询客户端结果 - client: {}, grantType: {}", client, client != null ? client.getGrantType() : "null");
// 查询不到 client 或 client 内不包含 grantType // 查询不到 client 或 client 内不包含 grantType
if (ObjectUtil.isNull(client) || !StringUtils.contains(client.getGrantType(), grantType)) { if (ObjectUtil.isNull(client)) {
log.info("客户端id: {} 认证类型:{} 异常!.", clientId, grantType); log.info("客户端id: {} 不存在!", clientId);
return R.fail(MessageUtils.message("auth.grant.type.error"));
}
if (!StringUtils.contains(client.getGrantType(), grantType)) {
log.info("客户端id: {} 认证类型:{} 不匹配! 数据库grantType: {}", clientId, grantType, client.getGrantType());
return R.fail(MessageUtils.message("auth.grant.type.error")); return R.fail(MessageUtils.message("auth.grant.type.error"));
} else if (!SystemConstants.NORMAL.equals(client.getStatus())) { } else if (!SystemConstants.NORMAL.equals(client.getStatus())) {
return R.fail(MessageUtils.message("auth.grant.type.blocked")); return R.fail(MessageUtils.message("auth.grant.type.blocked"));

View File

@@ -58,15 +58,15 @@ spring:
driverClassName: com.mysql.cj.jdbc.Driver driverClassName: com.mysql.cj.jdbc.Driver
# jdbc 所有参数配置参考 https://lionli.blog.csdn.net/article/details/122018562 # jdbc 所有参数配置参考 https://lionli.blog.csdn.net/article/details/122018562
# rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题) # rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题)
url: jdbc:mysql://127.0.0.1:3306/ruoyi_ai_agent?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true url: jdbc:mysql://127.0.0.1:3306/ruoyi-ai?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
username: root username: root
password: root password: root
agent: # agent:
url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true # url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
# url: jdbc:mysql://localhost:3306/agent_db # # url: jdbc:mysql://localhost:3306/agent_db
username: root # username: root
password: root # password: root
driverClassName: com.mysql.cj.jdbc.Driver # driverClassName: com.mysql.cj.jdbc.Driver
hikari: hikari:
# 最大连接池数量 # 最大连接池数量
@@ -268,4 +268,4 @@ justauth:
client-secret: 1f7d08**********5b7**********29e client-secret: 1f7d08**********5b7**********29e
redirect-uri: ${justauth.address}/social-callback?source=gitea redirect-uri: ${justauth.address}/social-callback?source=gitea
AGENT_ALLOWED_TABLES: "abtest_rule,abtest_project,agent_ban_log,agent_ban_logs,agent_install_sub_task,agent_install_sum_task,agent_install_task" AGENT_ALLOWED_TABLES: ""

View File

@@ -0,0 +1,271 @@
--- # 监控中心配置
spring.boot.admin.client:
# 增加客户端开关
enabled: false
url: http://localhost:9090/admin
instance:
service-host-type: IP
metadata:
username: ${spring.boot.admin.client.username}
userpassword: ${spring.boot.admin.client.password}
username: @monitor.username@
password: @monitor.password@
--- # mcp配置信息
mcp:
sse:
enabled: false
url: http://localhost:8085/sse
--- # 上传文件地址
sys:
upload:
path: D:\\DownLoad
--- # snail-job 配置
snail-job:
enabled: false
# 需要在 SnailJob 后台组管理创建对应名称的组,然后创建任务的时候选择对应的组,才能正确分派任务
group: "ruoyi_group"
# SnailJob 接入验证令牌 详见 script/sql/ry_job.sql `sj_group_config` 表
token: "SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT"
server:
host: 127.0.0.1
port: 17888
# 命名空间UUID 详见 script/sql/ry_job.sql `sj_namespace`表`unique_id`字段
namespace: ${spring.profiles.active}
# 随主应用端口漂移
port: 2${server.port}
# 客户端ip指定
host:
--- # 数据源配置
spring:
datasource:
type: com.zaxxer.hikari.HikariDataSource
# 动态数据源文档 https://www.kancloud.cn/tracy5546/dynamic-datasource/content
dynamic:
# 性能分析插件(有性能损耗 不建议生产环境使用)
p6spy: true
# 设置默认的数据源或者数据源组,默认值即为 master
primary: master
# 严格模式 匹配不到数据源则报错
strict: true
datasource:
# 主库数据源
master:
type: ${spring.datasource.type}
driverClassName: com.mysql.cj.jdbc.Driver
# jdbc 所有参数配置参考 https://lionli.blog.csdn.net/article/details/122018562
# rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题)
url: jdbc:mysql://127.0.0.1:3306/ruoyi-ai-agent?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
username: root
password: root
# agent:
# url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
# # url: jdbc:mysql://localhost:3306/agent_db
# username: root
# password: root
# driverClassName: com.mysql.cj.jdbc.Driver
hikari:
# 最大连接池数量
maxPoolSize: 20
# 最小空闲线程数量
minIdle: 10
# 配置获取连接等待超时的时间
connectionTimeout: 30000
# 校验超时时间
validationTimeout: 5000
# 空闲连接存活最大时间默认10分钟
idleTimeout: 600000
# 此属性控制池中连接的最长生命周期值0表示无限生命周期默认30分钟
maxLifetime: 1800000
# 多久检查一次连接的活性
keepaliveTime: 30000
--- # 上传文件地址
sys:
upload:
path: D:\\DownLoad
--- # redis 单机配置(单机与集群只能开启一个另一个需要注释掉)
spring.data:
redis:
# 地址
host: localhost
# 端口默认为6379
port: 6379
# 数据库索引
database: 0
# redis 密码必须配置
# password: 123456
# 连接超时时间
timeout: 10s
# 是否开启ssl
ssl.enabled: false
# redisson 配置
redisson:
# redis key前缀
keyPrefix:
# 线程池数量
threads: 4
# Netty线程池数量
nettyThreads: 8
# 单节点配置
singleServerConfig:
# 客户端名称 不能用中文
clientName: ruoyi-ai
# 最小空闲连接数
connectionMinimumIdleSize: 8
# 连接池大小
connectionPoolSize: 32
# 连接空闲超时,单位:毫秒
idleConnectionTimeout: 10000
# 命令等待超时,单位:毫秒
timeout: 3000
# 发布和订阅连接池大小
subscriptionConnectionPoolSize: 50
--- # mail 邮件发送
mail:
enabled: false
host: smtp.163.com
port: 465
# 是否需要用户名密码验证
auth: true
# 发送方遵循RFC-822标准
from: xxx@163.com
# 用户名注意如果使用foxmail邮箱此处user为qq号
user: xxx@163.com
# 密码注意某些邮箱需要为SMTP服务单独设置密码详情查看相关帮助
pass: xxxxxxxxxx
# 使用 STARTTLS安全连接STARTTLS是对纯文本通信协议的扩展。
starttlsEnable: true
# 使用SSL安全连接
sslEnable: true
# SMTP超时时长单位毫秒缺省值不超时
timeout: 0
# Socket连接超时值单位毫秒缺省值不超时
connectionTimeout: 0
--- # sms 短信 支持 阿里云 腾讯云 云片 等等各式各样的短信服务商
# https://sms4j.com/doc3/ 差异配置文档地址 支持单厂商多配置,可以配置多个同时使用
sms:
# 配置源类型用于标定配置来源(interface,yaml)
config-type: yaml
# 用于标定yml中的配置是否开启短信拦截接口配置不受此限制
restricted: true
# 短信拦截限制单手机号每分钟最大发送,只对开启了拦截的配置有效
minute-max: 1
# 短信拦截限制单手机号每日最大发送量,只对开启了拦截的配置有效
account-max: 30
# 以下配置来自于 org.dromara.sms4j.provider.config.BaseConfig类中
blends:
# 唯一ID 用于发送短信寻找具体配置 随便定义别用中文即可
# 可以同时存在两个相同厂商 例如: ali1 ali2 两个不同的阿里短信账号 也可用于区分租户
config1:
# 框架定义的厂商名称标识,标定此配置是哪个厂商,详细请看厂商标识介绍部分
supplier: alibaba
# 有些称为accessKey有些称之为apiKey也有称为sdkKey或者appId。
access-key-id: 您的accessKey
# 称为accessSecret有些称之为apiSecret
access-key-secret: 您的accessKeySecret
signature: 您的短信签名
sdk-app-id: 您的sdkAppId
config2:
# 厂商标识,标定此配置是哪个厂商,详细请看厂商标识介绍部分
supplier: tencent
access-key-id: 您的accessKey
access-key-secret: 您的accessKeySecret
signature: 您的短信签名
sdk-app-id: 您的sdkAppId
--- # 三方授权
justauth:
# 前端外网访问地址
address: http://localhost:80
type:
maxkey:
# maxkey 服务器地址
# 注意 如下均配置均不需要修改 maxkey 已经内置好了数据
server-url: http://sso.maxkey.top
client-id: 876892492581044224
client-secret: x1Y5MTMwNzIwMjMxNTM4NDc3Mzche8
redirect-uri: ${justauth.address}/social-callback?source=maxkey
topiam:
# topiam 服务器地址
server-url: http://127.0.0.1:1898/api/v1/authorize/y0q************spq***********8ol
client-id: 449c4*********937************759
client-secret: ac7***********1e0************28d
redirect-uri: ${justauth.address}/social-callback?source=topiam
scopes: [openid, email, phone, profile]
qq:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: ${justauth.address}/social-callback?source=qq
union-id: false
weibo:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: ${justauth.address}/social-callback?source=weibo
gitee:
client-id: 91436b7940090d09c72c7daf85b959cfd5f215d67eea73acbf61b6b590751a98
client-secret: 02c6fcfd70342980cd8dd2f2c06c1a350645d76c754d7a264c4e125f9ba915ac
redirect-uri: ${justauth.address}/social-callback?source=gitee
dingtalk:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: ${justauth.address}/social-callback?source=dingtalk
baidu:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: ${justauth.address}/social-callback?source=baidu
csdn:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: ${justauth.address}/social-callback?source=csdn
coding:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: ${justauth.address}/social-callback?source=coding
coding-group-name: xx
oschina:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: ${justauth.address}/social-callback?source=oschina
alipay_wallet:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: ${justauth.address}/social-callback?source=alipay_wallet
alipay-public-key: MIIB**************DAQAB
wechat_open:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: ${justauth.address}/social-callback?source=wechat_open
wechat_mp:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: ${justauth.address}/social-callback?source=wechat_mp
wechat_enterprise:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: ${justauth.address}/social-callback?source=wechat_enterprise
agent-id: 1000002
gitlab:
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: ${justauth.address}/social-callback?source=gitlab
gitea:
# 前端改动 https://gitee.com/JavaLionLi/plus-ui/pulls/204
# gitea 服务器地址
server-url: https://demo.gitea.com
client-id: 10**********6
client-secret: 1f7d08**********5b7**********29e
redirect-uri: ${justauth.address}/social-callback?source=gitea
AGENT_ALLOWED_TABLES: "abtest_rule,abtest_project,agent_ban_log,agent_ban_logs,agent_install_sub_task,agent_install_sum_task,agent_install_task"

View File

@@ -125,9 +125,6 @@ security:
- /*/api-docs/** - /*/api-docs/**
- /warm-flow-ui/config - /warm-flow-ui/config
- /workflow/run - /workflow/run
- /user/qrcode
- /user/login/qrcode
- /weixin/check
# 多租户配置 # 多租户配置
tenant: tenant:
# 是否开启 # 是否开启
@@ -206,6 +203,7 @@ springdoc:
name: ageerle name: ageerle
email: ageerle@163.com email: ageerle@163.com
url: https://gitee.com/ageerle/ruoyi-ai url: https://gitee.com/ageerle/ruoyi-ai
#这里定义了两个分组,可定义多个,也可以不定义 #这里定义了两个分组,可定义多个,也可以不定义
group-configs: group-configs:
- group: 1.演示模块 - group: 1.演示模块
@@ -247,12 +245,6 @@ management:
show-details: ALWAYS show-details: ALWAYS
logfile: logfile:
external-file: ./logs/sys-console.log external-file: ./logs/sys-console.log
health:
# ⚠️ 禁用 Neo4j 健康检查
# Spring Boot Actuator 会自动为 Neo4j 创建健康检查器
# 这会导致应用在启动时尝试连接到 Neo4j
neo4j:
enabled: false
--- # 默认/推荐使用sse推送 --- # 默认/推荐使用sse推送
sse: sse:
@@ -283,9 +275,9 @@ warm-flow:
# 向量库配置 # 向量库配置
vector-store: vector-store:
# 向量存储类型 可选(weaviate/milvus) # 向量存储类型 可选(weaviate/milvus/qdrant)
# 如需修改向量库类型,请修改此配置值! # 如需修改向量库类型,请修改此配置值!
type: weaviate type: milvus
# Weaviate配置 # Weaviate配置
weaviate: weaviate:
protocol: http protocol: http
@@ -295,81 +287,10 @@ vector-store:
milvus: milvus:
url: http://localhost:19530 url: http://localhost:19530
collectionname: LocalKnowledge collectionname: LocalKnowledge
# Qdrant配置
chat: qdrant:
memory: host: localhost
enabled: true port: 6334
maxMessages: 20 collectionname: LocalKnowledge
persistenceEnabled: true api-key:
use-tls: false
# 企业微信应用
wechat:
cp:
corpId:
appConfigs:
- agentId:
secret: ''
token: ''
aesKey: ''
--- # Neo4j 知识图谱配置
neo4j:
uri: bolt://117.72.192.162:7687
username: neo4j
password: MySecurePass123!
database: neo4j
max-connection-pool-size: 50
connection-timeout-seconds: 30
# 知识图谱配置
knowledge:
graph:
# 是否启用知识图谱功能
enabled: false
# 图数据库类型: neo4j 或 apache-age
database-type: neo4j
# 是否自动创建索引
auto-create-index: true
# 批量处理大小
batch-size: 1000
# 最大重试次数
max-retry-count: 3
# 实体抽取配置
extraction:
# 置信度阈值(低于此值的实体将被过滤)
confidence-threshold: 0.7
# 最大实体数量(每个文档)
max-entities-per-doc: 100
# 最大关系数量(每个文档)
max-relations-per-doc: 200
# 文本分片大小(用于长文档)
chunk-size: 2000
# 分片重叠大小
chunk-overlap: 200
# 查询配置
query:
# 默认查询限制数量
default-limit: 100
# 最大查询限制数量
max-limit: 1000
# 路径查询最大深度
max-path-depth: 5
# 查询超时时间(秒)
timeout-seconds: 30
# 是否启用查询缓存
cache-enabled: true
# 缓存过期时间(分钟)
cache-expire-minutes: 60
--- # MCP 模块配置
app:
mcp:
client:
# 请求超时时间(秒)
request-timeout: 30
# 连接超时时间(秒)
connection-timeout: 10
# 最大重试次数
max-retries: 3

View File

@@ -48,11 +48,6 @@ public class ChatMessageBo extends BaseEntity {
*/ */
private String role; private String role;
/**
* 扣除金额
*/
private Long deductCost;
/** /**
* 累计 Tokens * 累计 Tokens
*/ */
@@ -63,11 +58,6 @@ public class ChatMessageBo extends BaseEntity {
*/ */
private String modelName; private String modelName;
/**
* 计费类型1-token计费2-次数计费)
*/
private String billingType;
/** /**
* 备注 * 备注
*/ */

View File

@@ -45,30 +45,15 @@ public class ChatModelBo extends BaseEntity {
*/ */
private String modelDescribe; private String modelDescribe;
/**
* 模型价格
*/
private Long modelPrice;
/**
* 计费类型
*/
private String modelType;
/** /**
* 是否显示 * 是否显示
*/ */
private String modelShow; private String modelShow;
/** /**
* 是否免费 * 向量维度
*/ */
private String modelFree; private Integer modelDimension;
/**
* 模型优先级(值越大优先级越高)
*/
private Long priority;
/** /**
* 请求地址 * 请求地址

View File

@@ -1,45 +0,0 @@
package org.ruoyi.common.chat.domain.dto;
import lombok.Data;
/**
* 聊天消息DTO - 用于上下文传递
*
* @author ageerle@163.com
* @date 2025/12/13
*/
@Data
public class ChatMessageDTO {
/**
* 消息角色: system/user/assistant
*/
private String role;
/**
* 消息内容
*/
private String content;
public static ChatMessageDTO system(String content) {
ChatMessageDTO msg = new ChatMessageDTO();
msg.role = "system";
msg.content = content;
return msg;
}
public static ChatMessageDTO user(String content) {
ChatMessageDTO msg = new ChatMessageDTO();
msg.role = "user";
msg.content = content;
return msg;
}
public static ChatMessageDTO assistant(String content) {
ChatMessageDTO msg = new ChatMessageDTO();
msg.role = "assistant";
msg.content = content;
return msg;
}
}

View File

@@ -1,11 +1,11 @@
package org.ruoyi.common.chat.domain.dto.request; package org.ruoyi.common.chat.domain.dto.request;
import dev.langchain4j.data.message.ChatMessage; import com.alibaba.fastjson.annotation.JSONField;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotEmpty;
import lombok.Data; import lombok.Data;
import org.ruoyi.common.chat.domain.dto.ChatMessageDTO;
import java.util.List;
/** /**
* 对话请求对象 * 对话请求对象
@@ -16,11 +16,15 @@ import java.util.List;
@Data @Data
public class ChatRequest { public class ChatRequest {
@NotEmpty(message = "对话消息不能为空")
private List<ChatMessageDTO> messages;
@NotEmpty(message = "传入的模型不能为空") @NotEmpty(message = "传入的模型不能为空")
private String model; private String model;
/**
* 对话消息
*/
@NotEmpty(message = "对话消息不能为空")
private String content;
/** /**
* 工作流请求体 * 工作流请求体
*/ */
@@ -31,59 +35,49 @@ public class ChatRequest {
*/ */
private ReSumeRunner reSumeRunner; private ReSumeRunner reSumeRunner;
/**
* 是否为人机交互用户继续输入
*/
private Boolean isResume = false;
/** /**
* 是否启用工作流 * 是否启用工作流
*/ */
private Boolean enableWorkFlow; private Boolean enableWorkFlow = false;
/** /**
* 会话id * 会话id
*/ */
@JsonSerialize(using = ToStringSerializer.class)
@JSONField(serializeUsing = String.class)
private Long sessionId; private Long sessionId;
/**
* 应用ID
*/
private String appId;
/** /**
* 知识库id * 知识库id
*/ */
private String knowledgeId; private String knowledgeId;
/** /**
* 对话id(每个聊天窗口都不一样) * 应用ID
*/ */
private Long uuid; private String appId;
/** /**
* 是否为人机交互用户继续输入 * 对话id(每个聊天窗口都不一样)
*/ */
private Boolean isResume; @JsonSerialize(using = ToStringSerializer.class)
@JSONField(serializeUsing = String.class)
private Long uuid;
/** /**
* 是否启用深度思考 * 是否启用深度思考
*/ */
private Boolean enableThinking; private Boolean enableThinking = false;
/**
* 是否自动切换模型
*/
private Boolean autoSelectModel;
/** /**
* 是否支持联网 * 是否支持联网
*/ */
private Boolean enableInternet; private Boolean enableInternet;
/**
* 会话令牌为避免在非Web线程中获取Request入口处注入
*/
private String token;
/**
* 原生对话对象
*/
private List<ChatMessage> chatMessages;
} }

View File

@@ -5,8 +5,6 @@ import cn.idev.excel.annotation.ExcelProperty;
import io.github.linpeilie.annotations.AutoMapper; import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data; import lombok.Data;
import org.ruoyi.common.chat.entity.chat.ChatMessage; import org.ruoyi.common.chat.entity.chat.ChatMessage;
import org.ruoyi.common.excel.annotation.ExcelDictFormat;
import org.ruoyi.common.excel.convert.ExcelDictConvert;
import java.io.Serial; import java.io.Serial;
import java.io.Serializable; import java.io.Serializable;
@@ -56,12 +54,6 @@ public class ChatMessageVo implements Serializable {
@ExcelProperty(value = "对话角色") @ExcelProperty(value = "对话角色")
private String role; private String role;
/**
* 扣除金额
*/
@ExcelProperty(value = "扣除金额")
private Long deductCost;
/** /**
* 累计 Tokens * 累计 Tokens
*/ */
@@ -74,12 +66,6 @@ public class ChatMessageVo implements Serializable {
@ExcelProperty(value = "模型名称") @ExcelProperty(value = "模型名称")
private String modelName; private String modelName;
/**
* 计费类型1-token计费2-次数计费)
*/
@ExcelProperty(value = "计费类型", converter = ExcelDictConvert.class)
@ExcelDictFormat(readConverterExp = "1=-token计费2-次数计费")
private String billingType;
/** /**
* 备注 * 备注

View File

@@ -54,17 +54,6 @@ public class ChatModelVo implements Serializable {
@ExcelProperty(value = "模型描述") @ExcelProperty(value = "模型描述")
private String modelDescribe; private String modelDescribe;
/**
* 模型价格
*/
@ExcelProperty(value = "模型价格")
private Long modelPrice;
/**
* 计费类型
*/
@ExcelProperty(value = "计费类型")
private String modelType;
/** /**
* 是否显示 * 是否显示
@@ -73,16 +62,10 @@ public class ChatModelVo implements Serializable {
private String modelShow; private String modelShow;
/** /**
* 是否免费 * 向量维度
*/ */
@ExcelProperty(value = "是否免费") @ExcelProperty(value = "向量维度")
private String modelFree; private Integer modelDimension;
/**
* 模型优先级(值越大优先级越高)
*/
@ExcelProperty(value = "模型优先级(值越大优先级越高)")
private Long priority;
/** /**
* 请求地址 * 请求地址
@@ -102,11 +85,5 @@ public class ChatModelVo implements Serializable {
@ExcelProperty(value = "备注") @ExcelProperty(value = "备注")
private String remark; private String remark;
/**
* 模型维度
*/
private Integer dimension;
} }

View File

@@ -6,12 +6,14 @@ import com.baomidou.mybatisplus.annotation.TableId;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
import java.io.Serial;
import java.io.Serializable; import java.io.Serializable;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@Data @Data
public class BaseEntity implements Serializable { public class BaseEntity implements Serializable {
@Serial
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@TableId(type = IdType.AUTO) @TableId(type = IdType.AUTO)

View File

@@ -1,57 +0,0 @@
package org.ruoyi.common.chat.entity.chat;
import dev.langchain4j.model.chat.response.StreamingChatResponseHandler;
import jakarta.validation.constraints.NotNull;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.ruoyi.common.chat.domain.dto.request.ChatRequest;
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
/**
* 聊天对话上下文对象
*
* @author zengxb
* @date 2026-02-14
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Builder
public class ChatContext {
/**
* 模型管理视图对象
*/
@NotNull(message = "模型管理视图对象不能为空")
private ChatModelVo chatModelVo;
/**
* 对话请求对象
*/
@NotNull(message = "对话请求对象不能为空")
private ChatRequest chatRequest;
/**
* SSe连接对象
*/
@NotNull(message = "SSe连接对象不能为空")
private SseEmitter emitter;
/**
* 用户ID
*/
@NotNull(message = "用户ID不能为空")
private Long userId;
/**
* Token
*/
@NotNull(message = "Token不能为空")
private String tokenValue;
/**
* 响应处理器
*/
private StreamingChatResponseHandler handler;
}

View File

@@ -48,10 +48,6 @@ public class ChatMessage extends TenantEntity {
*/ */
private String role; private String role;
/**
* 扣除金额
*/
private Long deductCost;
/** /**
* 累计 Tokens * 累计 Tokens
@@ -63,11 +59,6 @@ public class ChatMessage extends TenantEntity {
*/ */
private String modelName; private String modelName;
/**
* 计费类型1-token计费2-次数计费)
*/
private String billingType;
/** /**
* 备注 * 备注
*/ */

View File

@@ -48,15 +48,6 @@ public class ChatModel extends TenantEntity {
*/ */
private String modelDescribe; private String modelDescribe;
/**
* 模型价格
*/
private Long modelPrice;
/**
* 计费类型
*/
private String modelType;
/** /**
* 是否显示 * 是否显示
@@ -64,14 +55,9 @@ public class ChatModel extends TenantEntity {
private String modelShow; private String modelShow;
/** /**
* 是否免费 * 向量维度
*/ */
private String modelFree; private Integer modelDimension;
/**
* 模型优先级(值越大优先级越高)
*/
private Long priority;
/** /**
* 请求地址 * 请求地址

View File

@@ -1,7 +1,8 @@
package org.ruoyi.common.chat.service.chat; package org.ruoyi.common.chat.service.chat;
import dev.langchain4j.model.chat.response.StreamingChatResponseHandler;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import org.ruoyi.common.chat.entity.chat.ChatContext; import org.ruoyi.common.chat.domain.dto.request.ChatRequest;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
/** /**
@@ -12,10 +13,15 @@ public interface IChatService {
/** /**
* 客户端发送对话消息到服务端 * 客户端发送对话消息到服务端
*/ */
SseEmitter chat(@Valid ChatContext chatContext); SseEmitter chat(@Valid ChatRequest chatRequest);
/** /**
* 获取服务提供商名称 * 支持外部 handler 的对话接口(跨模块调用)
* 同时发送到 SSE 和外部 handler
*
* @param chatRequest 聊天请求
* @param externalHandler 外部响应处理器(可为 null
*/ */
String getProviderName(); void chat(@Valid ChatRequest chatRequest, StreamingChatResponseHandler externalHandler);
} }

View File

@@ -1,59 +0,0 @@
package org.ruoyi.common.chat.service.chatMessage;
import org.ruoyi.common.chat.domain.bo.chat.ChatMessageBo;
import org.ruoyi.common.chat.domain.dto.request.ChatRequest;
import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
/**
* 聊天信息抽象基类 - 保存聊天信息
*
* @author Zengxb
* @date 2026-02-24
*/
public abstract class AbstractChatMessageService {
/**
* 创建日志对象
*/
Logger log = LoggerFactory.getLogger(AbstractChatMessageService.class);
@Autowired
private IChatMessageService chatMessageService;
/**
* 保存聊天信息
*/
public void saveChatMessage(ChatRequest chatRequest, Long userId, String content, String role, ChatModelVo chatModelVo){
try {
// 验证必要的上下文信息
if (chatRequest == null || userId == null) {
log.warn("缺少必要的聊天上下文信息,无法保存消息");
return;
}
// 创建ChatMessageBo对象
ChatMessageBo messageBO = new ChatMessageBo();
messageBO.setUserId(userId);
messageBO.setSessionId(chatRequest.getSessionId());
messageBO.setContent(content);
messageBO.setRole(role);
messageBO.setModelName(chatRequest.getModel());
messageBO.setBillingType(chatModelVo.getModelType());
messageBO.setRemark(null);
chatMessageService.insertByBo(messageBO);
} catch (Exception e) {
log.error("保存{}聊天消息时出错: {}", getProviderName(), e.getMessage(), e);
}
}
/**
* 获取服务提供商名称
*/
protected String getProviderName(){
return "默认工作流大模型";
}
}

View File

@@ -0,0 +1,61 @@
package org.ruoyi.common.core.utils.file;
import java.util.HashMap;
import java.util.Map;
/**
* @Author: xiaoen
* @Description: Content-Type 映射工具
* @Date: Created in 18:50 2026/3/17
*/
public class ContentTypeUtil {
private static final Map<String, String> CONTENT_TYPE_MAP = new HashMap<>();
static {
// 文本文件
CONTENT_TYPE_MAP.put(".txt", "text/plain; charset=UTF-8");
CONTENT_TYPE_MAP.put(".html", "text/html; charset=UTF-8");
CONTENT_TYPE_MAP.put(".htm", "text/html; charset=UTF-8");
CONTENT_TYPE_MAP.put(".css", "text/css; charset=UTF-8");
CONTENT_TYPE_MAP.put(".js", "application/javascript; charset=UTF-8");
CONTENT_TYPE_MAP.put(".json", "application/json; charset=UTF-8");
CONTENT_TYPE_MAP.put(".xml", "application/xml; charset=UTF-8");
CONTENT_TYPE_MAP.put(".csv", "text/csv; charset=UTF-8");
// 图片文件
CONTENT_TYPE_MAP.put(".jpg", "image/jpeg");
CONTENT_TYPE_MAP.put(".jpeg", "image/jpeg");
CONTENT_TYPE_MAP.put(".png", "image/png");
CONTENT_TYPE_MAP.put(".gif", "image/gif");
CONTENT_TYPE_MAP.put(".bmp", "image/bmp");
CONTENT_TYPE_MAP.put(".webp", "image/webp");
CONTENT_TYPE_MAP.put(".svg", "image/svg+xml");
// 应用文件
CONTENT_TYPE_MAP.put(".pdf", "application/pdf");
CONTENT_TYPE_MAP.put(".doc", "application/msword");
CONTENT_TYPE_MAP.put(".docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document");
CONTENT_TYPE_MAP.put(".xls", "application/vnd.ms-excel");
CONTENT_TYPE_MAP.put(".xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
CONTENT_TYPE_MAP.put(".ppt", "application/vnd.ms-powerpoint");
CONTENT_TYPE_MAP.put(".pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation");
// 其他
CONTENT_TYPE_MAP.put(".mp3", "audio/mpeg");
CONTENT_TYPE_MAP.put(".mp4", "video/mp4");
CONTENT_TYPE_MAP.put(".zip", "application/zip");
CONTENT_TYPE_MAP.put(".rar", "application/x-rar-compressed");
}
/**
* 根据后缀返回对应的 content-type
* @param suffix
* @param defaultContentType
* @return
*/
public static String getContentType(String suffix, String defaultContentType) {
String contentType = CONTENT_TYPE_MAP.get(suffix.toLowerCase());
return contentType != null ? contentType : defaultContentType;
}
}

View File

@@ -2,9 +2,11 @@ package org.ruoyi.common.sse.core;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil; import cn.hutool.core.map.MapUtil;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.ruoyi.common.core.utils.SpringUtils; import org.ruoyi.common.core.utils.SpringUtils;
import org.ruoyi.common.redis.utils.RedisUtils; import org.ruoyi.common.redis.utils.RedisUtils;
import org.ruoyi.common.sse.dto.SseEventDto;
import org.ruoyi.common.sse.dto.SseMessageDto; import org.ruoyi.common.sse.dto.SseMessageDto;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
@@ -65,7 +67,7 @@ public class SseEmitterManager {
emitter.onCompletion(() -> { emitter.onCompletion(() -> {
SseEmitter remove = emitters.remove(token); SseEmitter remove = emitters.remove(token);
if (remove != null) { if (remove != null) {
// remove.complete(); remove.complete();
} }
}); });
emitter.onTimeout(() -> { emitter.onTimeout(() -> {
@@ -174,9 +176,11 @@ public class SseEmitterManager {
if (MapUtil.isNotEmpty(emitters)) { if (MapUtil.isNotEmpty(emitters)) {
for (Map.Entry<String, SseEmitter> entry : emitters.entrySet()) { for (Map.Entry<String, SseEmitter> entry : emitters.entrySet()) {
try { try {
// 格式化为标准SSE JSON格式
SseEventDto eventDto = SseEventDto.content(message);
entry.getValue().send(SseEmitter.event() entry.getValue().send(SseEmitter.event()
.name("message") .name("message")
.data(message)); .data(JSONUtil.toJsonStr(eventDto)));
} catch (Exception e) { } catch (Exception e) {
SseEmitter remove = emitters.remove(entry.getKey()); SseEmitter remove = emitters.remove(entry.getKey());
if (remove != null) { if (remove != null) {
@@ -189,6 +193,33 @@ public class SseEmitterManager {
} }
} }
/**
* 向指定的用户会话发送结构化事件
*
* @param userId 要发送消息的用户id
* @param eventDto SSE事件对象
*/
public void sendEvent(Long userId, SseEventDto eventDto) {
Map<String, SseEmitter> emitters = USER_TOKEN_EMITTERS.get(userId);
if (MapUtil.isNotEmpty(emitters)) {
for (Map.Entry<String, SseEmitter> entry : emitters.entrySet()) {
try {
entry.getValue().send(SseEmitter.event()
.name(eventDto.getEvent())
.data(JSONUtil.toJsonStr(eventDto)));
} catch (Exception e) {
SseEmitter remove = emitters.remove(entry.getKey());
if (remove != null) {
remove.complete();
}
}
}
} else {
USER_TOKEN_EMITTERS.remove(userId);
}
}
/** /**
* 本机全用户会话发送消息 * 本机全用户会话发送消息
* *

View File

@@ -0,0 +1,92 @@
package org.ruoyi.common.sse.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
/**
* SSE 事件数据传输对象
* <p>
* 标准的 SSE 消息格式,支持不同事件类型
*
* @author ageerle@163.com
* @date 2025/03/19
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SseEventDto implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 事件类型
*/
private String event;
/**
* 消息内容
*/
private String content;
/**
* 推理内容(深度思考模式)
*/
private String reasoningContent;
/**
* 错误信息
*/
private String error;
/**
* 是否完成
*/
private Boolean done;
/**
* 创建内容事件
*/
public static SseEventDto content(String content) {
return SseEventDto.builder()
.event("content")
.content(content)
.build();
}
/**
* 创建推理内容事件
*/
public static SseEventDto reasoning(String reasoningContent) {
return SseEventDto.builder()
.event("reasoning")
.reasoningContent(reasoningContent)
.build();
}
/**
* 创建完成事件
*/
public static SseEventDto done() {
return SseEventDto.builder()
.event("done")
.done(true)
.build();
}
/**
* 创建错误事件
*/
public static SseEventDto error(String error) {
return SseEventDto.builder()
.event("error")
.error(error)
.build();
}
}

View File

@@ -1,10 +1,12 @@
package org.ruoyi.common.sse.utils; package org.ruoyi.common.sse.utils;
import java.util.Collections;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.ruoyi.common.core.utils.SpringUtils; import org.ruoyi.common.core.utils.SpringUtils;
import org.ruoyi.common.sse.core.SseEmitterManager; import org.ruoyi.common.sse.core.SseEmitterManager;
import org.ruoyi.common.sse.dto.SseEventDto;
import org.ruoyi.common.sse.dto.SseMessageDto; import org.ruoyi.common.sse.dto.SseMessageDto;
/** /**
@@ -27,6 +29,7 @@ public class SseMessageUtils {
/** /**
* 向指定的SSE会话发送消息 * 向指定的SSE会话发送消息
* 通过 Redis Pub/Sub 广播,确保跨模块消息可达
* *
* @param userId 要发送消息的用户id * @param userId 要发送消息的用户id
* @param message 要发送的消息内容 * @param message 要发送的消息内容
@@ -35,7 +38,11 @@ public class SseMessageUtils {
if (!isEnable()) { if (!isEnable()) {
return; return;
} }
MANAGER.sendMessage(userId, message); // 通过 Redis 广播,让所有模块的 SseTopicListener 接收并转发到本地 SSE 连接
SseMessageDto dto = new SseMessageDto();
dto.setMessage(message);
dto.setUserIds(Collections.singletonList(userId));
MANAGER.publishMessage(dto);
} }
/** /**
@@ -86,6 +93,58 @@ public class SseMessageUtils {
MANAGER.disconnect(userId, tokenValue); MANAGER.disconnect(userId, tokenValue);
} }
/**
* 向指定的SSE会话发送结构化事件
*
* @param userId 要发送消息的用户id
* @param eventDto SSE事件对象
*/
public static void sendEvent(Long userId, SseEventDto eventDto) {
if (!isEnable()) {
return;
}
MANAGER.sendEvent(userId, eventDto);
}
/**
* 发送内容事件
*
* @param userId 用户ID
* @param content 内容
*/
public static void sendContent(Long userId, String content) {
sendEvent(userId, SseEventDto.content(content));
}
/**
* 发送推理内容事件
*
* @param userId 用户ID
* @param reasoningContent 推理内容
*/
public static void sendReasoning(Long userId, String reasoningContent) {
sendEvent(userId, SseEventDto.reasoning(reasoningContent));
}
/**
* 发送完成事件
*
* @param userId 用户ID
*/
public static void sendDone(Long userId) {
sendEvent(userId, SseEventDto.done());
}
/**
* 发送错误事件
*
* @param userId 用户ID
* @param error 错误信息
*/
public static void sendError(Long userId, String error) {
sendEvent(userId, SseEventDto.error(error));
}
/** /**
* 是否开启 * 是否开启
*/ */

View File

@@ -12,7 +12,6 @@
<packaging>pom</packaging> <packaging>pom</packaging>
<modules> <modules>
<module>ruoyi-ai-copilot</module>
<module>ruoyi-monitor-admin</module> <module>ruoyi-monitor-admin</module>
<module>ruoyi-snailjob-server</module> <module>ruoyi-snailjob-server</module>
</modules> </modules>

View File

@@ -1,100 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>ruoyi-ai-copilot</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<name>copilot</name>
<description>SpringAI - Alibaba - Copilot</description>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring-boot.version>4.0.1</spring-boot.version>
<spring-ai.version>2.0.0-M2</spring-ai.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>${spring-ai.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- Spring Boot Starters -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring AI -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-client</artifactId>
</dependency>
<dependency>
<groupId>org.springaicommunity</groupId>
<artifactId>spring-ai-agent-utils</artifactId>
<version>0.4.2</version>
</dependency>
<!-- JSON Processing -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- JSON Schema Validation -->
<dependency>
<groupId>com.networknt</groupId>
<artifactId>json-schema-validator</artifactId>
<version>1.0.87</version>
</dependency>
<!-- Diff Utils -->
<dependency>
<groupId>io.github.java-diff-utils</groupId>
<artifactId>java-diff-utils</artifactId>
<version>4.12</version>
</dependency>
<!-- Apache Commons -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -1,82 +0,0 @@
package com.example.demo;
import com.example.demo.config.AppProperties;
import com.example.demo.utils.BrowserUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.event.EventListener;
import org.springframework.core.env.Environment;
/**
* 主要功能:
* 1. 文件读取、写入、编辑
* 2. 目录列表和结构查看
* 4. 连续性文件操作
*/
@SpringBootApplication
@EnableConfigurationProperties(AppProperties.class)
public class CopilotApplication {
private static final Logger logger = LoggerFactory.getLogger(CopilotApplication.class);
@Autowired
private AppProperties appProperties;
@Autowired
private Environment environment;
public static void main(String[] args) {
SpringApplication.run(CopilotApplication.class, args);
}
/**
* 应用启动完成后的事件监听器
* 自动打开浏览器访问应用首页
*/
@EventListener(ApplicationReadyEvent.class)
public void onApplicationReady() {
AppProperties.Browser browserConfig = appProperties.getBrowser();
if (!browserConfig.isAutoOpen()) {
logger.info("Browser auto-open is disabled");
return;
}
// 获取实际的服务器端口
String port = environment.getProperty("server.port", "8080");
String actualUrl = browserConfig.getUrl().replace("${server.port:8080}", port);
logger.info("Application started successfully!");
logger.info("Preparing to open browser in {} seconds...", browserConfig.getDelaySeconds());
// 在新线程中延迟打开浏览器,避免阻塞主线程
new Thread(() -> {
try {
Thread.sleep(browserConfig.getDelaySeconds() * 1000L);
if (BrowserUtil.isValidUrl(actualUrl)) {
boolean success = BrowserUtil.openBrowser(actualUrl);
if (success) {
logger.info("✅ Browser opened successfully: {}", actualUrl);
System.out.println("🌐 Web interface opened: " + actualUrl);
} else {
logger.warn("❌ Failed to open browser automatically");
System.out.println("⚠️ Please manually open: " + actualUrl);
}
} else {
logger.error("❌ Invalid URL: {}", actualUrl);
System.out.println("⚠️ Invalid URL configured: " + actualUrl);
}
} catch (InterruptedException e) {
logger.warn("Browser opening was interrupted", e);
Thread.currentThread().interrupt();
}
}).start();
}
}

View File

@@ -1,232 +0,0 @@
package com.example.demo.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.nio.file.Paths;
import java.util.List;
/**
* 应用配置属性
*/
@ConfigurationProperties(prefix = "app")
public class AppProperties {
private Workspace workspace = new Workspace();
private Security security = new Security();
private Tools tools = new Tools();
private Browser browser = new Browser();
// Getters and Setters
public Workspace getWorkspace() {
return workspace;
}
public void setWorkspace(Workspace workspace) {
this.workspace = workspace;
}
public Security getSecurity() {
return security;
}
public void setSecurity(Security security) {
this.security = security;
}
public Tools getTools() {
return tools;
}
public void setTools(Tools tools) {
this.tools = tools;
}
public Browser getBrowser() {
return browser;
}
public void setBrowser(Browser browser) {
this.browser = browser;
}
/**
* 审批模式
*/
public enum ApprovalMode {
DEFAULT, // 默认模式,危险操作需要确认
AUTO_EDIT, // 自动编辑模式,文件编辑不需要确认
YOLO // 完全自动模式,所有操作都不需要确认
}
/**
* 工作空间配置
*/
public static class Workspace {
// 使用 Paths.get() 和 File.separator 实现跨平台兼容
private String rootDirectory = Paths.get(System.getProperty("user.dir"), "workspace").toString();
private long maxFileSize = 10485760L; // 10MB
private List<String> allowedExtensions = List.of(
".txt", ".md", ".java", ".js", ".ts", ".json", ".xml",
".yml", ".yaml", ".properties", ".html", ".css", ".sql"
);
// Getters and Setters
public String getRootDirectory() {
return rootDirectory;
}
public void setRootDirectory(String rootDirectory) {
// 确保设置的路径也是跨平台兼容的
this.rootDirectory = Paths.get(rootDirectory).toString();
}
public long getMaxFileSize() {
return maxFileSize;
}
public void setMaxFileSize(long maxFileSize) {
this.maxFileSize = maxFileSize;
}
public List<String> getAllowedExtensions() {
return allowedExtensions;
}
public void setAllowedExtensions(List<String> allowedExtensions) {
this.allowedExtensions = allowedExtensions;
}
}
/**
* 安全配置
*/
public static class Security {
private ApprovalMode approvalMode = ApprovalMode.DEFAULT;
private List<String> dangerousCommands = List.of("rm", "del", "format", "fdisk", "mkfs");
// Getters and Setters
public ApprovalMode getApprovalMode() {
return approvalMode;
}
public void setApprovalMode(ApprovalMode approvalMode) {
this.approvalMode = approvalMode;
}
public List<String> getDangerousCommands() {
return dangerousCommands;
}
public void setDangerousCommands(List<String> dangerousCommands) {
this.dangerousCommands = dangerousCommands;
}
}
/**
* 工具配置
*/
public static class Tools {
private ToolConfig readFile = new ToolConfig(true);
private ToolConfig writeFile = new ToolConfig(true);
private ToolConfig editFile = new ToolConfig(true);
private ToolConfig listDirectory = new ToolConfig(true);
private ToolConfig shell = new ToolConfig(true);
// Getters and Setters
public ToolConfig getReadFile() {
return readFile;
}
public void setReadFile(ToolConfig readFile) {
this.readFile = readFile;
}
public ToolConfig getWriteFile() {
return writeFile;
}
public void setWriteFile(ToolConfig writeFile) {
this.writeFile = writeFile;
}
public ToolConfig getEditFile() {
return editFile;
}
public void setEditFile(ToolConfig editFile) {
this.editFile = editFile;
}
public ToolConfig getListDirectory() {
return listDirectory;
}
public void setListDirectory(ToolConfig listDirectory) {
this.listDirectory = listDirectory;
}
public ToolConfig getShell() {
return shell;
}
public void setShell(ToolConfig shell) {
this.shell = shell;
}
}
/**
* 工具配置
*/
public static class ToolConfig {
private boolean enabled;
public ToolConfig() {
}
public ToolConfig(boolean enabled) {
this.enabled = enabled;
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}
/**
* 浏览器配置
*/
public static class Browser {
private boolean autoOpen = true;
private String url = "http://localhost:8080";
private int delaySeconds = 2;
// Getters and Setters
public boolean isAutoOpen() {
return autoOpen;
}
public void setAutoOpen(boolean autoOpen) {
this.autoOpen = autoOpen;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public int getDelaySeconds() {
return delaySeconds;
}
public void setDelaySeconds(int delaySeconds) {
this.delaySeconds = delaySeconds;
}
}
}

View File

@@ -1,130 +0,0 @@
package com.example.demo.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.context.request.async.AsyncRequestTimeoutException;
import java.util.concurrent.TimeoutException;
/**
* 全局异常处理器
*/
@ControllerAdvice
public class GlobalExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
/**
* 处理超时异常
*/
@ExceptionHandler({TimeoutException.class, AsyncRequestTimeoutException.class})
public ResponseEntity<ErrorResponse> handleTimeoutException(Exception e, WebRequest request) {
logger.error("Request timeout occurred", e);
ErrorResponse errorResponse = new ErrorResponse(
"TIMEOUT_ERROR",
"Request timed out. The operation took too long to complete.",
"Please try again with a simpler request or check your network connection."
);
return ResponseEntity.status(HttpStatus.REQUEST_TIMEOUT).body(errorResponse);
}
/**
* 处理AI相关异常
*/
@ExceptionHandler(RuntimeException.class)
public ResponseEntity<ErrorResponse> handleRuntimeException(RuntimeException e, WebRequest request) {
logger.error("Runtime exception occurred", e);
// 检查是否是AI调用相关的异常
String message = e.getMessage();
if (message != null && (message.contains("tool") || message.contains("function") || message.contains("AI"))) {
ErrorResponse errorResponse = new ErrorResponse(
"AI_TOOL_ERROR",
"An error occurred during AI tool execution: " + message,
"The AI encountered an issue while processing your request. Please try rephrasing your request or try again."
);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse);
}
ErrorResponse errorResponse = new ErrorResponse(
"RUNTIME_ERROR",
"An unexpected error occurred: " + message,
"Please try again. If the problem persists, contact support."
);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse);
}
/**
* 处理所有其他异常
*/
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGenericException(Exception e, WebRequest request) {
logger.error("Unexpected exception occurred", e);
ErrorResponse errorResponse = new ErrorResponse(
"INTERNAL_ERROR",
"An internal server error occurred",
"Something went wrong on our end. Please try again later."
);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse);
}
/**
* 错误响应类
*/
public static class ErrorResponse {
private String errorCode;
private String message;
private String suggestion;
private long timestamp;
public ErrorResponse(String errorCode, String message, String suggestion) {
this.errorCode = errorCode;
this.message = message;
this.suggestion = suggestion;
this.timestamp = System.currentTimeMillis();
}
// Getters and setters
public String getErrorCode() {
return errorCode;
}
public void setErrorCode(String errorCode) {
this.errorCode = errorCode;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getSuggestion() {
return suggestion;
}
public void setSuggestion(String suggestion) {
this.suggestion = suggestion;
}
public long getTimestamp() {
return timestamp;
}
public void setTimestamp(long timestamp) {
this.timestamp = timestamp;
}
}
}

View File

@@ -1,49 +0,0 @@
package com.example.demo.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.EventListener;
import java.io.File;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
* 日志配置类
* 确保日志目录存在并记录应用启动信息
*/
@Configuration
public class LoggingConfiguration {
private static final Logger logger = LoggerFactory.getLogger(LoggingConfiguration.class);
@EventListener(ApplicationReadyEvent.class)
public void onApplicationReady() {
// 确保日志目录存在
File logsDir = new File("logs");
if (!logsDir.exists()) {
boolean created = logsDir.mkdirs();
if (created) {
logger.info("📁 创建日志目录: {}", logsDir.getAbsolutePath());
}
}
// 记录应用启动信息
String startTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
logger.info("🎉 ========================================");
logger.info("🚀 (♥◠‿◠)ノ゙ AI Copilot启动成功 ლ(´ڡ`ლ)゙");
logger.info("🕐 启动时间: {}", startTime);
logger.info("📝 日志级别: DEBUG (工具调用详细日志已启用)");
logger.info("📁 日志文件: logs/copilot-file-ops.log");
logger.info("🔧 支持的工具:");
logger.info(" 📖 read_file - 读取文件内容");
logger.info(" ✏️ write_file - 写入文件内容");
logger.info(" 📝 edit_file - 编辑文件内容");
logger.info(" 🔍 analyze_project - 分析项目结构");
logger.info(" 🏗️ scaffold_project - 创建项目脚手架");
logger.info(" 🧠 smart_edit - 智能编辑项目");
logger.info("🎉 ========================================");
}
}

View File

@@ -1,39 +0,0 @@
package com.example.demo.config;
import org.springaicommunity.agent.tools.FileSystemTools;
import org.springaicommunity.agent.tools.ShellTools;
import org.springaicommunity.agent.tools.SkillsTool;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import java.util.List;
/**
* Spring AI 配置 - 使用Spring AI 1.0.0规范
*/
@Configuration
public class SpringAIConfiguration {
@Value("${agent.skills.dirs:Unknown}") List<Resource> agentSkillsDirs;
@Bean
public ChatClient chatClient(ChatModel chatModel, AppProperties appProperties) {
// 动态获取工作目录路径
ChatClient.Builder chatClientBuilder = ChatClient.builder(chatModel);
return chatClientBuilder
.defaultSystem("Always use the available skills to assist the user in their requests.")
// Skills tool callbacks
.defaultToolCallbacks(SkillsTool.builder().addSkillsResources(agentSkillsDirs).build())
// Built-in tools
.defaultTools(
// FileSystemTools.builder().build(),
ShellTools.builder().build()
)
.build();
}
}

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