49 Commits b2bb5ffc4d ... 7f07a5e9f0

Author SHA1 Message Date
  dafengbaocy 7f07a5e9f0 upgrade 1 month ago
  ageer 5476a4b0b7 fix: 公众号登录功能同步 1 month ago
  ageerle cd490aa0e5 feat: 新增wechat模块 1 month ago
  ageerle b1ff44df4b feat: sse对话样式优化 1 month ago
  ageerle dc6d00f0fc feat: 查询gpts无需登录 1 month ago
  ageerle 990da8da6f feat: 增加工具调用超时时间配置 1 month ago
  ageerle 8fc7ad0359 feat: mcp开启后才执行工具调用 1 month ago
  ageer 8a89d9eb9c fix: 1.合并知识库对话接口 2.修复web无法查询套餐信息 1 month ago
  ageer abfa9ae97b fix: 1.合并知识库对话接口 2.修复web无法查询套餐信息 1 month ago
  ageer bde2e8ff8e fix: 1.合并知识库对话接口 2.修复web无法查询套餐信息 1 month ago
  ageer 4d8eb1850f feat: 附件上传逻辑调整 1 month ago
  ageer 5aaa3a5662 fix: 1. 修复No static resource tool/gen/list. 2. 修复新增数据库校验异常 1 month ago
  ageerle 8fec96f5b2 feat: 默认关闭mcp功能 1 month ago
  ageerle 15c306eca2 fix: 根据不同的模型构建对话客户端 1 month ago
  ageerle 620ea1fc76 fix: 系统提示词非必填 1 month ago
  ageerle c5c375dc6d fix: 扣费时无法获取用户id 1 month ago
  ageerle 1b793e822a fix: 扣费时无法获取用户id 1 month ago
  ageerle 6281840f36 fix: 修复后台管理系统登录异常 1 month ago
  ageer f5daa7eb78 feat: 设置server-filesystem默认路径为D盘 防止启动报错 1 month ago
  ageer 52cb563383 fix(1.修复/store/appList404 2.修复无法查询数据库 3.修复请求地址'/chat/config/configKey/logoImage',发生系统异常): 1 month ago
  ageerle 69efc3261e Merge branch 'dev' 1 month ago
  ageerle fb19fb29cc feat: 移除文件 1 month ago
  ageer 9f257cf712 feat: mcp升级依赖 1 month ago
  ageer 4af26fe4b4 feat: mcp升级依赖 1 month ago
  ageerle 9cd97a4dc5 feat: mcp支持远程调用 1 month ago
  ageerle 788b372e32 feat: mcp支持远程调用 1 month ago
  ageer 761d954ef1 feat: mcp 1.0.0 1 month ago
  ageerle d1006f50ad feat: mcp-1.0.0 1 month ago
  ageerle f24ff5bbdd feat: mcp 测试版本 1 month ago
  ageer a4314dbbde feat: mcp测试版 1 month ago
  ageerle 188dc1e55e feat: mcp 测试版本 1 month ago
  ageer 731352fd04 feat: mcp测试版 1 month ago
  ageer 4b53939002 feat: mcp支持,模块重构 1 month ago
  ageer 0dc6262b39 feat: mcp支持 1 month ago
  ageer 9b32e3b3b2 feat: mcp支持 1 month ago
  ageerle 37a8b7dad3 feat: 对话模块重构 1 month ago
  ageerle efeb0bd6fb fix: ollama兼容联网查询 知识库检索 1 month ago
  ageerle af33040117 fix: 语音功能无法使用 1 month ago
  ageerle ac4c037634 feat: 重构模块 1 month ago
  ageerle 9734ec53f7 feat: 重构模块 1 month ago
  ageer 2b8a92c7d6 fix: 重构模块 1 month ago
  ageer adb4538317 fix: 修复使用本地向量模型只能检索到一条知识内容 1 month ago
  ageerle 2509099146 feat: 重构模块 1 month ago
  ageerle 3be9005f95 feat: 调整知识库模块 1 month ago
  ageerle be6d027cad feat: 删除代码生成脚本 1 month ago
  ageerle 3d679f8749 Merge remote-tracking branch 'origin/dev' into dev 1 month ago
  ageer 5a5a48e153 feat: ruoyi-chat-api模块调整 2 months ago
  ageer e5da648941 feat: 代码生成脚本 2 months ago
  ageerle d2755f00bc feat: 测试版本提交 2 months ago
100 changed files with 2664 additions and 2621 deletions
  1. 23 10
      README.md
  2. BIN
      image/mcp-01.png
  3. 45 39
      pom.xml
  4. 0 35
      ruoyi-admin/Dockerfile
  5. 1 31
      ruoyi-admin/pom.xml
  6. 6 18
      ruoyi-admin/src/main/java/org/ruoyi/controller/AuthController.java
  7. 4 2
      ruoyi-admin/src/main/java/org/ruoyi/controller/IndexController.java
  8. 0 3
      ruoyi-admin/src/main/resources/application-dev.yml
  9. 19 2
      ruoyi-admin/src/main/resources/application.yml
  10. 22 0
      ruoyi-admin/src/main/resources/mcp-server.json
  11. 9 9
      ruoyi-common/pom.xml
  12. 1 16
      ruoyi-common/ruoyi-common-bom/pom.xml
  13. 14 35
      ruoyi-common/ruoyi-common-chat/pom.xml
  14. 3 0
      ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/config/properties/WebSocketProperties.java
  15. 0 73
      ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/ConsoleEventSourceListenerV2.java
  16. 0 92
      ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/ConsoleEventSourceListenerV3.java
  17. 0 417
      ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/PluginTest.java
  18. 0 24
      ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/WeatherPlugin.java
  19. 0 13
      ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/WeatherReq.java
  20. 0 15
      ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/WeatherResp.java
  21. 0 223
      ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/WebSearchToolsTest.java
  22. 0 75
      ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/request/ChatRequest.java
  23. 0 33
      ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/request/Dall3Request.java
  24. 0 2
      ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/handler/PlusWebSocketHandler.java
  25. 0 198
      ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/localModels/LocalModelsofitClient.java
  26. 0 25
      ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/localModels/SearchService.java
  27. 1 6
      ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/OpenAiStreamClient.java
  28. 0 36
      ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/plugin/CmdPlugin.java
  29. 33 13
      ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/request/ChatRequest.java
  30. 0 1
      ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/sse/ConsoleEventSourceListener.java
  31. 0 1
      ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/sse/PluginListener.java
  32. 11 29
      ruoyi-common/ruoyi-common-core/pom.xml
  33. 59 0
      ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/constant/RegexConstants.java
  34. 80 0
      ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/constant/SystemConstants.java
  35. 53 0
      ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/factory/RegexPatternPoolFactory.java
  36. 32 0
      ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/factory/YmlPropertySourceFactory.java
  37. 60 0
      ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/ObjectUtils.java
  38. 7 2
      ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/SpringUtils.java
  39. 17 5
      ruoyi-common/ruoyi-common-encrypt/pom.xml
  40. 5 3
      ruoyi-common/ruoyi-common-live/pom.xml
  41. 2 5
      ruoyi-common/ruoyi-common-mail/src/main/java/org/ruoyi/common/mail/config/MailConfig.java
  42. 1 1
      ruoyi-common/ruoyi-common-mail/src/main/java/org/ruoyi/common/mail/properties/MailProperties.java
  43. 7 3
      ruoyi-common/ruoyi-common-mybatis/pom.xml
  44. 0 45
      ruoyi-common/ruoyi-common-mybatis/src/main/java/com/baomidou/dynamic/datasource/processor/jakarta/DsJakartaHeaderProcessor.java
  45. 0 46
      ruoyi-common/ruoyi-common-mybatis/src/main/java/com/baomidou/dynamic/datasource/processor/jakarta/DsJakartaSessionProcessor.java
  46. 40 0
      ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/annotation/DataColumn.java
  47. 30 0
      ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/annotation/DataPermission.java
  48. 50 0
      ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/aspect/DataPermissionAspect.java
  49. 0 28
      ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/annotation/DataColumn.java
  50. 0 18
      ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/annotation/DataPermission.java
  51. 0 198
      ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/core/mapper/BaseMapperPlus.java
  52. 0 73
      ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/enums/DataScopeType.java
  53. 0 81
      ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/handler/InjectionMetaObjectHandler.java
  54. 0 198
      ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/handler/PlusDataPermissionHandler.java
  55. 0 93
      ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/helper/DataPermissionHelper.java
  56. 0 107
      ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/interceptor/PlusDataPermissionInterceptor.java
  57. 0 45
      ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/jakarta/DsJakartaHeaderProcessor.java
  58. 0 46
      ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/jakarta/DsJakartaSessionProcessor.java
  59. 44 8
      ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/config/MybatisPlusConfig.java
  60. 1 2
      ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/core/domain/BaseEntity.java
  61. 335 0
      ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/core/mapper/BaseMapperPlus.java
  62. 15 2
      ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/core/page/PageQuery.java
  63. 12 2
      ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/core/page/TableDataInfo.java
  64. 11 1
      ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/enums/DataBaseType.java
  65. 87 0
      ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/enums/DataScopeType.java
  66. 103 0
      ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/handler/InjectionMetaObjectHandler.java
  67. 5 3
      ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/handler/MybatisExceptionHandler.java
  68. 359 0
      ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/handler/PlusDataPermissionHandler.java
  69. 28 0
      ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/handler/PlusPostInitTableInfoHandler.java
  70. 6 4
      ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/helper/DataBaseHelper.java
  71. 176 0
      ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/helper/DataPermissionHelper.java
  72. 181 0
      ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/interceptor/PlusDataPermissionInterceptor.java
  73. 1 1
      ruoyi-common/ruoyi-common-mybatis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  74. 33 0
      ruoyi-common/ruoyi-common-mybatis/src/main/resources/common-mybatis.yml
  75. 20 0
      ruoyi-common/ruoyi-common-mybatis/src/main/resources/spy.properties
  76. 13 18
      ruoyi-common/ruoyi-common-pay/pom.xml
  77. 0 2
      ruoyi-common/ruoyi-common-pay/src/main/java/org/ruoyi/common/config/PayInit.java
  78. 12 0
      ruoyi-common/ruoyi-common-satoken/src/main/java/org/ruoyi/common/satoken/utils/LoginHelper.java
  79. 2 1
      ruoyi-common/ruoyi-common-tenant/src/main/java/org/ruoyi/common/tenant/config/TenantConfig.java
  80. 2 1
      ruoyi-common/ruoyi-common-tenant/src/main/java/org/ruoyi/common/tenant/core/TenantEntity.java
  81. 35 0
      ruoyi-common/ruoyi-common-tenant/src/main/java/org/ruoyi/common/tenant/helper/TenantHelper.java
  82. 0 26
      ruoyi-common/ruoyi-common-web/src/main/java/org/ruoyi/common/web/interceptor/PlusWebInvokeTimeInterceptor.java
  83. 7 0
      ruoyi-common/ruoyi-common-wechat/pom.xml
  84. 1 1
      ruoyi-common/ruoyi-common-wechat/src/main/java/org/ruoyi/common/wechat/web/enums/KeyMsgValueType.java
  85. 20 0
      ruoyi-extend/pom.xml
  86. 76 0
      ruoyi-extend/ruoyi-mcp-server/pom.xml
  87. 25 0
      ruoyi-extend/ruoyi-mcp-server/src/main/java/org/ruoyi/mcpserve/RuoyiMcpServeApplication.java
  88. 34 0
      ruoyi-extend/ruoyi-mcp-server/src/main/java/org/ruoyi/mcpserve/service/ToolService.java
  89. 10 0
      ruoyi-extend/ruoyi-mcp-server/src/main/resources/application.yml
  90. 85 0
      ruoyi-modules-api/pom.xml
  91. 76 0
      ruoyi-modules-api/ruoyi-chat-api/pom.xml
  92. 86 0
      ruoyi-modules-api/ruoyi-chat-api/src/main/java/org/ruoyi/domain/ChatAgentManage.java
  93. 9 11
      ruoyi-modules-api/ruoyi-chat-api/src/main/java/org/ruoyi/domain/ChatAppStore.java
  94. 8 21
      ruoyi-modules-api/ruoyi-chat-api/src/main/java/org/ruoyi/domain/ChatGpts.java
  95. 68 0
      ruoyi-modules-api/ruoyi-chat-api/src/main/java/org/ruoyi/domain/ChatMessage.java
  96. 10 12
      ruoyi-modules-api/ruoyi-chat-api/src/main/java/org/ruoyi/domain/ChatModel.java
  97. 9 9
      ruoyi-modules-api/ruoyi-chat-api/src/main/java/org/ruoyi/domain/ChatPackagePlan.java
  98. 7 6
      ruoyi-modules-api/ruoyi-chat-api/src/main/java/org/ruoyi/domain/ChatPayOrder.java
  99. 3 3
      ruoyi-modules-api/ruoyi-chat-api/src/main/java/org/ruoyi/domain/ChatPlugin.java
  100. 14 14
      ruoyi-modules-api/ruoyi-chat-api/src/main/java/org/ruoyi/domain/ChatRobConfig.java

+ 23 - 10
README.md

@@ -63,18 +63,19 @@
   用户名: admin 密码:admin123
 
 ### 源码地址
-[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
+[1]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
 
+[2]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
+
 [3]gitcode
 - 前端服务-用户端:https://gitcode.com/ageerle/ruoyi-web
 - 前端服务-管理端:  https://gitcode.com/ageerle/ruoyi-admin
@@ -83,7 +84,7 @@
 
 ### 配套文档
 - 配套文档: https://doc.pandarobot.chat
-  - 项目部署文档:https://doc.pandarobot.chat/guide/introduction/
+- 项目部署文档:https://doc.pandarobot.chat/guide/introduction/
 
 ### 核心功能
 1. 全套开源系统:提供完整的前端应用、后台管理以及小程序应用,基于MIT协议,开箱即用。
@@ -96,7 +97,16 @@
 
 ### 项目演示
 
-#### mcp支持(需要切换dev分支 下周发布正式版)
+#### mcp支持
+
+### 如何使用
+1. ruoyi-admin\src\main\resources\application.yml中mcp.client.enabled改为true
+2. application.yml中配置openai api-key(用于推理使用那个工具,并构建工具所需参数)
+3. 启动[ruoyi-mcp-server]
+4. [mcp-server.json]中配置fileSystem.command(npx本地安装路径)
+5. 指定fileSystem操作目录(本地必须存在指定的目录)
+6. 配置search1api.env.SEARCH1API_KEY 申请地址:https://www.search1api.com/
+7. 详情教程:https://blog.csdn.net/weixin_42416319/article/details/147385808
 <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);"/>
@@ -215,9 +225,12 @@
 
 ###  开发计划
 
-- 流程编排
+| 主题 | 方向                                | 时间节点   | 
+| --- |-----------------------------------|--------| 
+| 前端简化版 | 与element-plus-x框架合作,推出基于该框架的前端简化版 | 2025.5 | 
+| agent2agent | Agent2Agent协议支持                   | 2025.6 | 
+| 流程编排 | 通过可视化界面和灵活的配置方式,快速构建AI应用          | 2025.7 | 
 
-通过流程编排功能,用户可以将不同的模型按照业务逻辑进行有序连接。这将解决单一模型能力不足的问题,充分发挥多个模型的协同作用,从而更好地满足企业的复杂业务需求。
 
 -  感谢
 

BIN
image/mcp-01.png


+ 45 - 39
pom.xml

@@ -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>
+                <groupId>org.mybatis</groupId>
+                <artifactId>mybatis</artifactId>
+                <version>${mybatis.version}</version>
             </dependency>
 
             <dependency>
-                <groupId>org.mybatis.spring.boot</groupId>
-                <artifactId>mybatis-spring-boot-starter</artifactId>
-                <version>${spring-boot.mybatis}</version>
+                <groupId>com.baomidou</groupId>
+                <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
+                <version>${mybatis-plus.version}</version>
             </dependency>
 
             <dependency>
                 <groupId>com.baomidou</groupId>
-                <artifactId>mybatis-plus-boot-starter</artifactId>
+                <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>
@@ -321,10 +313,21 @@
                 <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>
 
@@ -336,7 +339,7 @@
 
             <dependency>
                 <groupId>org.ruoyi</groupId>
-                <artifactId>ruoyi-demo</artifactId>
+                <artifactId>ruoyi-wechat</artifactId>
                 <version>${revision}</version>
             </dependency>
 
@@ -344,10 +347,13 @@
     </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>

+ 0 - 35
ruoyi-admin/Dockerfile

@@ -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"]
-
-

+ 1 - 31
ruoyi-admin/pom.xml

@@ -42,11 +42,6 @@
             <artifactId>mssql-jdbc</artifactId>
         </dependency>
 
-        <dependency>
-            <groupId>org.ruoyi</groupId>
-            <artifactId>ruoyi-common-doc</artifactId>
-        </dependency>
-
         <dependency>
             <groupId>org.ruoyi</groupId>
             <artifactId>ruoyi-system</artifactId>
@@ -57,39 +52,14 @@
             <artifactId>ruoyi-chat</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-wechat</artifactId>
         </dependency>
 
     </dependencies>

+ 6 - 18
ruoyi-admin/src/main/java/org/ruoyi/controller/AuthController.java

@@ -2,8 +2,6 @@ package org.ruoyi.controller;
 
 import cn.dev33.satoken.annotation.SaIgnore;
 import cn.hutool.core.collection.CollUtil;
-import cn.hutool.json.JSONUtil;
-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;
@@ -16,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 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.ruoyi.system.domain.vo.LoginVo;
-
-import jakarta.servlet.http.HttpServletRequest;
-import lombok.RequiredArgsConstructor;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
@@ -50,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);
-    }
-
     /**
      * 登录方法
      *
@@ -75,6 +63,7 @@ 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();

+ 4 - 2
ruoyi-admin/src/main/java/org/ruoyi/controller/IndexController.java

@@ -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启动成功!";
     }
 
+
+
 }

+ 0 - 3
ruoyi-admin/src/main/resources/application-dev.yml

@@ -61,9 +61,6 @@ spring.data:
     password: 19881212cy
     # 连接超时时间
     timeout: 10S
-    # 是否开启ssl
-    ssl: false
-
 redisson:
   # redis key前缀
   keyPrefix:

+ 19 - 2
ruoyi-admin/src/main/resources/application.yml

@@ -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,20 @@ wechat:
         token:   ''
         aesKey: ''
 
-
+spring:
+  ai:
+    openai:
+      api-key: sk-xx
+      base-url: https://api.pandarobot.chat/
+    mcp:
+      client:
+        enabled: false
+        name: ruoyi-ai-mcp
+        sse:
+          connections:
+            server:
+              url: http://127.0.0.1:8081
+        stdio:
+          servers-configuration: classpath:mcp-server.json
+        request-timeout: 300s
 

+ 22 - 0
ruoyi-admin/src/main/resources/mcp-server.json

@@ -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"
+      }
+    }
+  }
+}

+ 9 - 9
ruoyi-common/pom.xml

@@ -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,16 +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>

+ 1 - 16
ruoyi-common/ruoyi-common-bom/pom.xml

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

+ 14 - 35
ruoyi-common/ruoyi-common-chat/pom.xml

@@ -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>
-        </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>
 
+        <!-- redis模块 -->
         <dependency>
             <groupId>org.ruoyi</groupId>
             <artifactId>ruoyi-common-redis</artifactId>
         </dependency>
+
         <dependency>
-            <groupId>org.ruoyi</groupId>
-            <artifactId>ruoyi-common-satoken</artifactId>
+            <groupId>com.azure</groupId>
+            <artifactId>azure-ai-openai</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>

+ 3 - 0
ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/config/properties/WebSocketProperties.java

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

+ 0 - 73
ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/ConsoleEventSourceListenerV2.java

@@ -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();
-    }
-}

+ 0 - 92
ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/ConsoleEventSourceListenerV3.java

@@ -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();
-    }
-}

+ 0 - 417
ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/PluginTest.java

@@ -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 "西瓜";
-    }
-}

+ 0 - 24
ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/WeatherPlugin.java

@@ -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();
-    }
-}

+ 0 - 13
ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/WeatherReq.java

@@ -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;
-}

+ 0 - 15
ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/WeatherResp.java

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

+ 0 - 223
ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/demo/WebSearchToolsTest.java

@@ -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());
-        });
-    }
-
-}

+ 0 - 75
ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/request/ChatRequest.java

@@ -1,75 +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;
-
-    /**
-     * 1 联网搜索
-     */
-    private int chat_type;
-
-    /**
-     * 应用ID
-     */
-    private String appId;
-//
-
-//
-//    /**
-//     * gpt的默认设置
-//     */
-//    private String systemMessage = "";
-//
-//
-//
-//    private double temperature = 0.2;
-//
-//    /**
-//     * 上下文的条数
-//     */
-//    private Integer contentNumber = 10;
-//
-//    /**
-//     * 是否携带上下文
-//     */
-//    private Boolean usingContext = Boolean.TRUE;
-
-}

+ 0 - 33
ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/request/Dall3Request.java

@@ -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;
-
-}

+ 0 - 2
ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/handler/PlusWebSocketHandler.java

@@ -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;
 

+ 0 - 198
ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/localModels/LocalModelsofitClient.java

@@ -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);
-//            }
-//        });
-//    }
-
-}

+ 0 - 25
ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/localModels/SearchService.java

@@ -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);
-}
-
-

+ 1 - 6
ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/openai/OpenAiStreamClient.java

@@ -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 {
     }
 
 
-
-
-
-
-
     /**
      * 构造
      *

+ 0 - 36
ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/plugin/CmdPlugin.java

@@ -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();
-    }
-}

+ 33 - 13
ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/request/ChatRequest.java

@@ -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 double top_p = 1;
+    private Long userId;
 
-    private double temperature = 0.2;
+    /**
+     * 应用ID
+     */
+    private String appId;
 
     /**
      * 上下文的条数
@@ -52,4 +71,5 @@ public class ChatRequest {
      */
     private Boolean usingContext = Boolean.TRUE;
 
+
 }

+ 0 - 1
ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/sse/ConsoleEventSourceListener.java

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

+ 0 - 1
ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/sse/PluginListener.java

@@ -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;

+ 11 - 29
ruoyi-common/ruoyi-common-core/pom.xml

@@ -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>

+ 59 - 0
ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/constant/RegexConstants.java

@@ -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]$";
+
+}

+ 80 - 0
ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/constant/SystemConstants.java

@@ -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";
+
+}

+ 53 - 0
ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/factory/RegexPatternPoolFactory.java

@@ -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);
+
+}

+ 32 - 0
ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/factory/YmlPropertySourceFactory.java

@@ -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);
+    }
+
+}

+ 60 - 0
ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/ObjectUtils.java

@@ -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;
+    }
+
+}

+ 7 - 2
ruoyi-common/ruoyi-common-core/src/main/java/org/ruoyi/common/core/utils/SpringUtils.java

@@ -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));
+    }
+
 }

+ 17 - 5
ruoyi-common/ruoyi-common-encrypt/pom.xml

@@ -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>

+ 5 - 3
ruoyi-common/ruoyi-common-live/pom.xml

@@ -1,17 +1,19 @@
 <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/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
     <parent>
         <groupId>org.ruoyi</groupId>
         <artifactId>ruoyi-common</artifactId>
         <version>${revision}</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
+
     <packaging>pom</packaging>
-    <modelVersion>4.0.0</modelVersion>
+
     <artifactId>ruoyi-common-live</artifactId>
 
     <description>
-         弹幕监听
+        AI直播
     </description>
 
     <modules>
@@ -31,7 +33,7 @@
 
         <brotli4j.version>1.13.0</brotli4j.version>
         <jackson-databind.version>2.16.0</jackson-databind.version>
-        <hutool-all.version>5.8.24</hutool-all.version>
+        <hutool-all.version>5.8.35</hutool-all.version>
         <netty-all.version>4.1.104.Final</netty-all.version>
         <logback-classic.version>1.4.12</logback-classic.version>
         <lombok.version>1.18.30</lombok.version>

+ 2 - 5
ruoyi-common/ruoyi-common-mail/src/main/java/org/ruoyi/common/mail/config/MailConfig.java

@@ -7,8 +7,7 @@ import org.ruoyi.common.core.service.ConfigService;
 import org.ruoyi.common.mail.utils.MailAccount;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.Scope;
-import org.springframework.scheduling.annotation.Scheduled;
+
 
 /**
  * JavaMail 配置
@@ -22,10 +21,9 @@ import org.springframework.scheduling.annotation.Scheduled;
 public class MailConfig {
 
     private final ConfigService configService;
-    private MailAccount account;  // 缓存MailAccount实例
+    private MailAccount account;
 
     @Bean
-    @Scope("singleton")
     public MailAccount mailAccount() {
         if (account == null) {
             account = new MailAccount();
@@ -34,7 +32,6 @@ public class MailConfig {
         return account;
     }
 
-    @Scheduled(fixedDelay = 10000)  // 每10秒检查一次
     public void updateMailAccount() {
         account.setHost(getKey("host"));
         account.setPort(NumberUtils.toInt(getKey("port"), 465));

+ 1 - 1
ruoyi-common/ruoyi-common-mail/src/main/java/org/ruoyi/common/mail/config/properties/MailProperties.java → ruoyi-common/ruoyi-common-mail/src/main/java/org/ruoyi/common/mail/properties/MailProperties.java

@@ -1,4 +1,4 @@
-package org.ruoyi.common.mail.config.properties;
+package org.ruoyi.common.mail.properties;
 
 import lombok.Data;
 

+ 7 - 3
ruoyi-common/ruoyi-common-mybatis/pom.xml

@@ -6,7 +6,6 @@
         <groupId>org.ruoyi</groupId>
         <artifactId>ruoyi-common</artifactId>
         <version>${revision}</version>
-        <relativePath>../pom.xml</relativePath>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
@@ -30,12 +29,17 @@
         <!-- dynamic-datasource 多数据源-->
         <dependency>
             <groupId>com.baomidou</groupId>
-            <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
+            <artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
         </dependency>
 
         <dependency>
             <groupId>com.baomidou</groupId>
-            <artifactId>mybatis-plus-boot-starter</artifactId>
+            <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-jsqlparser</artifactId>
         </dependency>
 
         <!-- sql性能分析插件 -->

+ 0 - 45
ruoyi-common/ruoyi-common-mybatis/src/main/java/com/baomidou/dynamic/datasource/processor/jakarta/DsJakartaHeaderProcessor.java

@@ -1,45 +0,0 @@
-/*
- * Copyright © 2018 organization baomidou
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.baomidou.dynamic.datasource.processor.jakarta;
-
-import com.baomidou.dynamic.datasource.processor.DsProcessor;
-import jakarta.servlet.http.HttpServletRequest;
-import org.aopalliance.intercept.MethodInvocation;
-import org.springframework.web.context.request.RequestContextHolder;
-import org.springframework.web.context.request.ServletRequestAttributes;
-
-/**
- * @author TaoYu
- * @since 3.6.0
- */
-public class DsJakartaHeaderProcessor extends DsProcessor {
-
-    /**
-     * header prefix
-     */
-    private static final String HEADER_PREFIX = "#header";
-
-    @Override
-    public boolean matches(String key) {
-        return key.startsWith(HEADER_PREFIX);
-    }
-
-    @Override
-    public String doDetermineDatasource(MethodInvocation invocation, String key) {
-        HttpServletRequest request = (HttpServletRequest) ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
-        return request.getHeader(key.substring(8));
-    }
-}

+ 0 - 46
ruoyi-common/ruoyi-common-mybatis/src/main/java/com/baomidou/dynamic/datasource/processor/jakarta/DsJakartaSessionProcessor.java

@@ -1,46 +0,0 @@
-/*
- * Copyright © 2018 organization baomidou
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.baomidou.dynamic.datasource.processor.jakarta;
-
-import com.baomidou.dynamic.datasource.processor.DsProcessor;
-import jakarta.servlet.http.HttpServletRequest;
-import org.aopalliance.intercept.MethodInvocation;
-import org.springframework.web.context.request.RequestContextHolder;
-import org.springframework.web.context.request.ServletRequestAttributes;
-
-
-/**
- * @author TaoYu
- * @since 3.6.0
- */
-public class DsJakartaSessionProcessor extends DsProcessor {
-
-    /**
-     * session开头
-     */
-    private static final String SESSION_PREFIX = "#session";
-
-    @Override
-    public boolean matches(String key) {
-        return key.startsWith(SESSION_PREFIX);
-    }
-
-    @Override
-    public String doDetermineDatasource(MethodInvocation invocation, String key) {
-        HttpServletRequest request = (HttpServletRequest) ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
-        return request.getSession().getAttribute(key.substring(9)).toString();
-    }
-}

+ 40 - 0
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/annotation/DataColumn.java

@@ -0,0 +1,40 @@
+package org.ruoyi.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 数据权限注解,用于标记数据权限的占位符关键字和替换值
+ * <p>
+ * 一个注解只能对应一个模板
+ * </p>
+ *
+ * @author Lion Li
+ * @version 3.5.0
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface DataColumn {
+
+    /**
+     * 数据权限模板的占位符关键字,默认为 "deptName"
+     *
+     * @return 占位符关键字数组
+     */
+    String[] key() default "deptName";
+
+    /**
+     * 数据权限模板的占位符替换值,默认为 "dept_id"
+     *
+     * @return 占位符替换值数组
+     */
+    String[] value() default "dept_id";
+
+    /**
+     * 权限标识符 用于通过菜单权限标识符来获取数据权限
+     * 拥有此标识符的角色 将不会拼接此角色的数据过滤sql
+     *
+     * @return 权限标识符
+     */
+    String permission() default "";
+}

+ 30 - 0
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/annotation/DataPermission.java

@@ -0,0 +1,30 @@
+package org.ruoyi.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 数据权限组注解,用于标记数据权限配置数组
+ *
+ * @author Lion Li
+ * @version 3.5.0
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface DataPermission {
+
+    /**
+     * 数据权限配置数组,用于指定数据权限的占位符关键字和替换值
+     *
+     * @return 数据权限配置数组
+     */
+    DataColumn[] value();
+
+    /**
+     * 权限拼接标识符(用于指定连接语句的sql符号)
+     * 如不填 默认 select 用 OR 其他语句用 AND
+     * 内容 OR 或者 AND
+     */
+    String joinStr() default "";
+
+}

+ 50 - 0
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/aspect/DataPermissionAspect.java

@@ -0,0 +1,50 @@
+package org.ruoyi.aspect;
+
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.annotation.AfterReturning;
+import org.aspectj.lang.annotation.AfterThrowing;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Before;
+import org.ruoyi.annotation.DataPermission;
+import org.ruoyi.helper.DataPermissionHelper;
+
+/**
+ * 数据权限处理
+ *
+ * @author Lion Li
+ */
+@Slf4j
+@Aspect
+public class DataPermissionAspect {
+
+    /**
+     * 处理请求前执行
+     */
+    @Before(value = "@annotation(dataPermission)")
+    public void doBefore(JoinPoint joinPoint, DataPermission dataPermission) {
+        DataPermissionHelper.setPermission(dataPermission);
+    }
+
+    /**
+     * 处理完请求后执行
+     *
+     * @param joinPoint 切点
+     */
+    @AfterReturning(pointcut = "@annotation(dataPermission)")
+    public void doAfterReturning(JoinPoint joinPoint, DataPermission dataPermission) {
+        DataPermissionHelper.removePermission();
+    }
+
+    /**
+     * 拦截异常操作
+     *
+     * @param joinPoint 切点
+     * @param e         异常
+     */
+    @AfterThrowing(value = "@annotation(dataPermission)", throwing = "e")
+    public void doAfterThrowing(JoinPoint joinPoint, DataPermission dataPermission, Exception e) {
+        DataPermissionHelper.removePermission();
+    }
+
+}

+ 0 - 28
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/annotation/DataColumn.java

@@ -1,28 +0,0 @@
-package org.ruoyi.common.mybatis.annotation;
-
-import java.lang.annotation.*;
-
-/**
- * 数据权限
- *
- * 一个注解只能对应一个模板
- *
- * @author Lion Li
- * @version 3.5.0
- */
-@Target(ElementType.METHOD)
-@Retention(RetentionPolicy.RUNTIME)
-@Documented
-public @interface DataColumn {
-
-    /**
-     * 占位符关键字
-     */
-    String[] key() default "deptName";
-
-    /**
-     * 占位符替换值
-     */
-    String[] value() default "dept_id";
-
-}

+ 0 - 18
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/annotation/DataPermission.java

@@ -1,18 +0,0 @@
-package org.ruoyi.common.mybatis.annotation;
-
-import java.lang.annotation.*;
-
-/**
- * 数据权限组
- *
- * @author Lion Li
- * @version 3.5.0
- */
-@Target({ElementType.METHOD, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-@Documented
-public @interface DataPermission {
-
-    DataColumn[] value();
-
-}

+ 0 - 198
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/core/mapper/BaseMapperPlus.java

@@ -1,198 +0,0 @@
-package org.ruoyi.common.mybatis.core.mapper;
-
-import cn.hutool.core.collection.CollUtil;
-import cn.hutool.core.util.ObjectUtil;
-import com.baomidou.mybatisplus.core.conditions.Wrapper;
-import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
-import com.baomidou.mybatisplus.core.mapper.BaseMapper;
-import com.baomidou.mybatisplus.core.metadata.IPage;
-import com.baomidou.mybatisplus.core.toolkit.ReflectionKit;
-import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
-import com.baomidou.mybatisplus.extension.toolkit.Db;
-import org.apache.ibatis.logging.Log;
-import org.apache.ibatis.logging.LogFactory;
-import org.ruoyi.common.core.utils.MapstructUtils;
-
-import java.io.Serializable;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.function.Function;
-import java.util.stream.Collectors;
-
-/**
- * 自定义 Mapper 接口, 实现 自定义扩展
- *
- * @param <T> table 泛型
- * @param <V> vo 泛型
- * @author Lion Li
- * @since 2021-05-13
- */
-@SuppressWarnings("unchecked")
-public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
-
-    Log log = LogFactory.getLog(BaseMapperPlus.class);
-
-    default Class<V> currentVoClass() {
-        return (Class<V>) ReflectionKit.getSuperClassGenericType(this.getClass(), BaseMapperPlus.class, 1);
-    }
-
-    default Class<T> currentModelClass() {
-        return (Class<T>) ReflectionKit.getSuperClassGenericType(this.getClass(), BaseMapperPlus.class, 0);
-    }
-
-    default List<T> selectList() {
-        return this.selectList(new QueryWrapper<>());
-    }
-
-    /**
-     * 批量插入
-     */
-    default boolean insertBatch(Collection<T> entityList) {
-        return Db.saveBatch(entityList);
-    }
-
-    /**
-     * 批量更新
-     */
-    default boolean updateBatchById(Collection<T> entityList) {
-        return Db.updateBatchById(entityList);
-    }
-
-    /**
-     * 批量插入或更新
-     */
-    default boolean insertOrUpdateBatch(Collection<T> entityList) {
-        return Db.saveOrUpdateBatch(entityList);
-    }
-
-    /**
-     * 批量插入(包含限制条数)
-     */
-    default boolean insertBatch(Collection<T> entityList, int batchSize) {
-        return Db.saveBatch(entityList, batchSize);
-    }
-
-    /**
-     * 批量更新(包含限制条数)
-     */
-    default boolean updateBatchById(Collection<T> entityList, int batchSize) {
-        return Db.updateBatchById(entityList, batchSize);
-    }
-
-    /**
-     * 批量插入或更新(包含限制条数)
-     */
-    default boolean insertOrUpdateBatch(Collection<T> entityList, int batchSize) {
-        return Db.saveOrUpdateBatch(entityList, batchSize);
-    }
-
-    /**
-     * 插入或更新(包含限制条数)
-     */
-    default boolean insertOrUpdate(T entity) {
-        return Db.saveOrUpdate(entity);
-    }
-
-    default V selectVoById(Serializable id) {
-        return selectVoById(id, this.currentVoClass());
-    }
-
-    /**
-     * 根据 ID 查询
-     */
-    default <C> C selectVoById(Serializable id, Class<C> voClass) {
-        T obj = this.selectById(id);
-        if (ObjectUtil.isNull(obj)) {
-            return null;
-        }
-        return MapstructUtils.convert(obj, voClass);
-    }
-
-    default List<V> selectVoBatchIds(Collection<? extends Serializable> idList) {
-        return selectVoBatchIds(idList, this.currentVoClass());
-    }
-
-    /**
-     * 查询(根据ID 批量查询)
-     */
-    default <C> List<C> selectVoBatchIds(Collection<? extends Serializable> idList, Class<C> voClass) {
-        List<T> list = this.selectBatchIds(idList);
-        if (CollUtil.isEmpty(list)) {
-            return CollUtil.newArrayList();
-        }
-        return MapstructUtils.convert(list, voClass);
-    }
-
-    default List<V> selectVoByMap(Map<String, Object> map) {
-        return selectVoByMap(map, this.currentVoClass());
-    }
-
-    /**
-     * 查询(根据 columnMap 条件)
-     */
-    default <C> List<C> selectVoByMap(Map<String, Object> map, Class<C> voClass) {
-        List<T> list = this.selectByMap(map);
-        if (CollUtil.isEmpty(list)) {
-            return CollUtil.newArrayList();
-        }
-        return MapstructUtils.convert(list, voClass);
-    }
-
-    default V selectVoOne(Wrapper<T> wrapper) {
-        return selectVoOne(wrapper, this.currentVoClass());
-    }
-
-    /**
-     * 根据 entity 条件,查询一条记录
-     */
-    default <C> C selectVoOne(Wrapper<T> wrapper, Class<C> voClass) {
-        T obj = this.selectOne(wrapper);
-        if (ObjectUtil.isNull(obj)) {
-            return null;
-        }
-        return MapstructUtils.convert(obj, voClass);
-    }
-
-    default List<V> selectVoList() {
-        return selectVoList(new QueryWrapper<>(), this.currentVoClass());
-    }
-
-    default List<V> selectVoList(Wrapper<T> wrapper) {
-        return selectVoList(wrapper, this.currentVoClass());
-    }
-
-    /**
-     * 根据 entity 条件,查询全部记录
-     */
-    default <C> List<C> selectVoList(Wrapper<T> wrapper, Class<C> voClass) {
-        List<T> list = this.selectList(wrapper);
-        if (CollUtil.isEmpty(list)) {
-            return CollUtil.newArrayList();
-        }
-        return MapstructUtils.convert(list, voClass);
-    }
-
-    default <P extends IPage<V>> P selectVoPage(IPage<T> page, Wrapper<T> wrapper) {
-        return selectVoPage(page, wrapper, this.currentVoClass());
-    }
-
-    /**
-     * 分页查询VO
-     */
-    default <C, P extends IPage<C>> P selectVoPage(IPage<T> page, Wrapper<T> wrapper, Class<C> voClass) {
-        IPage<T> pageData = this.selectPage(page, wrapper);
-        IPage<C> voPage = new Page<>(pageData.getCurrent(), pageData.getSize(), pageData.getTotal());
-        if (CollUtil.isEmpty(pageData.getRecords())) {
-            return (P) voPage;
-        }
-        voPage.setRecords(MapstructUtils.convert(pageData.getRecords(), voClass));
-        return (P) voPage;
-    }
-
-    default <C> List<C> selectObjs(Wrapper<T> wrapper, Function<? super Object, C> mapper) {
-        return this.selectObjs(wrapper).stream().filter(Objects::nonNull).map(mapper).collect(Collectors.toList());
-    }
-
-}

+ 0 - 73
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/enums/DataScopeType.java

@@ -1,73 +0,0 @@
-package org.ruoyi.common.mybatis.enums;
-
-import lombok.AllArgsConstructor;
-import lombok.Getter;
-import org.ruoyi.common.core.utils.StringUtils;
-import org.ruoyi.common.mybatis.helper.DataPermissionHelper;
-
-/**
- * 数据权限类型
- * <p>
- * 语法支持 spel 模板表达式
- * <p>
- * 内置数据 user 当前用户 内容参考 LoginUser
- * 如需扩展数据 可使用 {@link DataPermissionHelper} 操作
- * 内置服务 sdss 系统数据权限服务 内容参考 SysDataScopeService
- * 如需扩展更多自定义服务 可以参考 sdss 自行编写
- *
- * @author Lion Li
- * @version 3.5.0
- */
-@Getter
-@AllArgsConstructor
-public enum DataScopeType {
-
-    /**
-     * 全部数据权限
-     */
-    ALL("1", "", ""),
-
-    /**
-     * 自定数据权限
-     */
-    CUSTOM("2", " #{#deptName} IN ( #{@sdss.getRoleCustom( #user.roleId )} ) ", ""),
-
-    /**
-     * 部门数据权限
-     */
-    DEPT("3", " #{#deptName} = #{#user.deptId} ", ""),
-
-    /**
-     * 部门及以下数据权限
-     */
-    DEPT_AND_CHILD("4", " #{#deptName} IN ( #{@sdss.getDeptAndChild( #user.deptId )} )", ""),
-
-    /**
-     * 仅本人数据权限
-     */
-    SELF("5", " #{#userName} = #{#user.userId} ", " 1 = 0 ");
-
-    private final String code;
-
-    /**
-     * 语法 采用 spel 模板表达式
-     */
-    private final String sqlTemplate;
-
-    /**
-     * 不满足 sqlTemplate 则填充
-     */
-    private final String elseSql;
-
-    public static DataScopeType findCode(String code) {
-        if (StringUtils.isBlank(code)) {
-            return null;
-        }
-        for (DataScopeType type : values()) {
-            if (type.getCode().equals(code)) {
-                return type;
-            }
-        }
-        return null;
-    }
-}

+ 0 - 81
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/handler/InjectionMetaObjectHandler.java

@@ -1,81 +0,0 @@
-package org.ruoyi.common.mybatis.handler;
-
-import cn.hutool.core.util.ObjectUtil;
-import cn.hutool.http.HttpStatus;
-import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
-import lombok.extern.slf4j.Slf4j;
-import org.apache.ibatis.reflection.MetaObject;
-import org.ruoyi.common.core.domain.model.LoginUser;
-import org.ruoyi.common.core.exception.ServiceException;
-import org.ruoyi.common.mybatis.core.domain.BaseEntity;
-import org.ruoyi.common.satoken.utils.LoginHelper;
-
-import java.util.Date;
-
-/**
- * MP注入处理器
- *
- * @author Lion Li
- * @date 2021/4/25
- */
-@Slf4j
-public class InjectionMetaObjectHandler implements MetaObjectHandler {
-
-    @Override
-    public void insertFill(MetaObject metaObject) {
-        try {
-            if (ObjectUtil.isNotNull(metaObject) && metaObject.getOriginalObject() instanceof BaseEntity baseEntity) {
-                Date current = ObjectUtil.isNotNull(baseEntity.getCreateTime())
-                    ? baseEntity.getCreateTime() : new Date();
-                baseEntity.setCreateTime(current);
-                baseEntity.setUpdateTime(current);
-                LoginUser loginUser = getLoginUser();
-                if (ObjectUtil.isNotNull(loginUser)) {
-                    Long userId = ObjectUtil.isNotNull(baseEntity.getCreateBy())
-                        ? baseEntity.getCreateBy() : loginUser.getUserId();
-                    // 当前已登录 且 创建人为空 则填充
-                    baseEntity.setCreateBy(userId);
-                    // 当前已登录 且 更新人为空 则填充
-                    baseEntity.setUpdateBy(userId);
-                    baseEntity.setCreateDept(ObjectUtil.isNotNull(baseEntity.getCreateDept())
-                        ? baseEntity.getCreateDept() : loginUser.getDeptId());
-                }
-            }
-        } catch (Exception e) {
-            throw new ServiceException("自动注入异常 => " + e.getMessage(), HttpStatus.HTTP_UNAUTHORIZED);
-        }
-    }
-
-    @Override
-    public void updateFill(MetaObject metaObject) {
-        try {
-            if (ObjectUtil.isNotNull(metaObject) && metaObject.getOriginalObject() instanceof BaseEntity baseEntity) {
-                Date current = new Date();
-                // 更新时间填充(不管为不为空)
-                baseEntity.setUpdateTime(current);
-                LoginUser loginUser = getLoginUser();
-                // 当前已登录 更新人填充(不管为不为空)
-                if (ObjectUtil.isNotNull(loginUser)) {
-                    baseEntity.setUpdateBy(loginUser.getUserId());
-                }
-            }
-        } catch (Exception e) {
-            throw new ServiceException("自动注入异常 => " + e.getMessage(), HttpStatus.HTTP_UNAUTHORIZED);
-        }
-    }
-
-    /**
-     * 获取登录用户名
-     */
-    private LoginUser getLoginUser() {
-        LoginUser loginUser;
-        try {
-            loginUser = LoginHelper.getLoginUser();
-        } catch (Exception e) {
-            log.warn("自动注入警告 => 用户未登录");
-            return null;
-        }
-        return loginUser;
-    }
-
-}

+ 0 - 198
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/handler/PlusDataPermissionHandler.java

@@ -1,198 +0,0 @@
-package org.ruoyi.common.mybatis.handler;
-
-import cn.hutool.core.annotation.AnnotationUtil;
-import cn.hutool.core.collection.CollUtil;
-import cn.hutool.core.collection.ConcurrentHashSet;
-import cn.hutool.core.util.ArrayUtil;
-import cn.hutool.core.util.ClassUtil;
-import cn.hutool.core.util.ObjectUtil;
-import lombok.extern.slf4j.Slf4j;
-import net.sf.jsqlparser.JSQLParserException;
-import net.sf.jsqlparser.expression.Expression;
-import net.sf.jsqlparser.expression.Parenthesis;
-import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
-import net.sf.jsqlparser.parser.CCJSqlParserUtil;
-import org.ruoyi.common.core.domain.dto.RoleDTO;
-import org.ruoyi.common.core.domain.model.LoginUser;
-import org.ruoyi.common.core.exception.ServiceException;
-import org.ruoyi.common.core.utils.SpringUtils;
-import org.ruoyi.common.core.utils.StreamUtils;
-import org.ruoyi.common.core.utils.StringUtils;
-import org.ruoyi.common.mybatis.annotation.DataColumn;
-import org.ruoyi.common.mybatis.annotation.DataPermission;
-import org.ruoyi.common.mybatis.enums.DataScopeType;
-import org.ruoyi.common.mybatis.helper.DataPermissionHelper;
-import org.ruoyi.common.satoken.utils.LoginHelper;
-import org.springframework.context.expression.BeanFactoryResolver;
-import org.springframework.expression.BeanResolver;
-import org.springframework.expression.ExpressionParser;
-import org.springframework.expression.ParserContext;
-import org.springframework.expression.common.TemplateParserContext;
-import org.springframework.expression.spel.standard.SpelExpressionParser;
-import org.springframework.expression.spel.support.StandardEvaluationContext;
-
-import java.lang.reflect.Method;
-import java.util.*;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.function.Function;
-
-/**
- * 数据权限过滤
- *
- * @author Lion Li
- * @version 3.5.0
- */
-@Slf4j
-public class PlusDataPermissionHandler {
-
-    /**
-     * 方法或类(名称) 与 注解的映射关系缓存
-     */
-    private final Map<String, DataPermission> dataPermissionCacheMap = new ConcurrentHashMap<>();
-
-    /**
-     * 无效注解方法缓存用于快速返回
-     */
-    private final Set<String> invalidCacheSet = new ConcurrentHashSet<>();
-
-    /**
-     * spel 解析器
-     */
-    private final ExpressionParser parser = new SpelExpressionParser();
-    private final ParserContext parserContext = new TemplateParserContext();
-    /**
-     * bean解析器 用于处理 spel 表达式中对 bean 的调用
-     */
-    private final BeanResolver beanResolver = new BeanFactoryResolver(SpringUtils.getBeanFactory());
-
-
-    public Expression getSqlSegment(Expression where, String mappedStatementId, boolean isSelect) {
-        DataColumn[] dataColumns = findAnnotation(mappedStatementId);
-        if (ArrayUtil.isEmpty(dataColumns)) {
-            invalidCacheSet.add(mappedStatementId);
-            return where;
-        }
-        LoginUser currentUser = DataPermissionHelper.getVariable("user");
-        if (ObjectUtil.isNull(currentUser)) {
-            currentUser = LoginHelper.getLoginUser();
-            DataPermissionHelper.setVariable("user", currentUser);
-        }
-        // 如果是超级管理员或租户管理员,则不过滤数据
-        if (LoginHelper.isSuperAdmin() || LoginHelper.isTenantAdmin()) {
-            return where;
-        }
-        String dataFilterSql = buildDataFilter(dataColumns, isSelect);
-        if (StringUtils.isBlank(dataFilterSql)) {
-            return where;
-        }
-        try {
-            Expression expression = CCJSqlParserUtil.parseExpression(dataFilterSql);
-            // 数据权限使用单独的括号 防止与其他条件冲突
-            Parenthesis parenthesis = new Parenthesis(expression);
-            if (ObjectUtil.isNotNull(where)) {
-                return new AndExpression(where, parenthesis);
-            } else {
-                return parenthesis;
-            }
-        } catch (JSQLParserException e) {
-            throw new ServiceException("数据权限解析异常 => " + e.getMessage());
-        }
-    }
-
-    /**
-     * 构造数据过滤sql
-     */
-    private String buildDataFilter(DataColumn[] dataColumns, boolean isSelect) {
-        // 更新或删除需满足所有条件
-        String joinStr = isSelect ? " OR " : " AND ";
-        LoginUser user = DataPermissionHelper.getVariable("user");
-        StandardEvaluationContext context = new StandardEvaluationContext();
-        context.setBeanResolver(beanResolver);
-        DataPermissionHelper.getContext().forEach(context::setVariable);
-        Set<String> conditions = new HashSet<>();
-        for (RoleDTO role : user.getRoles()) {
-            user.setRoleId(role.getRoleId());
-            // 获取角色权限泛型
-            DataScopeType type = DataScopeType.findCode(role.getDataScope());
-            if (ObjectUtil.isNull(type)) {
-                throw new ServiceException("角色数据范围异常 => " + role.getDataScope());
-            }
-            // 全部数据权限直接返回
-            if (type == DataScopeType.ALL) {
-                return "";
-            }
-            boolean isSuccess = false;
-            for (DataColumn dataColumn : dataColumns) {
-                if (dataColumn.key().length != dataColumn.value().length) {
-                    throw new ServiceException("角色数据范围异常 => key与value长度不匹配");
-                }
-                // 不包含 key 变量 则不处理
-                if (!StringUtils.containsAny(type.getSqlTemplate(),
-                    Arrays.stream(dataColumn.key()).map(key -> "#" + key).toArray(String[]::new)
-                )) {
-                    continue;
-                }
-                // 设置注解变量 key 为表达式变量 value 为变量值
-                for (int i = 0; i < dataColumn.key().length; i++) {
-                    context.setVariable(dataColumn.key()[i], dataColumn.value()[i]);
-                }
-
-                // 解析sql模板并填充
-                String sql = parser.parseExpression(type.getSqlTemplate(), parserContext).getValue(context, String.class);
-                conditions.add(joinStr + sql);
-                isSuccess = true;
-            }
-            // 未处理成功则填充兜底方案
-            if (!isSuccess && StringUtils.isNotBlank(type.getElseSql())) {
-                conditions.add(joinStr + type.getElseSql());
-            }
-        }
-
-        if (CollUtil.isNotEmpty(conditions)) {
-            String sql = StreamUtils.join(conditions, Function.identity(), "");
-            return sql.substring(joinStr.length());
-        }
-        return "";
-    }
-
-    private DataColumn[] findAnnotation(String mappedStatementId) {
-        StringBuilder sb = new StringBuilder(mappedStatementId);
-        int index = sb.lastIndexOf(".");
-        String clazzName = sb.substring(0, index);
-        String methodName = sb.substring(index + 1, sb.length());
-        Class<?> clazz = ClassUtil.loadClass(clazzName);
-        List<Method> methods = Arrays.stream(ClassUtil.getDeclaredMethods(clazz))
-            .filter(method -> method.getName().equals(methodName)).toList();
-        DataPermission dataPermission;
-        // 获取方法注解
-        for (Method method : methods) {
-            dataPermission = dataPermissionCacheMap.get(mappedStatementId);
-            if (ObjectUtil.isNotNull(dataPermission)) {
-                return dataPermission.value();
-            }
-            if (AnnotationUtil.hasAnnotation(method, DataPermission.class)) {
-                dataPermission = AnnotationUtil.getAnnotation(method, DataPermission.class);
-                dataPermissionCacheMap.put(mappedStatementId, dataPermission);
-                return dataPermission.value();
-            }
-        }
-        dataPermission = dataPermissionCacheMap.get(clazz.getName());
-        if (ObjectUtil.isNotNull(dataPermission)) {
-            return dataPermission.value();
-        }
-        // 获取类注解
-        if (AnnotationUtil.hasAnnotation(clazz, DataPermission.class)) {
-            dataPermission = AnnotationUtil.getAnnotation(clazz, DataPermission.class);
-            dataPermissionCacheMap.put(clazz.getName(), dataPermission);
-            return dataPermission.value();
-        }
-        return null;
-    }
-
-    /**
-     * 是否为无效方法 无数据权限
-     */
-    public boolean isInvalid(String mappedStatementId) {
-        return invalidCacheSet.contains(mappedStatementId);
-    }
-}

+ 0 - 93
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/helper/DataPermissionHelper.java

@@ -1,93 +0,0 @@
-package org.ruoyi.common.mybatis.helper;
-
-import cn.dev33.satoken.context.SaHolder;
-import cn.dev33.satoken.context.model.SaStorage;
-import cn.hutool.core.util.ObjectUtil;
-import com.baomidou.mybatisplus.core.plugins.IgnoreStrategy;
-import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
-import lombok.AccessLevel;
-import lombok.NoArgsConstructor;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.function.Supplier;
-
-/**
- * 数据权限助手
- *
- * @author Lion Li
- * @version 3.5.0
- */
-@NoArgsConstructor(access = AccessLevel.PRIVATE)
-@SuppressWarnings("unchecked cast")
-public class DataPermissionHelper {
-
-    private static final String DATA_PERMISSION_KEY = "data:permission";
-
-    public static <T> T getVariable(String key) {
-        Map<String, Object> context = getContext();
-        return (T) context.get(key);
-    }
-
-
-    public static void setVariable(String key, Object value) {
-        Map<String, Object> context = getContext();
-        context.put(key, value);
-    }
-
-    public static Map<String, Object> getContext() {
-        SaStorage saStorage = SaHolder.getStorage();
-        Object attribute = saStorage.get(DATA_PERMISSION_KEY);
-        if (ObjectUtil.isNull(attribute)) {
-            saStorage.set(DATA_PERMISSION_KEY, new HashMap<>());
-            attribute = saStorage.get(DATA_PERMISSION_KEY);
-        }
-        if (attribute instanceof Map map) {
-            return map;
-        }
-        throw new NullPointerException("data permission context type exception");
-    }
-
-    /**
-     * 开启忽略数据权限(开启后需手动调用 {@link #disableIgnore()} 关闭)
-     */
-    public static void enableIgnore() {
-        InterceptorIgnoreHelper.handle(IgnoreStrategy.builder().dataPermission(true).build());
-    }
-
-    /**
-     * 关闭忽略数据权限
-     */
-    public static void disableIgnore() {
-        InterceptorIgnoreHelper.clearIgnoreStrategy();
-    }
-
-    /**
-     * 在忽略数据权限中执行
-     *
-     * @param handle 处理执行方法
-     */
-    public static void ignore(Runnable handle) {
-        enableIgnore();
-        try {
-            handle.run();
-        } finally {
-            disableIgnore();
-        }
-    }
-
-    /**
-     * 在忽略数据权限中执行
-     *
-     * @param handle 处理执行方法
-     */
-    public static <T> T ignore(Supplier<T> handle) {
-        enableIgnore();
-        try {
-            return handle.get();
-        } finally {
-            disableIgnore();
-        }
-    }
-
-}

+ 0 - 107
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/interceptor/PlusDataPermissionInterceptor.java

@@ -1,107 +0,0 @@
-package org.ruoyi.common.mybatis.interceptor;
-
-import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
-import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
-import com.baomidou.mybatisplus.extension.parser.JsqlParserSupport;
-import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
-import net.sf.jsqlparser.expression.Expression;
-import net.sf.jsqlparser.statement.delete.Delete;
-import net.sf.jsqlparser.statement.select.PlainSelect;
-import net.sf.jsqlparser.statement.select.Select;
-import net.sf.jsqlparser.statement.select.SelectBody;
-import net.sf.jsqlparser.statement.select.SetOperationList;
-import net.sf.jsqlparser.statement.update.Update;
-import org.apache.ibatis.executor.Executor;
-import org.apache.ibatis.executor.statement.StatementHandler;
-import org.apache.ibatis.mapping.BoundSql;
-import org.apache.ibatis.mapping.MappedStatement;
-import org.apache.ibatis.mapping.SqlCommandType;
-import org.apache.ibatis.session.ResultHandler;
-import org.apache.ibatis.session.RowBounds;
-import org.ruoyi.common.mybatis.handler.PlusDataPermissionHandler;
-
-import java.sql.Connection;
-import java.sql.SQLException;
-import java.util.List;
-
-/**
- * 数据权限拦截器
- *
- * @author Lion Li
- * @version 3.5.0
- */
-public class PlusDataPermissionInterceptor extends JsqlParserSupport implements InnerInterceptor {
-
-    private final PlusDataPermissionHandler dataPermissionHandler = new PlusDataPermissionHandler();
-
-    @Override
-    public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
-        // 检查忽略注解
-        if (InterceptorIgnoreHelper.willIgnoreDataPermission(ms.getId())) {
-            return;
-        }
-        // 检查是否无效 无数据权限注解
-        if (dataPermissionHandler.isInvalid(ms.getId())) {
-            return;
-        }
-        // 解析 sql 分配对应方法
-        PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql);
-        mpBs.sql(parserSingle(mpBs.sql(), ms.getId()));
-    }
-
-    @Override
-    public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) {
-        PluginUtils.MPStatementHandler mpSh = PluginUtils.mpStatementHandler(sh);
-        MappedStatement ms = mpSh.mappedStatement();
-        SqlCommandType sct = ms.getSqlCommandType();
-        if (sct == SqlCommandType.UPDATE || sct == SqlCommandType.DELETE) {
-            if (InterceptorIgnoreHelper.willIgnoreDataPermission(ms.getId())) {
-                return;
-            }
-            PluginUtils.MPBoundSql mpBs = mpSh.mPBoundSql();
-            mpBs.sql(parserMulti(mpBs.sql(), ms.getId()));
-        }
-    }
-
-    @Override
-    protected void processSelect(Select select, int index, String sql, Object obj) {
-        SelectBody selectBody = select.getSelectBody();
-        if (selectBody instanceof PlainSelect plainSelect) {
-            this.setWhere(plainSelect, (String) obj);
-        } else if (selectBody instanceof SetOperationList setOperationList) {
-            List<SelectBody> selectBodyList = setOperationList.getSelects();
-            selectBodyList.forEach(s -> this.setWhere((PlainSelect) s, (String) obj));
-        }
-    }
-
-    @Override
-    protected void processUpdate(Update update, int index, String sql, Object obj) {
-        Expression sqlSegment = dataPermissionHandler.getSqlSegment(update.getWhere(), (String) obj, false);
-        if (null != sqlSegment) {
-            update.setWhere(sqlSegment);
-        }
-    }
-
-    @Override
-    protected void processDelete(Delete delete, int index, String sql, Object obj) {
-        Expression sqlSegment = dataPermissionHandler.getSqlSegment(delete.getWhere(), (String) obj, false);
-        if (null != sqlSegment) {
-            delete.setWhere(sqlSegment);
-        }
-    }
-
-    /**
-     * 设置 where 条件
-     *
-     * @param plainSelect       查询对象
-     * @param mappedStatementId 执行方法id
-     */
-    protected void setWhere(PlainSelect plainSelect, String mappedStatementId) {
-        Expression sqlSegment = dataPermissionHandler.getSqlSegment(plainSelect.getWhere(), mappedStatementId, true);
-        if (null != sqlSegment) {
-            plainSelect.setWhere(sqlSegment);
-        }
-    }
-
-}
-

+ 0 - 45
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/jakarta/DsJakartaHeaderProcessor.java

@@ -1,45 +0,0 @@
-/*
- * Copyright © 2018 organization baomidou
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.ruoyi.common.mybatis.jakarta;
-
-import com.baomidou.dynamic.datasource.processor.DsProcessor;
-import jakarta.servlet.http.HttpServletRequest;
-import org.aopalliance.intercept.MethodInvocation;
-import org.springframework.web.context.request.RequestContextHolder;
-import org.springframework.web.context.request.ServletRequestAttributes;
-
-/**
- * @author TaoYu
- * @since 3.6.0
- */
-public class DsJakartaHeaderProcessor extends DsProcessor {
-
-    /**
-     * header prefix
-     */
-    private static final String HEADER_PREFIX = "#header";
-
-    @Override
-    public boolean matches(String key) {
-        return key.startsWith(HEADER_PREFIX);
-    }
-
-    @Override
-    public String doDetermineDatasource(MethodInvocation invocation, String key) {
-        HttpServletRequest request = (HttpServletRequest) ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
-        return request.getHeader(key.substring(8));
-    }
-}

+ 0 - 46
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/jakarta/DsJakartaSessionProcessor.java

@@ -1,46 +0,0 @@
-/*
- * Copyright © 2018 organization baomidou
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.ruoyi.common.mybatis.jakarta;
-
-import com.baomidou.dynamic.datasource.processor.DsProcessor;
-import jakarta.servlet.http.HttpServletRequest;
-import org.aopalliance.intercept.MethodInvocation;
-import org.springframework.web.context.request.RequestContextHolder;
-import org.springframework.web.context.request.ServletRequestAttributes;
-
-
-/**
- * @author TaoYu
- * @since 3.6.0
- */
-public class DsJakartaSessionProcessor extends DsProcessor {
-
-    /**
-     * session开头
-     */
-    private static final String SESSION_PREFIX = "#session";
-
-    @Override
-    public boolean matches(String key) {
-        return key.startsWith(SESSION_PREFIX);
-    }
-
-    @Override
-    public String doDetermineDatasource(MethodInvocation invocation, String key) {
-        HttpServletRequest request = (HttpServletRequest) ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
-        return request.getSession().getAttribute(key.substring(9)).toString();
-    }
-}

+ 44 - 8
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/config/MybatisPlusConfig.java → ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/config/MybatisPlusConfig.java

@@ -1,17 +1,25 @@
-package org.ruoyi.common.mybatis.config;
+package org.ruoyi.config;
 
 import cn.hutool.core.net.NetUtil;
 import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
+import com.baomidou.mybatisplus.core.handlers.PostInitTableInfoHandler;
 import com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator;
 import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
 import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
 import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
 import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
-import org.ruoyi.common.mybatis.handler.InjectionMetaObjectHandler;
-import org.ruoyi.common.mybatis.interceptor.PlusDataPermissionInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
+import org.ruoyi.aspect.DataPermissionAspect;
+import org.ruoyi.common.core.factory.YmlPropertySourceFactory;
+import org.ruoyi.common.core.utils.SpringUtils;
+import org.ruoyi.handler.InjectionMetaObjectHandler;
+import org.ruoyi.handler.MybatisExceptionHandler;
+import org.ruoyi.handler.PlusPostInitTableInfoHandler;
+import org.ruoyi.interceptor.PlusDataPermissionInterceptor;
 import org.mybatis.spring.annotation.MapperScan;
-import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.beans.BeansException;
 import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.PropertySource;
 import org.springframework.transaction.annotation.EnableTransactionManagement;
 
 /**
@@ -20,13 +28,19 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
  * @author Lion Li
  */
 @EnableTransactionManagement(proxyTargetClass = true)
-@AutoConfiguration
 @MapperScan("${mybatis-plus.mapperPackage}")
+@PropertySource(value = "classpath:common-mybatis.yml", factory = YmlPropertySourceFactory.class)
 public class MybatisPlusConfig {
 
     @Bean
     public MybatisPlusInterceptor mybatisPlusInterceptor() {
         MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
+        // 多租户插件 必须放到第一位
+        try {
+            TenantLineInnerInterceptor tenant = SpringUtils.getBean(TenantLineInnerInterceptor.class);
+            interceptor.addInnerInterceptor(tenant);
+        } catch (BeansException ignore) {
+        }
         // 数据权限处理
         interceptor.addInnerInterceptor(dataPermissionInterceptor());
         // 分页插件
@@ -40,7 +54,15 @@ public class MybatisPlusConfig {
      * 数据权限拦截器
      */
     public PlusDataPermissionInterceptor dataPermissionInterceptor() {
-        return new PlusDataPermissionInterceptor();
+        return new PlusDataPermissionInterceptor(SpringUtils.getProperty("mybatis-plus.mapperPackage"));
+    }
+
+    /**
+     * 数据权限切面处理器
+     */
+    @Bean
+    public DataPermissionAspect dataPermissionAspect() {
+        return new DataPermissionAspect();
     }
 
     /**
@@ -48,8 +70,6 @@ public class MybatisPlusConfig {
      */
     public PaginationInnerInterceptor paginationInnerInterceptor() {
         PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
-        // 设置最大单页限制数量,默认 500 条,-1 不受限制
-        paginationInnerInterceptor.setMaxLimit(-1L);
         // 分页合理化
         paginationInnerInterceptor.setOverflow(true);
         return paginationInnerInterceptor;
@@ -79,6 +99,22 @@ public class MybatisPlusConfig {
         return new DefaultIdentifierGenerator(NetUtil.getLocalhost());
     }
 
+    /**
+     * 异常处理器
+     */
+    @Bean
+    public MybatisExceptionHandler mybatisExceptionHandler() {
+        return new MybatisExceptionHandler();
+    }
+
+    /**
+     * 初始化表对象处理器
+     */
+    @Bean
+    public PostInitTableInfoHandler postInitTableInfoHandler() {
+        return new PlusPostInitTableInfoHandler();
+    }
+
     /**
      * PaginationInnerInterceptor 分页插件,自动识别数据库类型
      * https://baomidou.com/pages/97710a/

+ 1 - 2
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/core/domain/BaseEntity.java → ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/core/domain/BaseEntity.java

@@ -1,4 +1,4 @@
-package org.ruoyi.common.mybatis.core.domain;
+package org.ruoyi.core.domain;
 
 import com.baomidou.mybatisplus.annotation.FieldFill;
 import com.baomidou.mybatisplus.annotation.TableField;
@@ -17,7 +17,6 @@ import java.util.Map;
  *
  * @author Lion Li
  */
-
 @Data
 public class BaseEntity implements Serializable {
 

+ 335 - 0
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/core/mapper/BaseMapperPlus.java

@@ -0,0 +1,335 @@
+package org.ruoyi.core.mapper;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.reflect.GenericTypeUtils;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.toolkit.Db;
+import org.apache.ibatis.logging.Log;
+import org.apache.ibatis.logging.LogFactory;
+import org.ruoyi.common.core.utils.MapstructUtils;
+import org.ruoyi.common.core.utils.StreamUtils;
+
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+
+/**
+ * 自定义 Mapper 接口, 实现 自定义扩展
+ *
+ * @param <T> table 泛型
+ * @param <V> vo 泛型
+ * @author Lion Li
+ * @since 2021-05-13
+ */
+@SuppressWarnings("unchecked")
+public interface BaseMapperPlus<T, V> extends BaseMapper<T> {
+
+    Log log = LogFactory.getLog(BaseMapperPlus.class);
+
+    /**
+     * 获取当前实例对象关联的泛型类型 V 的 Class 对象
+     *
+     * @return 返回当前实例对象关联的泛型类型 V 的 Class 对象
+     */
+    default Class<V> currentVoClass() {
+        return (Class<V>) GenericTypeUtils.resolveTypeArguments(this.getClass(), BaseMapperPlus.class)[1];
+    }
+
+    /**
+     * 获取当前实例对象关联的泛型类型 T 的 Class 对象
+     *
+     * @return 返回当前实例对象关联的泛型类型 T 的 Class 对象
+     */
+    default Class<T> currentModelClass() {
+        return (Class<T>) GenericTypeUtils.resolveTypeArguments(this.getClass(), BaseMapperPlus.class)[0];
+    }
+
+    /**
+     * 使用默认的查询条件查询并返回结果列表
+     *
+     * @return 返回查询结果的列表
+     */
+    default List<T> selectList() {
+        return this.selectList(new QueryWrapper<>());
+    }
+
+    /**
+     * 批量插入实体对象集合
+     *
+     * @param entityList 实体对象集合
+     * @return 插入操作是否成功的布尔值
+     */
+    default boolean insertBatch(Collection<T> entityList) {
+        return Db.saveBatch(entityList);
+    }
+
+    /**
+     * 批量根据ID更新实体对象集合
+     *
+     * @param entityList 实体对象集合
+     * @return 更新操作是否成功的布尔值
+     */
+    default boolean updateBatchById(Collection<T> entityList) {
+        return Db.updateBatchById(entityList);
+    }
+
+    /**
+     * 批量插入或更新实体对象集合
+     *
+     * @param entityList 实体对象集合
+     * @return 插入或更新操作是否成功的布尔值
+     */
+    default boolean insertOrUpdateBatch(Collection<T> entityList) {
+        return Db.saveOrUpdateBatch(entityList);
+    }
+
+    /**
+     * 批量插入实体对象集合并指定批处理大小
+     *
+     * @param entityList 实体对象集合
+     * @param batchSize  批处理大小
+     * @return 插入操作是否成功的布尔值
+     */
+    default boolean insertBatch(Collection<T> entityList, int batchSize) {
+        return Db.saveBatch(entityList, batchSize);
+    }
+
+    /**
+     * 批量根据ID更新实体对象集合并指定批处理大小
+     *
+     * @param entityList 实体对象集合
+     * @param batchSize  批处理大小
+     * @return 更新操作是否成功的布尔值
+     */
+    default boolean updateBatchById(Collection<T> entityList, int batchSize) {
+        return Db.updateBatchById(entityList, batchSize);
+    }
+
+    /**
+     * 批量插入或更新实体对象集合并指定批处理大小
+     *
+     * @param entityList 实体对象集合
+     * @param batchSize  批处理大小
+     * @return 插入或更新操作是否成功的布尔值
+     */
+    default boolean insertOrUpdateBatch(Collection<T> entityList, int batchSize) {
+        return Db.saveOrUpdateBatch(entityList, batchSize);
+    }
+
+    /**
+     * 根据ID查询单个VO对象
+     *
+     * @param id 主键ID
+     * @return 查询到的单个VO对象
+     */
+    default V selectVoById(Serializable id) {
+        return selectVoById(id, this.currentVoClass());
+    }
+
+    /**
+     * 根据ID查询单个VO对象并将其转换为指定的VO类
+     *
+     * @param id      主键ID
+     * @param voClass 要转换的VO类的Class对象
+     * @param <C>     VO类的类型
+     * @return 查询到的单个VO对象,经过转换为指定的VO类后返回
+     */
+    default <C> C selectVoById(Serializable id, Class<C> voClass) {
+        T obj = this.selectById(id);
+        if (ObjectUtil.isNull(obj)) {
+            return null;
+        }
+        return MapstructUtils.convert(obj, voClass);
+    }
+
+    /**
+     * 根据ID集合批量查询VO对象列表
+     *
+     * @param idList 主键ID集合
+     * @return 查询到的VO对象列表
+     */
+    default List<V> selectVoByIds(Collection<? extends Serializable> idList) {
+        return selectVoByIds(idList, this.currentVoClass());
+    }
+
+    /**
+     * 根据ID集合批量查询实体对象列表,并将其转换为指定的VO对象列表
+     *
+     * @param idList  主键ID集合
+     * @param voClass 要转换的VO类的Class对象
+     * @param <C>     VO类的类型
+     * @return 查询到的VO对象列表,经过转换为指定的VO类后返回
+     */
+    default <C> List<C> selectVoByIds(Collection<? extends Serializable> idList, Class<C> voClass) {
+        List<T> list = this.selectByIds(idList);
+        if (CollUtil.isEmpty(list)) {
+            return CollUtil.newArrayList();
+        }
+        return MapstructUtils.convert(list, voClass);
+    }
+
+    /**
+     * 根据查询条件Map查询VO对象列表
+     *
+     * @param map 查询条件Map
+     * @return 查询到的VO对象列表
+     */
+    default List<V> selectVoByMap(Map<String, Object> map) {
+        return selectVoByMap(map, this.currentVoClass());
+    }
+
+    /**
+     * 根据查询条件Map查询实体对象列表,并将其转换为指定的VO对象列表
+     *
+     * @param map     查询条件Map
+     * @param voClass 要转换的VO类的Class对象
+     * @param <C>     VO类的类型
+     * @return 查询到的VO对象列表,经过转换为指定的VO类后返回
+     */
+    default <C> List<C> selectVoByMap(Map<String, Object> map, Class<C> voClass) {
+        List<T> list = this.selectByMap(map);
+        if (CollUtil.isEmpty(list)) {
+            return CollUtil.newArrayList();
+        }
+        return MapstructUtils.convert(list, voClass);
+    }
+
+    /**
+     * 根据条件查询单个VO对象
+     *
+     * @param wrapper 查询条件Wrapper
+     * @return 查询到的单个VO对象
+     */
+    default V selectVoOne(Wrapper<T> wrapper) {
+        return selectVoOne(wrapper, this.currentVoClass());
+    }
+
+    /**
+     * 根据条件查询单个VO对象,并根据需要决定是否抛出异常
+     *
+     * @param wrapper 查询条件Wrapper
+     * @param throwEx 是否抛出异常的标志
+     * @return 查询到的单个VO对象
+     */
+    default V selectVoOne(Wrapper<T> wrapper, boolean throwEx) {
+        return selectVoOne(wrapper, this.currentVoClass(), throwEx);
+    }
+
+    /**
+     * 根据条件查询单个VO对象,并指定返回的VO对象的类型
+     *
+     * @param wrapper 查询条件Wrapper
+     * @param voClass 返回的VO对象的Class对象
+     * @param <C>     返回的VO对象的类型
+     * @return 查询到的单个VO对象,经过类型转换为指定的VO类后返回
+     */
+    default <C> C selectVoOne(Wrapper<T> wrapper, Class<C> voClass) {
+        return selectVoOne(wrapper, voClass, true);
+    }
+
+    /**
+     * 根据条件查询单个实体对象,并将其转换为指定的VO对象
+     *
+     * @param wrapper 查询条件Wrapper
+     * @param voClass 要转换的VO类的Class对象
+     * @param throwEx 是否抛出异常的标志
+     * @param <C>     VO类的类型
+     * @return 查询到的单个VO对象,经过转换为指定的VO类后返回
+     */
+    default <C> C selectVoOne(Wrapper<T> wrapper, Class<C> voClass, boolean throwEx) {
+        T obj = this.selectOne(wrapper, throwEx);
+        if (ObjectUtil.isNull(obj)) {
+            return null;
+        }
+        return MapstructUtils.convert(obj, voClass);
+    }
+
+    /**
+     * 查询所有VO对象列表
+     *
+     * @return 查询到的VO对象列表
+     */
+    default List<V> selectVoList() {
+        return selectVoList(new QueryWrapper<>(), this.currentVoClass());
+    }
+
+    /**
+     * 根据条件查询VO对象列表
+     *
+     * @param wrapper 查询条件Wrapper
+     * @return 查询到的VO对象列表
+     */
+    default List<V> selectVoList(Wrapper<T> wrapper) {
+        return selectVoList(wrapper, this.currentVoClass());
+    }
+
+    /**
+     * 根据条件查询实体对象列表,并将其转换为指定的VO对象列表
+     *
+     * @param wrapper 查询条件Wrapper
+     * @param voClass 要转换的VO类的Class对象
+     * @param <C>     VO类的类型
+     * @return 查询到的VO对象列表,经过转换为指定的VO类后返回
+     */
+    default <C> List<C> selectVoList(Wrapper<T> wrapper, Class<C> voClass) {
+        List<T> list = this.selectList(wrapper);
+        if (CollUtil.isEmpty(list)) {
+            return CollUtil.newArrayList();
+        }
+        return MapstructUtils.convert(list, voClass);
+    }
+
+    /**
+     * 根据条件分页查询VO对象列表
+     *
+     * @param page    分页信息
+     * @param wrapper 查询条件Wrapper
+     * @return 查询到的VO对象分页列表
+     */
+    default <P extends IPage<V>> P selectVoPage(IPage<T> page, Wrapper<T> wrapper) {
+        return selectVoPage(page, wrapper, this.currentVoClass());
+    }
+
+    /**
+     * 根据条件分页查询实体对象列表,并将其转换为指定的VO对象分页列表
+     *
+     * @param page    分页信息
+     * @param wrapper 查询条件Wrapper
+     * @param voClass 要转换的VO类的Class对象
+     * @param <C>     VO类的类型
+     * @param <P>     VO对象分页列表的类型
+     * @return 查询到的VO对象分页列表,经过转换为指定的VO类后返回
+     */
+    default <C, P extends IPage<C>> P selectVoPage(IPage<T> page, Wrapper<T> wrapper, Class<C> voClass) {
+        // 根据条件分页查询实体对象列表
+        List<T> list = this.selectList(page, wrapper);
+        // 创建一个新的VO对象分页列表,并设置分页信息
+        IPage<C> voPage = new Page<>(page.getCurrent(), page.getSize(), page.getTotal());
+        if (CollUtil.isEmpty(list)) {
+            return (P) voPage;
+        }
+        voPage.setRecords(MapstructUtils.convert(list, voClass));
+        return (P) voPage;
+    }
+
+    /**
+     * 根据条件查询符合条件的对象,并将其转换为指定类型的对象列表
+     *
+     * @param wrapper 查询条件Wrapper
+     * @param mapper  转换函数,用于将查询到的对象转换为指定类型的对象
+     * @param <C>     要转换的对象的类型
+     * @return 查询到的符合条件的对象列表,经过转换为指定类型的对象后返回
+     */
+    default <C> List<C> selectObjs(Wrapper<T> wrapper, Function<? super Object, C> mapper) {
+        return StreamUtils.toList(this.selectObjs(wrapper), mapper);
+    }
+
+}

+ 15 - 2
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/core/page/PageQuery.java → ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/core/page/PageQuery.java

@@ -1,9 +1,10 @@
-package org.ruoyi.common.mybatis.core.page;
+package org.ruoyi.core.page;
 
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.ObjectUtil;
 import com.baomidou.mybatisplus.core.metadata.OrderItem;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.fasterxml.jackson.annotation.JsonIgnore;
 import lombok.Data;
 import org.ruoyi.common.core.exception.ServiceException;
 import org.ruoyi.common.core.utils.StringUtils;
@@ -19,7 +20,6 @@ import java.util.List;
  *
  * @author Lion Li
  */
-
 @Data
 public class PageQuery implements Serializable {
 
@@ -56,6 +56,9 @@ public class PageQuery implements Serializable {
      */
     public static final int DEFAULT_PAGE_SIZE = Integer.MAX_VALUE;
 
+    /**
+     * 构建分页对象
+     */
     public <T> Page<T> build() {
         Integer pageNum = ObjectUtil.defaultIfNull(getPageNum(), DEFAULT_PAGE_NUM);
         Integer pageSize = ObjectUtil.defaultIfNull(getPageSize(), DEFAULT_PAGE_SIZE);
@@ -111,4 +114,14 @@ public class PageQuery implements Serializable {
         return list;
     }
 
+    @JsonIgnore
+    public Integer getFirstNum() {
+        return (pageNum - 1) * pageSize;
+    }
+
+    public PageQuery(Integer pageSize, Integer pageNum) {
+        this.pageSize = pageSize;
+        this.pageNum = pageNum;
+    }
+
 }

+ 12 - 2
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/core/page/TableDataInfo.java → ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/core/page/TableDataInfo.java

@@ -1,4 +1,4 @@
-package org.ruoyi.common.mybatis.core.page;
+package org.ruoyi.core.page;
 
 import cn.hutool.http.HttpStatus;
 import com.baomidou.mybatisplus.core.metadata.IPage;
@@ -14,7 +14,6 @@ import java.util.List;
  *
  * @author Lion Li
  */
-
 @Data
 @NoArgsConstructor
 public class TableDataInfo<T> implements Serializable {
@@ -51,8 +50,13 @@ public class TableDataInfo<T> implements Serializable {
     public TableDataInfo(List<T> list, long total) {
         this.rows = list;
         this.total = total;
+        this.code = HttpStatus.HTTP_OK;
+        this.msg = "查询成功";
     }
 
+    /**
+     * 根据分页对象构建表格分页数据对象
+     */
     public static <T> TableDataInfo<T> build(IPage<T> page) {
         TableDataInfo<T> rspData = new TableDataInfo<>();
         rspData.setCode(HttpStatus.HTTP_OK);
@@ -62,6 +66,9 @@ public class TableDataInfo<T> implements Serializable {
         return rspData;
     }
 
+    /**
+     * 根据数据列表构建表格分页数据对象
+     */
     public static <T> TableDataInfo<T> build(List<T> list) {
         TableDataInfo<T> rspData = new TableDataInfo<>();
         rspData.setCode(HttpStatus.HTTP_OK);
@@ -71,6 +78,9 @@ public class TableDataInfo<T> implements Serializable {
         return rspData;
     }
 
+    /**
+     * 构建表格分页数据对象
+     */
     public static <T> TableDataInfo<T> build() {
         TableDataInfo<T> rspData = new TableDataInfo<>();
         rspData.setCode(HttpStatus.HTTP_OK);

+ 11 - 1
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/enums/DataBaseType.java → ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/enums/DataBaseType.java

@@ -1,9 +1,10 @@
-package org.ruoyi.common.mybatis.enums;
+package org.ruoyi.enums;
 
 import lombok.AllArgsConstructor;
 import lombok.Getter;
 import org.ruoyi.common.core.utils.StringUtils;
 
+
 /**
  * 数据库类型
  *
@@ -33,8 +34,17 @@ public enum DataBaseType {
      */
     SQL_SERVER("Microsoft SQL Server");
 
+    /**
+     * 数据库类型
+     */
     private final String type;
 
+    /**
+     * 根据数据库产品名称查找对应的数据库类型
+     *
+     * @param databaseProductName 数据库产品名称
+     * @return 对应的数据库类型枚举值,如果未找到则返回 null
+     */
     public static DataBaseType find(String databaseProductName) {
         if (StringUtils.isBlank(databaseProductName)) {
             return null;

+ 87 - 0
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/enums/DataScopeType.java

@@ -0,0 +1,87 @@
+package org.ruoyi.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.ruoyi.common.core.domain.model.LoginUser;
+import org.ruoyi.common.core.utils.StringUtils;
+import org.ruoyi.helper.DataPermissionHelper;
+
+/**
+ * 数据权限类型枚举
+ * <p>
+ * 支持使用 SpEL 模板表达式定义 SQL 查询条件
+ * 内置数据:
+ * - {@code user}: 当前登录用户信息,参考 {@link LoginUser}
+ * 内置服务:
+ * - {@code sdss}: 系统数据权限服务,参考 ISysDataScopeService
+ * 如需扩展数据,可以通过 {@link DataPermissionHelper} 进行操作
+ * 如需扩展服务,可以通过 ISysDataScopeService 自行编写
+ * </p>
+ *
+ * @author Lion Li
+ * @version 3.5.0
+ */
+@Getter
+@AllArgsConstructor
+public enum DataScopeType {
+
+    /**
+     * 全部数据权限
+     */
+    ALL("1", "", ""),
+
+    /**
+     * 自定数据权限
+     */
+    CUSTOM("2", " #{#deptName} IN ( #{@sdss.getRoleCustom( #user.roleId )} ) ", " 1 = 0 "),
+
+    /**
+     * 部门数据权限
+     */
+    DEPT("3", " #{#deptName} = #{#user.deptId} ", " 1 = 0 "),
+
+    /**
+     * 部门及以下数据权限
+     */
+    DEPT_AND_CHILD("4", " #{#deptName} IN ( #{@sdss.getDeptAndChild( #user.deptId )} )", " 1 = 0 "),
+
+    /**
+     * 仅本人数据权限
+     */
+    SELF("5", " #{#userName} = #{#user.userId} ", " 1 = 0 "),
+
+    /**
+     * 部门及以下或本人数据权限
+     */
+    DEPT_AND_CHILD_OR_SELF("6", " #{#deptName} IN ( #{@sdss.getDeptAndChild( #user.deptId )} ) OR #{#userName} = #{#user.userId} ", " 1 = 0 ");
+
+    private final String code;
+
+    /**
+     * SpEL 模板表达式,用于构建 SQL 查询条件
+     */
+    private final String sqlTemplate;
+
+    /**
+     * 如果不满足 {@code sqlTemplate} 的条件,则使用此默认 SQL 表达式
+     */
+    private final String elseSql;
+
+    /**
+     * 根据枚举代码查找对应的枚举值
+     *
+     * @param code 枚举代码
+     * @return 对应的枚举值,如果未找到则返回 null
+     */
+    public static DataScopeType findCode(String code) {
+        if (StringUtils.isBlank(code)) {
+            return null;
+        }
+        for (DataScopeType type : values()) {
+            if (type.getCode().equals(code)) {
+                return type;
+            }
+        }
+        return null;
+    }
+}

+ 103 - 0
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/handler/InjectionMetaObjectHandler.java

@@ -0,0 +1,103 @@
+package org.ruoyi.handler;
+
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.http.HttpStatus;
+import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.ibatis.reflection.MetaObject;
+import org.ruoyi.common.core.domain.model.LoginUser;
+import org.ruoyi.common.core.exception.ServiceException;
+import org.ruoyi.common.core.utils.ObjectUtils;
+import org.ruoyi.common.satoken.utils.LoginHelper;
+import org.ruoyi.core.domain.BaseEntity;
+
+
+import java.util.Date;
+
+/**
+ * MP注入处理器
+ *
+ * @author Lion Li
+ * @date 2021/4/25
+ */
+@Slf4j
+public class InjectionMetaObjectHandler implements MetaObjectHandler {
+
+    /**
+     * 插入填充方法,用于在插入数据时自动填充实体对象中的创建时间、更新时间、创建人、更新人等信息
+     *
+     * @param metaObject 元对象,用于获取原始对象并进行填充
+     */
+    @Override
+    public void insertFill(MetaObject metaObject) {
+        try {
+            if (ObjectUtil.isNotNull(metaObject) && metaObject.getOriginalObject() instanceof BaseEntity baseEntity) {
+                // 获取当前时间作为创建时间和更新时间,如果创建时间不为空,则使用创建时间,否则使用当前时间
+                Date current = ObjectUtils.notNull(baseEntity.getCreateTime(), new Date());
+                baseEntity.setCreateTime(current);
+                baseEntity.setUpdateTime(current);
+
+                // 如果创建人为空,则填充当前登录用户的信息
+                if (ObjectUtil.isNull(baseEntity.getCreateBy())) {
+                    LoginUser loginUser = getLoginUser();
+                    if (ObjectUtil.isNotNull(loginUser)) {
+                        Long userId = loginUser.getUserId();
+                        // 填充创建人、更新人和创建部门信息
+                        baseEntity.setCreateBy(userId);
+                        baseEntity.setUpdateBy(userId);
+                        baseEntity.setCreateDept(ObjectUtils.notNull(baseEntity.getCreateDept(), loginUser.getDeptId()));
+                    }
+                }
+            } else {
+                Date date = new Date();
+                this.strictInsertFill(metaObject, "createTime", Date.class, date);
+                this.strictInsertFill(metaObject, "updateTime", Date.class, date);
+            }
+        } catch (Exception e) {
+            throw new ServiceException("自动注入异常 => " + e.getMessage(), HttpStatus.HTTP_UNAUTHORIZED);
+        }
+    }
+
+    /**
+     * 更新填充方法,用于在更新数据时自动填充实体对象中的更新时间和更新人信息
+     *
+     * @param metaObject 元对象,用于获取原始对象并进行填充
+     */
+    @Override
+    public void updateFill(MetaObject metaObject) {
+        try {
+            if (ObjectUtil.isNotNull(metaObject) && metaObject.getOriginalObject() instanceof BaseEntity baseEntity) {
+                // 获取当前时间作为更新时间,无论原始对象中的更新时间是否为空都填充
+                Date current = new Date();
+                baseEntity.setUpdateTime(current);
+
+                // 获取当前登录用户的ID,并填充更新人信息
+                Long userId = LoginHelper.getUserId();
+                if (ObjectUtil.isNotNull(userId)) {
+                    baseEntity.setUpdateBy(userId);
+                }
+            } else {
+                this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date());
+            }
+        } catch (Exception e) {
+            throw new ServiceException("自动注入异常 => " + e.getMessage(), HttpStatus.HTTP_UNAUTHORIZED);
+        }
+    }
+
+    /**
+     * 获取当前登录用户信息
+     *
+     * @return 当前登录用户的信息,如果用户未登录则返回 null
+     */
+    private LoginUser getLoginUser() {
+        LoginUser loginUser;
+        try {
+            loginUser = LoginHelper.getLoginUser();
+        } catch (Exception e) {
+            log.warn("自动注入警告 => 用户未登录");
+            return null;
+        }
+        return loginUser;
+    }
+
+}

+ 5 - 3
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/handler/MybatisExceptionHandler.java → ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/handler/MybatisExceptionHandler.java

@@ -1,9 +1,11 @@
-package org.ruoyi.common.mybatis.handler;
+package org.ruoyi.handler;
 
 import jakarta.servlet.http.HttpServletRequest;
 import lombok.extern.slf4j.Slf4j;
-import org.ruoyi.common.core.domain.R;
+
 import org.mybatis.spring.MyBatisSystemException;
+import org.ruoyi.common.core.domain.R;
+import org.ruoyi.common.core.utils.StringUtils;
 import org.springframework.dao.DuplicateKeyException;
 import org.springframework.web.bind.annotation.ExceptionHandler;
 import org.springframework.web.bind.annotation.RestControllerAdvice;
@@ -34,7 +36,7 @@ public class MybatisExceptionHandler {
     public R<Void> handleCannotFindDataSourceException(MyBatisSystemException e, HttpServletRequest request) {
         String requestURI = request.getRequestURI();
         String message = e.getMessage();
-        if (message.contains("CannotFindDataSourceException")) {
+        if (StringUtils.contains("CannotFindDataSourceException", message)) {
             log.error("请求地址'{}', 未找到数据源", requestURI);
             return R.fail("未找到数据源,请联系管理员确认");
         }

+ 359 - 0
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/handler/PlusDataPermissionHandler.java

@@ -0,0 +1,359 @@
+package org.ruoyi.handler;
+
+import cn.hutool.core.annotation.AnnotationUtil;
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjectUtil;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import net.sf.jsqlparser.JSQLParserException;
+import net.sf.jsqlparser.expression.Expression;
+import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
+import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList;
+import net.sf.jsqlparser.parser.CCJSqlParserUtil;
+import org.apache.ibatis.io.Resources;
+
+import org.ruoyi.annotation.DataColumn;
+import org.ruoyi.annotation.DataPermission;
+import org.ruoyi.common.core.domain.dto.RoleDTO;
+import org.ruoyi.common.core.domain.model.LoginUser;
+import org.ruoyi.common.core.exception.ServiceException;
+import org.ruoyi.common.core.utils.SpringUtils;
+import org.ruoyi.common.core.utils.StreamUtils;
+import org.ruoyi.common.core.utils.StringUtils;
+import org.ruoyi.common.satoken.utils.LoginHelper;
+import org.ruoyi.enums.DataScopeType;
+import org.ruoyi.helper.DataPermissionHelper;
+
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.expression.BeanFactoryResolver;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
+import org.springframework.core.io.support.ResourcePatternResolver;
+import org.springframework.core.type.ClassMetadata;
+import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
+import org.springframework.expression.*;
+import org.springframework.expression.common.TemplateParserContext;
+import org.springframework.expression.spel.standard.SpelExpressionParser;
+import org.springframework.expression.spel.support.StandardEvaluationContext;
+import org.springframework.util.ClassUtils;
+
+import java.lang.reflect.Method;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Function;
+
+/**
+ * 数据权限过滤
+ *
+ * @author Lion Li
+ * @version 3.5.0
+ */
+@Slf4j
+public class PlusDataPermissionHandler {
+
+    /**
+     * 类名称与注解的映射关系缓存(由于aop无法拦截mybatis接口类上的注解 只能通过启动预扫描的方式进行)
+     */
+    private final Map<String, DataPermission> dataPermissionCacheMap = new ConcurrentHashMap<>();
+
+    /**
+     * spel 解析器
+     */
+    private final ExpressionParser parser = new SpelExpressionParser();
+    private final ParserContext parserContext = new TemplateParserContext();
+    /**
+     * bean解析器 用于处理 spel 表达式中对 bean 的调用
+     */
+    private final BeanResolver beanResolver = new BeanFactoryResolver(SpringUtils.getBeanFactory());
+
+    /**
+     * 构造方法,扫描指定包下的 Mapper 类并初始化缓存
+     *
+     * @param mapperPackage Mapper 类所在的包路径
+     */
+    public PlusDataPermissionHandler(String mapperPackage) {
+        scanMapperClasses(mapperPackage);
+    }
+
+    /**
+     * 获取数据过滤条件的 SQL 片段
+     *
+     * @param where             原始的查询条件表达式
+     * @param mappedStatementId Mapper 方法的 ID
+     * @param isSelect          是否为查询语句
+     * @return 数据过滤条件的 SQL 片段
+     */
+    public Expression getSqlSegment(Expression where, String mappedStatementId, boolean isSelect) {
+        try {
+            // 获取数据权限配置
+            DataPermission dataPermission = getDataPermission(mappedStatementId);
+            // 获取当前登录用户信息
+            LoginUser currentUser = DataPermissionHelper.getVariable("user");
+            if (ObjectUtil.isNull(currentUser)) {
+                currentUser = LoginHelper.getLoginUser();
+                DataPermissionHelper.setVariable("user", currentUser);
+            }
+            // 如果是超级管理员或租户管理员,则不过滤数据
+            if (LoginHelper.isSuperAdmin() || LoginHelper.isTenantAdmin()) {
+                return where;
+            }
+            // 构造数据过滤条件的 SQL 片段
+            String dataFilterSql = buildDataFilter(dataPermission, isSelect);
+            if (StringUtils.isBlank(dataFilterSql)) {
+                return where;
+            }
+            Expression expression = CCJSqlParserUtil.parseExpression(dataFilterSql);
+            // 数据权限使用单独的括号 防止与其他条件冲突
+            ParenthesedExpressionList<Expression> parenthesis = new ParenthesedExpressionList<>(expression);
+            if (ObjectUtil.isNotNull(where)) {
+                return new AndExpression(where, parenthesis);
+            } else {
+                return parenthesis;
+            }
+        } catch (JSQLParserException e) {
+            throw new ServiceException("数据权限解析异常 => " + e.getMessage());
+        } finally {
+            DataPermissionHelper.removePermission();
+        }
+    }
+
+    /**
+     * 构建数据过滤条件的 SQL 语句
+     *
+     * @param dataPermission 数据权限注解
+     * @param isSelect       标志当前操作是否为查询操作,查询操作和更新或删除操作在处理过滤条件时会有不同的处理方式
+     * @return 构建的数据过滤条件的 SQL 语句
+     * @throws ServiceException 如果角色的数据范围异常或者 key 与 value 的长度不匹配,则抛出 ServiceException 异常
+     */
+    private String buildDataFilter(DataPermission dataPermission, boolean isSelect) {
+        // 更新或删除需满足所有条件
+        String joinStr = isSelect ? " OR " : " AND ";
+        if (StringUtils.isNotBlank(dataPermission.joinStr())) {
+            joinStr = " " + dataPermission.joinStr() + " ";
+        }
+        LoginUser user = DataPermissionHelper.getVariable("user");
+        Object defaultValue = "-1";
+        NullSafeStandardEvaluationContext context = new NullSafeStandardEvaluationContext(defaultValue);
+        context.addPropertyAccessor(new NullSafePropertyAccessor(context.getPropertyAccessors().get(0), defaultValue));
+        context.setBeanResolver(beanResolver);
+        DataPermissionHelper.getContext().forEach(context::setVariable);
+        Set<String> conditions = new HashSet<>();
+        // 优先设置变量
+        List<String> keys = new ArrayList<>();
+        Map<DataColumn, Boolean> ignoreMap = new HashMap<>();
+        for (DataColumn dataColumn : dataPermission.value()) {
+            if (dataColumn.key().length != dataColumn.value().length) {
+                throw new ServiceException("角色数据范围异常 => key与value长度不匹配");
+            }
+            // 包含权限标识符 这直接跳过
+            if (StringUtils.isNotBlank(dataColumn.permission()) &&
+                CollUtil.contains(user.getMenuPermission(), dataColumn.permission())
+            ) {
+                ignoreMap.put(dataColumn, Boolean.TRUE);
+                continue;
+            }
+            // 设置注解变量 key 为表达式变量 value 为变量值
+            for (int i = 0; i < dataColumn.key().length; i++) {
+                context.setVariable(dataColumn.key()[i], dataColumn.value()[i]);
+            }
+            keys.addAll(Arrays.stream(dataColumn.key()).map(key -> "#" + key).toList());
+        }
+
+        for (RoleDTO role : user.getRoles()) {
+            user.setRoleId(role.getRoleId());
+            // 获取角色权限泛型
+            DataScopeType type = DataScopeType.findCode(role.getDataScope());
+            if (ObjectUtil.isNull(type)) {
+                throw new ServiceException("角色数据范围异常 => " + role.getDataScope());
+            }
+            // 全部数据权限直接返回
+            if (type == DataScopeType.ALL) {
+                return StringUtils.EMPTY;
+            }
+            boolean isSuccess = false;
+            for (DataColumn dataColumn : dataPermission.value()) {
+                // 包含权限标识符 这直接跳过
+                if (ignoreMap.containsKey(dataColumn)) {
+                    // 修复多角色与权限标识符共用问题 https://gitee.com/dromara/RuoYi-Vue-Plus/issues/IB4CS4
+                    conditions.add(joinStr + " 1 = 1 ");
+                    isSuccess = true;
+                    continue;
+                }
+                // 不包含 key 变量 则不处理
+                if (!StringUtils.containsAny(type.getSqlTemplate(), keys.toArray(String[]::new))) {
+                    continue;
+                }
+                // 当前注解不满足模板 不处理
+                if (!StringUtils.containsAny(type.getSqlTemplate(), dataColumn.key())) {
+                    continue;
+                }
+                // 忽略数据权限 防止spel表达式内有其他sql查询导致死循环调用
+                String sql = DataPermissionHelper.ignore(() ->
+                    parser.parseExpression(type.getSqlTemplate(), parserContext).getValue(context, String.class)
+                );
+                // 解析sql模板并填充
+                conditions.add(joinStr + sql);
+                isSuccess = true;
+            }
+            // 未处理成功则填充兜底方案
+            if (!isSuccess && StringUtils.isNotBlank(type.getElseSql())) {
+                conditions.add(joinStr + type.getElseSql());
+            }
+        }
+
+        if (CollUtil.isNotEmpty(conditions)) {
+            String sql = StreamUtils.join(conditions, Function.identity(), "");
+            return sql.substring(joinStr.length());
+        }
+        return StringUtils.EMPTY;
+    }
+
+    /**
+     * 扫描指定包下的 Mapper 类,并查找其中带有特定注解的方法或类
+     *
+     * @param mapperPackage Mapper 类所在的包路径
+     */
+    private void scanMapperClasses(String mapperPackage) {
+        // 创建资源解析器和元数据读取工厂
+        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
+        CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
+        // 将 Mapper 包路径按分隔符拆分为数组
+        String[] packagePatternArray = StringUtils.splitPreserveAllTokens(mapperPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
+        String classpath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX;
+        try {
+            for (String packagePattern : packagePatternArray) {
+                // 将包路径转换为资源路径
+                String path = ClassUtils.convertClassNameToResourcePath(packagePattern);
+                // 获取指定路径下的所有 .class 文件资源
+                Resource[] resources = resolver.getResources(classpath + path + "/*.class");
+                for (Resource resource : resources) {
+                    // 获取资源的类元数据
+                    ClassMetadata classMetadata = factory.getMetadataReader(resource).getClassMetadata();
+                    // 获取资源对应的类对象
+                    Class<?> clazz = Resources.classForName(classMetadata.getClassName());
+                    // 查找类中的特定注解
+                    findAnnotation(clazz);
+                }
+            }
+        } catch (Exception e) {
+            log.error("初始化数据安全缓存时出错:{}", e.getMessage());
+        }
+    }
+
+    /**
+     * 在指定的类中查找特定的注解 DataPermission,并将带有这个注解的方法或类存储到 dataPermissionCacheMap 中
+     *
+     * @param clazz 要查找的类
+     */
+    private void findAnnotation(Class<?> clazz) {
+        DataPermission dataPermission;
+        for (Method method : clazz.getMethods()) {
+            if (method.isDefault() || method.isVarArgs()) {
+                continue;
+            }
+            String mappedStatementId = clazz.getName() + "." + method.getName();
+            if (AnnotationUtil.hasAnnotation(method, DataPermission.class)) {
+                dataPermission = AnnotationUtil.getAnnotation(method, DataPermission.class);
+                dataPermissionCacheMap.put(mappedStatementId, dataPermission);
+            }
+        }
+        if (AnnotationUtil.hasAnnotation(clazz, DataPermission.class)) {
+            dataPermission = AnnotationUtil.getAnnotation(clazz, DataPermission.class);
+            dataPermissionCacheMap.put(clazz.getName(), dataPermission);
+        }
+    }
+
+    /**
+     * 根据映射语句 ID 或类名获取对应的 DataPermission 注解对象
+     *
+     * @param mapperId 映射语句 ID
+     * @return DataPermission 注解对象,如果不存在则返回 null
+     */
+    public DataPermission getDataPermission(String mapperId) {
+        // 检查上下文中是否包含映射语句 ID 对应的 DataPermission 注解对象
+        if (DataPermissionHelper.getPermission() != null) {
+            return DataPermissionHelper.getPermission();
+        }
+        // 检查缓存中是否包含映射语句 ID 对应的 DataPermission 注解对象
+        if (dataPermissionCacheMap.containsKey(mapperId)) {
+            return dataPermissionCacheMap.get(mapperId);
+        }
+        // 如果缓存中不包含映射语句 ID 对应的 DataPermission 注解对象,则尝试使用类名作为键查找
+        String clazzName = mapperId.substring(0, mapperId.lastIndexOf("."));
+        if (dataPermissionCacheMap.containsKey(clazzName)) {
+            return dataPermissionCacheMap.get(clazzName);
+        }
+        return null;
+    }
+
+    /**
+     * 检查给定的映射语句 ID 是否有效,即是否能够找到对应的 DataPermission 注解对象
+     *
+     * @param mapperId 映射语句 ID
+     * @return 如果找到对应的 DataPermission 注解对象,则返回 false;否则返回 true
+     */
+    public boolean invalid(String mapperId) {
+        return getDataPermission(mapperId) == null;
+    }
+
+    /**
+     * 对所有null变量找不到的变量返回默认值
+     */
+    @AllArgsConstructor
+    private static class NullSafeStandardEvaluationContext extends StandardEvaluationContext {
+
+        private final Object defaultValue;
+
+        @Override
+        public Object lookupVariable(String name) {
+            Object obj = super.lookupVariable(name);
+            // 如果读取到的值是 null,则返回默认值
+            if (obj == null) {
+                return defaultValue;
+            }
+            return obj;
+        }
+
+    }
+
+    /**
+     * 对所有null变量找不到的变量返回默认值 委托模式 将不需要处理的方法委托给原处理器
+     */
+    @AllArgsConstructor
+    private static class NullSafePropertyAccessor implements PropertyAccessor {
+
+        private final PropertyAccessor delegate;
+        private final Object defaultValue;
+
+        @Override
+        public Class<?>[] getSpecificTargetClasses() {
+            return delegate.getSpecificTargetClasses();
+        }
+
+        @Override
+        public boolean canRead(EvaluationContext context, Object target, String name) throws AccessException {
+            return delegate.canRead(context, target, name);
+        }
+
+        @Override
+        public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException {
+            TypedValue value = delegate.read(context, target, name);
+            // 如果读取到的值是 null,则返回默认值
+            if (value.getValue() == null) {
+                return new TypedValue(defaultValue);
+            }
+            return value;
+        }
+
+        @Override
+        public boolean canWrite(EvaluationContext context, Object target, String name) throws AccessException {
+            return delegate.canWrite(context, target, name);
+        }
+
+        @Override
+        public void write(EvaluationContext context, Object target, String name, Object newValue) throws AccessException {
+            delegate.write(context, target, name, newValue);
+        }
+    }
+
+}

+ 28 - 0
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/handler/PlusPostInitTableInfoHandler.java

@@ -0,0 +1,28 @@
+package org.ruoyi.handler;
+
+import cn.hutool.core.convert.Convert;
+import com.baomidou.mybatisplus.core.handlers.PostInitTableInfoHandler;
+import com.baomidou.mybatisplus.core.metadata.TableInfo;
+import org.apache.ibatis.session.Configuration;
+import org.ruoyi.common.core.utils.SpringUtils;
+import org.ruoyi.common.core.utils.reflect.ReflectUtils;
+
+
+/**
+ * 修改表信息初始化方式
+ * 目前用于全局修改是否使用逻辑删除
+ *
+ * @author Lion Li
+ */
+public class PlusPostInitTableInfoHandler implements PostInitTableInfoHandler {
+
+    @Override
+    public void postTableInfo(TableInfo tableInfo, Configuration configuration) {
+        String flag = SpringUtils.getProperty("mybatis-plus.enableLogicDelete", "true");
+        // 只有关闭时 统一设置false 为true时mp自动判断不处理
+        if (!Convert.toBool(flag)) {
+            ReflectUtils.setFieldValue(tableInfo, "withLogicDelete", false);
+        }
+    }
+
+}

+ 6 - 4
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/common/mybatis/helper/DataBaseHelper.java → ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/helper/DataBaseHelper.java

@@ -1,12 +1,13 @@
-package org.ruoyi.common.mybatis.helper;
+package org.ruoyi.helper;
 
 import cn.hutool.core.convert.Convert;
 import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
 import lombok.AccessLevel;
 import lombok.NoArgsConstructor;
+
 import org.ruoyi.common.core.exception.ServiceException;
 import org.ruoyi.common.core.utils.SpringUtils;
-import org.ruoyi.common.mybatis.enums.DataBaseType;
+import org.ruoyi.enums.DataBaseType;
 
 import javax.sql.DataSource;
 import java.sql.Connection;
@@ -62,8 +63,8 @@ public class DataBaseHelper {
             // charindex(',100,' , ',0,100,101,') <> 0
             return "charindex(',%s,' , ','+%s+',') <> 0".formatted(var, var2);
         } else if (dataBasyType == DataBaseType.POSTGRE_SQL) {
-            // (select position(',100,' in ',0,100,101,')) <> 0
-            return "(select position(',%s,' in ','||%s||',')) <> 0".formatted(var, var2);
+            // (select strpos(',0,100,101,' , ',100,')) <> 0
+            return "(select strpos(','||%s||',' , ',%s,')) <> 0".formatted(var2, var);
         } else if (dataBasyType == DataBaseType.ORACLE) {
             // instr(',0,100,101,' , ',100,') <> 0
             return "instr(','||%s||',' , ',%s,') <> 0".formatted(var2, var);
@@ -71,6 +72,7 @@ public class DataBaseHelper {
         // find_in_set(100 , '0,100,101')
         return "find_in_set('%s' , %s) <> 0".formatted(var, var2);
     }
+
     /**
      * 获取当前加载的数据库名
      */

+ 176 - 0
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/helper/DataPermissionHelper.java

@@ -0,0 +1,176 @@
+package org.ruoyi.helper;
+
+import cn.dev33.satoken.context.SaHolder;
+import cn.dev33.satoken.context.model.SaStorage;
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.plugins.IgnoreStrategy;
+import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.ruoyi.annotation.DataPermission;
+import org.ruoyi.common.core.utils.reflect.ReflectUtils;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Stack;
+import java.util.function.Supplier;
+
+/**
+ * 数据权限助手
+ *
+ * @author Lion Li
+ * @version 3.5.0
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+@SuppressWarnings("unchecked cast")
+public class DataPermissionHelper {
+
+    private static final String DATA_PERMISSION_KEY = "data:permission";
+
+    private static final ThreadLocal<Stack<Integer>> REENTRANT_IGNORE = ThreadLocal.withInitial(Stack::new);
+
+    private static final ThreadLocal<DataPermission> PERMISSION_CACHE = new ThreadLocal<>();
+
+    /**
+     * 获取当前执行mapper权限注解
+     *
+     * @return 返回当前执行mapper权限注解
+     */
+    public static DataPermission getPermission() {
+        return PERMISSION_CACHE.get();
+    }
+
+    /**
+     * 设置当前执行mapper权限注解
+     *
+     * @param dataPermission   数据权限注解
+     */
+    public static void setPermission(DataPermission dataPermission) {
+        PERMISSION_CACHE.set(dataPermission);
+    }
+
+    /**
+     * 删除当前执行mapper权限注解
+     */
+    public static void removePermission() {
+        PERMISSION_CACHE.remove();
+    }
+
+    /**
+     * 从上下文中获取指定键的变量值,并将其转换为指定的类型
+     *
+     * @param key 变量的键
+     * @param <T> 变量值的类型
+     * @return 指定键的变量值,如果不存在则返回 null
+     */
+    public static <T> T getVariable(String key) {
+        Map<String, Object> context = getContext();
+        return (T) context.get(key);
+    }
+
+    /**
+     * 向上下文中设置指定键的变量值
+     *
+     * @param key   要设置的变量的键
+     * @param value 要设置的变量值
+     */
+    public static void setVariable(String key, Object value) {
+        Map<String, Object> context = getContext();
+        context.put(key, value);
+    }
+
+    /**
+     * 获取数据权限上下文
+     *
+     * @return 存储在SaStorage中的Map对象,用于存储数据权限相关的上下文信息
+     * @throws NullPointerException 如果数据权限上下文类型异常,则抛出NullPointerException
+     */
+    public static Map<String, Object> getContext() {
+        SaStorage saStorage = SaHolder.getStorage();
+        Object attribute = saStorage.get(DATA_PERMISSION_KEY);
+        if (ObjectUtil.isNull(attribute)) {
+            saStorage.set(DATA_PERMISSION_KEY, new HashMap<>());
+            attribute = saStorage.get(DATA_PERMISSION_KEY);
+        }
+        if (attribute instanceof Map map) {
+            return map;
+        }
+        throw new NullPointerException("data permission context type exception");
+    }
+
+    private static IgnoreStrategy getIgnoreStrategy() {
+        Object ignoreStrategyLocal = ReflectUtils.getStaticFieldValue(ReflectUtils.getField(InterceptorIgnoreHelper.class, "IGNORE_STRATEGY_LOCAL"));
+        if (ignoreStrategyLocal instanceof ThreadLocal<?> IGNORE_STRATEGY_LOCAL) {
+            if (IGNORE_STRATEGY_LOCAL.get() instanceof IgnoreStrategy ignoreStrategy) {
+                return ignoreStrategy;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 开启忽略数据权限(开启后需手动调用 {@link #disableIgnore()} 关闭)
+     */
+    public static void enableIgnore() {
+        IgnoreStrategy ignoreStrategy = getIgnoreStrategy();
+        if (ObjectUtil.isNull(ignoreStrategy)) {
+            InterceptorIgnoreHelper.handle(IgnoreStrategy.builder().dataPermission(true).build());
+        } else {
+            ignoreStrategy.setDataPermission(true);
+        }
+        Stack<Integer> reentrantStack = REENTRANT_IGNORE.get();
+        reentrantStack.push(reentrantStack.size() + 1);
+    }
+
+    /**
+     * 关闭忽略数据权限
+     */
+    public static void disableIgnore() {
+        IgnoreStrategy ignoreStrategy = getIgnoreStrategy();
+        if (ObjectUtil.isNotNull(ignoreStrategy)) {
+            boolean noOtherIgnoreStrategy = !Boolean.TRUE.equals(ignoreStrategy.getDynamicTableName())
+                && !Boolean.TRUE.equals(ignoreStrategy.getBlockAttack())
+                && !Boolean.TRUE.equals(ignoreStrategy.getIllegalSql())
+                && !Boolean.TRUE.equals(ignoreStrategy.getTenantLine())
+                && CollectionUtil.isEmpty(ignoreStrategy.getOthers());
+            Stack<Integer> reentrantStack = REENTRANT_IGNORE.get();
+            boolean empty = reentrantStack.isEmpty() || reentrantStack.pop() == 1;
+            if (noOtherIgnoreStrategy && empty) {
+                InterceptorIgnoreHelper.clearIgnoreStrategy();
+            } else if (empty) {
+                ignoreStrategy.setDataPermission(false);
+            }
+
+        }
+    }
+
+    /**
+     * 在忽略数据权限中执行
+     *
+     * @param handle 处理执行方法
+     */
+    public static void ignore(Runnable handle) {
+        enableIgnore();
+        try {
+            handle.run();
+        } finally {
+            disableIgnore();
+        }
+    }
+
+    /**
+     * 在忽略数据权限中执行
+     *
+     * @param handle 处理执行方法
+     */
+    public static <T> T ignore(Supplier<T> handle) {
+        enableIgnore();
+        try {
+            return handle.get();
+        } finally {
+            disableIgnore();
+        }
+    }
+
+}

+ 181 - 0
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/ruoyi/interceptor/PlusDataPermissionInterceptor.java

@@ -0,0 +1,181 @@
+package org.ruoyi.interceptor;
+
+import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
+import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
+import com.baomidou.mybatisplus.extension.plugins.handler.MultiDataPermissionHandler;
+import com.baomidou.mybatisplus.extension.plugins.inner.BaseMultiTableInnerInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
+import lombok.extern.slf4j.Slf4j;
+import net.sf.jsqlparser.expression.Expression;
+import net.sf.jsqlparser.schema.Table;
+import net.sf.jsqlparser.statement.delete.Delete;
+import net.sf.jsqlparser.statement.select.PlainSelect;
+import net.sf.jsqlparser.statement.select.Select;
+import net.sf.jsqlparser.statement.select.SetOperationList;
+import net.sf.jsqlparser.statement.update.Update;
+import org.apache.ibatis.executor.Executor;
+import org.apache.ibatis.executor.statement.StatementHandler;
+import org.apache.ibatis.mapping.BoundSql;
+import org.apache.ibatis.mapping.MappedStatement;
+import org.apache.ibatis.mapping.SqlCommandType;
+import org.apache.ibatis.session.ResultHandler;
+import org.apache.ibatis.session.RowBounds;
+import org.ruoyi.handler.PlusDataPermissionHandler;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.List;
+
+/**
+ * 数据权限拦截器
+ *
+ * @author Lion Li
+ * @version 3.5.0
+ */
+@Slf4j
+public class PlusDataPermissionInterceptor extends BaseMultiTableInnerInterceptor implements InnerInterceptor {
+
+    private final PlusDataPermissionHandler dataPermissionHandler;
+
+    /**
+     * 构造函数,初始化 PlusDataPermissionHandler 实例
+     *
+     * @param mapperPackage 扫描的映射器包
+     */
+    public PlusDataPermissionInterceptor(String mapperPackage) {
+        this.dataPermissionHandler = new PlusDataPermissionHandler(mapperPackage);
+    }
+
+    /**
+     * 在执行查询之前,检查并处理数据权限相关逻辑
+     *
+     * @param executor      MyBatis 执行器对象
+     * @param ms            映射语句对象
+     * @param parameter     方法参数
+     * @param rowBounds     分页对象
+     * @param resultHandler 结果处理器
+     * @param boundSql      绑定的 SQL 对象
+     * @throws SQLException 如果发生 SQL 异常
+     */
+    @Override
+    public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
+        // 检查是否需要忽略数据权限处理
+        if (InterceptorIgnoreHelper.willIgnoreDataPermission(ms.getId())) {
+            return;
+        }
+        // 检查是否缺少有效的数据权限注解
+        if (dataPermissionHandler.invalid(ms.getId())) {
+            return;
+        }
+        // 解析 sql 分配对应方法
+        PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql);
+        mpBs.sql(parserSingle(mpBs.sql(), ms.getId()));
+    }
+
+    /**
+     * 在准备 SQL 语句之前,检查并处理更新和删除操作的数据权限相关逻辑
+     *
+     * @param sh                 MyBatis StatementHandler 对象
+     * @param connection         数据库连接对象
+     * @param transactionTimeout 事务超时时间
+     */
+    @Override
+    public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) {
+        PluginUtils.MPStatementHandler mpSh = PluginUtils.mpStatementHandler(sh);
+        MappedStatement ms = mpSh.mappedStatement();
+        // 获取 SQL 命令类型(增、删、改、查)
+        SqlCommandType sct = ms.getSqlCommandType();
+
+        // 只处理更新和删除操作的 SQL 语句
+        if (sct == SqlCommandType.UPDATE || sct == SqlCommandType.DELETE) {
+            if (InterceptorIgnoreHelper.willIgnoreDataPermission(ms.getId())) {
+                return;
+            }
+            // 检查是否缺少有效的数据权限注解
+            if (dataPermissionHandler.invalid(ms.getId())) {
+                return;
+            }
+            PluginUtils.MPBoundSql mpBs = mpSh.mPBoundSql();
+            mpBs.sql(parserMulti(mpBs.sql(), ms.getId()));
+        }
+    }
+
+    /**
+     * 处理 SELECT 查询语句中的 WHERE 条件
+     *
+     * @param select SELECT 查询对象
+     * @param index  查询语句的索引
+     * @param sql    查询语句
+     * @param obj    WHERE 条件参数
+     */
+    @Override
+    protected void processSelect(Select select, int index, String sql, Object obj) {
+        if (select instanceof PlainSelect) {
+            this.setWhere((PlainSelect) select, (String) obj);
+        } else if (select instanceof SetOperationList setOperationList) {
+            List<Select> selectBodyList = setOperationList.getSelects();
+            selectBodyList.forEach(s -> this.setWhere((PlainSelect) s, (String) obj));
+        }
+    }
+
+    /**
+     * 处理 UPDATE 语句中的 WHERE 条件
+     *
+     * @param update UPDATE 查询对象
+     * @param index  查询语句的索引
+     * @param sql    查询语句
+     * @param obj    WHERE 条件参数
+     */
+    @Override
+    protected void processUpdate(Update update, int index, String sql, Object obj) {
+        Expression sqlSegment = dataPermissionHandler.getSqlSegment(update.getWhere(), (String) obj, false);
+        if (null != sqlSegment) {
+            update.setWhere(sqlSegment);
+        }
+    }
+
+    /**
+     * 处理 DELETE 语句中的 WHERE 条件
+     *
+     * @param delete DELETE 查询对象
+     * @param index  查询语句的索引
+     * @param sql    查询语句
+     * @param obj    WHERE 条件参数
+     */
+    @Override
+    protected void processDelete(Delete delete, int index, String sql, Object obj) {
+        Expression sqlSegment = dataPermissionHandler.getSqlSegment(delete.getWhere(), (String) obj, false);
+        if (null != sqlSegment) {
+            delete.setWhere(sqlSegment);
+        }
+    }
+
+    /**
+     * 设置 SELECT 语句的 WHERE 条件
+     *
+     * @param plainSelect       SELECT 查询对象
+     * @param mappedStatementId 映射语句的 ID
+     */
+    protected void setWhere(PlainSelect plainSelect, String mappedStatementId) {
+        Expression sqlSegment = dataPermissionHandler.getSqlSegment(plainSelect.getWhere(), mappedStatementId, true);
+        if (null != sqlSegment) {
+            plainSelect.setWhere(sqlSegment);
+        }
+    }
+
+    /**
+     * 构建表达式,用于处理表的数据权限
+     *
+     * @param table        表对象
+     * @param where        WHERE 条件表达式
+     * @param whereSegment WHERE 条件片段
+     * @return 构建的表达式
+     */
+    @Override
+    public Expression buildTableExpression(Table table, Expression where, String whereSegment) {
+        // 只有新版数据权限处理器才会执行到这里
+        final MultiDataPermissionHandler handler = (MultiDataPermissionHandler) dataPermissionHandler;
+        return handler.getSqlSegment(table, where, whereSegment);
+    }
+}
+

+ 1 - 1
ruoyi-common/ruoyi-common-mybatis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

@@ -1 +1 @@
-org.ruoyi.common.mybatis.config.MybatisPlusConfig
+org.ruoyi.config.MybatisPlusConfig

+ 33 - 0
ruoyi-common/ruoyi-common-mybatis/src/main/resources/common-mybatis.yml

@@ -0,0 +1,33 @@
+# 内置配置 不允许修改 如需修改请在 nacos 上写相同配置覆盖
+# MyBatisPlus配置
+# https://baomidou.com/config/
+mybatis-plus:
+  # 启动时是否检查 MyBatis XML 文件的存在,默认不检查
+  checkConfigLocation: false
+  configuration:
+    # 自动驼峰命名规则(camel case)映射
+    mapUnderscoreToCamelCase: true
+    # MyBatis 自动映射策略
+    # NONE:不启用 PARTIAL:只对非嵌套 resultMap 自动映射 FULL:对所有 resultMap 自动映射
+    autoMappingBehavior: FULL
+    # MyBatis 自动映射时未知列或未知属性处理策
+    # NONE:不做处理 WARNING:打印相关警告 FAILING:抛出异常和详细信息
+    autoMappingUnknownColumnBehavior: NONE
+    # 更详细的日志输出 会有性能损耗 org.apache.ibatis.logging.stdout.StdOutImpl
+    # 关闭日志记录 (可单纯使用 p6spy 分析) org.apache.ibatis.logging.nologging.NoLoggingImpl
+    # 默认日志输出 org.apache.ibatis.logging.slf4j.Slf4jImpl
+    logImpl: org.apache.ibatis.logging.nologging.NoLoggingImpl
+  global-config:
+    # 是否打印 Logo banner
+    banner: true
+    dbConfig:
+      # 主键类型
+      # AUTO 自增 NONE 空 INPUT 用户输入 ASSIGN_ID 雪花 ASSIGN_UUID 唯一 UUID
+      idType: ASSIGN_ID
+      # 逻辑已删除值(可按需求随意修改)
+      logicDeleteValue: 1
+      # 逻辑未删除值
+      logicNotDeleteValue: 0
+      insertStrategy: NOT_NULL
+      updateStrategy: NOT_NULL
+      whereStrategy: NOT_NULL

+ 20 - 0
ruoyi-common/ruoyi-common-mybatis/src/main/resources/spy.properties

@@ -0,0 +1,20 @@
+# p6spy 性能分析插件配置文件
+modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory
+# 自定义日志打印
+logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger
+#日志输出到控制台
+appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
+# 使用日志系统记录 sql
+#appender=com.p6spy.engine.spy.appender.Slf4JLogger
+# 取消JDBC URL前缀
+useprefix=true
+# 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset.
+excludecategories=info,debug,result,commit,resultset
+# 日期格式
+dateformat=yyyy-MM-dd HH:mm:ss
+# SQL语句打印时间格式
+databaseDialectTimestampFormat=yyyy-MM-dd HH:mm:ss
+# 是否过滤 Log
+filter=true
+# 过滤 Log 时所排除的 sql 关键字,以逗号分隔
+exclude=

+ 13 - 18
ruoyi-common/ruoyi-common-pay/pom.xml

@@ -11,38 +11,33 @@
     </parent>
 
     <artifactId>ruoyi-common-pay</artifactId>
+
+    <properties>
+        <maven.compiler.source>17</maven.compiler.source>
+        <maven.compiler.target>17</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <zxing.version>3.3.3</zxing.version>
+        <stripe.version>22.5.1</stripe.version>
+    </properties>
     <dependencies>
+
         <dependency>
             <groupId>org.ruoyi</groupId>
             <artifactId>ruoyi-common-web</artifactId>
         </dependency>
+
         <dependency>
             <groupId>com.google.zxing</groupId>
             <artifactId>core</artifactId>
-            <version>3.3.3</version>
-        </dependency>
-        <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-test</artifactId>
-            <scope>test</scope>
+            <version>${zxing.version}</version>
         </dependency>
 
+
         <dependency>
             <groupId>com.stripe</groupId>
             <artifactId>stripe-java</artifactId>
-            <version>22.5.1</version>
-        </dependency>
-
-        <dependency>
-            <groupId>cn.hutool</groupId>
-            <artifactId>hutool-all</artifactId>
-            <version>5.8.12</version>
+            <version>${stripe.version}</version>
         </dependency>
     </dependencies>
-    <properties>
-        <maven.compiler.source>17</maven.compiler.source>
-        <maven.compiler.target>17</maven.compiler.target>
-        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
-    </properties>
 
 </project>

+ 0 - 2
ruoyi-common/ruoyi-common-pay/src/main/java/org/ruoyi/common/config/PayInit.java

@@ -23,7 +23,6 @@ public class PayInit {
     private PayConfig payConfig;
 
     @Bean
-    @Scope("singleton")
     public PayConfig payConfig() {
         if (payConfig == null) {
             payConfig = new PayConfig();
@@ -32,7 +31,6 @@ public class PayInit {
         return payConfig;
     }
 
-    @Scheduled(fixedDelay = 10000)  // 每10秒检查一次
     public void updatePayConfig() {
         payConfig.setType("wxpay");
         payConfig.setDevice("pc");

+ 12 - 0
ruoyi-common/ruoyi-common-satoken/src/main/java/org/ruoyi/common/satoken/utils/LoginHelper.java

@@ -166,4 +166,16 @@ public class LoginHelper {
         return isTenantAdmin(getLoginUser().getRolePermission());
     }
 
+    /**
+     * 检查当前用户是否已登录
+     *
+     * @return 结果
+     */
+    public static boolean isLogin() {
+        try {
+            return getLoginUser() != null;
+        } catch (Exception e) {
+            return false;
+        }
+    }
 }

+ 2 - 1
ruoyi-common/ruoyi-common-tenant/src/main/java/org/ruoyi/common/tenant/config/TenantConfig.java

@@ -6,7 +6,7 @@ import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
 import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
 import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
 import org.ruoyi.common.core.utils.reflect.ReflectUtils;
-import org.ruoyi.common.mybatis.config.MybatisPlusConfig;
+
 import org.ruoyi.common.redis.config.RedisConfig;
 import org.ruoyi.common.redis.config.properties.RedissonProperties;
 import org.ruoyi.common.tenant.core.TenantSaTokenDao;
@@ -17,6 +17,7 @@ import org.ruoyi.common.tenant.properties.TenantProperties;
 import org.redisson.config.ClusterServersConfig;
 import org.redisson.config.SingleServerConfig;
 import org.redisson.spring.starter.RedissonAutoConfigurationCustomizer;
+import org.ruoyi.config.MybatisPlusConfig;
 import org.springframework.boot.autoconfigure.AutoConfiguration;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;

+ 2 - 1
ruoyi-common/ruoyi-common-tenant/src/main/java/org/ruoyi/common/tenant/core/TenantEntity.java

@@ -2,7 +2,8 @@ package org.ruoyi.common.tenant.core;
 
 import lombok.Data;
 import lombok.EqualsAndHashCode;
-import org.ruoyi.common.mybatis.core.domain.BaseEntity;
+import org.ruoyi.core.domain.BaseEntity;
+
 
 /**
  * 租户基类

+ 35 - 0
ruoyi-common/ruoyi-common-tenant/src/main/java/org/ruoyi/common/tenant/helper/TenantHelper.java

@@ -94,6 +94,27 @@ public class TenantHelper {
         SaHolder.getStorage().set(cacheKey, tenantId);
     }
 
+    /**
+     * 设置动态租户(一直有效 需要手动清理)
+     * <p>
+     * 如果为未登录状态下 那么只在当前线程内生效
+     *
+     * @param tenantId 租户id
+     * @param global   是否全局生效
+     */
+    public static void setDynamic(String tenantId, boolean global) {
+        if (!isEnable()) {
+            return;
+        }
+        if (!LoginHelper.isLogin() || !global) {
+            TEMP_DYNAMIC_TENANT.set(tenantId);
+            return;
+        }
+        String cacheKey = DYNAMIC_TENANT_KEY + ":" + LoginHelper.getUserId();
+        RedisUtils.setCacheObject(cacheKey, tenantId);
+        SaHolder.getStorage().set(cacheKey, tenantId);
+    }
+
     /**
      * 获取动态租户(一直有效 需要手动清理)
      * <p>
@@ -137,4 +158,18 @@ public class TenantHelper {
         return tenantId;
     }
 
+    /**
+     * 在动态租户中执行
+     *
+     * @param handle 处理执行方法
+     */
+    public static void dynamic(String tenantId, Runnable handle) {
+        setDynamic(tenantId);
+        try {
+            handle.run();
+        } finally {
+            clearDynamic();
+        }
+    }
+
 }

+ 0 - 26
ruoyi-common/ruoyi-common-web/src/main/java/org/ruoyi/common/web/interceptor/PlusWebInvokeTimeInterceptor.java

@@ -36,20 +36,6 @@ public class PlusWebInvokeTimeInterceptor implements HandlerInterceptor {
         String url = request.getMethod() + " " + request.getRequestURI();
         String domainName =  request.getServerName();
         log.info("域名信息:{}",domainName);
-
-        String requestURI = request.getRequestURI();
-        List<String> urls = whitelistUrls();
-        boolean isWhitelisted = urls.stream().anyMatch(requestURI::startsWith);
-
-        if (!isWhitelisted){
-            // 根据授权编号查询激活状态
-//            ConfigService configService = SpringUtils.context().getBean(ConfigService.class);
-//            String authNo = configService.getConfigValue("sys", "authcode");
-//            if(!configService.checkAuth(authNo,domainName)){
-//                throw new BaseException("系统未激活,请联系管理员授权");
-//            }
-        }
-
         // 打印请求参数
         if (isJsonRequest(request)) {
             String jsonParam = "";
@@ -67,7 +53,6 @@ public class PlusWebInvokeTimeInterceptor implements HandlerInterceptor {
                 log.debug("[PLUS]开始请求 => URL[{}],无参数", url);
             }
         }
-
         StopWatch stopWatch = new StopWatch();
         invokeTimeTL.set(stopWatch);
         stopWatch.start();
@@ -99,15 +84,4 @@ public class PlusWebInvokeTimeInterceptor implements HandlerInterceptor {
         }
         return false;
     }
-
-    // 授权白名单
-    public List<String> whitelistUrls() {
-        return Arrays.asList(
-            "/chat/config",
-            "/pay",
-            "/weixin",
-            "/user/qrcode",
-            "/user/login/qrcode"
-        );
-    }
 }

+ 7 - 0
ruoyi-common/ruoyi-common-wechat/pom.xml

@@ -8,9 +8,11 @@
         <version>${revision}</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
+
     <artifactId>ruoyi-common-wechat</artifactId>
     <version>1.0.0</version>
     <description>ruoyi-common-wechat 微信服务</description>
+
     <properties>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
     </properties>
@@ -21,6 +23,7 @@
             <artifactId>jfinal</artifactId>
             <version>3.5</version>
         </dependency>
+
         <dependency>
             <groupId>com.jfinal</groupId>
             <artifactId>cos</artifactId>
@@ -44,16 +47,19 @@
             <artifactId>emoji-java</artifactId>
             <version>3.2.0</version>
         </dependency>
+
         <dependency>
             <groupId>javax.activation</groupId>
             <artifactId>activation</artifactId>
             <version>1.1.1</version>
         </dependency>
+
         <dependency>
             <groupId>net.mamoe</groupId>
             <artifactId>mirai-core-jvm</artifactId>
             <version>2.16.0</version>
         </dependency>
+
         <dependency>
             <groupId>org.ruoyi</groupId>
             <artifactId>ruoyi-common-json</artifactId>
@@ -64,5 +70,6 @@
             <artifactId>fastjson</artifactId>
             <version>1.2.31</version>
         </dependency>
+
     </dependencies>
 </project>

+ 1 - 1
ruoyi-common/ruoyi-common-wechat/src/main/java/org/ruoyi/common/wechat/web/enums/KeyMsgValueType.java

@@ -3,7 +3,7 @@ package org.ruoyi.common.wechat.web.enums;
 import com.jfinal.plugin.activerecord.Record;
 
 import java.util.*;
-public enum KeyMsgValueType {
+public enum  KeyMsgValueType {
 
     IMG("IMG", "图片"),
     FILE("FILE", "文件"),

+ 20 - 0
ruoyi-extend/pom.xml

@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 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>
+
+    <artifactId>ruoyi-extend</artifactId>
+    <packaging>pom</packaging>
+
+    <modules>
+        <module>ruoyi-mcp-server</module>
+    </modules>
+
+</project>

+ 76 - 0
ruoyi-extend/ruoyi-mcp-server/pom.xml

@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <version>3.4.4</version>
+        <relativePath/> <!-- lookup parent from repository -->
+    </parent>
+    <groupId>org.ruoyi</groupId>
+    <artifactId>ruoyi-mcp-server</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+    <name>ruoyi-mcp-serve</name>
+    <description>ruoyi-mcp-serve</description>
+    <url/>
+    <licenses>
+        <license/>
+    </licenses>
+    <developers>
+        <developer/>
+    </developers>
+    <scm>
+        <connection/>
+        <developerConnection/>
+        <tag/>
+        <url/>
+    </scm>
+    <properties>
+        <java.version>17</java.version>
+        <spring-ai.version>1.0.0-M7</spring-ai.version>
+    </properties>
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.ai</groupId>
+            <artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+
+
+
+
+    </dependencies>
+    <dependencyManagement>
+        <dependencies>
+            <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>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

+ 25 - 0
ruoyi-extend/ruoyi-mcp-server/src/main/java/org/ruoyi/mcpserve/RuoyiMcpServeApplication.java

@@ -0,0 +1,25 @@
+package org.ruoyi.mcpserve;
+
+import org.ruoyi.mcpserve.service.ToolService;
+import org.springframework.ai.tool.ToolCallbackProvider;
+import org.springframework.ai.tool.method.MethodToolCallbackProvider;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Bean;
+
+/**
+ * @author ageer
+ */
+@SpringBootApplication
+public class RuoyiMcpServeApplication {
+
+    public static void main(String[] args) {
+        SpringApplication.run(RuoyiMcpServeApplication.class, args);
+    }
+
+    @Bean
+    public ToolCallbackProvider systemTools(ToolService toolService) {
+        return MethodToolCallbackProvider.builder().toolObjects(toolService).build();
+    }
+
+}

+ 34 - 0
ruoyi-extend/ruoyi-mcp-server/src/main/java/org/ruoyi/mcpserve/service/ToolService.java

@@ -0,0 +1,34 @@
+package org.ruoyi.mcpserve.service;
+
+import org.springframework.ai.tool.annotation.Tool;
+import org.springframework.ai.tool.annotation.ToolParam;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.UUID;
+
+
+/**
+ * @author ageer
+ */
+@Service
+public class ToolService {
+
+    @Tool(description = "获取一个指定前缀的随机数")
+    public String add(@ToolParam(description = "字符前缀") String prefix) {
+        // 定义日期格式
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyMMdd");
+        //根据当前时间获取yyMMdd格式的时间字符串
+        String format = LocalDate.now().format(formatter);
+        //生成随机数
+        String replace = prefix + UUID.randomUUID().toString().replace("-", "");
+        return format + replace;
+    }
+
+    @Tool(description = "获取当前时间")
+    public LocalDateTime getCurrentTime() {
+        return LocalDateTime.now();
+    }
+}

+ 10 - 0
ruoyi-extend/ruoyi-mcp-server/src/main/resources/application.yml

@@ -0,0 +1,10 @@
+server:
+  port: 8081
+spring:
+  ai:
+    mcp:
+      server:
+        name: ruoyi-mcp-serve
+        version: 1.0.0
+
+

+ 85 - 0
ruoyi-modules-api/pom.xml

@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 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>
+
+    <artifactId>ruoyi-modules-api</artifactId>
+    <packaging>pom</packaging>
+
+    <modules>
+        <module>ruoyi-chat-api</module>
+        <module>ruoyi-knowledge-api</module>
+        <module>ruoyi-system-api</module>
+    </modules>
+
+    <properties>
+        <maven.compiler.source>17</maven.compiler.source>
+        <maven.compiler.target>17</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+    <dependencies>
+        <!-- 系统核心模块 -->
+        <dependency>
+            <groupId>org.ruoyi</groupId>
+            <artifactId>ruoyi-common-core</artifactId>
+        </dependency>
+
+        <!-- mybaits基础模块 -->
+        <dependency>
+            <groupId>org.ruoyi</groupId>
+            <artifactId>ruoyi-common-mybatis</artifactId>
+        </dependency>
+
+        <!-- 脱敏模块 -->
+        <dependency>
+            <groupId>org.ruoyi</groupId>
+            <artifactId>ruoyi-common-sensitive</artifactId>
+        </dependency>
+
+        <!-- excel模块-->
+        <dependency>
+            <groupId>org.ruoyi</groupId>
+            <artifactId>ruoyi-common-excel</artifactId>
+        </dependency>
+
+        <!-- 租户基础模块 -->
+        <dependency>
+            <groupId>org.ruoyi</groupId>
+            <artifactId>ruoyi-common-tenant</artifactId>
+        </dependency>
+
+        <!-- 字段翻译基础模块 -->
+        <dependency>
+            <groupId>org.ruoyi</groupId>
+            <artifactId>ruoyi-common-translation</artifactId>
+        </dependency>
+
+        <!-- 系统日志模块 -->
+        <dependency>
+            <groupId>org.ruoyi</groupId>
+            <artifactId>ruoyi-common-log</artifactId>
+        </dependency>
+
+        <!-- 对象存储模块 -->
+        <dependency>
+            <groupId>org.ruoyi</groupId>
+            <artifactId>ruoyi-common-oss</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+        </dependency>
+
+
+    </dependencies>
+
+</project>

+ 76 - 0
ruoyi-modules-api/ruoyi-chat-api/pom.xml

@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.ruoyi</groupId>
+        <artifactId>ruoyi-modules-api</artifactId>
+        <version>${revision}</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>ruoyi-chat-api</artifactId>
+
+    <properties>
+        <maven.compiler.source>17</maven.compiler.source>
+        <maven.compiler.target>17</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <spring-ai.version>1.0.0-M7</spring-ai.version>
+    </properties>
+
+    <dependencyManagement>
+        <dependencies>
+            <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>
+
+<!--        <dependency>-->
+<!--            <groupId>io.modelcontextprotocol.sdk</groupId>-->
+<!--            <artifactId>mcp-spring-webflux</artifactId>-->
+<!--            <version>0.8.0</version>-->
+<!--        </dependency>-->
+
+        <dependency>
+            <groupId>org.ruoyi</groupId>
+            <artifactId>ruoyi-common-chat</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.ai</groupId>
+            <artifactId>spring-ai-starter-mcp-client</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.ai</groupId>
+            <artifactId>spring-ai-starter-model-openai</artifactId>
+        </dependency>
+
+    </dependencies>
+
+</project>

+ 86 - 0
ruoyi-modules-api/ruoyi-chat-api/src/main/java/org/ruoyi/domain/ChatAgentManage.java

@@ -0,0 +1,86 @@
+package org.ruoyi.domain;
+
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.ruoyi.core.domain.BaseEntity;
+
+import java.io.Serial;
+
+/**
+ * 智能体管理对象 chat_agent_manage
+ *
+ * @author ageerle
+ * @date 2025-04-08
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("chat_agent_manage")
+public class ChatAgentManage extends BaseEntity {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键id
+     */
+    @TableId(value = "id")
+    private Long id;
+
+    /**
+     * 应用名称
+     */
+    private String appName;
+
+    /**
+     * 应用类型
+     */
+    private String appType;
+
+    /**
+     * 应用头像
+     */
+    private String appIcon;
+
+    /**
+     * 应用描述
+     */
+    private String appDescription;
+
+    /**
+     * 开场介绍
+     */
+    private String introduction;
+
+    /**
+     * 模型
+     */
+    private String model;
+
+    /**
+     * 对话可选模型
+     */
+    private String conversationModel;
+
+    /**
+     * 应用设定
+     */
+    private String applicationSettings;
+
+    /**
+     * 插件id
+     */
+    private String pluginId;
+
+    /**
+     * 知识库id
+     */
+    private Long knowledgeId;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+
+}

+ 9 - 11
ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/ChatAppStore.java → ruoyi-modules-api/ruoyi-chat-api/src/main/java/org/ruoyi/domain/ChatAppStore.java

@@ -1,18 +1,17 @@
-package org.ruoyi.system.domain;
+package org.ruoyi.domain;
 
-import com.baomidou.mybatisplus.annotation.TableId;
-import com.baomidou.mybatisplus.annotation.TableName;
-import org.ruoyi.common.mybatis.core.domain.BaseEntity;
+import com.baomidou.mybatisplus.annotation.*;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
+import org.ruoyi.core.domain.BaseEntity;
 
 import java.io.Serial;
 
 /**
- * 应用市场
+ * 应用商店对象 chat_app_store
  *
- * @author Lion Li
- * @date 2024-03-19
+ * @author ageerle
+ * @date 2025-04-08
  */
 @Data
 @EqualsAndHashCode(callSuper = true)
@@ -23,7 +22,7 @@ public class ChatAppStore extends BaseEntity {
     private static final long serialVersionUID = 1L;
 
     /**
-     * 主键
+     * id
      */
     @TableId(value = "id")
     private Long id;
@@ -39,16 +38,15 @@ public class ChatAppStore extends BaseEntity {
     private String description;
 
     /**
-     * 头像
+     * logo
      */
     private String avatar;
 
     /**
-     * 应用地址
+     * 地址
      */
     private String appUrl;
 
-
     /**
      * 备注
      */

+ 8 - 21
ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/ChatGpts.java → ruoyi-modules-api/ruoyi-chat-api/src/main/java/org/ruoyi/domain/ChatGpts.java

@@ -1,20 +1,17 @@
-package org.ruoyi.system.domain;
+package org.ruoyi.domain;
 
-import com.baomidou.mybatisplus.annotation.TableId;
-import com.baomidou.mybatisplus.annotation.TableLogic;
-import com.baomidou.mybatisplus.annotation.TableName;
-import com.baomidou.mybatisplus.annotation.Version;
+import com.baomidou.mybatisplus.annotation.*;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
-import org.ruoyi.common.mybatis.core.domain.BaseEntity;
+import org.ruoyi.core.domain.BaseEntity;
 
 import java.io.Serial;
 
 /**
- * gpts管理对象 chat_gpts
+ * 应用管理对象 chat_gpts
  *
- * @author Lion Li
- * @date 2024-07-09
+ * @author ageerle
+ * @date 2025-04-08
  */
 @Data
 @EqualsAndHashCode(callSuper = true)
@@ -63,12 +60,12 @@ public class ChatGpts extends BaseEntity {
     /**
      * 点赞
      */
-    private String useCnt;
+    private Long useCnt;
 
     /**
      * 差评
      */
-    private String bad;
+    private Long bad;
 
     /**
      * 类型
@@ -97,15 +94,5 @@ public class ChatGpts extends BaseEntity {
      */
     private String updateIp;
 
-    /**
-     * 模型名称
-     */
-    private String modelName;
-
-
-    /**
-     * 模型system
-     */
-    private String systemPrompt;
 
 }

+ 68 - 0
ruoyi-modules-api/ruoyi-chat-api/src/main/java/org/ruoyi/domain/ChatMessage.java

@@ -0,0 +1,68 @@
+package org.ruoyi.domain;
+
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.ruoyi.core.domain.BaseEntity;
+
+import java.math.BigDecimal;
+
+import java.io.Serial;
+
+/**
+ * 聊天消息对象 chat_message
+ *
+ * @author ageerle
+ * @date 2025-04-08
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("chat_message")
+public class ChatMessage extends BaseEntity {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键
+     */
+    @TableId(value = "id")
+    private Long id;
+
+    /**
+     * 用户id
+     */
+    private Long userId;
+
+    /**
+     * 消息内容
+     */
+    private String content;
+
+    /**
+     * 对话角色
+     */
+    private String role;
+
+    /**
+     * 扣除金额
+     */
+    private BigDecimal deductCost;
+
+    /**
+     * 累计 Tokens
+     */
+    private Long totalTokens;
+
+    /**
+     * 模型名称
+     */
+    private String modelName;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+
+}

+ 10 - 12
ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysModel.java → ruoyi-modules-api/ruoyi-chat-api/src/main/java/org/ruoyi/domain/ChatModel.java

@@ -1,24 +1,23 @@
-package org.ruoyi.system.domain;
+package org.ruoyi.domain;
 
-import com.alibaba.excel.annotation.ExcelProperty;
-import com.baomidou.mybatisplus.annotation.TableId;
-import com.baomidou.mybatisplus.annotation.TableName;
+
+import com.baomidou.mybatisplus.annotation.*;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
-import org.ruoyi.common.mybatis.core.domain.BaseEntity;
+import org.ruoyi.core.domain.BaseEntity;
 
 import java.io.Serial;
 
 /**
- * 系统模型对象 sys_model
+ * 聊天模型对象 chat_model
  *
- * @author Lion Li
- * @date 2024-04-04
+ * @author ageerle
+ * @date 2025-04-08
  */
 @Data
 @EqualsAndHashCode(callSuper = true)
 @TableName("chat_model")
-public class SysModel extends BaseEntity {
+public class ChatModel extends BaseEntity {
 
     @Serial
     private static final long serialVersionUID = 1L;
@@ -32,7 +31,6 @@ public class SysModel extends BaseEntity {
     /**
      * 模型分类
      */
-    @ExcelProperty(value = "模型分类")
     private String category;
 
     /**
@@ -48,7 +46,7 @@ public class SysModel extends BaseEntity {
     /**
      * 模型价格
      */
-    private double modelPrice;
+    private Long modelPrice;
 
     /**
      * 计费类型
@@ -60,7 +58,6 @@ public class SysModel extends BaseEntity {
      */
     private String modelShow;
 
-
     /**
      * 系统提示词
      */
@@ -81,4 +78,5 @@ public class SysModel extends BaseEntity {
      */
     private String remark;
 
+
 }

+ 9 - 9
ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/SysPackagePlan.java → ruoyi-modules-api/ruoyi-chat-api/src/main/java/org/ruoyi/domain/ChatPackagePlan.java

@@ -1,24 +1,24 @@
-package org.ruoyi.system.domain;
+package org.ruoyi.domain;
 
-import com.baomidou.mybatisplus.annotation.TableId;
-import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.annotation.*;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
-import org.ruoyi.common.mybatis.core.domain.BaseEntity;
+import org.ruoyi.core.domain.BaseEntity;
 
-import java.io.Serial;
 import java.math.BigDecimal;
 
+import java.io.Serial;
+
 /**
- * 套餐管理对象 sys_package_plan
+ * 套餐管理对象 chat_package_plan
  *
- * @author Lion Li
- * @date 2024-05-05
+ * @author ageerle
+ * @date 2025-04-08
  */
 @Data
 @EqualsAndHashCode(callSuper = true)
 @TableName("chat_package_plan")
-public class SysPackagePlan extends BaseEntity {
+public class ChatPackagePlan extends BaseEntity {
 
     @Serial
     private static final long serialVersionUID = 1L;

+ 7 - 6
ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/PaymentOrder.java → ruoyi-modules-api/ruoyi-chat-api/src/main/java/org/ruoyi/domain/ChatPayOrder.java

@@ -1,23 +1,24 @@
-package org.ruoyi.system.domain;
+package org.ruoyi.domain;
 
 import com.baomidou.mybatisplus.annotation.*;
-import org.ruoyi.common.mybatis.core.domain.BaseEntity;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
+import org.ruoyi.core.domain.BaseEntity;
+
 import java.math.BigDecimal;
 
 import java.io.Serial;
 
 /**
- * 支付订单对象 payment_orders
+ * 支付订单对象 chat_pay_order
  *
- * @author Lion Li
- * @date 2024-04-16
+ * @author ageerle
+ * @date 2025-04-08
  */
 @Data
 @EqualsAndHashCode(callSuper = true)
 @TableName("chat_pay_order")
-public class PaymentOrder extends BaseEntity {
+public class ChatPayOrder extends BaseEntity {
 
     @Serial
     private static final long serialVersionUID = 1L;

+ 3 - 3
ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/ChatPlugin.java → ruoyi-modules-api/ruoyi-chat-api/src/main/java/org/ruoyi/domain/ChatPlugin.java

@@ -1,9 +1,9 @@
-package org.ruoyi.system.domain;
+package org.ruoyi.domain;
 
 import com.baomidou.mybatisplus.annotation.*;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
-import org.ruoyi.common.mybatis.core.domain.BaseEntity;
+import org.ruoyi.core.domain.BaseEntity;
 
 import java.io.Serial;
 
@@ -11,7 +11,7 @@ import java.io.Serial;
  * 插件管理对象 chat_plugin
  *
  * @author ageerle
- * @date 2025-03-30
+ * @date 2025-04-08
  */
 @Data
 @EqualsAndHashCode(callSuper = true)

+ 14 - 14
ruoyi-modules/ruoyi-system/src/main/java/org/ruoyi/system/domain/WxRobConfig.java → ruoyi-modules-api/ruoyi-chat-api/src/main/java/org/ruoyi/domain/ChatRobConfig.java

@@ -1,22 +1,22 @@
-package org.ruoyi.system.domain;
+package org.ruoyi.domain;
 
 import com.baomidou.mybatisplus.annotation.*;
-import org.ruoyi.common.mybatis.core.domain.BaseEntity;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
+import org.ruoyi.core.domain.BaseEntity;
 
 import java.io.Serial;
 
 /**
- * 微信机器人对象 wx_rob_config
+ * 聊天机器人配置对象 chat_rob_config
  *
- * @author Lion Li
- * @date 2024-05-01
+ * @author ageerle
+ * @date 2025-04-08
  */
 @Data
 @EqualsAndHashCode(callSuper = true)
 @TableName("chat_rob_config")
-public class WxRobConfig extends BaseEntity {
+public class ChatRobConfig extends BaseEntity {
 
     @Serial
     private static final long serialVersionUID = 1L;
@@ -28,7 +28,7 @@ public class WxRobConfig extends BaseEntity {
     private Long id;
 
     /**
-     * 用户id
+     * 所属用户
      */
     private Long userId;
 
@@ -42,11 +42,6 @@ public class WxRobConfig extends BaseEntity {
      */
     private String uniqueKey;
 
-    /**
-     * 备注(微信号)
-     */
-    private String remark;
-
     /**
      * 默认好友回复开关
      */
@@ -57,10 +52,15 @@ public class WxRobConfig extends BaseEntity {
      */
     private String defaultGroup;
 
-
     /**
-     * 机器启用1禁用0
+     * 机器人状态  0正常 1启用
      */
     private String enable;
 
+    /**
+     * 备注
+     */
+    private String remark;
+
+
 }

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