60 Commits

Author SHA1 Message Date
ageerle
15c306eca2 fix: 根据不同的模型构建对话客户端 2025-04-22 11:28:10 +08:00
ageerle
620ea1fc76 fix: 系统提示词非必填 2025-04-22 11:06:38 +08:00
ageerle
c5c375dc6d fix: 扣费时无法获取用户id 2025-04-22 10:43:54 +08:00
ageerle
1b793e822a fix: 扣费时无法获取用户id 2025-04-22 10:40:36 +08:00
ageerle
6281840f36 fix: 修复后台管理系统登录异常 2025-04-22 09:12:56 +08:00
ageer
f5daa7eb78 feat: 设置server-filesystem默认路径为D盘 防止启动报错 2025-04-21 20:34:05 +08:00
ageer
52cb563383 fix(1.修复/store/appList404 2.修复无法查询数据库 3.修复请求地址'/chat/config/configKey/logoImage',发生系统异常): 2025-04-21 20:16:53 +08:00
ageerle
69efc3261e Merge branch 'dev' 2025-04-21 09:44:09 +08:00
ageerle
fb19fb29cc feat: 移除文件 2025-04-21 09:43:09 +08:00
ageer
9f257cf712 feat: mcp升级依赖 2025-04-20 22:52:09 +08:00
ageer
4af26fe4b4 feat: mcp升级依赖 2025-04-20 22:52:00 +08:00
ageerle
ab2a118ee9 feat: 更新项目说明 2025-04-18 11:11:48 +08:00
ageerle
9cd97a4dc5 feat: mcp支持远程调用 2025-04-18 10:55:42 +08:00
ageerle
788b372e32 feat: mcp支持远程调用 2025-04-17 16:23:32 +08:00
lh
744f9b6c7f 更新readme.md 2025-04-17 11:09:00 +08:00
ageer
761d954ef1 feat: mcp 1.0.0 2025-04-16 21:56:05 +08:00
ageerle
d1006f50ad feat: mcp-1.0.0 2025-04-16 10:25:10 +08:00
ageerle
f24ff5bbdd feat: mcp 测试版本 2025-04-15 17:34:21 +08:00
ageer
a4314dbbde feat: mcp测试版 2025-04-14 23:10:17 +08:00
ageerle
188dc1e55e feat: mcp 测试版本 2025-04-14 17:23:33 +08:00
ageer
731352fd04 feat: mcp测试版 2025-04-14 00:22:21 +08:00
ageer
4b53939002 feat: mcp支持,模块重构 2025-04-12 15:18:16 +08:00
lh
b50c15755d 更新readme.md 2025-04-12 14:19:51 +08:00
ageer
0dc6262b39 feat: mcp支持 2025-04-12 10:09:01 +08:00
ageer
9b32e3b3b2 feat: mcp支持 2025-04-12 10:08:51 +08:00
ageerle
37a8b7dad3 feat: 对话模块重构 2025-04-11 15:47:05 +08:00
ageerle
efeb0bd6fb fix: ollama兼容联网查询 知识库检索 2025-04-11 12:05:13 +08:00
ageerle
af33040117 fix: 语音功能无法使用 2025-04-11 10:45:02 +08:00
ageerle
ac4c037634 feat: 重构模块 2025-04-11 09:51:23 +08:00
ageerle
9734ec53f7 feat: 重构模块 2025-04-11 09:34:37 +08:00
ageer
2b8a92c7d6 fix: 重构模块 2025-04-10 22:18:44 +08:00
ageer
adb4538317 fix: 修复使用本地向量模型只能检索到一条知识内容 2025-04-10 21:39:31 +08:00
ageerle
2509099146 feat: 重构模块 2025-04-10 17:25:23 +08:00
ageerle
3be9005f95 feat: 调整知识库模块 2025-04-09 17:41:29 +08:00
ageerle
be6d027cad feat: 删除代码生成脚本 2025-04-09 09:42:14 +08:00
ageerle
3d679f8749 Merge remote-tracking branch 'origin/dev' into dev 2025-04-09 09:40:28 +08:00
ageer
5a5a48e153 feat: ruoyi-chat-api模块调整 2025-04-09 09:40:17 +08:00
ageer
e5da648941 feat: 代码生成脚本 2025-04-08 21:30:01 +08:00
ageerle
d2755f00bc feat: 测试版本提交 2025-04-08 16:48:06 +08:00
lh
09abc0b5af 新增讨论群信息 2025-04-08 14:05:05 +08:00
ageerle
00f362acf1 Merge remote-tracking branch 'origin/main' 2025-04-08 11:11:10 +08:00
ageerle
65d479458e fix: 修复部门树无法加载 2025-04-08 11:11:00 +08:00
ageerle
57e17e0dda Merge pull request #58 from winkeylucky/winkey-0407-02
修改应用使用,通过配置的应用的appid modelname字段关联 0407 02
2025-04-08 09:12:50 +08:00
winkey
691d1735fc 数据库更新 2025-04-07 22:42:06 +08:00
winkey
360984bc4b 应用增加系统角色提示词 2025-04-07 22:41:56 +08:00
winkey
0153f004f4 修改应用使用,通过配置的应用的appid modelname字段关联 2025-04-07 22:41:33 +08:00
ageerle
cc23508527 Merge pull request #57 from winkeylucky/winkey_0407
向量模型通过模型管理获取配置
2025-04-07 16:00:33 +08:00
winkey
c884f4f2d3 向量模型插入模型库sql 2025-04-07 15:27:20 +08:00
winkey
fab6de1f5c 知识库对话修改 2025-04-07 15:16:50 +08:00
winkey
c02f66636d 向量模型通过模型管理获取配置 2025-04-07 15:16:31 +08:00
ageerle
c1162148b1 Merge pull request #50 from winkeylucky/winkey-0502
解决登录异常_和公共资源例外 0502
2025-04-07 14:02:35 +08:00
ageerle
f76fdbf3ad Merge pull request #46 from winkeylucky/winkey-main
增加deepseek深度思考返回
2025-04-07 14:02:18 +08:00
winkey
feca08b3ec 获取公告和版权信息权限例外 2025-04-02 15:13:53 +08:00
winkey
72675b17c4 解决登录异常问题 2025-04-02 15:13:39 +08:00
ageerle
9ea5186f49 feat: 微信机器人应用脚本 2025-04-02 10:51:53 +08:00
winkey
d0a2eadc38 增加deepseek深度思考返回 2025-04-01 13:34:31 +08:00
ageer
ae141a6591 feat: 更新SQL脚本(本次更新范围较大,请先备份再拉取代码) 2025-03-31 20:12:31 +08:00
ageer
d3f4d7b8ca feat: 配置信息数据脱敏 2025-03-31 19:55:44 +08:00
ageer
b8e7a406d3 feat: 1. 调整项目结构 2.增加插件管理 2025-03-31 19:14:55 +08:00
ageer
412e8bdc10 feat: 1. 调整项目结构 2.增加插件管理 2025-03-31 19:13:27 +08:00
1138 changed files with 9071 additions and 18475 deletions

5
.gitignore vendored
View File

@@ -1,6 +1,9 @@
######################################################################
# Build Tools
.gradle
/build/
!gradle/wrapper/gradle-wrapper.jar
@@ -8,6 +11,8 @@
target/
!.mvn/wrapper/maven-wrapper.jar
ruoyi-modules/ruoyi-generator/src/main/resources/vm/vben5
######################################################################
# IDE

136
README.md
View File

@@ -36,48 +36,75 @@
## 目录
- [系统体验](#系统体验)
- [源码地址](#源码地址)
- [特色功能](#特色功能)
- [配套文档](#项目文档)
- [核心功能](#核心功能)
- [项目演示](#项目演示)
- [后台管理](#后台管理)
- [管理](#管理)
- [用户端](#用户端)
- [小程序端](#小程序端)
- [开发前的配置要求](#开发前的配置要求)
- [文件目录说明](#文件目录说明)
- [使用到的框架](#使用到的框架)
- [开发环境](#开发环境)
- [项目结构](#项目结构)
- [ruoyi-ai](#ruoyi-ai)
- [注意事项](#注意事项)
- [vben模板](#vben模板)
- [贡献者](#贡献者)
- [如何参与开源项目](#如何参与开源项目)
- [版本控制](#版本控制)
- [作者](#作者)
- [鸣谢](#鸣谢)
- [技术讨论群](#技术讨论群)
### 系统体验
- 用户端https://web.pandarobot.chat
- 管理端https://admin.pandarobot.chat
用户名: admin 密码admin123
### 源码地址
- 项目文档: https://doc.pandarobot.chat
- 前端-后台管理: https://github.com/ageerle/ruoyi-admin
- 前端-用户端: https://github.com/ageerle/ruoyi-web
- 小程序端: https://github.com/ageerle/ruoyi-uniapp
- 演示地址: https://web.pandarobot.chat
- 后台管理: https://admin.pandarobot.chat
- 用户名: admin 密码admin123
-
### gitcode源码地址
- https://gitcode.com/ageerle/ruoyi-ai
- https://gitcode.com/ageerle/ruoyi-web
- https://gitcode.com/ageerle/ruoyi-admin
- https://gitcode.com/ageerle/ruoyi-uniapp
[1]github
- 前端服务-用户端: https://github.com/ageerle/ruoyi-web
- 前端服务-管理端: https://github.com/ageerle/ruoyi-admin
- 前端服务-小程序端: https://github.com/ageerle/ruoyi-uniapp
- 后端服务:https://github.com/ageerle/ruoyi-ai
### 特色功能
[2]gitee
- 前端服务-用户端: https://gitee.com/ageerle/ruoyi-web
- 前端服务-管理端: https://gitee.com/ageerle/ruoyi-admin
- 前端服务-小程序端: https://gitee.com/ageerle/ruoyi-uniapp
- 后端服务https://gitee.com/ageerle/ruoyi-ai
[3]gitcode
- 前端服务-用户端https://gitcode.com/ageerle/ruoyi-web
- 前端服务-管理端: https://gitcode.com/ageerle/ruoyi-admin
- 前端服务-小程序端: https://gitcode.com/ageerle/ruoyi-uniapp
- 后端服务https://gitcode.com/ageerle/ruoyi-ai
### 配套文档
- 配套文档: https://doc.pandarobot.chat
- 项目部署文档https://doc.pandarobot.chat/guide/introduction/
### 核心功能
1. 全套开源系统提供完整的前端应用、后台管理以及小程序应用基于MIT协议开箱即用。
2. 本地RAG方案集成Milvus/Weaviate向量库、本地向量化模型与Ollama实现本地化RAG
2. 本地RAG方案集成Milvus/Weaviate向量库、本地向量化模型与Ollama实现本地化RAG
3. 丰富插件功能支持联网、SQL查询插件及Text2API插件扩展系统能力与应用场景。
4. 内置SSE、websocket等网络协议支持对接多种大语言模型同时还集成了MidJourney和DALLE AI绘画功能
5. 强大的多媒体功能支持AI翻译、PPT制作、语音克隆和翻唱等
6. 扩展功能:支持将大模型接入个人或企业微信
7. 支付功能:支持易支付、微信支付等多种支付方式
4. 内置SSE、websocket等网络协议支持对接多种大语言模型同时还集成了MidJourney和DALLE AI绘画功能
5. 强大的多媒体功能支持AI翻译、PPT制作、语音克隆和翻唱等
6. 扩展功能:支持将大模型接入个人或企业微信
7. 支付功能:支持易支付、微信支付等多种支付方式
### 项目演示
#### 后台管理
#### mcp支持(需要切换dev分支 下周发布正式版)
<div style="display: flex; flex-wrap: wrap; gap: 20px; justify-content: center;">
<img src="image/mcp-01.png" alt="drawing" style="width: 600px; height: 300px; border: 2px solid #ddd; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);"/>
<img src="image/mcp-02.png" alt="drawing" style="width: 600px; height: 300px; border: 2px solid #ddd; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);"/>
<img src="image/mcp-03.png" alt="drawing" style="width: 600px; height: 300px; border: 2px solid #ddd; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);"/>
<img src="image/mcp-04.png" alt="drawing" style="width: 600px; height: 300px; border: 2px solid #ddd; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);"/>
</div>
#### 管理端
<div style="display: flex; flex-wrap: wrap; gap: 20px; justify-content: center;">
<img src="image/02.png" alt="drawing" style="width: 600px; height: 300px; border: 2px solid #ddd; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);"/>
<img src="image/03.png" alt="drawing" style="width: 600px; height: 300px; border: 2px solid #ddd; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);"/>
@@ -100,7 +127,7 @@
<img src="image/07.png" alt="drawing" style="width: 320px; height: 600px; border: 2px solid #ddd; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);"/>
</div>
### 开发前的配置要求
### 开发环境
1. jdk 17
2. mysql 5.7、8.0
@@ -108,8 +135,14 @@
4. maven 3.8+
5. nodejs 20+ & pnpm
### 文件目录说明
RuoYi-AI
- 附-部署配套视频https://www.bilibili.com/video/BV1jDXkYWEba
<div>
<img src="image/教程搭建.png" alt="drawing" width="600px" height="300px"/>
</div>
### 项目结构
- RuoYi-AI
```
├─ ruoyi-admin // 管理模块
@@ -158,6 +191,14 @@ RuoYi-AI
```
### 注意事项
- vben模板
Qvben5 的模板默认是没有的吗?
Avben模板是收费的 请联系vben-vue-plus作者获取。
### 版本控制
该项目使用Git进行版本管理。您可以在repository参看当前可用版本。
@@ -168,23 +209,11 @@ RuoYi-AI
该项目使用了MIT授权许可详情请参阅 [LICENSE.txt](https://github.com/ageerle/ruoyi-ai/blob/main/LICENSE)
### 项目现状
### 作者寄语
目前项目还处于早期阶段距离成熟还有很长的路要走。由于个人精力有限项目的发展速度受到了一定的限制。为了加快项目的进度我真诚地希望更多人能够参与到项目中来。无论是经验丰富的开发者还是刚刚入门的小白我都热烈欢迎你们提交Pull RequestPR👏👏👏。即使代码修改得很少或者存在一些错误都没有关系。我会认真审核每一位贡献者的代码并和大家一起完善项目⛽
最近我们的项目意外地受到了广泛关注甚至被许多人误以为是一个已经成熟且能够快速落地的项目。然而事实并非如此。这个项目是我个人在业余时间进行的研究主要目的是学习和探索。它是一个以人工智能AI为核心的平台旨在帮助企业通过配置的方式快速构建AI应用。
#### 项目现状
目前项目还处于早期阶段距离成熟还有很长的路要走。由于个人精力有限项目的发展速度受到了一定的限制。为了加快项目的进度我真诚地希望更多人能够参与到项目中来。无论是经验丰富的开发者还是刚刚入门的小白我都热烈欢迎你们提交Pull RequestPR。即使代码修改得很少或者存在一些错误都没有关系。我会认真审核每一位贡献者的代码并和大家一起完善项目。
#### 开发计划
- 智能体管理
通过设置提示词、插件、知识库等用户可以快速构建一个AI应用。这将极大地简化AI应用的开发流程降低开发门槛使更多企业能够轻松地利用AI技术。
<div>
<img src="image/13.png" alt="drawing" width="600px" height="300px"/>
</div>
### 开发计划
- 流程编排
@@ -198,7 +227,7 @@ RuoYi-AI
#### 如何参与开源项目
贡献使开源社区成为一个学习、激励和创造的绝佳场所。你所作的任何贡献都是**非常感谢**的。
贡献使开源社区成为一个学习、激励和创造的绝佳场所。你所作的任何贡献,我们都非常感谢!🙏
1. Fork 这个项目
2. 创建你的功能分支 (`git checkout -b feature/dev`)
@@ -236,4 +265,23 @@ RuoYi-AI
[linkedin-shield]: https://img.shields.io/badge/-LinkedIn-black.svg?style=flat-square&logo=linkedin&colorB=555
### 附:技术讨论群
#### 全面开放,欢迎加入
🏠 wxruoyi-ai加人备注ruoyi-ai
🏠 qq1603234088 加人备注ruoyi-ai
👏👏👏 ruoyi-ai官方交流1群qq区1034554687
<div style="display: flex; flex-wrap: wrap; gap: 20px; justify-content: center;">
<img src="image/QQ区-官方交流1群.png" alt="drawing" style="width: 400px; height: 400px; border: 2px solid #ddd; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);"/>
</div>
👏👏👏 ruoyi-ai官方交流4群微信区
<div style="display: flex; flex-wrap: wrap; gap: 20px; justify-content: center;">
<img src="image/WX区-官方交流4群.jpg" alt="drawing" style="width: 400px; height: 400px; border: 2px solid #ddd; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);"/>
</div>

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 KiB

BIN
image/mcp-01.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

BIN
image/mcp-02.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 251 KiB

BIN
image/mcp-03.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 KiB

BIN
image/mcp-04.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

BIN
image/qq-msg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 391 KiB

BIN
image/wx-msg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

BIN
image/wx-msg2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

BIN
image/教程搭建.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 341 KiB

98
pom.xml
View File

@@ -14,25 +14,26 @@
<properties>
<revision>1.0.0</revision>
<spring-boot.version>3.0.6</spring-boot.version>
<spring-boot.version>3.4.4</spring-boot.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>17</java.version>
<spring-boot.mybatis>3.0.1</spring-boot.mybatis>
<mysql.version>8.0.33</mysql.version>
<mybatis.version>3.5.16</mybatis.version>
<springdoc.version>2.1.0</springdoc.version>
<therapi-javadoc.version>0.15.0</therapi-javadoc.version>
<poi.version>5.2.3</poi.version>
<easyexcel.version>3.2.1</easyexcel.version>
<velocity.version>2.3</velocity.version>
<satoken.version>1.34.0</satoken.version>
<mybatis-plus.version>3.5.3.1</mybatis-plus.version>
<mybatis-plus.version>3.5.11</mybatis-plus.version>
<p6spy.version>3.9.1</p6spy.version>
<hutool.version>5.8.18</hutool.version>
<hutool.version>5.8.35</hutool.version>
<okhttp.version>4.10.0</okhttp.version>
<dynamic-ds.version>4.3.1</dynamic-ds.version>
<spring-boot-admin.version>3.0.3</spring-boot-admin.version>
<redisson.version>3.20.1</redisson.version>
<lock4j.version>2.2.4</lock4j.version>
<dynamic-ds.version>3.6.1</dynamic-ds.version>
<alibaba-ttl.version>2.14.2</alibaba-ttl.version>
<xxl-job.version>2.4.0</xxl-job.version>
<mapstruct-plus.version>1.2.1</mapstruct-plus.version>
@@ -41,10 +42,6 @@
<bouncycastle.version>1.72</bouncycastle.version>
<!-- 离线IP地址定位库 -->
<ip2region.version>2.7.0</ip2region.version>
<!-- 临时修复 snakeyaml 漏洞 -->
<snakeyaml.version>1.33</snakeyaml.version>
<!-- OSS 配置 -->
<aws-java-sdk-s3.version>1.12.400</aws-java-sdk-s3.version>
<!-- SMS 配置 -->
@@ -60,6 +57,7 @@
<weixin-java-miniapp.version>4.5.0</weixin-java-miniapp.version>
<weixin-java-pay.version>4.6.0</weixin-java-pay.version>
<weixin-java-cp.version>4.6.0</weixin-java-cp.version>
<weixin-java-cp.version>4.6.0</weixin-java-cp.version>
</properties>
<profiles>
@@ -96,6 +94,12 @@
<dependencyManagement>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!-- SpringBoot的依赖配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
@@ -194,22 +198,22 @@
<version>${satoken.version}</version>
</dependency>
<!-- dynamic-datasource 多数据源-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>${dynamic-ds.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${spring-boot.mybatis}</version>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-jsqlparser</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
@@ -219,6 +223,13 @@
<version>${mybatis-plus.version}</version>
</dependency>
<!-- dynamic-datasource 多数据源-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
<version>${dynamic-ds.version}</version>
</dependency>
<!-- sql性能分析插件 -->
<dependency>
<groupId>p6spy</groupId>
@@ -244,18 +255,6 @@
<version>${tencent.sms.version}</version>
</dependency>
<!-- <dependency>-->
<!-- <groupId>de.codecentric</groupId>-->
<!-- <artifactId>spring-boot-admin-starter-server</artifactId>-->
<!-- <version>${spring-boot-admin.version}</version>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>de.codecentric</groupId>-->
<!-- <artifactId>spring-boot-admin-starter-client</artifactId>-->
<!-- <version>${spring-boot-admin.version}</version>-->
<!-- </dependency>-->
<!--redisson-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
@@ -281,13 +280,6 @@
<version>${alibaba-ttl.version}</version>
</dependency>
<!-- 临时修复 snakeyaml 漏洞 -->
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>${snakeyaml.version}</version>
</dependency>
<!-- 加密包引入 -->
<dependency>
<groupId>org.bouncycastle</groupId>
@@ -317,14 +309,25 @@
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-fusion</artifactId>
<artifactId>ruoyi-chat</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-knowledge-api</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-knowledge</artifactId>
<artifactId>ruoyi-chat-api</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-system-api</artifactId>
<version>${revision}</version>
</dependency>
@@ -334,20 +337,23 @@
<version>${revision}</version>
</dependency>
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-demo</artifactId>
<version>${revision}</version>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.ruoyi</groupId>-->
<!-- <artifactId>ruoyi-demo</artifactId>-->
<!-- <version>${revision}</version>-->
<!-- </dependency>-->
</dependencies>
</dependencyManagement>
<modules>
<module>ruoyi-admin</module>
<module>ruoyi-common</module>
<module>ruoyi-modules</module>
<module>ruoyi-modules-api</module>
<module>ruoyi-admin</module>
<module>ruoyi-extend</module>
</modules>
<packaging>pom</packaging>
<build>

View File

@@ -1,35 +0,0 @@
#基础镜像
FROM findepi/graalvm:java17-native
# 设置环境变量
ENV LANG C.UTF-8
ENV LANGUAGE C.UTF-8
ENV LC_ALL C.UTF-8
ENV SERVER_PORT=6039
MAINTAINER ageerle
RUN mkdir -p /ruoyi/server/logs \
/ruoyi/server/temp \
/ruoyi/skywalking/agent
#工作空间
WORKDIR /ruoyi/server
EXPOSE ${SERVER_PORT}
ADD ./target/ruoyi-admin.jar ./app.jar
ENTRYPOINT ["java", \
"-Djava.security.egd=file:/dev/./urandom", \
"-Dserver.port=${SERVER_PORT}", \
# 应用名称 如果想区分集群节点监控 改成不同的名称即可
# "-Dskywalking.agent.service_name=ruoyi-server", \
# "-javaagent:/ruoyi/skywalking/agent/skywalking-agent.jar", \
"-jar", "app.jar"]

View File

@@ -42,10 +42,11 @@
<artifactId>mssql-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-common-doc</artifactId>
</dependency>
<!-- demo模块 -->
<!-- <dependency>-->
<!-- <groupId>org.ruoyi</groupId>-->
<!-- <artifactId>ruoyi-demo</artifactId>-->
<!-- </dependency>-->
<dependency>
<groupId>org.ruoyi</groupId>
@@ -54,42 +55,7 @@
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-fusion</artifactId>
</dependency>
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-knowledge</artifactId>
</dependency>
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-generator</artifactId>
</dependency>
<!-- demo模块 -->
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-demo</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 添加thumbnailator依赖 -->
<dependency>
<groupId>net.coobird</groupId>
<artifactId>thumbnailator</artifactId>
<version>0.4.11</version>
</dependency>
<dependency>
<groupId>io.github.ollama4j</groupId>
<artifactId>ollama4j</artifactId>
<version>1.0.79</version>
<scope>compile</scope>
<artifactId>ruoyi-chat</artifactId>
</dependency>
</dependencies>

View File

@@ -2,9 +2,6 @@ package org.ruoyi.controller;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.json.JSONUtil;
import cn.hutool.json.ObjectMapper;
import me.chanjar.weixin.common.error.WxErrorException;
import org.ruoyi.common.core.constant.Constants;
import org.ruoyi.common.core.domain.R;
import org.ruoyi.common.core.domain.model.EmailLoginBody;
@@ -17,18 +14,17 @@ import org.ruoyi.common.core.utils.StreamUtils;
import org.ruoyi.common.core.utils.StringUtils;
import org.ruoyi.common.satoken.utils.LoginHelper;
import org.ruoyi.common.tenant.helper.TenantHelper;
import org.ruoyi.system.domain.bo.SysTenantBo;
import org.ruoyi.system.domain.vo.LoginTenantVo;
import org.ruoyi.system.domain.vo.SysTenantVo;
import org.ruoyi.system.domain.vo.TenantListVo;
import org.ruoyi.system.service.ISysTenantService;
import org.ruoyi.system.service.SysLoginService;
import org.ruoyi.system.service.SysRegisterService;
import org.ruoyi.system.domain.vo.LoginVo;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.ruoyi.system.domain.bo.SysTenantBo;
import org.ruoyi.system.domain.vo.LoginTenantVo;
import org.ruoyi.system.domain.vo.LoginVo;
import org.ruoyi.system.domain.vo.SysTenantVo;
import org.ruoyi.system.domain.vo.TenantListVo;
import org.ruoyi.system.service.ISysTenantService;
import org.ruoyi.system.service.SysLoginService;
import org.ruoyi.system.service.SysRegisterService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@@ -51,15 +47,6 @@ public class AuthController {
private final SysRegisterService registerService;
private final ISysTenantService tenantService;
@PostMapping("/xcxLogin")
public R<LoginVo> login(@Validated @RequestBody String xcxCode) throws WxErrorException {
String openidFromCode = loginService.getOpenidFromCode((String) JSONUtil.parseObj(xcxCode).get("xcxCode"));
LoginVo loginVo = loginService.mpLogin(openidFromCode);
return R.ok(loginVo);
}
/**
* 登录方法
*
@@ -76,6 +63,8 @@ public class AuthController {
body.getUsername(), body.getPassword(),
body.getCode(), body.getUuid());
loginVo.setToken(token);
// 兼容后台管理登录
loginVo.setAccess_token(token);
loginVo.setUserInfo(LoginHelper.getLoginUser());
return R.ok(loginVo);
}
@@ -97,7 +86,6 @@ public class AuthController {
/**
* 访客登录
*
* @param loginBody 登录信息
* @return token信息
*/
@@ -136,7 +124,7 @@ public class AuthController {
*/
@PostMapping("/register")
public R<Void> register(@Validated @RequestBody RegisterBody user, HttpServletRequest request) {
String domainName = request.getServerName();
String domainName = request.getServerName();
user.setDomainName(domainName);
registerService.register(user);
return R.ok();

View File

@@ -2,6 +2,7 @@ package org.ruoyi.controller;
import cn.dev33.satoken.annotation.SaIgnore;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@@ -11,7 +12,6 @@ import org.springframework.web.bind.annotation.RestController;
* @author Lion Li
*/
@SaIgnore
@RequiredArgsConstructor
@RestController
public class IndexController {
@@ -20,7 +20,9 @@ public class IndexController {
*/
@GetMapping("/")
public String index() {
return "RuoYi-AI 启动成功";
return "RuoYi AI启动成功";
}
}

View File

@@ -1,208 +0,0 @@
package org.ruoyi.controller;
import cn.dev33.satoken.stp.StpUtil;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.ruoyi.common.chat.config.ChatConfig;
import org.ruoyi.common.chat.domain.request.ChatRequest;
import org.ruoyi.common.chat.entity.chat.ChatCompletion;
import org.ruoyi.common.chat.entity.chat.Message;
import org.ruoyi.common.chat.openai.OpenAiStreamClient;
import org.ruoyi.common.core.domain.R;
import org.ruoyi.common.core.validate.AddGroup;
import org.ruoyi.common.excel.utils.ExcelUtil;
import org.ruoyi.common.log.annotation.Log;
import org.ruoyi.common.log.enums.BusinessType;
import org.ruoyi.common.mybatis.core.page.PageQuery;
import org.ruoyi.common.mybatis.core.page.TableDataInfo;
import org.ruoyi.common.satoken.utils.LoginHelper;
import org.ruoyi.common.web.core.BaseController;
import org.ruoyi.knowledge.domain.bo.KnowledgeAttachBo;
import org.ruoyi.knowledge.domain.bo.KnowledgeFragmentBo;
import org.ruoyi.knowledge.domain.bo.KnowledgeInfoBo;
import org.ruoyi.knowledge.domain.req.KnowledgeInfoUploadRequest;
import org.ruoyi.knowledge.domain.vo.KnowledgeAttachVo;
import org.ruoyi.knowledge.domain.vo.KnowledgeFragmentVo;
import org.ruoyi.knowledge.domain.vo.KnowledgeInfoVo;
import org.ruoyi.knowledge.service.EmbeddingService;
import org.ruoyi.knowledge.service.IKnowledgeAttachService;
import org.ruoyi.knowledge.service.IKnowledgeFragmentService;
import org.ruoyi.knowledge.service.IKnowledgeInfoService;
import org.ruoyi.system.listener.SSEEventSourceListener;
import org.ruoyi.system.service.ISseService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.ruoyi.knowledge.chain.vectorstore.VectorStore;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.util.List;
/**
* 知识库
*
* @author Lion Li
* @date 2024-10-21
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/knowledge")
public class KnowledgeController extends BaseController {
private final IKnowledgeInfoService knowledgeInfoService;
private final VectorStore vectorStore;
private final IKnowledgeAttachService attachService;
private final IKnowledgeFragmentService fragmentService;
private final EmbeddingService embeddingService;
private OpenAiStreamClient openAiStreamClient;
private final ChatConfig chatConfig;
private final ISseService sseService;
/**
* 知识库对话
*/
@PostMapping("/send")
public SseEmitter send(@RequestBody @Valid ChatRequest chatRequest) {
openAiStreamClient = chatConfig.getOpenAiStreamClient();
SseEmitter sseEmitter = new SseEmitter(0L);
SSEEventSourceListener openAIEventSourceListener = new SSEEventSourceListener(sseEmitter);
List<Message> messages = chatRequest.getMessages();
String content = messages.get(messages.size() - 1).getContent().toString();
List<String> nearestList;
List<Double> queryVector = embeddingService.getQueryVector(content, chatRequest.getKid());
nearestList = vectorStore.nearest(queryVector,chatRequest.getKid());
for (String prompt : nearestList) {
Message sysMessage = Message.builder().content(prompt).role(Message.Role.USER).build();
messages.add(sysMessage);
}
Message userMessage = Message.builder().content(content + (nearestList.size() > 0 ? "\n\n注意回答问题时须严格根据我给你的系统上下文内容原文进行回答请不要自己发挥,回答时保持原来文本的段落层级" : "") ).role(Message.Role.USER).build();
messages.add(userMessage);
if (chatRequest.getModel().startsWith("ollama")) {
return sseService.ollamaChat(chatRequest);
}
ChatCompletion completion = ChatCompletion
.builder()
.messages(messages)
.model(chatRequest.getModel())
.temperature(chatRequest.getTemperature())
.topP(chatRequest.getTop_p())
.stream(true)
.build();
openAiStreamClient.streamChatCompletion(completion, openAIEventSourceListener);
return sseEmitter;
}
/**
* 根据用户信息查询本地知识库
*/
@GetMapping("/list")
public TableDataInfo<KnowledgeInfoVo> list(KnowledgeInfoBo bo, PageQuery pageQuery) {
if(!StpUtil.isLogin()){
return null;
}
bo.setUid(LoginHelper.getUserId());
return knowledgeInfoService.queryPageList(bo, pageQuery);
}
/**
* 新增知识库
*/
@Log(title = "知识库", businessType = BusinessType.INSERT)
@PostMapping("/save")
public R<Void> save(@Validated(AddGroup.class) @RequestBody KnowledgeInfoBo bo) {
knowledgeInfoService.saveOne(bo);
return R.ok();
}
/**
* 删除知识库
*/
@PostMapping("/remove/{id}")
public R<String> remove(@PathVariable String id){
knowledgeInfoService.removeKnowledge(id);
return R.ok("删除知识库成功!");
}
/**
* 修改知识库
*/
@Log(title = "知识库", businessType = BusinessType.UPDATE)
@PostMapping("/edit")
public R<Void> edit( @RequestBody KnowledgeInfoBo bo) {
return toAjax(knowledgeInfoService.updateByBo(bo));
}
/**
* 导出知识库列表
*/
@Log(title = "知识库", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(KnowledgeInfoBo bo, HttpServletResponse response) {
List<KnowledgeInfoVo> list = knowledgeInfoService.queryList(bo);
ExcelUtil.exportExcel(list, "知识库", KnowledgeInfoVo.class, response);
}
/**
* 查询知识附件信息
*/
@GetMapping("/detail/{kid}")
public TableDataInfo<KnowledgeAttachVo> attach(KnowledgeAttachBo bo, PageQuery pageQuery,@PathVariable String kid){
bo.setKid(kid);
return attachService.queryPageList(bo, pageQuery);
}
/**
* 上传知识库附件
*/
@PostMapping(value = "/attach/upload")
public R<String> upload(KnowledgeInfoUploadRequest request){
knowledgeInfoService.upload(request);
return R.ok("上传知识库附件成功!");
}
/**
* 获取知识库附件详细信息
*
* @param id 主键
*/
@GetMapping("attach/info/{id}")
public R<KnowledgeAttachVo> getAttachInfo(@NotNull(message = "主键不能为空")
@PathVariable Long id) {
return R.ok(attachService.queryById(id));
}
/**
* 删除知识库附件
*
*/
@PostMapping("attach/remove/{docId}")
public R<Void> removeAttach(@NotEmpty(message = "主键不能为空") @PathVariable String docId) {
attachService.removeKnowledgeAttach(docId);
return R.ok();
}
/**
* 查询知识片段
*/
@GetMapping("/fragment/list/{docId}")
public TableDataInfo<KnowledgeFragmentVo> fragmentList(KnowledgeFragmentBo bo, PageQuery pageQuery, @PathVariable String docId) {
bo.setDocId(docId);
return fragmentService.queryPageList(bo, pageQuery);
}
}

View File

@@ -25,10 +25,9 @@ spring:
master:
type: ${spring.datasource.type}
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://43.139.70.230:3306/ruoyi-ai?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
username: ruoyi-ai
password: ruoyi-ai
url: jdbc:mysql://43.139.70.230:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true
username: ry-vue
password: xx
hikari:
# 最大连接池数量
@@ -61,9 +60,6 @@ spring.data:
# password: 123456
# 连接超时时间
timeout: 10S
# 是否开启ssl
ssl: false
redisson:
# redis key前缀
keyPrefix:
@@ -97,3 +93,4 @@ sms:
signName: 测试
# 腾讯专用
sdkAppId:

View File

@@ -49,8 +49,10 @@ server:
# 日志配置
logging:
level:
org.ruoyi: @logging.level@
org.dromara: @logging.level@
org.springframework: warn
org.mybatis.spring.mapper: error
org.apache.fury: warn
config: classpath:logback-plus.xml
# 用户配置
@@ -318,5 +320,19 @@ wechat:
token: ''
aesKey: ''
spring:
ai:
openai:
api-key: sk-xxx
base-url: https://api.pandarobot.chat/
mcp:
client:
enabled: true
name: ruoyi-ai-mcp
sse:
connections:
server:
url: http://127.0.0.1:8081
stdio:
servers-configuration: classpath:mcp-server.json

View File

@@ -0,0 +1,22 @@
{
"mcpServers": {
"fileSystem": {
"command": "C:\\Program Files\\nodejs\\npx.cmd",
"args": [
"-y",
"@modelcontextprotocol/server-filesystem",
"D:\\"
]
},
"search1api": {
"command": "C:\\Program Files\\nodejs\\npx.cmd",
"args": [
"-y",
"search1api-mcp"
],
"env": {
"SEARCH1API_KEY": "xx"
}
}
}
}

View File

@@ -2,13 +2,20 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>ruoyi-ai</artifactId>
<groupId>org.ruoyi</groupId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ruoyi-common</artifactId>
<packaging>pom</packaging>
<description>
common 通用模块
</description>
<modules>
<module>ruoyi-common-bom</module>
@@ -32,15 +39,9 @@
<module>ruoyi-common-encrypt</module>
<module>ruoyi-common-tenant</module>
<module>ruoyi-common-chat</module>
<module>ruoyi-common-pay</module>
<module>ruoyi-common-wechat</module>
<module>ruoyi-common-pay</module>
<module>ruoyi-common-live</module>
</modules>
<artifactId>ruoyi-common</artifactId>
<packaging>pom</packaging>
<description>
common 通用模块
</description>
</project>

View File

@@ -159,29 +159,14 @@
<version>${revision}</version>
</dependency>
<!-- 微信模块 -->
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-common-wechat</artifactId>
<artifactId>ruoyi-chat</artifactId>
<version>${revision}</version>
</dependency>
<!-- AI绘画 -->
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-fusion</artifactId>
<version>${revision}</version>
</dependency>
<!-- 支付模块 -->
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-common-pay</artifactId>
<version>${revision}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>

View File

@@ -18,6 +18,10 @@
<properties>
<retrofit2.version>2.9.0</retrofit2.version>
<azure.version>1.0.0-beta.12</azure.version>
<chatglm.version>release-V4-2.3.0</chatglm.version>
<okhttp.version>2.7.5</okhttp.version>
<jtokkit.version>0.5.0</jtokkit.version>
</properties>
<dependencies>
@@ -26,38 +30,22 @@
<artifactId>ruoyi-common-core</artifactId>
</dependency>
<!-- 序列化模块 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-common-json</artifactId>
</dependency>
<!-- redis模块 -->
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-common-redis</artifactId>
</dependency>
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-ai-openai</artifactId>
<version>1.0.0-beta.12</version>
</dependency>
<dependency>
<groupId>io.github.ollama4j</groupId>
<artifactId>ollama4j</artifactId>
<version>1.0.79</version>
</dependency>
<!-- 序列化模块 -->
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-common-json</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-common-redis</artifactId>
</dependency>
<dependency>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-common-satoken</artifactId>
<version>${azure.version}</version>
</dependency>
<dependency>
@@ -79,13 +67,7 @@
<dependency>
<groupId>com.knuddels</groupId>
<artifactId>jtokkit</artifactId>
<version>0.5.0</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.12</version>
<version>${jtokkit.version}</version>
</dependency>
<dependency>
@@ -98,21 +80,18 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<dependency>
<groupId>cn.bigmodel.openapi</groupId>
<artifactId>oapi-java-sdk</artifactId>
<version>release-V4-2.3.0</version>
<version>${chatglm.version}</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp</groupId>
<artifactId>okhttp</artifactId>
<version>2.7.5</version>
<scope>compile</scope>
<version>${okhttp.version}</version>
</dependency>
</dependencies>
</project>

View File

@@ -12,6 +12,9 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
@Data
public class WebSocketProperties {
/**
* 是否开启
*/
private Boolean enabled;
/**

View File

@@ -1,73 +0,0 @@
package org.ruoyi.common.chat.demo;
import cn.hutool.json.JSONUtil;
import lombok.Getter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.sse.EventSource;
import okhttp3.sse.EventSourceListener;
import org.ruoyi.common.chat.entity.chat.ChatCompletionResponse;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
/**
* 描述: sse
*
* @author https:www.unfbx.com
* 2023-06-15
*/
@Slf4j
public class ConsoleEventSourceListenerV2 extends EventSourceListener {
@Getter
String args = "";
final CountDownLatch countDownLatch;
public ConsoleEventSourceListenerV2(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
public void onOpen(EventSource eventSource, Response response) {
log.info("OpenAI建立sse连接...");
}
@Override
public void onEvent(EventSource eventSource, String id, String type, String data) {
log.info("OpenAI返回数据{}", data);
if (data.equals("[DONE]")) {
log.info("OpenAI返回数据结束了");
countDownLatch.countDown();
return;
}
ChatCompletionResponse chatCompletionResponse = JSONUtil.toBean(data, ChatCompletionResponse.class);
if(Objects.nonNull(chatCompletionResponse.getChoices().get(0).getDelta().getFunctionCall())){
args += chatCompletionResponse.getChoices().get(0).getDelta().getFunctionCall().getArguments();
}
}
@Override
public void onClosed(EventSource eventSource) {
log.info("OpenAI关闭sse连接...");
}
@SneakyThrows
@Override
public void onFailure(EventSource eventSource, Throwable t, Response response) {
if(Objects.isNull(response)){
log.error("OpenAI sse连接异常:{}", t);
eventSource.cancel();
return;
}
ResponseBody body = response.body();
if (Objects.nonNull(body)) {
log.error("OpenAI sse连接异常data{},异常:{}", body.string(), t);
} else {
log.error("OpenAI sse连接异常data{},异常:{}", response, t);
}
eventSource.cancel();
}
}

View File

@@ -1,92 +0,0 @@
package org.ruoyi.common.chat.demo;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.json.JSONUtil;
import lombok.Getter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.sse.EventSource;
import okhttp3.sse.EventSourceListener;
import org.ruoyi.common.chat.entity.chat.ChatCompletionResponse;
import org.ruoyi.common.chat.entity.chat.Message;
import org.ruoyi.common.chat.entity.chat.tool.ToolCallFunction;
import org.ruoyi.common.chat.entity.chat.tool.ToolCalls;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
/**
* 描述: demo测试实现类仅供思路参考
*
* @author https:www.unfbx.com
* 2023-11-12
*/
@Slf4j
public class ConsoleEventSourceListenerV3 extends EventSourceListener {
@Getter
List<ToolCalls> choices = new ArrayList<>();
@Getter
ToolCalls toolCalls = new ToolCalls();
@Getter
ToolCallFunction toolCallFunction = ToolCallFunction.builder().name("").arguments("").build();
final CountDownLatch countDownLatch;
public ConsoleEventSourceListenerV3(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
public void onOpen(EventSource eventSource, Response response) {
log.info("OpenAI建立sse连接...");
}
@Override
public void onEvent(EventSource eventSource, String id, String type, String data) {
log.info("OpenAI返回数据{}", data);
if (data.equals("[DONE]")) {
log.info("OpenAI返回数据结束了");
return;
}
ChatCompletionResponse chatCompletionResponse = JSONUtil.toBean(data, ChatCompletionResponse.class);
Message delta = chatCompletionResponse.getChoices().get(0).getDelta();
if (CollectionUtil.isNotEmpty(delta.getToolCalls())) {
choices.addAll(delta.getToolCalls());
}
}
@Override
public void onClosed(EventSource eventSource) {
if(CollectionUtil.isNotEmpty(choices)){
toolCalls.setId(choices.get(0).getId());
toolCalls.setType(choices.get(0).getType());
choices.forEach(e -> {
toolCallFunction.setName(e.getFunction().getName());
toolCallFunction.setArguments(toolCallFunction.getArguments() + e.getFunction().getArguments());
toolCalls.setFunction(toolCallFunction);
});
}
log.info("OpenAI关闭sse连接...");
countDownLatch.countDown();
}
@SneakyThrows
@Override
public void onFailure(EventSource eventSource, Throwable t, Response response) {
if(Objects.isNull(response)){
log.error("OpenAI sse连接异常:{}", t);
eventSource.cancel();
return;
}
ResponseBody body = response.body();
if (Objects.nonNull(body)) {
log.error("OpenAI sse连接异常data{},异常:{}", body.string(), t);
} else {
log.error("OpenAI sse连接异常data{},异常:{}", response, t);
}
eventSource.cancel();
}
}

View File

@@ -1,417 +0,0 @@
package org.ruoyi.common.chat.demo;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSONObject;
import lombok.Builder;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import org.junit.Before;
import org.junit.Test;
import org.ruoyi.common.chat.entity.chat.*;
import org.ruoyi.common.chat.entity.chat.tool.ToolCallFunction;
import org.ruoyi.common.chat.entity.chat.tool.ToolCalls;
import org.ruoyi.common.chat.entity.chat.tool.Tools;
import org.ruoyi.common.chat.entity.chat.tool.ToolsFunction;
import org.ruoyi.common.chat.openai.OpenAiClient;
import org.ruoyi.common.chat.openai.OpenAiStreamClient;
import org.ruoyi.common.chat.openai.function.KeyRandomStrategy;
import org.ruoyi.common.chat.openai.interceptor.DynamicKeyOpenAiAuthInterceptor;
import org.ruoyi.common.chat.openai.interceptor.OpenAILogger;
import org.ruoyi.common.chat.openai.interceptor.OpenAiResponseInterceptor;
import org.ruoyi.common.chat.openai.plugin.PluginAbstract;
import org.ruoyi.common.chat.plugin.CmdPlugin;
import org.ruoyi.common.chat.plugin.CmdReq;
import org.ruoyi.common.chat.sse.ConsoleEventSourceListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* 描述:
*
* @author ageerle@163.com
* date 2025/3/8
*/
@Slf4j
public class PluginTest {
private OpenAiClient openAiClient;
private OpenAiStreamClient openAiStreamClient;
@Before
public void before() {
//可以为null
// Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1", 7890));
HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor(new OpenAILogger());
//千万别再生产或者测试环境打开BODY级别日志
//生产或者测试环境建议设置为这三种级别NONE,BASIC,HEADERS,
httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.HEADERS);
OkHttpClient okHttpClient = new OkHttpClient
.Builder()
// .proxy(proxy)
.addInterceptor(httpLoggingInterceptor)
.addInterceptor(new OpenAiResponseInterceptor())
.connectTimeout(10, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build();
openAiClient = OpenAiClient.builder()
//支持多key传入请求时候随机选择
.apiKey(Arrays.asList("sk-xx"))
//自定义key的获取策略默认KeyRandomStrategy
//.keyStrategy(new KeyRandomStrategy())
.keyStrategy(new KeyRandomStrategy())
.okHttpClient(okHttpClient)
//自己做了代理就传代理地址,没有可不不传,(关注公众号回复openai ,获取免费的测试代理地址)
.apiHost("https://api.pandarobot.chat/")
.build();
openAiStreamClient = OpenAiStreamClient.builder()
//支持多key传入请求时候随机选择
.apiKey(Arrays.asList("sk-xx"))
//自定义key的获取策略默认KeyRandomStrategy
.keyStrategy(new KeyRandomStrategy())
.authInterceptor(new DynamicKeyOpenAiAuthInterceptor())
.okHttpClient(okHttpClient)
//自己做了代理就传代理地址,没有可不不传,(关注公众号回复openai ,获取免费的测试代理地址)
.apiHost("https://api.pandarobot.chat/")
.build();
}
@Test
public void chatFunction() {
//模型GPT_3_5_TURBO_16K_0613
Message message = Message.builder().role(Message.Role.USER).content("给我输出一个长度为2的中文词语并解释下词语对应物品的用途").build();
//属性一
JSONObject wordLength = new JSONObject();
wordLength.put("type", "number");
wordLength.put("description", "词语的长度");
//属性二
JSONObject language = new JSONObject();
language.put("type", "string");
language.put("enum", Arrays.asList("zh", "en"));
language.put("description", "语言类型例如zh代表中文、en代表英语");
//参数
JSONObject properties = new JSONObject();
properties.put("wordLength", wordLength);
properties.put("language", language);
Parameters parameters = Parameters.builder()
.type("object")
.properties(properties)
.required(Collections.singletonList("wordLength")).build();
Functions functions = Functions.builder()
.name("getOneWord")
.description("获取一个指定长度和语言类型的词语")
.parameters(parameters)
.build();
ChatCompletion chatCompletion = ChatCompletion
.builder()
.messages(Collections.singletonList(message))
.functions(Collections.singletonList(functions))
.functionCall("auto")
.model(ChatCompletion.Model.GPT_4_1106_PREVIEW.getName())
.build();
ChatCompletionResponse chatCompletionResponse = openAiClient.chatCompletion(chatCompletion);
ChatChoice chatChoice = chatCompletionResponse.getChoices().get(0);
log.info("构造的方法值:{}", chatChoice.getMessage().getFunctionCall());
log.info("构造的方法名称:{}", chatChoice.getMessage().getFunctionCall().getName());
log.info("构造的方法参数:{}", chatChoice.getMessage().getFunctionCall().getArguments());
WordParam wordParam = JSONUtil.toBean(chatChoice.getMessage().getFunctionCall().getArguments(), WordParam.class);
String oneWord = getOneWord(wordParam);
FunctionCall functionCall = FunctionCall.builder()
.arguments(chatChoice.getMessage().getFunctionCall().getArguments())
.name("getOneWord")
.build();
Message message2 = Message.builder().role(Message.Role.ASSISTANT).content("方法参数").functionCall(functionCall).build();
String content
= "{ " +
"\"wordLength\": \"3\", " +
"\"language\": \"zh\", " +
"\"word\": \"" + oneWord + "\"," +
"\"用途\": [\"直接吃\", \"做沙拉\", \"售卖\"]" +
"}";
Message message3 = Message.builder().role(Message.Role.FUNCTION).name("getOneWord").content(content).build();
List<Message> messageList = Arrays.asList(message, message2, message3);
ChatCompletion chatCompletionV2 = ChatCompletion
.builder()
.messages(messageList)
.model(ChatCompletion.Model.GPT_4_1106_PREVIEW.getName())
.build();
ChatCompletionResponse chatCompletionResponseV2 = openAiClient.chatCompletion(chatCompletionV2);
log.info("自定义的方法返回值:{}",chatCompletionResponseV2.getChoices().get(0).getMessage().getContent());
}
@Test
public void plugin() {
CmdPlugin plugin = new CmdPlugin(CmdReq.class);
// 插件名称
plugin.setName("命令行工具");
// 方法名称
plugin.setFunction("openCmd");
// 方法说明
plugin.setDescription("提供一个命令行指令,比如<记事本>,指令使用中文,以function返回结果为准");
PluginAbstract.Arg arg = new PluginAbstract.Arg();
// 参数名称
arg.setName("cmd");
// 参数说明
arg.setDescription("命令行指令");
// 参数类型
arg.setType("string");
arg.setRequired(true);
plugin.setArgs(Collections.singletonList(arg));
Message message2 = Message.builder().role(Message.Role.USER).content("帮我打开计算器,结合上下文判断指令是否执行成功,只用回复成功或者失败").build();
List<Message> messages = new ArrayList<>();
messages.add(message2);
//有四个重载方法,都可以使用
ChatCompletionResponse response = openAiClient.chatCompletionWithPlugin(messages,"gpt-4o-mini",plugin);
log.info("自定义的方法返回值:{}", response.getChoices().get(0).getMessage().getContent());
}
/**
* 自定义返回数据格式
*/
@Test
public void diyReturnDataModelChat() {
Message message = Message.builder().role(Message.Role.USER).content("随机输出10个单词使用json输出").build();
ChatCompletion chatCompletion = ChatCompletion
.builder()
.messages(Collections.singletonList(message))
.responseFormat(ResponseFormat.builder().type(ResponseFormat.Type.JSON_OBJECT.getName()).build())
.model(ChatCompletion.Model.GPT_4_1106_PREVIEW.getName())
.build();
ChatCompletionResponse chatCompletionResponse = openAiClient.chatCompletion(chatCompletion);
chatCompletionResponse.getChoices().forEach(e -> System.out.println(e.getMessage()));
}
@Test
public void streamPlugin() {
WeatherPlugin plugin = new WeatherPlugin(WeatherReq.class);
plugin.setName("知心天气");
plugin.setFunction("getLocationWeather");
plugin.setDescription("提供一个地址,方法将会获取该地址的天气的实时温度信息。");
PluginAbstract.Arg arg = new PluginAbstract.Arg();
arg.setName("location");
arg.setDescription("地名");
arg.setType("string");
arg.setRequired(true);
plugin.setArgs(Collections.singletonList(arg));
// Message message1 = Message.builder().role(Message.Role.USER).content("秦始皇统一了哪六国。").build();
Message message2 = Message.builder().role(Message.Role.USER).content("获取上海市的天气现在多少度然后再给出3个推荐的户外运动。").build();
List<Message> messages = new ArrayList<>();
// messages.add(message1);
messages.add(message2);
//默认模型GPT_3_5_TURBO_16K_0613
//有四个重载方法,都可以使用
openAiStreamClient.streamChatCompletionWithPlugin(messages, ChatCompletion.Model.GPT_4_1106_PREVIEW.getName(), new ConsoleEventSourceListener(), plugin);
CountDownLatch countDownLatch = new CountDownLatch(1);
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* tools使用示例
*/
@Test
public void toolsChat() {
Message message = Message.builder().role(Message.Role.USER).content("给我输出一个长度为2的中文词语并解释下词语对应物品的用途").build();
//属性一
JSONObject wordLength = new JSONObject();
wordLength.put("type", "number");
wordLength.put("description", "词语的长度");
//属性二
JSONObject language = new JSONObject();
language.put("type", "string");
language.put("enum", Arrays.asList("zh", "en"));
language.put("description", "语言类型例如zh代表中文、en代表英语");
//参数
JSONObject properties = new JSONObject();
properties.put("wordLength", wordLength);
properties.put("language", language);
Parameters parameters = Parameters.builder()
.type("object")
.properties(properties)
.required(Collections.singletonList("wordLength")).build();
Tools tools = Tools.builder()
.type(Tools.Type.FUNCTION.getName())
.function(ToolsFunction.builder().name("getOneWord").description("获取一个指定长度和语言类型的词语").parameters(parameters).build())
.build();
ChatCompletion chatCompletion = ChatCompletion
.builder()
.messages(Collections.singletonList(message))
.tools(Collections.singletonList(tools))
.model(ChatCompletion.Model.GPT_4_1106_PREVIEW.getName())
.build();
ChatCompletionResponse chatCompletionResponse = openAiClient.chatCompletion(chatCompletion);
ChatChoice chatChoice = chatCompletionResponse.getChoices().get(0);
log.info("构造的方法值:{}", chatChoice.getMessage().getToolCalls());
ToolCalls openAiReturnToolCalls = chatChoice.getMessage().getToolCalls().get(0);
WordParam wordParam = JSONUtil.toBean(openAiReturnToolCalls.getFunction().getArguments(), WordParam.class);
String oneWord = getOneWord(wordParam);
ToolCallFunction tcf = ToolCallFunction.builder().name("getOneWord").arguments(openAiReturnToolCalls.getFunction().getArguments()).build();
ToolCalls tc = ToolCalls.builder().id(openAiReturnToolCalls.getId()).type(ToolCalls.Type.FUNCTION.getName()).function(tcf).build();
//构造tool call
Message message2 = Message.builder().role(Message.Role.ASSISTANT).content("方法参数").toolCalls(Collections.singletonList(tc)).build();
String content
= "{ " +
"\"wordLength\": \"3\", " +
"\"language\": \"zh\", " +
"\"word\": \"" + oneWord + "\"," +
"\"用途\": [\"直接吃\", \"做沙拉\", \"售卖\"]" +
"}";
Message message3 = Message.builder().toolCallId(openAiReturnToolCalls.getId()).role(Message.Role.TOOL).name("getOneWord").content(content).build();
List<Message> messageList = Arrays.asList(message, message2, message3);
ChatCompletion chatCompletionV2 = ChatCompletion
.builder()
.messages(messageList)
.model(ChatCompletion.Model.GPT_4_1106_PREVIEW.getName())
.build();
ChatCompletionResponse chatCompletionResponseV2 = openAiClient.chatCompletion(chatCompletionV2);
log.info("自定义的方法返回值:{}", chatCompletionResponseV2.getChoices().get(0).getMessage().getContent());
}
/**
* tools流式输出使用示例
*/
@Test
public void streamToolsChat() {
CountDownLatch countDownLatch = new CountDownLatch(1);
ConsoleEventSourceListenerV3 eventSourceListener = new ConsoleEventSourceListenerV3(countDownLatch);
Message message = Message.builder().role(Message.Role.USER).content("给我输出一个长度为2的中文词语并解释下词语对应物品的用途").build();
//属性一
JSONObject wordLength = new JSONObject();
wordLength.put("type", "number");
wordLength.put("description", "词语的长度");
//属性二
JSONObject language = new JSONObject();
language.put("type", "string");
language.put("enum", Arrays.asList("zh", "en"));
language.put("description", "语言类型例如zh代表中文、en代表英语");
//参数
JSONObject properties = new JSONObject();
properties.put("wordLength", wordLength);
properties.put("language", language);
Parameters parameters = Parameters.builder()
.type("object")
.properties(properties)
.required(Collections.singletonList("wordLength")).build();
Tools tools = Tools.builder()
.type(Tools.Type.FUNCTION.getName())
.function(ToolsFunction.builder().name("getOneWord").description("获取一个指定长度和语言类型的词语").parameters(parameters).build())
.build();
ChatCompletion chatCompletion = ChatCompletion
.builder()
.messages(Collections.singletonList(message))
.tools(Collections.singletonList(tools))
.model(ChatCompletion.Model.GPT_4_1106_PREVIEW.getName())
.build();
openAiStreamClient.streamChatCompletion(chatCompletion, eventSourceListener);
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
ToolCalls openAiReturnToolCalls = eventSourceListener.getToolCalls();
WordParam wordParam = JSONUtil.toBean(openAiReturnToolCalls.getFunction().getArguments(), WordParam.class);
String oneWord = getOneWord(wordParam);
ToolCallFunction tcf = ToolCallFunction.builder().name("getOneWord").arguments(openAiReturnToolCalls.getFunction().getArguments()).build();
ToolCalls tc = ToolCalls.builder().id(openAiReturnToolCalls.getId()).type(ToolCalls.Type.FUNCTION.getName()).function(tcf).build();
//构造tool call
Message message2 = Message.builder().role(Message.Role.ASSISTANT).content("方法参数").toolCalls(Collections.singletonList(tc)).build();
String content
= "{ " +
"\"wordLength\": \"3\", " +
"\"language\": \"zh\", " +
"\"word\": \"" + oneWord + "\"," +
"\"用途\": [\"直接吃\", \"做沙拉\", \"售卖\"]" +
"}";
Message message3 = Message.builder().toolCallId(openAiReturnToolCalls.getId()).role(Message.Role.TOOL).name("getOneWord").content(content).build();
List<Message> messageList = Arrays.asList(message, message2, message3);
ChatCompletion chatCompletionV2 = ChatCompletion
.builder()
.messages(messageList)
.model(ChatCompletion.Model.GPT_4_1106_PREVIEW.getName())
.build();
CountDownLatch countDownLatch1 = new CountDownLatch(1);
openAiStreamClient.streamChatCompletion(chatCompletionV2, new ConsoleEventSourceListenerV3(countDownLatch));
try {
countDownLatch1.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
countDownLatch1.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Data
@Builder
static class WordParam {
private int wordLength;
@Builder.Default
private String language = "zh";
}
/**
* 获取一个词语(根据语言和字符长度查询)
* @param wordParam
* @return
*/
public String getOneWord(WordParam wordParam) {
List<String> zh = Arrays.asList("大香蕉", "哈密瓜", "苹果");
List<String> en = Arrays.asList("apple", "banana", "cantaloupe");
if (wordParam.getLanguage().equals("zh")) {
for (String e : zh) {
if (e.length() == wordParam.getWordLength()) {
return e;
}
}
}
if (wordParam.getLanguage().equals("en")) {
for (String e : en) {
if (e.length() == wordParam.getWordLength()) {
return e;
}
}
}
return "西瓜";
}
}

View File

@@ -1,24 +0,0 @@
package org.ruoyi.common.chat.demo;
import org.ruoyi.common.chat.openai.plugin.PluginAbstract;
public class WeatherPlugin extends PluginAbstract<WeatherReq, WeatherResp> {
public WeatherPlugin(Class<?> r) {
super(r);
}
@Override
public WeatherResp func(WeatherReq args) {
WeatherResp weatherResp = new WeatherResp();
weatherResp.setTemp("25到28摄氏度");
weatherResp.setLevel(3);
return weatherResp;
}
@Override
public String content(WeatherResp weatherResp) {
return "当前天气温度:" + weatherResp.getTemp() + ",风力等级:" + weatherResp.getLevel();
}
}

View File

@@ -1,13 +0,0 @@
package org.ruoyi.common.chat.demo;
import lombok.Data;
import org.ruoyi.common.chat.openai.plugin.PluginParam;
@Data
public class WeatherReq extends PluginParam {
/**
* 城市
*/
private String location;
}

View File

@@ -1,15 +0,0 @@
package org.ruoyi.common.chat.demo;
import lombok.Data;
@Data
public class WeatherResp {
/**
* 温度
*/
private String temp;
/**
* 风力等级
*/
private Integer level;
}

View File

@@ -1,223 +0,0 @@
package org.ruoyi.common.chat.demo;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zhipu.oapi.ClientV4;
import com.zhipu.oapi.Constants;
import com.zhipu.oapi.service.v4.tools.*;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import com.zhipu.oapi.service.v4.model.*;
import io.reactivex.Flowable;
import java.util.HashMap;
import java.util.Map;
public class WebSearchToolsTest {
private final static Logger logger = LoggerFactory.getLogger(WebSearchToolsTest.class);
private static final String API_SECRET_KEY = "xx";
private static final ClientV4 client = new ClientV4.Builder(API_SECRET_KEY)
.networkConfig(300, 100, 100, 100, TimeUnit.SECONDS)
.connectionPool(new okhttp3.ConnectionPool(8, 1, TimeUnit.SECONDS))
.build();
private static final ObjectMapper mapper = new ObjectMapper();
// 请自定义自己的业务id
private static final String requestIdTemplate = "mycompany-%d";
@Test
public void test1() throws JsonProcessingException {
// json 转换 ArrayList<SearchChatMessage>
String jsonString = "[\n" +
" {\n" +
" \"content\": \"今天武汉天气怎么样\",\n" +
" \"role\": \"user\"\n" +
" }\n" +
" ]";
ArrayList<SearchChatMessage> messages = new ObjectMapper().readValue(jsonString, new TypeReference<ArrayList<SearchChatMessage>>() {
});
String requestId = String.format(requestIdTemplate, System.currentTimeMillis());
WebSearchParamsRequest chatCompletionRequest = WebSearchParamsRequest.builder()
.model("web-search-pro")
.stream(Boolean.TRUE)
.messages(messages)
.requestId(requestId)
.build();
WebSearchApiResponse webSearchApiResponse = client.webSearchProStreamingInvoke(chatCompletionRequest);
if (webSearchApiResponse.isSuccess()) {
AtomicBoolean isFirst = new AtomicBoolean(true);
List<ChoiceDelta> choices = new ArrayList<>();
AtomicReference<WebSearchPro> lastAccumulator = new AtomicReference<>();
webSearchApiResponse.getFlowable().map(result -> result)
.doOnNext(accumulator -> {
{
if (isFirst.getAndSet(false)) {
logger.info("Response: ");
}
ChoiceDelta delta = accumulator.getChoices().get(0).getDelta();
if (delta != null && delta.getToolCalls() != null) {
logger.info("tool_calls: {}", mapper.writeValueAsString(delta.getToolCalls()));
}
choices.add(delta);
lastAccumulator.set(accumulator);
}
})
.doOnComplete(() -> System.out.println("Stream completed."))
.doOnError(throwable -> System.err.println("Error: " + throwable)) // Handle errors
.blockingSubscribe();// Use blockingSubscribe instead of blockingGet()
WebSearchPro chatMessageAccumulator = lastAccumulator.get();
webSearchApiResponse.setFlowable(null);// 打印前置空
webSearchApiResponse.setData(chatMessageAccumulator);
}
logger.info("model output: {}", mapper.writeValueAsString(webSearchApiResponse));
client.getConfig().getHttpClient().dispatcher().executorService().shutdown();
client.getConfig().getHttpClient().connectionPool().evictAll();
// List all active threads
for (Thread t : Thread.getAllStackTraces().keySet()) {
logger.info("Thread: " + t.getName() + " State: " + t.getState());
}
}
@Test
public void test2() throws JsonProcessingException {
// json 转换 ArrayList<SearchChatMessage>
String jsonString = "[\n" +
" {\n" +
" \"content\": \"今天天气怎么样\",\n" +
" \"role\": \"user\"\n" +
" }\n" +
" ]";
ArrayList<SearchChatMessage> messages = new ObjectMapper().readValue(jsonString, new TypeReference<ArrayList<SearchChatMessage>>() {
});
String requestId = String.format(requestIdTemplate, System.currentTimeMillis());
WebSearchParamsRequest chatCompletionRequest = WebSearchParamsRequest.builder()
.model("web-search-pro")
.stream(Boolean.FALSE)
.messages(messages)
.requestId(requestId)
.build();
WebSearchApiResponse webSearchApiResponse = client.invokeWebSearchPro(chatCompletionRequest);
logger.info("model output: {}", mapper.writeValueAsString(webSearchApiResponse));
}
@Test
public void testFunctionSSE() throws JsonProcessingException {
List<ChatMessage> messages = new ArrayList<>();
ChatMessage chatMessage = new ChatMessage(ChatMessageRole.USER.value(), "成都到北京要多久,天气如何");
messages.add(chatMessage);
String requestId = String.format(requestIdTemplate, System.currentTimeMillis());
// 函数调用参数构建部分
List<ChatTool> chatToolList = new ArrayList<>();
ChatTool chatTool = new ChatTool();
chatTool.setType(ChatToolType.FUNCTION.value());
ChatFunctionParameters chatFunctionParameters = new ChatFunctionParameters();
chatFunctionParameters.setType("object");
Map<String, Object> properties = new HashMap<>();
properties.put("location", new HashMap<String, Object>() {{
put("type", "string");
put("description", "城市,如:北京");
}});
properties.put("unit", new HashMap<String, Object>() {{
put("type", "string");
put("enum", new ArrayList<String>() {{
add("celsius");
add("fahrenheit");
}});
}});
chatFunctionParameters.setProperties(properties);
ChatFunction chatFunction = ChatFunction.builder()
.name("get_weather")
.description("Get the current weather of a location")
.parameters(chatFunctionParameters)
.build();
chatTool.setFunction(chatFunction);
chatToolList.add(chatTool);
HashMap<String, Object> extraJson = new HashMap<>();
extraJson.put("temperature", 0.5);
extraJson.put("max_tokens", 50);
ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder()
.model(Constants.ModelChatGLM4)
.stream(Boolean.TRUE)
.messages(messages)
.requestId(requestId)
.tools(chatToolList)
.toolChoice("auto")
.extraJson(extraJson)
.build();
ModelApiResponse sseModelApiResp = client.invokeModelApi(chatCompletionRequest);
if (sseModelApiResp.isSuccess()) {
AtomicBoolean isFirst = new AtomicBoolean(true);
List<Choice> choices = new ArrayList<>();
ChatMessageAccumulator chatMessageAccumulator = mapStreamToAccumulator(sseModelApiResp.getFlowable())
.doOnNext(accumulator -> {
{
if (isFirst.getAndSet(false)) {
logger.info("Response: ");
}
if (accumulator.getDelta() != null && accumulator.getDelta().getTool_calls() != null) {
String jsonString = mapper.writeValueAsString(accumulator.getDelta().getTool_calls());
logger.info("tool_calls: {}", jsonString);
}
if (accumulator.getDelta() != null && accumulator.getDelta().getContent() != null) {
logger.info(accumulator.getDelta().getContent());
}
choices.add(accumulator.getChoice());
}
})
.doOnComplete(System.out::println)
.lastElement()
.blockingGet();
ModelData data = new ModelData();
data.setChoices(choices);
data.setUsage(chatMessageAccumulator.getUsage());
data.setId(chatMessageAccumulator.getId());
data.setCreated(chatMessageAccumulator.getCreated());
data.setRequestId(chatCompletionRequest.getRequestId());
sseModelApiResp.setFlowable(null);// 打印前置空
sseModelApiResp.setData(data);
}
logger.info("model output: {}", mapper.writeValueAsString(sseModelApiResp));
}
public static Flowable<ChatMessageAccumulator> mapStreamToAccumulator(Flowable<ModelData> flowable) {
return flowable.map(chunk -> {
return new ChatMessageAccumulator(chunk.getChoices().get(0).getDelta(), null, chunk.getChoices().get(0), chunk.getUsage(), chunk.getCreated(), chunk.getId());
});
}
}

View File

@@ -1,65 +0,0 @@
package org.ruoyi.common.chat.domain.request;
import org.ruoyi.common.chat.entity.chat.Message;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import java.util.List;
/**
* 描述:
*
* @author https:www.unfbx.com
* @sine 2023-04-08
*/
@Data
public class ChatRequest {
private String frequency_penalty;
private String max_tokens;
@NotEmpty(message = "对话消息不能为空")
List<Message> messages;
@NotEmpty(message = "传入的模型不能为空")
private String model;
private String presence_penalty;
private String stream;
private double temperature;
private double top_p = 1;
/**
* 知识库id
*/
private String kid;
private String userId;
//
//
// /**
// * gpt的默认设置
// */
// private String systemMessage = "";
//
//
//
// private double temperature = 0.2;
//
// /**
// * 上下文的条数
// */
// private Integer contentNumber = 10;
//
// /**
// * 是否携带上下文
// */
// private Boolean usingContext = Boolean.TRUE;
}

View File

@@ -1,33 +0,0 @@
package org.ruoyi.common.chat.domain.request;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
/**
* 描述:
*
* @author https:www.unfbx.com
* @sine 2023-04-08
*/
@Data
public class Dall3Request {
@NotEmpty(message = "传入的模型不能为空")
private String model;
@NotEmpty(message = "提示词不能为空")
private String prompt;
/** 图片大小 */
@NotEmpty(message = "图片大小不能为空")
private String size ;
/** 图片质量 */
@NotEmpty(message = "图片质量不能为空")
private String quality;
/** 图片风格 */
@NotEmpty(message = "图片风格不能为空")
private String style;
}

View File

@@ -2,6 +2,7 @@ package org.ruoyi.common.chat.entity.chat;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import org.ruoyi.common.chat.entity.chat.tool.ToolCalls;
@@ -20,6 +21,8 @@ import java.util.List;
public class Message extends BaseMessage implements Serializable {
private Object content;
@JsonProperty("reasoning_content")
private String reasoningContent;
public static Builder builder() {
return new Builder();

View File

@@ -2,7 +2,6 @@ package org.ruoyi.common.chat.handler;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson2.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.ruoyi.common.chat.config.LocalCache;
import org.ruoyi.common.chat.entity.chat.ChatCompletion;
@@ -12,7 +11,6 @@ import org.ruoyi.common.chat.listener.WebSocketEventListener;
import org.ruoyi.common.chat.openai.OpenAiStreamClient;
import org.ruoyi.common.chat.utils.WebSocketUtils;
import org.ruoyi.common.core.utils.SpringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.socket.*;
import org.springframework.web.socket.handler.AbstractWebSocketHandler;

View File

@@ -1,198 +0,0 @@
package org.ruoyi.common.chat.localModels;
import io.micrometer.common.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient;
import org.ruoyi.common.chat.entity.models.LocalModelsSearchRequest;
import org.ruoyi.common.chat.entity.models.LocalModelsSearchResponse;
import org.springframework.stereotype.Service;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.jackson.JacksonConverterFactory;
import java.util.List;
import java.util.concurrent.CountDownLatch;
@Slf4j
@Service
public class LocalModelsofitClient {
private static final String BASE_URL = "http://127.0.0.1:5000"; // Flask 服务的 URL
private static Retrofit retrofit = null;
// 获取 Retrofit 实例
public static Retrofit getRetrofitInstance() {
if (retrofit == null) {
OkHttpClient client = new OkHttpClient.Builder()
.build();
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.client(client)
.addConverterFactory(JacksonConverterFactory.create()) // 使用 Jackson 处理 JSON 转换
.build();
}
return retrofit;
}
/**
* 向 Flask 服务发送文本向量化请求
*
* @param queries 查询文本列表
* @param modelName 模型名称
* @param delimiter 文本分隔符
* @param topK 返回的结果数
* @param blockSize 文本块大小
* @param overlapChars 重叠字符数
* @return 返回计算得到的 Top K 嵌入向量列表
*/
public static List<List<Double>> getTopKEmbeddings(
List<String> queries,
String modelName,
String delimiter,
int topK,
int blockSize,
int overlapChars) {
modelName = (!StringUtils.isEmpty(modelName)) ? modelName : "msmarco-distilbert-base-tas-b"; // 默认模型名称
delimiter = (!StringUtils.isEmpty(delimiter) ) ? delimiter : "."; // 默认分隔符
topK = (topK > 0) ? topK : 3; // 默认返回 3 个结果
blockSize = (blockSize > 0) ? blockSize : 500; // 默认文本块大小为 500
overlapChars = (overlapChars > 0) ? overlapChars : 50; // 默认重叠字符数为 50
// 创建 Retrofit 实例
Retrofit retrofit = getRetrofitInstance();
// 创建 SearchService 接口
SearchService service = retrofit.create(SearchService.class);
// 创建请求对象 LocalModelsSearchRequest
LocalModelsSearchRequest request = new LocalModelsSearchRequest(
queries, // 查询文本列表
modelName, // 模型名称
delimiter, // 文本分隔符
topK, // 返回的结果数
blockSize, // 文本块大小
overlapChars // 重叠字符数
);
final CountDownLatch latch = new CountDownLatch(1); // 创建一个 CountDownLatch
final List<List<Double>>[] topKEmbeddings = new List[]{null}; // 使用数组来存储结果(因为 Java 不支持直接修改 List
// 发起异步请求
service.vectorize(request).enqueue(new Callback<LocalModelsSearchResponse>() {
@Override
public void onResponse(Call<LocalModelsSearchResponse> call, Response<LocalModelsSearchResponse> response) {
if (response.isSuccessful()) {
LocalModelsSearchResponse searchResponse = response.body();
if (searchResponse != null) {
topKEmbeddings[0] = searchResponse.getTopKEmbeddings().get(0); // 获取结果
log.info("Successfully retrieved embeddings");
} else {
log.error("Response body is null");
}
} else {
log.error("Request failed. HTTP error code: " + response.code());
}
latch.countDown(); // 请求完成,减少计数
}
@Override
public void onFailure(Call<LocalModelsSearchResponse> call, Throwable t) {
t.printStackTrace();
log.error("Request failed: ", t);
latch.countDown(); // 请求失败,减少计数
}
});
try {
latch.await(); // 等待请求完成
} catch (InterruptedException e) {
e.printStackTrace();
}
return topKEmbeddings[0]; // 返回结果
}
// public static void main(String[] args) {
// // 示例调用
// List<String> queries = Arrays.asList("What is artificial intelligence?", "AI is transforming industries.");
// String modelName = "msmarco-distilbert-base-tas-b";
// String delimiter = ".";
// int topK = 3;
// int blockSize = 500;
// int overlapChars = 50;
//
// List<List<Double>> topKEmbeddings = getTopKEmbeddings(queries, modelName, delimiter, topK, blockSize, overlapChars);
//
// // 打印结果
// if (topKEmbeddings != null) {
// System.out.println("Top K embeddings: ");
// for (List<Double> embedding : topKEmbeddings) {
// System.out.println(embedding);
// }
// } else {
// System.out.println("No embeddings returned.");
// }
// }
// public static void main(String[] args) {
// // 创建 Retrofit 实例
// Retrofit retrofit = LocalModelsofitClient.getRetrofitInstance();
//
// // 创建 SearchService 接口
// SearchService service = retrofit.create(SearchService.class);
//
// // 创建请求对象 LocalModelsSearchRequest
// LocalModelsSearchRequest request = new LocalModelsSearchRequest(
// Arrays.asList("What is artificial intelligence?", "AI is transforming industries."), // 查询文本列表
// "msmarco-distilbert-base-tas-b", // 模型名称
// ".", // 分隔符
// 3, // 返回的结果数
// 500, // 文本块大小
// 50 // 重叠字符数
// );
//
// // 发起请求
// service.vectorize(request).enqueue(new Callback<LocalModelsSearchResponse>() {
// @Override
// public void onResponse(Call<LocalModelsSearchResponse> call, Response<LocalModelsSearchResponse> response) {
// if (response.isSuccessful()) {
// LocalModelsSearchResponse searchResponse = response.body();
// System.out.println("Response Body: " + response.body()); // Print the whole response body for debugging
//
// if (searchResponse != null) {
// // If the response is not null, process it.
// // Example: Extract the embeddings and print them
// List<List<List<Double>>> topKEmbeddings = searchResponse.getTopKEmbeddings();
// if (topKEmbeddings != null) {
// // Print the Top K embeddings
//
// } else {
// System.err.println("Top K embeddings are null");
// }
//
// // If there is more information you want to process, handle it here
//
// } else {
// System.err.println("Response body is null");
// }
// } else {
// System.err.println("Request failed. HTTP error code: " + response.code());
// log.error("Failed to retrieve data. HTTP error code: " + response.code());
// }
// }
//
// @Override
// public void onFailure(Call<LocalModelsSearchResponse> call, Throwable t) {
// // 请求失败,打印错误
// t.printStackTrace();
// log.error("Request failed: ", t);
// }
// });
// }
}

View File

@@ -1,25 +0,0 @@
package org.ruoyi.common.chat.localModels;
import org.ruoyi.common.chat.entity.models.LocalModelsSearchRequest;
import org.ruoyi.common.chat.entity.models.LocalModelsSearchResponse;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.POST;
/**
* @program: RUOYIAI
* @ClassName SearchService
* @description: 请求模型
* @author: hejh
* @create: 2025-03-15 17:27
* @Version 1.0
**/
public interface SearchService {
@POST("/vectorize") // 与 Flask 服务中的路由匹配
Call<LocalModelsSearchResponse> vectorize(@Body LocalModelsSearchRequest request);
}

View File

@@ -466,8 +466,8 @@ public class OpenAiStreamClient {
* @since 1.1.3
*/
public ResponseBody textToSpeech(TextToSpeech textToSpeech){
Call<ResponseBody> responseBody = this.openAiApi.textToSpeech(textToSpeech);
try {
Call<ResponseBody> responseBody = this.openAiApi.textToSpeech(textToSpeech);
return responseBody.execute().body();
} catch (IOException e) {
throw new BaseException("文本转语音(同步)失败: "+e.getMessage());
@@ -593,11 +593,6 @@ public class OpenAiStreamClient {
}
/**
* 构造
*

View File

@@ -1,36 +0,0 @@
package org.ruoyi.common.chat.plugin;
import org.ruoyi.common.chat.openai.plugin.PluginAbstract;
import java.io.IOException;
public class CmdPlugin extends PluginAbstract<CmdReq, CmdResp> {
public CmdPlugin(Class<?> r) {
super(r);
}
@Override
public CmdResp func(CmdReq args) {
try {
if("计算器".equals(args.getCmd())){
Runtime.getRuntime().exec("calc");
}else if("记事本".equals(args.getCmd())){
Runtime.getRuntime().exec("notepad");
}else if("命令行".equals(args.getCmd())){
String [] cmd={"cmd","/C","start copy exel exe2"};
Runtime.getRuntime().exec(cmd);
}
} catch (IOException e) {
throw new RuntimeException("指令执行失败");
}
CmdResp resp = new CmdResp();
resp.setResult(args.getCmd()+"指令执行成功!");
return resp;
}
@Override
public String content(CmdResp resp) {
return resp.getResult();
}
}

View File

@@ -2,31 +2,49 @@ package org.ruoyi.common.chat.request;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import org.ruoyi.common.chat.entity.chat.Content;
import org.ruoyi.common.chat.entity.chat.Message;
import java.util.List;
/**
* 描述:
* 描述:对话请求对象
*
* @author https:www.unfbx.com
* @author ageerle
* @sine 2023-04-08
*/
@Data
public class ChatRequest {
@NotEmpty(message = "传入的模型不能为空")
private String model;
@NotEmpty(message = "对话消息不能为空")
List<Message> messages;
List<Content> imageContent;
@NotEmpty(message = "传入的模型不能为空")
private String model;
/**
* 提示词
*/
private String prompt;
private String userId;
/**
* 系统提示词
*/
private String sysPrompt;
/**
* 是否开启流式对话
*/
private Boolean stream = Boolean.TRUE;
/**
* 是否开启联网搜索(0关闭 1开启)
*/
private Boolean search = Boolean.FALSE;
/**
* 是否开启mcp
*/
private Boolean isMcp = Boolean.FALSE;
/**
* 知识库id
@@ -34,13 +52,14 @@ public class ChatRequest {
private String kid;
/**
* gpt的默认设置
* 用户id
*/
private String systemMessage = "";
private Long userId;
private double top_p = 1;
private double temperature = 0.2;
/**
* 应用ID
*/
private String appId;
/**
* 上下文的条数
@@ -52,4 +71,5 @@ public class ChatRequest {
*/
private Boolean usingContext = Boolean.TRUE;
}

View File

@@ -28,7 +28,6 @@ public class ConsoleEventSourceListener extends EventSourceListener {
log.info("OpenAI返回数据{}", data);
if ("[DONE]".equals(data)) {
log.info("OpenAI返回数据结束了");
return;
}
}

View File

@@ -8,7 +8,6 @@ import okhttp3.ResponseBody;
import okhttp3.sse.EventSource;
import okhttp3.sse.EventSourceListener;
import org.jetbrains.annotations.NotNull;
import org.ruoyi.common.chat.constant.OpenAIConst;
import org.ruoyi.common.chat.entity.chat.ChatCompletion;
import org.ruoyi.common.chat.entity.chat.ChatCompletionResponse;
import org.ruoyi.common.chat.entity.chat.FunctionCall;

View File

@@ -47,25 +47,11 @@
<artifactId>jakarta.servlet-api</artifactId>
</dependency>
<!-- hutool工具模块 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-http</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-extra</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-json</artifactId>
<scope>provided</scope>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
<dependency>
@@ -73,18 +59,6 @@
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-cp</artifactId>
<version>${weixin-java-miniapp.version}</version>
</dependency>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-cp</artifactId>
<version>${weixin-java-cp.version}</version>
</dependency>
<!-- 自动生成YML配置关联JSON文件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
@@ -108,6 +82,11 @@
<artifactId>ip2region</artifactId>
</dependency>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-cp</artifactId>
<version>${weixin-java-cp.version}</version>
</dependency>
<dependency>
<groupId>com.github.binarywang</groupId>
@@ -130,16 +109,19 @@
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>${okhttp.version}</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp-sse</artifactId>
<version>${okhttp.version}</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>logging-interceptor</artifactId>
<version>${okhttp.version}</version>
</dependency>
</dependencies>

View File

@@ -0,0 +1,59 @@
package org.ruoyi.common.core.constant;
import cn.hutool.core.lang.RegexPool;
/**
* 常用正则表达式字符串
* <p>
* 常用正则表达式集合,更多正则见: https://any86.github.io/any-rule/
*
* @author Feng
*/
public interface RegexConstants extends RegexPool {
/**
* 字典类型必须以字母开头,且只能为(小写字母,数字,下滑线)
*/
String DICTIONARY_TYPE = "^[a-z][a-z0-9_]*$";
/**
* 权限标识必须符合以下格式:
* 1. 标准格式xxx:yyy:zzz
* - 第一部分xxx只能包含字母、数字和下划线_不能使用 `*`
* - 第二部分yyy可以包含字母、数字、下划线_和 `*`
* - 第三部分zzz可以包含字母、数字、下划线_和 `*`
* 2. 允许空字符串(""),表示没有权限标识
*/
String PERMISSION_STRING = "^$|^[a-zA-Z0-9_]+:[a-zA-Z0-9_*]+:[a-zA-Z0-9_*]+$";
/**
* 身份证号码后6位
*/
String ID_CARD_LAST_6 = "^(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx]$";
/**
* QQ号码
*/
String QQ_NUMBER = "^[1-9][0-9]\\d{4,9}$";
/**
* 邮政编码
*/
String POSTAL_CODE = "^[1-9]\\d{5}$";
/**
* 注册账号
*/
String ACCOUNT = "^[a-zA-Z][a-zA-Z0-9_]{4,15}$";
/**
* 密码包含至少8个字符包括大写字母、小写字母、数字和特殊字符
*/
String PASSWORD = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]{8,}$";
/**
* 通用状态0表示正常1表示停用
*/
String STATUS = "^[01]$";
}

View File

@@ -0,0 +1,80 @@
package org.ruoyi.common.core.constant;
/**
* 系统常量信息
*
* @author Lion Li
*/
public interface SystemConstants {
/**
* 正常状态
*/
String NORMAL = "0";
/**
* 异常状态
*/
String DISABLE = "1";
/**
* 是否为系统默认(是)
*/
String YES = "Y";
/**
* 是否为系统默认(否)
*/
String NO = "N";
/**
* 是否菜单外链(是)
*/
String YES_FRAME = "0";
/**
* 是否菜单外链(否)
*/
String NO_FRAME = "1";
/**
* 菜单类型(目录)
*/
String TYPE_DIR = "M";
/**
* 菜单类型(菜单)
*/
String TYPE_MENU = "C";
/**
* 菜单类型(按钮)
*/
String TYPE_BUTTON = "F";
/**
* Layout组件标识
*/
String LAYOUT = "Layout";
/**
* ParentView组件标识
*/
String PARENT_VIEW = "ParentView";
/**
* InnerLink组件标识
*/
String INNER_LINK = "InnerLink";
/**
* 超级管理员ID
*/
Long SUPER_ADMIN_ID = 1L;
/**
* 根部门祖级列表
*/
String ROOT_DEPT_ANCESTORS = "0";
}

View File

@@ -0,0 +1,53 @@
package org.ruoyi.common.core.factory;
import cn.hutool.core.lang.PatternPool;
import org.ruoyi.common.core.constant.RegexConstants;
import java.util.regex.Pattern;
/**
* 正则表达式模式池工厂
* <p>初始化的时候将正则表达式加入缓存池当中</p>
* <p>提高正则表达式的性能,避免重复编译相同的正则表达式</p>
*
* @author 21001
*/
public class RegexPatternPoolFactory extends PatternPool {
/**
* 字典类型必须以字母开头,且只能为(小写字母,数字,下滑线)
*/
public static final Pattern DICTIONARY_TYPE = get(RegexConstants.DICTIONARY_TYPE);
/**
* 身份证号码后6位
*/
public static final Pattern ID_CARD_LAST_6 = get(RegexConstants.ID_CARD_LAST_6);
/**
* QQ号码
*/
public static final Pattern QQ_NUMBER = get(RegexConstants.QQ_NUMBER);
/**
* 邮政编码
*/
public static final Pattern POSTAL_CODE = get(RegexConstants.POSTAL_CODE);
/**
* 注册账号
*/
public static final Pattern ACCOUNT = get(RegexConstants.ACCOUNT);
/**
* 密码包含至少8个字符包括大写字母、小写字母、数字和特殊字符
*/
public static final Pattern PASSWORD = get(RegexConstants.PASSWORD);
/**
* 通用状态0表示正常1表示停用
*/
public static final Pattern STATUS = get(RegexConstants.STATUS);
}

View File

@@ -0,0 +1,32 @@
package org.ruoyi.common.core.factory;
import org.ruoyi.common.core.utils.StringUtils;
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.support.DefaultPropertySourceFactory;
import org.springframework.core.io.support.EncodedResource;
import java.io.IOException;
/**
* yml 配置源工厂
*
* @author Lion Li
*/
public class YmlPropertySourceFactory extends DefaultPropertySourceFactory {
@Override
public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
String sourceName = resource.getResource().getFilename();
if (StringUtils.isNotBlank(sourceName) && StringUtils.endsWithAny(sourceName, ".yml", ".yaml")) {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(resource.getResource());
factory.afterPropertiesSet();
return new PropertiesPropertySource(sourceName, factory.getObject());
}
return super.createPropertySource(name, resource);
}
}

View File

@@ -0,0 +1,60 @@
package org.ruoyi.common.core.utils;
import cn.hutool.core.util.ObjectUtil;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import java.util.function.Function;
/**
* 对象工具类
*
* @author 秋辞未寒
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ObjectUtils extends ObjectUtil {
/**
* 如果对象不为空,则获取对象中的某个字段 ObjectUtils.notNullGetter(user, User::getName);
*
* @param obj 对象
* @param func 获取方法
* @return 对象字段
*/
public static <T, E> E notNullGetter(T obj, Function<T, E> func) {
if (isNotNull(obj) && isNotNull(func)) {
return func.apply(obj);
}
return null;
}
/**
* 如果对象不为空,则获取对象中的某个字段,否则返回默认值
*
* @param obj 对象
* @param func 获取方法
* @param defaultValue 默认值
* @return 对象字段
*/
public static <T, E> E notNullGetter(T obj, Function<T, E> func, E defaultValue) {
if (isNotNull(obj) && isNotNull(func)) {
return func.apply(obj);
}
return defaultValue;
}
/**
* 如果值不为空,则返回值,否则返回默认值
*
* @param obj 对象
* @param defaultValue 默认值
* @return 对象字段
*/
public static <T> T notNull(T obj, T defaultValue) {
if (isNotNull(obj)) {
return obj;
}
return defaultValue;
}
}

View File

@@ -1,9 +1,10 @@
package org.ruoyi.common.core.utils;
import cn.hutool.extra.spring.SpringUtil;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.boot.autoconfigure.thread.Threading;
import org.springframework.context.ApplicationContext;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
/**
@@ -48,7 +49,7 @@ public final class SpringUtils extends SpringUtil {
*/
@SuppressWarnings("unchecked")
public static <T> T getAopProxy(T invoker) {
return (T) AopContext.currentProxy();
return (T) getBean(invoker.getClass());
}
@@ -59,4 +60,8 @@ public final class SpringUtils extends SpringUtil {
return getApplicationContext();
}
public static boolean isVirtual() {
return Threading.VIRTUAL.isActive(getBean(Environment.class));
}
}

View File

@@ -23,11 +23,6 @@
<artifactId>ruoyi-common-core</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15to18</artifactId>
@@ -38,6 +33,23 @@
<artifactId>hutool-crypto</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<optional>true</optional>
<exclusions>
<exclusion>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>

View File

@@ -27,7 +27,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-live</artifactId>
<artifactId>ruoyi-common-live</artifactId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@@ -27,7 +27,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.ruoyi</groupId>
<artifactId>ruoyi-live</artifactId>
<artifactId>ruoyi-common-live</artifactId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>

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