mirror of
https://gitee.com/zhijiantianya/ruoyi-vue-pro.git
synced 2026-06-30 02:51:59 +08:00
Compare commits
80 Commits
feature/im
...
master-jdk
| Author | SHA1 | Date | |
|---|---|---|---|
| fa66aca3cf | |||
| 74aab657c3 | |||
| 74c88fd90e | |||
| b3a8a8f158 | |||
| 7caaf3e543 | |||
| 2d3d539a46 | |||
| aad44c2d60 | |||
| 6dcd00f197 | |||
| 6850555c19 | |||
| 137b30b72f | |||
| 0f10fcc0c4 | |||
| 7df6262b8a | |||
| d56607e0aa | |||
| dafa4dadc7 | |||
| 4227de4b9b | |||
| 6c643f57d1 | |||
| a70c1cb1b4 | |||
| 88bee9c4e3 | |||
| a763646e7f | |||
| 66c6ae22ab | |||
| e3c9e7ebdd | |||
| a0084223ed | |||
| 13a5631804 | |||
| 3550f099bd | |||
| 38970d07b7 | |||
| dc0bda7b97 | |||
| c20c828b50 | |||
| 76753b32c9 | |||
| 7e35f1dab9 | |||
| 9705ce1195 | |||
| a25dd36be6 | |||
| 4f3701b47c | |||
| b17432dfa2 | |||
| 6bd737d291 | |||
| 10a40f21fa | |||
| defab9fa3b | |||
| f08eefe1cf | |||
| 64cd8c3cbb | |||
| 501c658a3c | |||
| 78b4c814f7 | |||
| 86eae989f9 | |||
| a7abc52f5e | |||
| 6eac16b0a3 | |||
| 478a41c3f7 | |||
| c98fd9555b | |||
| 6742cd0085 | |||
| 1b5a63ef4b | |||
| da36b1bf8f | |||
| 6ade5c35c6 | |||
| da2021edd2 | |||
| a60accfc2f | |||
| ea4fa68579 | |||
| 1cc3483aa0 | |||
| 253d508cd4 | |||
| 210a93e8c8 | |||
| 8ffb627c82 | |||
| 6ff3174270 | |||
| c24a3a02c2 | |||
| c48f52ec9a | |||
| cb015642df | |||
| f6b5ef06d9 | |||
| adbec22e95 | |||
| f587d4f756 | |||
| 93eabb4efd | |||
| ca367c376f | |||
| 296eb22b68 | |||
| ca815b4c30 | |||
| c779a47661 | |||
| 5d1fd70dc3 | |||
| 57f575bf2e | |||
| bb538ba4f8 | |||
| d9b320f29f | |||
| cd47e9dda6 | |||
| 1ef9ab67ae | |||
| 7965212859 | |||
| cb3bebfbd8 | |||
| 047196f762 | |||
| 72332d53d4 | |||
| cd8f7a8ed8 | |||
| f41d61c654 |
2
.gitignore
vendored
2
.gitignore
vendored
@ -52,4 +52,4 @@ application-my.yaml
|
||||
|
||||
/yudao-ui-app/unpackage/
|
||||
.DS_Store
|
||||
**/.DS_Store
|
||||
**/.DS_Store
|
||||
62
README.md
62
README.md
@ -1,5 +1,5 @@
|
||||
<p align="center">
|
||||
<img src="https://img.shields.io/badge/Spring%20Boot-3.4.5-blue.svg" alt="Downloads">
|
||||
<img src="https://img.shields.io/badge/Spring%20Boot-4.1.0-blue.svg" alt="Downloads">
|
||||
<img src="https://img.shields.io/badge/Vue-3.2-blue.svg" alt="Downloads">
|
||||
<img src="https://img.shields.io/github/license/YunaiV/ruoyi-vue-pro" alt="Downloads" />
|
||||
</p>
|
||||
@ -23,10 +23,10 @@
|
||||
|
||||
## 🐰 版本说明
|
||||
|
||||
| 版本 | JDK 8 + Spring Boot 2.7 | JDK 17/21 + Spring Boot 3.2 |
|
||||
|---------------------------------------------------------------------|---------------------------------------------------------------------------|---------------------------------------------------------------------------------------|
|
||||
| 【完整版】[ruoyi-vue-pro](https://gitee.com/zhijiantianya/ruoyi-vue-pro) | [`master`](https://gitee.com/zhijiantianya/ruoyi-vue-pro/tree/master/) 分支 | [`master-jdk17`](https://gitee.com/zhijiantianya/ruoyi-vue-pro/tree/master-jdk17/) 分支 |
|
||||
| 【精简版】[yudao-boot-mini](https://gitee.com/yudaocode/yudao-boot-mini) | [`master`](https://gitee.com/yudaocode/yudao-boot-mini/tree/master/) 分支 | [`master-jdk17`](https://gitee.com/yudaocode/yudao-boot-mini/tree/master-jdk17/) 分支 |
|
||||
| 版本 | JDK 8 + Spring Boot 2.7 | JDK 17/21 + Spring Boot 3.5 | JDK 25 + Spring Boot 4.x |
|
||||
|---------------------------------------------------------------------|---------------------------------------------------------------------------|---------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------|
|
||||
| 【完整版】[ruoyi-vue-pro](https://gitee.com/zhijiantianya/ruoyi-vue-pro) | [`master`](https://gitee.com/zhijiantianya/ruoyi-vue-pro/tree/master/) 分支 | [`master-jdk17`](https://gitee.com/zhijiantianya/ruoyi-vue-pro/tree/master-jdk17/) 分支 | [`master-jdk25`](https://gitee.com/zhijiantianya/ruoyi-vue-pro/tree/master-jdk25/) 分支 |
|
||||
| 【精简版】[yudao-boot-mini](https://gitee.com/yudaocode/yudao-boot-mini) | [`master`](https://gitee.com/yudaocode/yudao-boot-mini/tree/master/) 分支 | [`master-jdk17`](https://gitee.com/yudaocode/yudao-boot-mini/tree/master-jdk17/) 分支 | [`master-jdk25`](https://gitee.com/yudaocode/yudao-boot-mini/tree/master-jdk25/) 分支 |
|
||||
|
||||
* 【完整版】:包括系统功能、基础设施、会员中心、数据报表、工作流程、商城系统、微信公众号、CRM、ERP、WMS、MES、IM 即时通讯、AI 大模型、IoT 物联网等功能
|
||||
* 【精简版】:只包括系统功能、基础设施功能,不包括会员中心、数据报表、工作流程、商城系统、微信公众号、CRM、ERP、WMS、MES、IM 即时通讯、AI 大模型、IoT 物联网等功能
|
||||
@ -43,7 +43,7 @@
|
||||
|
||||

|
||||
|
||||
* Java 后端:`master` 分支为 JDK 8 + Spring Boot 2.7,`master-jdk17` 分支为 JDK 17/21 + Spring Boot 3.2
|
||||
* Java 后端:`master` 分支为 JDK 8 + Spring Boot 2.7,`master-jdk17` 分支为 JDK 17/21 + Spring Boot 3.5,`master-jdk25` 分支为 JDK 25 + Spring Boot 4.x
|
||||
* 管理后台的电脑端:Vue3 提供 `element-plus`、`vben(ant-design-vue)` 两个版本,Vue2 提供 `element-ui` 版本
|
||||
* 管理后台的移动端:采用 `uni-app` 方案,一份代码多终端适配,同时支持 APP、小程序、H5!
|
||||
* 后端采用 Spring Boot 多模块架构、MySQL + MyBatis Plus、Redis + Redisson
|
||||
@ -350,42 +350,42 @@
|
||||
|
||||
| 框架 | 说明 | 版本 | 学习指南 |
|
||||
|---------------------------------------------------------------------------------------------|------------------|----------------|----------------------------------------------------------------|
|
||||
| [Spring Boot](https://spring.io/projects/spring-boot) | 应用开发框架 | 3.5.5 | [文档](https://github.com/YunaiV/SpringBoot-Labs) |
|
||||
| [Spring Boot](https://spring.io/projects/spring-boot) | 应用开发框架 | 4.1.0 | [文档](https://github.com/YunaiV/SpringBoot-Labs) |
|
||||
| [MySQL](https://www.mysql.com/cn/) | 数据库服务器 | 5.7 / 8.0+ | |
|
||||
| [Druid](https://github.com/alibaba/druid) | JDBC 连接池、监控组件 | 1.2.27 | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) |
|
||||
| [MyBatis Plus](https://mp.baomidou.com/) | MyBatis 增强工具包 | 3.5.12 | [文档](http://www.iocoder.cn/Spring-Boot/MyBatis/?yudao) |
|
||||
| [Dynamic Datasource](https://dynamic-datasource.com/) | 动态数据源 | 4.3.1 | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) |
|
||||
| [Druid](https://github.com/alibaba/druid) | JDBC 连接池、监控组件 | 1.2.28 | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) |
|
||||
| [MyBatis Plus](https://mp.baomidou.com/) | MyBatis 增强工具包 | 3.5.16 | [文档](http://www.iocoder.cn/Spring-Boot/MyBatis/?yudao) |
|
||||
| [Dynamic Datasource](https://dynamic-datasource.com/) | 动态数据源 | 4.5.0 | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) |
|
||||
| [Redis](https://redis.io/) | key-value 数据库 | 5.0 / 6.0 /7.0 | |
|
||||
| [Redisson](https://github.com/redisson/redisson) | Redis 客户端 | 3.35.0 | [文档](http://www.iocoder.cn/Spring-Boot/Redis/?yudao) |
|
||||
| [Spring MVC](https://github.com/spring-projects/spring-framework/tree/master/spring-webmvc) | MVC 框架 | 6.2.9 | [文档](http://www.iocoder.cn/SpringMVC/MVC/?yudao) |
|
||||
| [Spring Security](https://github.com/spring-projects/spring-security) | Spring 安全框架 | 6.5.2 | [文档](http://www.iocoder.cn/Spring-Boot/Spring-Security/?yudao) |
|
||||
| [Hibernate Validator](https://github.com/hibernate/hibernate-validator) | 参数校验组件 | 8.0.2 | [文档](http://www.iocoder.cn/Spring-Boot/Validation/?yudao) |
|
||||
| [Flowable](https://github.com/flowable/flowable-engine) | 工作流引擎 | 7.0.0 | [文档](https://doc.iocoder.cn/bpm/) |
|
||||
| [Quartz](https://github.com/quartz-scheduler) | 任务调度组件 | 2.5.0 | [文档](http://www.iocoder.cn/Spring-Boot/Job/?yudao) |
|
||||
| [Springdoc](https://springdoc.org/) | Swagger 文档 | 2.8.9 | [文档](http://www.iocoder.cn/Spring-Boot/Swagger/?yudao) |
|
||||
| [SkyWalking](https://skywalking.apache.org/) | 分布式应用追踪系统 | 9.5.0 | [文档](http://www.iocoder.cn/Spring-Boot/SkyWalking/?yudao) |
|
||||
| [Spring Boot Admin](https://github.com/codecentric/spring-boot-admin) | Spring Boot 监控平台 | 3.5.2 | [文档](http://www.iocoder.cn/Spring-Boot/Admin/?yudao) |
|
||||
| [Jackson](https://github.com/FasterXML/jackson) | JSON 工具库 | 2.30.14 | |
|
||||
| [Redisson](https://github.com/redisson/redisson) | Redis 客户端 | 4.6.1 | [文档](http://www.iocoder.cn/Spring-Boot/Redis/?yudao) |
|
||||
| [Spring MVC](https://github.com/spring-projects/spring-framework/tree/master/spring-webmvc) | MVC 框架 | 7.0.8 | [文档](http://www.iocoder.cn/SpringMVC/MVC/?yudao) |
|
||||
| [Spring Security](https://github.com/spring-projects/spring-security) | Spring 安全框架 | 7.1.0 | [文档](http://www.iocoder.cn/Spring-Boot/Spring-Security/?yudao) |
|
||||
| [Hibernate Validator](https://github.com/hibernate/hibernate-validator) | 参数校验组件 | 9.1.0 | [文档](http://www.iocoder.cn/Spring-Boot/Validation/?yudao) |
|
||||
| [Flowable](https://github.com/flowable/flowable-engine) | 工作流引擎 | 8.0.0 | [文档](https://doc.iocoder.cn/bpm/) |
|
||||
| [Quartz](https://github.com/quartz-scheduler) | 任务调度组件 | 2.5.2 | [文档](http://www.iocoder.cn/Spring-Boot/Job/?yudao) |
|
||||
| [Springdoc](https://springdoc.org/) | Swagger 文档 | 3.0.3 | [文档](http://www.iocoder.cn/Spring-Boot/Swagger/?yudao) |
|
||||
| [SkyWalking](https://skywalking.apache.org/) | 分布式应用追踪系统 | 9.6.0 | [文档](http://www.iocoder.cn/Spring-Boot/SkyWalking/?yudao) |
|
||||
| [Spring Boot Admin](https://github.com/codecentric/spring-boot-admin) | Spring Boot 监控平台 | 4.0.4 | [文档](http://www.iocoder.cn/Spring-Boot/Admin/?yudao) |
|
||||
| [Jackson](https://github.com/FasterXML/jackson) | JSON 工具库 | 3.1.4 | |
|
||||
| [MapStruct](https://mapstruct.org/) | Java Bean 转换 | 1.6.3 | [文档](http://www.iocoder.cn/Spring-Boot/MapStruct/?yudao) |
|
||||
| [Lombok](https://projectlombok.org/) | 消除冗长的 Java 代码 | 1.18.38 | [文档](http://www.iocoder.cn/Spring-Boot/Lombok/?yudao) |
|
||||
| [JUnit](https://junit.org/junit5/) | Java 单元测试框架 | 5.12.2 | - |
|
||||
| [Mockito](https://github.com/mockito/mockito) | Java Mock 框架 | 5.17.0 | - |
|
||||
| [Lombok](https://projectlombok.org/) | 消除冗长的 Java 代码 | 1.18.46 | [文档](http://www.iocoder.cn/Spring-Boot/Lombok/?yudao) |
|
||||
| [JUnit](https://junit.org/junit5/) | Java 单元测试框架 | 6.0.3 | - |
|
||||
| [Mockito](https://github.com/mockito/mockito) | Java Mock 框架 | 5.23.0 | - |
|
||||
|
||||
## 🐷 演示图
|
||||
|
||||
### 系统功能
|
||||
|
||||
| 模块 | biu | biu | biu |
|
||||
|----------|-----------------------------|---------------------------|--------------------------|
|
||||
| 模块 | biu | biu | biu |
|
||||
|----------|-----------------------------|---------------------------|---------------------------|
|
||||
| 登录 & 首页 |  |  |  |
|
||||
| 用户 & 应用 |  |  |  |
|
||||
| 租户 & 套餐 |  |  | - |
|
||||
| 部门 & 岗位 |  |  | - |
|
||||
| 菜单 & 角色 |  |  | - |
|
||||
| 审计日志 |  |  | - |
|
||||
| 租户 & 套餐 |  |  | - |
|
||||
| 部门 & 岗位 |  |  | - |
|
||||
| 菜单 & 角色 |  |  | - |
|
||||
| 审计日志 |  |  | - |
|
||||
| 短信 |  |  |  |
|
||||
| 字典 & 敏感词 |  |  |  |
|
||||
| 错误码 & 通知 |  |  | - |
|
||||
| 字典 & 敏感词 |  |  |  |
|
||||
| 错误码 & 通知 |  |  | - |
|
||||
|
||||
### 工作流程
|
||||
|
||||
|
||||
14
pom.xml
14
pom.xml
@ -16,19 +16,19 @@
|
||||
<module>yudao-module-system</module>
|
||||
<module>yudao-module-infra</module>
|
||||
<!-- <module>yudao-module-member</module>-->
|
||||
<!-- <module>yudao-module-bpm</module>-->
|
||||
<module>yudao-module-bpm</module>
|
||||
<!-- <module>yudao-module-report</module>-->
|
||||
<!-- <module>yudao-module-mp</module>-->
|
||||
<!-- <module>yudao-module-pay</module>-->
|
||||
<!-- <module>yudao-module-mall</module>-->
|
||||
<!-- <module>yudao-module-crm</module>-->
|
||||
<!-- <module>yudao-module-erp</module>-->
|
||||
<module>yudao-module-erp</module>
|
||||
<!-- <module>yudao-module-iot</module>-->
|
||||
<!-- <module>yudao-module-mes</module>-->
|
||||
<!-- <module>yudao-module-wms</module>-->
|
||||
<!-- <module>yudao-module-im</module>-->
|
||||
<!-- 请参考 https://doc.iocoder.cn/ai/build/ 文档,完成 AI 模块的启动!!! -->
|
||||
<!-- <module>yudao-module-ai</module>-->
|
||||
<module>yudao-module-ai</module>
|
||||
</modules>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
@ -36,17 +36,17 @@
|
||||
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
|
||||
|
||||
<properties>
|
||||
<revision>2026.05-SNAPSHOT</revision>
|
||||
<revision>2026.05-jdk25-SNAPSHOT</revision>
|
||||
<!-- Maven 相关 -->
|
||||
<java.version>17</java.version>
|
||||
<java.version>25</java.version>
|
||||
<maven.compiler.source>${java.version}</maven.compiler.source>
|
||||
<maven.compiler.target>${java.version}</maven.compiler.target>
|
||||
<maven-surefire-plugin.version>3.5.3</maven-surefire-plugin.version>
|
||||
<maven-compiler-plugin.version>3.14.0</maven-compiler-plugin.version>
|
||||
<flatten-maven-plugin.version>1.7.2</flatten-maven-plugin.version>
|
||||
<!-- maven-surefire-plugin 暂时无法通过 bom 的依赖读取(兼容老版本 IDEA 2024 及以前版本) -->
|
||||
<lombok.version>1.18.42</lombok.version>
|
||||
<spring.boot.version>3.5.9</spring.boot.version>
|
||||
<lombok.version>1.18.46</lombok.version>
|
||||
<spring.boot.version>4.1.0</spring.boot.version>
|
||||
<mapstruct.version>1.6.3</mapstruct.version>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1371,6 +1371,8 @@ INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_t
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3034, 1, 'ttt', 'tt', 'iot_ota_task_record_status', 0, 'success', '', NULL, '1', '2025-09-06 00:02:21', '1', '2025-09-06 00:02:31', '0');
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3035, 40, '支付宝小程序', '40', 'system_social_type', 0, '', '', '', '1', '2023-11-04 13:05:38', '1', '2023-11-04 13:07:16', '0');
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3036, 60, 'Admin Uniapp 移动端', '60', 'infra_codegen_front_type', 0, '', '', NULL, '1', '2025-12-16 19:25:51', '1', '2025-12-17 09:46:15', '0');
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3037, 42, 'Vben5.0 Antdv Next Schema 模版', '42', 'infra_codegen_front_type', 0, '', '', '', '1', NOW(), '1', NOW(), '0');
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3038, 43, 'Vben5.0 Antdv Next 标准模版', '43', 'infra_codegen_front_type', 0, '', '', '', '1', NOW(), '1', NOW(), '0');
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3040, 1, 'UDP', 'udp', 'iot_protocol_type', 0, '', '', 'UDP 协议', '1', '2026-02-04 00:32:47', '1', '2026-02-04 00:32:47', '0');
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3041, 2, 'WebSocket', 'websocket', 'iot_protocol_type', 0, '', '', 'WebSocket 协议', '1', '2026-02-04 00:32:55', '1', '2026-02-04 00:32:55', '0');
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3042, 3, 'HTTP', 'http', 'iot_protocol_type', 0, '', '', 'HTTP 协议', '1', '2026-02-04 00:32:55', '1', '2026-02-04 00:32:55', '0');
|
||||
|
||||
@ -1371,6 +1371,8 @@ INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_t
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3034, 1, 'ttt', 'tt', 'iot_ota_task_record_status', 0, 'success', '', NULL, '1', '2025-09-06 00:02:21', '1', '2025-09-06 00:02:31', '0');
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3035, 40, '支付宝小程序', '40', 'system_social_type', 0, '', '', '', '1', '2023-11-04 13:05:38', '1', '2023-11-04 13:07:16', '0');
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3036, 60, 'Admin Uniapp 移动端', '60', 'infra_codegen_front_type', 0, '', '', NULL, '1', '2025-12-16 19:25:51', '1', '2025-12-17 09:46:15', '0');
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3037, 42, 'Vben5.0 Antdv Next Schema 模版', '42', 'infra_codegen_front_type', 0, '', '', '', '1', NOW(), '1', NOW(), '0');
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3038, 43, 'Vben5.0 Antdv Next 标准模版', '43', 'infra_codegen_front_type', 0, '', '', '', '1', NOW(), '1', NOW(), '0');
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3040, 1, 'UDP', 'udp', 'iot_protocol_type', 0, '', '', 'UDP 协议', '1', '2026-02-04 00:32:47', '1', '2026-02-04 00:32:47', '0');
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3041, 2, 'WebSocket', 'websocket', 'iot_protocol_type', 0, '', '', 'WebSocket 协议', '1', '2026-02-04 00:32:55', '1', '2026-02-04 00:32:55', '0');
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3042, 3, 'HTTP', 'http', 'iot_protocol_type', 0, '', '', 'HTTP 协议', '1', '2026-02-04 00:32:55', '1', '2026-02-04 00:32:55', '0');
|
||||
|
||||
@ -1323,6 +1323,8 @@ INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_t
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3034, 1, 'ttt', 'tt', 'iot_ota_task_record_status', 0, 'success', '', NULL, '1', to_date('2025-09-06 00:02:21', 'SYYYY-MM-DD HH24:MI:SS'), '1', to_date('2025-09-06 00:02:31', 'SYYYY-MM-DD HH24:MI:SS'), '0');
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3035, 40, '支付宝小程序', '40', 'system_social_type', 0, '', '', '', '1', to_date('2023-11-04 13:05:38', 'SYYYY-MM-DD HH24:MI:SS'), '1', to_date('2023-11-04 13:07:16', 'SYYYY-MM-DD HH24:MI:SS'), '0');
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3036, 60, 'Admin Uniapp 移动端', '60', 'infra_codegen_front_type', 0, '', '', NULL, '1', to_date('2025-12-16 19:25:51', 'SYYYY-MM-DD HH24:MI:SS'), '1', to_date('2025-12-17 09:46:15', 'SYYYY-MM-DD HH24:MI:SS'), '0');
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3037, 42, 'Vben5.0 Antdv Next Schema 模版', '42', 'infra_codegen_front_type', 0, '', '', '', '1', SYSDATE, '1', SYSDATE, '0');
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3038, 43, 'Vben5.0 Antdv Next 标准模版', '43', 'infra_codegen_front_type', 0, '', '', '', '1', SYSDATE, '1', SYSDATE, '0');
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3040, 1, 'UDP', 'udp', 'iot_protocol_type', 0, '', '', 'UDP 协议', '1', to_date('2026-02-04 00:32:47', 'SYYYY-MM-DD HH24:MI:SS'), '1', to_date('2026-02-04 00:32:47', 'SYYYY-MM-DD HH24:MI:SS'), '0');
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3041, 2, 'WebSocket', 'websocket', 'iot_protocol_type', 0, '', '', 'WebSocket 协议', '1', to_date('2026-02-04 00:32:55', 'SYYYY-MM-DD HH24:MI:SS'), '1', to_date('2026-02-04 00:32:55', 'SYYYY-MM-DD HH24:MI:SS'), '0');
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3042, 3, 'HTTP', 'http', 'iot_protocol_type', 0, '', '', 'HTTP 协议', '1', to_date('2026-02-04 00:32:55', 'SYYYY-MM-DD HH24:MI:SS'), '1', to_date('2026-02-04 00:32:55', 'SYYYY-MM-DD HH24:MI:SS'), '0');
|
||||
|
||||
@ -1371,6 +1371,8 @@ INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_t
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3034, 1, 'ttt', 'tt', 'iot_ota_task_record_status', 0, 'success', '', NULL, '1', '2025-09-06 00:02:21', '1', '2025-09-06 00:02:31', '0');
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3035, 40, '支付宝小程序', '40', 'system_social_type', 0, '', '', '', '1', '2023-11-04 13:05:38', '1', '2023-11-04 13:07:16', '0');
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3036, 60, 'Admin Uniapp 移动端', '60', 'infra_codegen_front_type', 0, '', '', NULL, '1', '2025-12-16 19:25:51', '1', '2025-12-17 09:46:15', '0');
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3037, 42, 'Vben5.0 Antdv Next Schema 模版', '42', 'infra_codegen_front_type', 0, '', '', '', '1', NOW(), '1', NOW(), '0');
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3038, 43, 'Vben5.0 Antdv Next 标准模版', '43', 'infra_codegen_front_type', 0, '', '', '', '1', NOW(), '1', NOW(), '0');
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3040, 1, 'UDP', 'udp', 'iot_protocol_type', 0, '', '', 'UDP 协议', '1', '2026-02-04 00:32:47', '1', '2026-02-04 00:32:47', '0');
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3041, 2, 'WebSocket', 'websocket', 'iot_protocol_type', 0, '', '', 'WebSocket 协议', '1', '2026-02-04 00:32:55', '1', '2026-02-04 00:32:55', '0');
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3042, 3, 'HTTP', 'http', 'iot_protocol_type', 0, '', '', 'HTTP 协议', '1', '2026-02-04 00:32:55', '1', '2026-02-04 00:32:55', '0');
|
||||
|
||||
@ -3367,6 +3367,10 @@ INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_t
|
||||
GO
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3036, 60, N'Admin Uniapp 移动端', N'60', N'infra_codegen_front_type', 0, N'', N'', NULL, N'1', N'2025-12-16 19:25:51', N'1', N'2025-12-17 09:46:15', N'0')
|
||||
GO
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3037, 42, N'Vben5.0 Antdv Next Schema 模版', N'42', N'infra_codegen_front_type', 0, N'', N'', N'', N'1', N'2026-05-16 00:00:00', N'1', N'2026-05-16 00:00:00', N'0')
|
||||
GO
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3038, 43, N'Vben5.0 Antdv Next 标准模版', N'43', N'infra_codegen_front_type', 0, N'', N'', N'', N'1', N'2026-05-16 00:00:00', N'1', N'2026-05-16 00:00:00', N'0')
|
||||
GO
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3040, 1, N'UDP', N'udp', N'iot_protocol_type', 0, N'', N'', N'UDP 协议', N'1', N'2026-02-04 00:32:47', N'1', N'2026-02-04 00:32:47', N'0')
|
||||
GO
|
||||
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (3041, 2, N'WebSocket', N'websocket', N'iot_protocol_type', 0, N'', N'', N'WebSocket 协议', N'1', N'2026-02-04 00:32:55', N'1', N'2026-02-04 00:32:55', N'0')
|
||||
|
||||
@ -57,10 +57,12 @@ docker load -i dm8_20240715_x86_rh6_rq_single.tar
|
||||
```Bash
|
||||
docker compose up -d dm8
|
||||
# 注意:启动完 dm 后,需要手动再执行如下命令,因为 dm 不支持初始化脚本
|
||||
docker compose exec dm8 bash -c '/opt/dmdbms/bin/disql SYSDBA/SYSDBA001 \`/tmp/schema.sql'
|
||||
docker compose exec dm8 bash -c 'printf "SET DEFINE OFF;\n" > /tmp/schema-with-define-off.sql && cat /tmp/schema.sql >> /tmp/schema-with-define-off.sql && /opt/dmdbms/bin/disql SYSDBA/SYSDBA001 \`/tmp/schema-with-define-off.sql'
|
||||
exit
|
||||
```
|
||||
|
||||
> 注意:项目 DM8 脚本使用 `varchar(n char)` 保持和 MySQL `varchar(n)` 一致的字符长度语义,建议初始化 DM8 时使用 `PAGE_SIZE=16`、`UNICODE_FLAG=1`。`sql/tools/docker-compose.yaml` 已按该配置提供示例。使用 `disql` 导入时需要先执行 `SET DEFINE OFF;`,避免数据里的 `&` 被当作变量替换。
|
||||
|
||||
### 1.6 KingbaseES 人大金仓
|
||||
|
||||
① 下载人大金仓 Docker 镜像:
|
||||
|
||||
@ -871,7 +871,9 @@ class DM8Convertor(Convertor):
|
||||
type = type.lower()
|
||||
|
||||
if type == "varchar":
|
||||
return f"varchar({size})"
|
||||
# MySQL varchar(n) is character-oriented. DM8 may treat varchar(n)
|
||||
# as bytes, so use explicit CHAR semantics for generated scripts.
|
||||
return f"varchar({size} char)"
|
||||
if type in ("int", "int unsigned"):
|
||||
return "int"
|
||||
if type in ("bigint", "bigint unsigned"):
|
||||
|
||||
@ -14,12 +14,12 @@
|
||||
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
|
||||
|
||||
<properties>
|
||||
<revision>2026.05-SNAPSHOT</revision>
|
||||
<revision>2026.05-jdk25-SNAPSHOT</revision>
|
||||
<flatten-maven-plugin.version>1.7.2</flatten-maven-plugin.version>
|
||||
<!-- 统一依赖管理 -->
|
||||
<spring.boot.version>3.5.14</spring.boot.version>
|
||||
<spring.boot.version>4.1.0</spring.boot.version>
|
||||
<!-- Web 相关 -->
|
||||
<springdoc.version>2.8.17</springdoc.version>
|
||||
<springdoc.version>3.0.3</springdoc.version>
|
||||
<knife4j.version>4.5.0</knife4j.version>
|
||||
<!-- DB 相关 -->
|
||||
<druid.version>1.2.28</druid.version>
|
||||
@ -27,23 +27,23 @@
|
||||
<mybatis-plus.version>3.5.16</mybatis-plus.version>
|
||||
<mybatis-plus-join.version>1.5.7</mybatis-plus-join.version>
|
||||
<dynamic-datasource.version>4.5.0</dynamic-datasource.version>
|
||||
<easy-trans.version>3.0.6</easy-trans.version>
|
||||
<redisson.version>4.4.0</redisson.version>
|
||||
<easy-trans.version>3.1.5</easy-trans.version>
|
||||
<redisson.version>4.6.1</redisson.version>
|
||||
<dm8.jdbc.version>8.1.3.140</dm8.jdbc.version>
|
||||
<kingbase.jdbc.version>9.0.1.jre7</kingbase.jdbc.version>
|
||||
<opengauss.jdbc.version>7.0.0-RC3-og</opengauss.jdbc.version>
|
||||
<taos.version>3.8.3</taos.version>
|
||||
<taos.version>3.8.4</taos.version>
|
||||
<!-- 消息队列 -->
|
||||
<rocketmq-spring.version>2.3.5</rocketmq-spring.version>
|
||||
<rocketmq-spring.version>2.3.6</rocketmq-spring.version>
|
||||
<!-- 服务保障相关 -->
|
||||
<lock4j.version>2.2.7</lock4j.version>
|
||||
<!-- 监控相关 -->
|
||||
<skywalking.version>9.6.0</skywalking.version>
|
||||
<spring-boot-admin.version>3.5.8</spring-boot-admin.version>
|
||||
<spring-boot-admin.version>4.0.4</spring-boot-admin.version>
|
||||
<opentracing.version>0.33.0</opentracing.version>
|
||||
<!-- Test 测试相关 -->
|
||||
<podam.version>8.0.2.RELEASE</podam.version>
|
||||
<jedis-mock.version>1.1.12</jedis-mock.version>
|
||||
<jedis-mock.version>1.1.15</jedis-mock.version>
|
||||
<mockito-inline.version>5.2.0</mockito-inline.version>
|
||||
<!-- Bpm 工作流相关 -->
|
||||
<flowable.version>8.0.0</flowable.version>
|
||||
@ -54,35 +54,35 @@
|
||||
<pinyin4j.version>2.5.1</pinyin4j.version>
|
||||
<lombok.version>1.18.46</lombok.version>
|
||||
<mapstruct.version>1.6.3</mapstruct.version>
|
||||
<hutool-5.version>5.8.44</hutool-5.version>
|
||||
<hutool-5.version>5.8.46</hutool-5.version>
|
||||
<hutool-6.version>6.0.0-M22</hutool-6.version>
|
||||
<fastexcel.version>1.3.0</fastexcel.version>
|
||||
<velocity.version>2.4.1</velocity.version>
|
||||
<fastjson.version>1.2.83</fastjson.version>
|
||||
<fastjson2.version>2.0.61</fastjson2.version>
|
||||
<fastjson2.version>2.0.62</fastjson2.version>
|
||||
<guava.version>33.6.0-jre</guava.version>
|
||||
<transmittable-thread-local.version>2.14.5</transmittable-thread-local.version>
|
||||
<commons-net.version>3.13.0</commons-net.version>
|
||||
<commons-lang3.version>3.20.0</commons-lang3.version>
|
||||
<jsch.version>2.28.2</jsch.version>
|
||||
<tika-core.version>3.3.0</tika-core.version>
|
||||
<jsch.version>2.28.3</jsch.version>
|
||||
<tika-core.version>3.3.1</tika-core.version>
|
||||
<ip2region.version>2.7.0</ip2region.version>
|
||||
<bizlog-sdk.version>3.0.6</bizlog-sdk.version>
|
||||
<netty.version>4.2.14.Final</netty.version>
|
||||
<netty.version>4.2.15.Final</netty.version>
|
||||
<mqtt.version>1.2.5</mqtt.version>
|
||||
<vertx.version>4.5.26</vertx.version>
|
||||
<okhttp.version>4.12.0</okhttp.version>
|
||||
<californium.version>3.14.0</californium.version>
|
||||
<j2mod.version>3.3.0</j2mod.version>
|
||||
<!-- 三方云服务相关 -->
|
||||
<awssdk.version>2.44.0</awssdk.version>
|
||||
<awssdk.version>2.46.17</awssdk.version>
|
||||
<justauth.version>1.16.7</justauth.version>
|
||||
<justauth-starter.version>1.4.0</justauth-starter.version>
|
||||
<jimureport.version>2.3.4</jimureport.version>
|
||||
<jimubi.version>2.3.2</jimubi.version>
|
||||
<weixin-java.version>4.8.2-20260501.180637</weixin-java.version>
|
||||
<weixin-java.version>4.8.4-20260623.211820</weixin-java.version>
|
||||
<bouncycastle.version>1.80</bouncycastle.version>
|
||||
<alipay-sdk-java.version>4.40.806.ALL</alipay-sdk-java.version>
|
||||
<alipay-sdk-java.version>4.40.865.ALL</alipay-sdk-java.version>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
@ -139,6 +139,17 @@
|
||||
<version>${spring.boot.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aspectj</artifactId>
|
||||
<version>${spring.boot.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-jackson</artifactId>
|
||||
<version>${spring.boot.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Web 相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.boot</groupId>
|
||||
@ -184,7 +195,7 @@
|
||||
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>druid-spring-boot-3-starter</artifactId>
|
||||
<artifactId>druid-spring-boot-4-starter</artifactId>
|
||||
<version>${druid.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@ -195,7 +206,7 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
|
||||
<artifactId>mybatis-plus-spring-boot4-starter</artifactId>
|
||||
<version>${mybatis-plus.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@ -210,7 +221,7 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>dynamic-datasource-spring-boot3-starter</artifactId> <!-- 多数据源 -->
|
||||
<artifactId>dynamic-datasource-spring-boot4-starter</artifactId> <!-- 多数据源 -->
|
||||
<version>${dynamic-datasource.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@ -220,7 +231,7 @@
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fhs-opensource</groupId> <!-- VO 数据翻译 -->
|
||||
<groupId>org.dromara</groupId> <!-- VO 数据翻译 -->
|
||||
<artifactId>easy-trans-spring-boot-starter</artifactId>
|
||||
<version>${easy-trans.version}</version>
|
||||
<exclusions>
|
||||
@ -235,12 +246,12 @@
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fhs-opensource</groupId>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>easy-trans-mybatis-plus-extend</artifactId>
|
||||
<version>${easy-trans.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fhs-opensource</groupId>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>easy-trans-anno</artifactId>
|
||||
<version>${easy-trans.version}</version>
|
||||
</dependency>
|
||||
|
||||
@ -96,20 +96,15 @@
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<groupId>tools.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<scope>provided</scope> <!-- 设置为 provided,只有工具类需要使用到 -->
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<groupId>tools.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
<scope>provided</scope> <!-- 设置为 provided,只有工具类需要使用到 -->
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||
<scope>provided</scope> <!-- 设置为 provided,只有工具类需要使用到 -->
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
@ -134,7 +129,7 @@
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fhs-opensource</groupId> <!-- VO 数据翻译 -->
|
||||
<groupId>org.dromara</groupId> <!-- VO 数据翻译 -->
|
||||
<artifactId>easy-trans-anno</artifactId> <!-- 默认引入的原因,方便 xxx-module-api 包使用 -->
|
||||
</dependency>
|
||||
|
||||
|
||||
@ -3,7 +3,6 @@ package cn.iocoder.yudao.framework.common.util.date;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.date.DatePattern;
|
||||
import cn.hutool.core.date.LocalDateTimeUtil;
|
||||
import cn.hutool.core.date.TemporalAccessorUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.enums.DateIntervalEnum;
|
||||
@ -16,6 +15,7 @@ import java.time.temporal.ChronoUnit;
|
||||
import java.time.temporal.TemporalAdjusters;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import static cn.hutool.core.date.DatePattern.*;
|
||||
|
||||
@ -33,6 +33,11 @@ public class LocalDateTimeUtils {
|
||||
|
||||
public static DateTimeFormatter UTC_MS_WITH_XXX_OFFSET_FORMATTER = createFormatter(UTC_MS_WITH_XXX_OFFSET_PATTERN);
|
||||
|
||||
/**
|
||||
* 默认时区
|
||||
*/
|
||||
private static final ZoneId DEFAULT_ZONE_ID = TimeZone.getTimeZone(DateUtils.TIME_ZONE_DEFAULT).toZoneId();
|
||||
|
||||
/**
|
||||
* 解析时间
|
||||
*
|
||||
@ -65,6 +70,27 @@ public class LocalDateTimeUtils {
|
||||
return date.isAfter(LocalDateTime.now());
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 Unix 秒时间戳转换为默认时区的本地时间
|
||||
*
|
||||
* @param epochSecond Unix 秒时间戳
|
||||
* @return 本地时间
|
||||
*/
|
||||
public static LocalDateTime ofEpochSecond(long epochSecond) {
|
||||
return ofEpochSecond(epochSecond, DEFAULT_ZONE_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 Unix 秒时间戳转换为指定时区的本地时间
|
||||
*
|
||||
* @param epochSecond Unix 秒时间戳
|
||||
* @param zoneId 时区编号
|
||||
* @return 本地时间
|
||||
*/
|
||||
public static LocalDateTime ofEpochSecond(long epochSecond, ZoneId zoneId) {
|
||||
return LocalDateTime.ofInstant(Instant.ofEpochSecond(epochSecond), zoneId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建指定时间
|
||||
*
|
||||
@ -393,7 +419,18 @@ public class LocalDateTimeUtils {
|
||||
* @throws DateTimeException 如果转换过程中发生时间超出范围或其他时间处理异常
|
||||
*/
|
||||
public static Long toEpochSecond(LocalDateTime sourceDateTime) {
|
||||
return TemporalAccessorUtil.toInstant(sourceDateTime).getEpochSecond();
|
||||
return toEpochSecond(sourceDateTime, DEFAULT_ZONE_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将给定的 {@link LocalDateTime} 按指定时区转换为自 Unix 纪元时间(1970-01-01T00:00:00Z)以来的秒数。
|
||||
*
|
||||
* @param sourceDateTime 需要转换的本地日期时间,不能为空
|
||||
* @param zoneId 时区编号
|
||||
* @return 自 1970-01-01T00:00:00Z 起的秒数(epoch second)
|
||||
*/
|
||||
public static Long toEpochSecond(LocalDateTime sourceDateTime, ZoneId zoneId) {
|
||||
return sourceDateTime.atZone(zoneId).toEpochSecond();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -6,18 +6,18 @@ import cn.hutool.json.JSONUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.json.databind.TimestampLocalDateTimeDeserializer;
|
||||
import cn.iocoder.yudao.framework.common.util.json.databind.TimestampLocalDateTimeSerializer;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import lombok.Getter;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import tools.jackson.core.JacksonException;
|
||||
import tools.jackson.core.type.TypeReference;
|
||||
import tools.jackson.databind.DeserializationFeature;
|
||||
import tools.jackson.databind.JsonNode;
|
||||
import tools.jackson.databind.ObjectMapper;
|
||||
import tools.jackson.databind.SerializationFeature;
|
||||
import tools.jackson.databind.json.JsonMapper;
|
||||
import tools.jackson.databind.module.SimpleModule;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
@ -33,17 +33,19 @@ import java.util.Map;
|
||||
public class JsonUtils {
|
||||
|
||||
@Getter
|
||||
private static ObjectMapper objectMapper = new ObjectMapper();
|
||||
private static ObjectMapper objectMapper = buildObjectMapper();
|
||||
|
||||
static {
|
||||
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
|
||||
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); // 忽略 null 值
|
||||
// 解决 LocalDateTime 的序列化
|
||||
SimpleModule simpleModule = new JavaTimeModule()
|
||||
private static ObjectMapper buildObjectMapper() {
|
||||
SimpleModule simpleModule = new SimpleModule()
|
||||
// 解决 LocalDateTime 的序列化
|
||||
.addSerializer(LocalDateTime.class, TimestampLocalDateTimeSerializer.INSTANCE)
|
||||
.addDeserializer(LocalDateTime.class, TimestampLocalDateTimeDeserializer.INSTANCE);
|
||||
objectMapper.registerModules(simpleModule);
|
||||
return JsonMapper.builder()
|
||||
.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
|
||||
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
|
||||
.changeDefaultPropertyInclusion(value -> JsonInclude.Value.construct(JsonInclude.Include.NON_NULL, JsonInclude.Include.NON_NULL))
|
||||
.addModule(simpleModule)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -78,7 +80,7 @@ public class JsonUtils {
|
||||
}
|
||||
try {
|
||||
return objectMapper.readValue(text, clazz);
|
||||
} catch (IOException e) {
|
||||
} catch (JacksonException e) {
|
||||
log.error("json parse err,json:{}", text, e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
@ -92,7 +94,7 @@ public class JsonUtils {
|
||||
JsonNode treeNode = objectMapper.readTree(text);
|
||||
JsonNode pathNode = treeNode.path(path);
|
||||
return objectMapper.readValue(pathNode.toString(), clazz);
|
||||
} catch (IOException e) {
|
||||
} catch (JacksonException e) {
|
||||
log.error("json parse err,json:{}", text, e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
@ -104,7 +106,7 @@ public class JsonUtils {
|
||||
}
|
||||
try {
|
||||
return objectMapper.readValue(text, objectMapper.getTypeFactory().constructType(type));
|
||||
} catch (IOException e) {
|
||||
} catch (JacksonException e) {
|
||||
log.error("json parse err,json:{}", text, e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
@ -116,7 +118,7 @@ public class JsonUtils {
|
||||
}
|
||||
try {
|
||||
return objectMapper.readValue(text, objectMapper.getTypeFactory().constructType(type));
|
||||
} catch (IOException e) {
|
||||
} catch (JacksonException e) {
|
||||
log.error("json parse err,json:{}", text, e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
@ -144,7 +146,7 @@ public class JsonUtils {
|
||||
}
|
||||
try {
|
||||
return objectMapper.readValue(bytes, clazz);
|
||||
} catch (IOException e) {
|
||||
} catch (JacksonException e) {
|
||||
log.error("json parse err,json:{}", bytes, e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
@ -153,7 +155,7 @@ public class JsonUtils {
|
||||
public static <T> T parseObject(String text, TypeReference<T> typeReference) {
|
||||
try {
|
||||
return objectMapper.readValue(text, typeReference);
|
||||
} catch (IOException e) {
|
||||
} catch (JacksonException e) {
|
||||
log.error("json parse err,json:{}", text, e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
@ -169,7 +171,7 @@ public class JsonUtils {
|
||||
public static <T> T parseObjectQuietly(String text, TypeReference<T> typeReference) {
|
||||
try {
|
||||
return objectMapper.readValue(text, typeReference);
|
||||
} catch (IOException e) {
|
||||
} catch (JacksonException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -186,7 +188,7 @@ public class JsonUtils {
|
||||
}
|
||||
try {
|
||||
return objectMapper.readValue(text, new TypeReference<Map<String, Object>>() {});
|
||||
} catch (IOException e) {
|
||||
} catch (JacksonException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -204,7 +206,7 @@ public class JsonUtils {
|
||||
}
|
||||
try {
|
||||
return objectMapper.readValue(text, clazz);
|
||||
} catch (IOException e) {
|
||||
} catch (JacksonException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -215,7 +217,7 @@ public class JsonUtils {
|
||||
}
|
||||
try {
|
||||
return objectMapper.readValue(text, objectMapper.getTypeFactory().constructCollectionType(List.class, clazz));
|
||||
} catch (IOException e) {
|
||||
} catch (JacksonException e) {
|
||||
log.error("json parse err,json:{}", text, e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
@ -229,7 +231,7 @@ public class JsonUtils {
|
||||
JsonNode treeNode = objectMapper.readTree(text);
|
||||
JsonNode pathNode = treeNode.path(path);
|
||||
return objectMapper.readValue(pathNode.toString(), objectMapper.getTypeFactory().constructCollectionType(List.class, clazz));
|
||||
} catch (IOException e) {
|
||||
} catch (JacksonException e) {
|
||||
log.error("json parse err,json:{}", text, e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
@ -238,7 +240,7 @@ public class JsonUtils {
|
||||
public static JsonNode parseTree(String text) {
|
||||
try {
|
||||
return objectMapper.readTree(text);
|
||||
} catch (IOException e) {
|
||||
} catch (JacksonException e) {
|
||||
log.error("json parse err,json:{}", text, e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
@ -247,7 +249,7 @@ public class JsonUtils {
|
||||
public static JsonNode parseTree(byte[] text) {
|
||||
try {
|
||||
return objectMapper.readTree(text);
|
||||
} catch (IOException e) {
|
||||
} catch (JacksonException e) {
|
||||
log.error("json parse err,json:{}", text, e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
package cn.iocoder.yudao.framework.common.util.json.databind;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
|
||||
|
||||
import java.io.IOException;
|
||||
import tools.jackson.core.JacksonException;
|
||||
import tools.jackson.core.JsonGenerator;
|
||||
import tools.jackson.databind.SerializationContext;
|
||||
import tools.jackson.databind.annotation.JacksonStdImpl;
|
||||
|
||||
/**
|
||||
* Long 序列化规则
|
||||
@ -14,7 +13,7 @@ import java.io.IOException;
|
||||
* @author 星语
|
||||
*/
|
||||
@JacksonStdImpl
|
||||
public class NumberSerializer extends com.fasterxml.jackson.databind.ser.std.NumberSerializer {
|
||||
public class NumberSerializer extends tools.jackson.databind.ser.jdk.NumberSerializer {
|
||||
|
||||
private static final long MAX_SAFE_INTEGER = 9007199254740991L;
|
||||
private static final long MIN_SAFE_INTEGER = -9007199254740991L;
|
||||
@ -26,7 +25,7 @@ public class NumberSerializer extends com.fasterxml.jackson.databind.ser.std.Num
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize(Number value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
|
||||
public void serialize(Number value, JsonGenerator gen, SerializationContext serializers) throws JacksonException {
|
||||
// 超出范围 序列化位字符串
|
||||
if (value.longValue() > MIN_SAFE_INTEGER && value.longValue() < MAX_SAFE_INTEGER) {
|
||||
super.serialize(value, gen, serializers);
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
package cn.iocoder.yudao.framework.common.util.json.databind;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import tools.jackson.core.JacksonException;
|
||||
import tools.jackson.core.JsonParser;
|
||||
import tools.jackson.databind.DeserializationContext;
|
||||
import tools.jackson.databind.ValueDeserializer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
@ -14,12 +14,12 @@ import java.time.ZoneId;
|
||||
*
|
||||
* @author 老五
|
||||
*/
|
||||
public class TimestampLocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {
|
||||
public class TimestampLocalDateTimeDeserializer extends ValueDeserializer<LocalDateTime> {
|
||||
|
||||
public static final TimestampLocalDateTimeDeserializer INSTANCE = new TimestampLocalDateTimeDeserializer();
|
||||
|
||||
@Override
|
||||
public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
|
||||
public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws JacksonException {
|
||||
// 将 Long 时间戳,转换为 LocalDateTime 对象
|
||||
return LocalDateTime.ofInstant(Instant.ofEpochMilli(p.getValueAsLong()), ZoneId.systemDefault());
|
||||
}
|
||||
|
||||
@ -5,12 +5,12 @@ import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import tools.jackson.core.JacksonException;
|
||||
import tools.jackson.core.JsonGenerator;
|
||||
import tools.jackson.databind.SerializationContext;
|
||||
import tools.jackson.databind.ser.std.StdScalarSerializer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
@ -25,18 +25,22 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
* @author 老五
|
||||
*/
|
||||
@Slf4j
|
||||
public class TimestampLocalDateTimeSerializer extends JsonSerializer<LocalDateTime> {
|
||||
public class TimestampLocalDateTimeSerializer extends StdScalarSerializer<LocalDateTime> {
|
||||
|
||||
public static final TimestampLocalDateTimeSerializer INSTANCE = new TimestampLocalDateTimeSerializer();
|
||||
|
||||
private static final Map<Class<?>, Map<String, Field>> FIELD_CACHE = new ConcurrentHashMap<>();
|
||||
|
||||
public TimestampLocalDateTimeSerializer() {
|
||||
super(LocalDateTime.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
|
||||
public void serialize(LocalDateTime value, JsonGenerator gen, SerializationContext serializers) throws JacksonException {
|
||||
// 情况一:有 JsonFormat 自定义注解,则使用它。https://github.com/YunaiV/ruoyi-vue-pro/pull/1019
|
||||
String fieldName = gen.getOutputContext().getCurrentName();
|
||||
String fieldName = gen.streamWriteContext().currentName();
|
||||
if (fieldName != null) {
|
||||
Object currentValue = gen.getOutputContext().getCurrentValue();
|
||||
Object currentValue = gen.currentValue();
|
||||
if (currentValue != null) {
|
||||
Class<?> clazz = currentValue.getClass();
|
||||
Map<String, Field> fieldMap = FIELD_CACHE.computeIfAbsent(clazz, this::buildFieldMap);
|
||||
|
||||
@ -26,10 +26,9 @@ public class ServletUtils {
|
||||
* @param response 响应
|
||||
* @param object 对象,会序列化成 JSON 字符串
|
||||
*/
|
||||
@SuppressWarnings("deprecation") // 必须使用 APPLICATION_JSON_UTF8_VALUE,否则会乱码
|
||||
public static void writeJSON(HttpServletResponse response, Object object) {
|
||||
String content = JsonUtils.toJsonString(object);
|
||||
JakartaServletUtil.write(response, content, MediaType.APPLICATION_JSON_UTF8_VALUE);
|
||||
JakartaServletUtil.write(response, content, MediaType.APPLICATION_JSON_VALUE + ";charset=UTF-8");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -4,7 +4,7 @@ import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
|
||||
import cn.iocoder.yudao.framework.datapermission.core.aop.DataPermissionContextHolder;
|
||||
import com.fhs.trans.service.impl.SimpleTransService;
|
||||
import org.dromara.trans.service.impl.SimpleTransService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.Collections;
|
||||
@ -65,7 +65,7 @@ public class DataPermissionRuleFactoryImpl implements DataPermissionRuleFactory
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为数据翻译 {@link com.fhs.core.trans.anno.Trans} 的调用
|
||||
* 判断是否为数据翻译 {@link org.dromara.core.trans.anno.Trans} 的调用
|
||||
*
|
||||
* 目前暂时只有这个办法,已经和 easy-trans 做过沟通
|
||||
*
|
||||
|
||||
@ -129,6 +129,7 @@ public class YudaoTenantAutoConfiguration {
|
||||
*
|
||||
* @return 忽略租户的 URL 集合
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
private Set<String> getTenantIgnoreUrls() {
|
||||
Set<String> ignoreUrls = new HashSet<>();
|
||||
// 获得接口对应的 HandlerMethod 集合
|
||||
|
||||
@ -2,8 +2,8 @@ package cn.iocoder.yudao.framework.tenant.core.mq.kafka;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.EnvironmentPostProcessor;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.env.EnvironmentPostProcessor;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
|
||||
/**
|
||||
|
||||
@ -27,7 +27,7 @@ import org.springframework.core.DefaultParameterNameDiscoverer;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ParameterNameDiscoverer;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.handler.HandlerMethod;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
@ -1,2 +1,2 @@
|
||||
org.springframework.boot.env.EnvironmentPostProcessor=\
|
||||
org.springframework.boot.EnvironmentPostProcessor=\
|
||||
cn.iocoder.yudao.framework.tenant.core.mq.kafka.TenantKafkaEnvironmentPostProcessor
|
||||
|
||||
@ -24,7 +24,7 @@
|
||||
<!-- Spring 核心 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
<artifactId>spring-boot-starter-aspectj</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Web 相关 -->
|
||||
|
||||
@ -2,7 +2,7 @@ package cn.iocoder.yudao.framework.tracer.config;
|
||||
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.actuate.autoconfigure.metrics.MeterRegistryCustomizer;
|
||||
import org.springframework.boot.micrometer.metrics.autoconfigure.MeterRegistryCustomizer;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
package cn.iocoder.yudao.framework.mq.rabbitmq.config;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
|
||||
import org.springframework.amqp.support.converter.JacksonJsonMessageConverter;
|
||||
import org.springframework.amqp.support.converter.MessageConverter;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
@ -18,11 +18,11 @@ import org.springframework.context.annotation.Bean;
|
||||
public class YudaoRabbitMQAutoConfiguration {
|
||||
|
||||
/**
|
||||
* Jackson2JsonMessageConverter Bean:使用 jackson 序列化消息
|
||||
* JacksonJsonMessageConverter Bean:使用 jackson 序列化消息
|
||||
*/
|
||||
@Bean
|
||||
public MessageConverter createMessageConverter() {
|
||||
return new Jackson2JsonMessageConverter();
|
||||
return new JacksonJsonMessageConverter();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -71,11 +71,11 @@
|
||||
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>druid-spring-boot-3-starter</artifactId>
|
||||
<artifactId>druid-spring-boot-4-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
|
||||
<artifactId>mybatis-plus-spring-boot4-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
@ -83,13 +83,7 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>dynamic-datasource-spring-boot3-starter</artifactId> <!-- 多数据源 -->
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-undertow</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
<artifactId>dynamic-datasource-spring-boot4-starter</artifactId> <!-- 多数据源 -->
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@ -98,11 +92,11 @@
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fhs-opensource</groupId> <!-- VO 数据翻译 -->
|
||||
<groupId>org.dromara</groupId> <!-- VO 数据翻译 -->
|
||||
<artifactId>easy-trans-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fhs-opensource</groupId>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>easy-trans-mybatis-plus-extend</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
package cn.iocoder.yudao.framework.datasource.config;
|
||||
|
||||
import cn.iocoder.yudao.framework.datasource.core.filter.DruidAdRemoveFilter;
|
||||
import com.alibaba.druid.spring.boot3.autoconfigure.properties.DruidStatProperties;
|
||||
import com.alibaba.druid.spring.boot4.autoconfigure.properties.DruidStatProperties;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
|
||||
@ -6,8 +6,8 @@ import cn.iocoder.yudao.framework.mybatis.core.util.JdbcUtils;
|
||||
import com.baomidou.mybatisplus.annotation.DbType;
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.EnvironmentPostProcessor;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.env.EnvironmentPostProcessor;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.MapPropertySource;
|
||||
|
||||
|
||||
@ -6,16 +6,14 @@ import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.handler.DefaultDBFieldHandler;
|
||||
import com.baomidou.mybatisplus.annotation.DbType;
|
||||
import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration;
|
||||
import com.baomidou.mybatisplus.core.handlers.IJsonTypeHandler;
|
||||
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
|
||||
import com.baomidou.mybatisplus.core.incrementer.IKeyGenerator;
|
||||
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
|
||||
import com.baomidou.mybatisplus.extension.handlers.Jackson3TypeHandler;
|
||||
import com.baomidou.mybatisplus.extension.incrementer.*;
|
||||
import com.baomidou.mybatisplus.extension.parser.JsqlParserGlobal;
|
||||
import com.baomidou.mybatisplus.extension.parser.cache.JdkSerialCaffeineJsqlParseCache;
|
||||
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
@ -25,6 +23,7 @@ import org.springframework.core.env.ConfigurableEnvironment;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import tools.jackson.databind.ObjectMapper;
|
||||
|
||||
/**
|
||||
* MyBaits 配置类
|
||||
@ -81,15 +80,15 @@ public class YudaoMybatisAutoConfiguration {
|
||||
throw new IllegalArgumentException(StrUtil.format("DbType{} 找不到合适的 IKeyGenerator 实现类", dbType));
|
||||
}
|
||||
|
||||
@Bean // 特殊:返回结果使用 Object 而不用 JacksonTypeHandler 的原因,避免因为 JacksonTypeHandler 被 mybatis 全局使用!
|
||||
@Bean // 特殊:返回结果使用 Object 而不用 Jackson3TypeHandler 的原因,避免因为 Jackson3TypeHandler 被 mybatis 全局使用!
|
||||
public Object jacksonTypeHandler(List<ObjectMapper> objectMappers) {
|
||||
// 特殊:设置 JacksonTypeHandler 的 ObjectMapper!
|
||||
// 特殊:设置 Jackson3TypeHandler 的 ObjectMapper!
|
||||
ObjectMapper objectMapper = CollUtil.getFirst(objectMappers);
|
||||
if (objectMapper == null) {
|
||||
objectMapper = JsonUtils.getObjectMapper();
|
||||
}
|
||||
JacksonTypeHandler.setObjectMapper(objectMapper);
|
||||
return new JacksonTypeHandler(Object.class);
|
||||
Jackson3TypeHandler.setObjectMapper(objectMapper);
|
||||
return new Jackson3TypeHandler(Object.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@ import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableLogic;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fhs.core.trans.vo.TransPojo;
|
||||
import org.dromara.core.trans.vo.TransPojo;
|
||||
import lombok.Data;
|
||||
import org.apache.ibatis.type.JdbcType;
|
||||
|
||||
|
||||
@ -1,15 +1,17 @@
|
||||
package cn.iocoder.yudao.framework.translate.config;
|
||||
|
||||
import cn.iocoder.yudao.framework.translate.core.TranslateUtils;
|
||||
import com.fhs.trans.service.impl.TransService;
|
||||
import org.dromara.trans.service.impl.TransService;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
@AutoConfiguration
|
||||
public class YudaoTranslateAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@SuppressWarnings({"InstantiationOfUtilityClass", "SpringJavaInjectionPointsAutowiringInspection"})
|
||||
@ConditionalOnBean(TransService.class)
|
||||
@SuppressWarnings("InstantiationOfUtilityClass")
|
||||
public TranslateUtils translateUtils(TransService transService) {
|
||||
TranslateUtils.init(transService);
|
||||
return new TranslateUtils();
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
package cn.iocoder.yudao.framework.translate.core;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import com.fhs.core.trans.vo.VO;
|
||||
import com.fhs.trans.service.impl.TransService;
|
||||
import org.dromara.core.trans.vo.VO;
|
||||
import org.dromara.trans.service.impl.TransService;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@ -1,2 +1,2 @@
|
||||
org.springframework.boot.env.EnvironmentPostProcessor=\
|
||||
org.springframework.boot.EnvironmentPostProcessor=\
|
||||
cn.iocoder.yudao.framework.mybatis.config.IdTypeEnvironmentPostProcessor
|
||||
|
||||
@ -33,8 +33,8 @@
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-jackson</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@ package cn.iocoder.yudao.framework.redis.config;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.redis.core.TimeoutRedisCacheManager;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.cache.CacheProperties;
|
||||
import org.springframework.boot.cache.autoconfigure.CacheProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
@ -1,9 +1,6 @@
|
||||
package cn.iocoder.yudao.framework.redis.config;
|
||||
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import org.redisson.spring.starter.RedissonAutoConfigurationV2;
|
||||
import org.redisson.spring.starter.RedissonAutoConfigurationV4;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
@ -13,7 +10,7 @@ import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
/**
|
||||
* Redis 配置类
|
||||
*/
|
||||
@AutoConfiguration(before = RedissonAutoConfigurationV2.class) // 目的:使用自己定义的 RedisTemplate Bean
|
||||
@AutoConfiguration(before = RedissonAutoConfigurationV4.class) // 目的:使用自己定义的 RedisTemplate Bean
|
||||
public class YudaoRedisAutoConfiguration {
|
||||
|
||||
/**
|
||||
@ -28,17 +25,18 @@ public class YudaoRedisAutoConfiguration {
|
||||
// 使用 String 序列化方式,序列化 KEY 。
|
||||
template.setKeySerializer(RedisSerializer.string());
|
||||
template.setHashKeySerializer(RedisSerializer.string());
|
||||
// 使用 JSON 序列化方式(库是 Jackson ),序列化 VALUE 。
|
||||
template.setValueSerializer(buildRedisSerializer());
|
||||
template.setHashValueSerializer(buildRedisSerializer());
|
||||
// 使用 JSON 序列化方式,序列化 VALUE
|
||||
RedisSerializer<?> redisSerializer = buildRedisSerializer();
|
||||
template.setValueSerializer(redisSerializer);
|
||||
template.setHashValueSerializer(redisSerializer);
|
||||
return template;
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnnecessaryLocalVariable")
|
||||
public static RedisSerializer<?> buildRedisSerializer() {
|
||||
RedisSerializer<Object> json = RedisSerializer.json();
|
||||
// 解决 LocalDateTime 的序列化
|
||||
ObjectMapper objectMapper = (ObjectMapper) ReflectUtil.getFieldValue(json, "mapper");
|
||||
objectMapper.registerModules(new JavaTimeModule());
|
||||
// 特殊:spring boot 4.x 无需解决 LocalDateTime 的序列化
|
||||
// 原因:Spring Data Redis 4 使用 Jackson 3,RedisSerializer.json() 已支持 Java Time 类型
|
||||
return json;
|
||||
}
|
||||
|
||||
|
||||
@ -27,7 +27,7 @@
|
||||
<!-- Spring 核心 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
<artifactId>spring-boot-starter-aspectj</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Web 相关 -->
|
||||
|
||||
@ -156,6 +156,7 @@ public class YudaoWebSecurityConfigurerAdapter {
|
||||
return webProperties.getAppApi().getPrefix() + url;
|
||||
}
|
||||
|
||||
@SuppressWarnings("removal")
|
||||
private Multimap<HttpMethod, String> getPermitAllUrlsFromAnnotations() {
|
||||
Multimap<HttpMethod, String> result = HashMultimap.create();
|
||||
// 获得接口对应的 HandlerMethod 集合
|
||||
|
||||
@ -5,7 +5,7 @@ import cn.hutool.core.util.ObjUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.security.core.LoginUser;
|
||||
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
package cn.iocoder.yudao.framework.test.config;
|
||||
|
||||
import com.github.fppt.jedismock.RedisServer;
|
||||
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
|
||||
import org.springframework.boot.data.redis.autoconfigure.DataRedisProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@ -16,14 +16,14 @@ import java.io.IOException;
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Lazy(false) // 禁止延迟加载
|
||||
@EnableConfigurationProperties(RedisProperties.class)
|
||||
@EnableConfigurationProperties(DataRedisProperties.class)
|
||||
public class RedisTestConfiguration {
|
||||
|
||||
/**
|
||||
* 创建模拟的 Redis Server 服务器
|
||||
*/
|
||||
@Bean
|
||||
public RedisServer redisServer(RedisProperties properties) throws IOException {
|
||||
public RedisServer redisServer(DataRedisProperties properties) throws IOException {
|
||||
RedisServer redisServer = new RedisServer(properties.getPort());
|
||||
// 一次执行多个单元测试时,貌似创建多个 spring 容器,导致不进行 stop。这样,就导致端口被占用,无法启动。。。
|
||||
try {
|
||||
|
||||
@ -3,7 +3,7 @@ package cn.iocoder.yudao.framework.test.config;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
|
||||
import org.springframework.boot.autoconfigure.sql.init.SqlInitializationProperties;
|
||||
import org.springframework.boot.sql.autoconfigure.init.SqlInitializationProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.jdbc.init.DataSourceScriptDatabaseInitializer;
|
||||
import org.springframework.boot.sql.init.AbstractScriptDatabaseInitializer;
|
||||
@ -17,7 +17,7 @@ import javax.sql.DataSource;
|
||||
/**
|
||||
* SQL 初始化的测试 Configuration
|
||||
*
|
||||
* 为什么不使用 org.springframework.boot.autoconfigure.sql.init.DataSourceInitializationConfiguration 呢?
|
||||
* 为什么不使用 org.springframework.boot.sql.autoconfigure.init.DataSourceInitializationConfiguration 呢?
|
||||
* 因为我们在单元测试会使用 spring.main.lazy-initialization 为 true,开启延迟加载。此时,会导致 DataSourceInitializationConfiguration 初始化
|
||||
* 不过呢,当前类的实现代码,基本是复制 DataSourceInitializationConfiguration 的哈!
|
||||
*
|
||||
|
||||
@ -6,12 +6,12 @@ import cn.iocoder.yudao.framework.mybatis.config.YudaoMybatisAutoConfiguration;
|
||||
import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration;
|
||||
import cn.iocoder.yudao.framework.test.config.RedisTestConfiguration;
|
||||
import cn.iocoder.yudao.framework.test.config.SqlInitializationTestConfiguration;
|
||||
import com.alibaba.druid.spring.boot3.autoconfigure.DruidDataSourceAutoConfigure;
|
||||
import com.alibaba.druid.spring.boot4.autoconfigure.DruidDataSourceAutoConfigure;
|
||||
import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration;
|
||||
import org.redisson.spring.starter.RedissonAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
|
||||
import org.redisson.spring.starter.RedissonAutoConfigurationV4;
|
||||
import org.springframework.boot.data.redis.autoconfigure.DataRedisAutoConfiguration;
|
||||
import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration;
|
||||
import org.springframework.boot.jdbc.autoconfigure.DataSourceTransactionManagerAutoConfiguration;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
@ -43,8 +43,8 @@ public class BaseDbAndRedisUnitTest {
|
||||
// Redis 配置类
|
||||
RedisTestConfiguration.class, // Redis 测试配置类,用于启动 RedisServer
|
||||
YudaoRedisAutoConfiguration.class, // 自己的 Redis 配置类
|
||||
RedisAutoConfiguration.class, // Spring Redis 自动配置类
|
||||
RedissonAutoConfiguration.class, // Redisson 自动配置类
|
||||
DataRedisAutoConfiguration.class, // Spring Redis 自动配置类
|
||||
RedissonAutoConfigurationV4.class, // Redisson 自动配置类
|
||||
|
||||
// 其它配置类
|
||||
SpringUtil.class
|
||||
|
||||
@ -4,11 +4,11 @@ import cn.hutool.extra.spring.SpringUtil;
|
||||
import cn.iocoder.yudao.framework.datasource.config.YudaoDataSourceAutoConfiguration;
|
||||
import cn.iocoder.yudao.framework.mybatis.config.YudaoMybatisAutoConfiguration;
|
||||
import cn.iocoder.yudao.framework.test.config.SqlInitializationTestConfiguration;
|
||||
import com.alibaba.druid.spring.boot3.autoconfigure.DruidDataSourceAutoConfigure;
|
||||
import com.alibaba.druid.spring.boot4.autoconfigure.DruidDataSourceAutoConfigure;
|
||||
import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration;
|
||||
import com.github.yulichang.autoconfigure.MybatisPlusJoinAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
|
||||
import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration;
|
||||
import org.springframework.boot.jdbc.autoconfigure.DataSourceTransactionManagerAutoConfiguration;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
|
||||
@ -3,8 +3,8 @@ package cn.iocoder.yudao.framework.test.core.ut;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration;
|
||||
import cn.iocoder.yudao.framework.test.config.RedisTestConfiguration;
|
||||
import org.redisson.spring.starter.RedissonAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
|
||||
import org.redisson.spring.starter.RedissonAutoConfigurationV4;
|
||||
import org.springframework.boot.data.redis.autoconfigure.DataRedisAutoConfiguration;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
@ -23,9 +23,9 @@ public class BaseRedisUnitTest {
|
||||
@Import({
|
||||
// Redis 配置类
|
||||
RedisTestConfiguration.class, // Redis 测试配置类,用于启动 RedisServer
|
||||
RedisAutoConfiguration.class, // Spring Redis 自动配置类
|
||||
DataRedisAutoConfiguration.class, // Spring Redis 自动配置类
|
||||
YudaoRedisAutoConfiguration.class, // 自己的 Redis 配置类
|
||||
RedissonAutoConfiguration.class, // Redisson 自动配置类
|
||||
RedissonAutoConfigurationV4.class, // Redisson 自动配置类
|
||||
|
||||
// 其它配置类
|
||||
SpringUtil.class
|
||||
|
||||
@ -26,6 +26,14 @@
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-jackson</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-restclient</artifactId>
|
||||
</dependency>
|
||||
<!-- spring boot 配置所需依赖 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
|
||||
@ -19,7 +19,6 @@ import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
|
||||
import cn.iocoder.yudao.framework.web.config.WebProperties;
|
||||
import cn.iocoder.yudao.framework.web.core.filter.ApiRequestFilter;
|
||||
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.FilterChain;
|
||||
@ -29,6 +28,7 @@ import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import tools.jackson.databind.JsonNode;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@ -3,7 +3,7 @@ package cn.iocoder.yudao.framework.desensitize.core.base.annotation;
|
||||
import cn.iocoder.yudao.framework.desensitize.core.base.handler.DesensitizationHandler;
|
||||
import cn.iocoder.yudao.framework.desensitize.core.base.serializer.StringDesensitizeSerializer;
|
||||
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import tools.jackson.databind.annotation.JsonSerialize;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
|
||||
@ -7,16 +7,15 @@ import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.desensitize.core.base.annotation.DesensitizeBy;
|
||||
import cn.iocoder.yudao.framework.desensitize.core.base.handler.DesensitizationHandler;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.BeanProperty;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
|
||||
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import tools.jackson.core.JacksonException;
|
||||
import tools.jackson.core.JsonGenerator;
|
||||
import tools.jackson.databind.BeanProperty;
|
||||
import tools.jackson.databind.SerializationContext;
|
||||
import tools.jackson.databind.ValueSerializer;
|
||||
import tools.jackson.databind.ser.std.StdSerializer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
@ -28,7 +27,7 @@ import java.lang.reflect.Field;
|
||||
* @author gaibu
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public class StringDesensitizeSerializer extends StdSerializer<String> implements ContextualSerializer {
|
||||
public class StringDesensitizeSerializer extends StdSerializer<String> {
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@ -39,7 +38,7 @@ public class StringDesensitizeSerializer extends StdSerializer<String> implement
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) {
|
||||
public ValueSerializer<?> createContextual(SerializationContext serializerProvider, BeanProperty beanProperty) {
|
||||
DesensitizeBy annotation = beanProperty.getAnnotation(DesensitizeBy.class);
|
||||
if (annotation == null) {
|
||||
return this;
|
||||
@ -52,7 +51,7 @@ public class StringDesensitizeSerializer extends StdSerializer<String> implement
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void serialize(String value, JsonGenerator gen, SerializerProvider serializerProvider) throws IOException {
|
||||
public void serialize(String value, JsonGenerator gen, SerializationContext serializerProvider) throws JacksonException {
|
||||
if (StrUtil.isBlank(value)) {
|
||||
gen.writeNull();
|
||||
return;
|
||||
@ -83,7 +82,7 @@ public class StringDesensitizeSerializer extends StdSerializer<String> implement
|
||||
* @return 字段
|
||||
*/
|
||||
private Field getField(JsonGenerator generator) {
|
||||
String currentName = generator.getOutputContext().getCurrentName();
|
||||
String currentName = generator.streamWriteContext().currentName();
|
||||
Object currentValue = generator.currentValue();
|
||||
Class<?> currentValueClass = currentValue.getClass();
|
||||
return ReflectUtil.getField(currentValueClass, currentName);
|
||||
|
||||
@ -4,18 +4,18 @@ import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.json.databind.NumberSerializer;
|
||||
import cn.iocoder.yudao.framework.common.util.json.databind.TimestampLocalDateTimeDeserializer;
|
||||
import cn.iocoder.yudao.framework.common.util.json.databind.TimestampLocalDateTimeSerializer;
|
||||
import com.fasterxml.jackson.databind.Module;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
|
||||
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
|
||||
import org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration;
|
||||
import org.springframework.boot.jackson.autoconfigure.JsonMapperBuilderCustomizer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import tools.jackson.databind.JacksonModule;
|
||||
import tools.jackson.databind.ObjectMapper;
|
||||
import tools.jackson.databind.ext.javatime.deser.LocalDateDeserializer;
|
||||
import tools.jackson.databind.ext.javatime.deser.LocalTimeDeserializer;
|
||||
import tools.jackson.databind.ext.javatime.ser.LocalDateSerializer;
|
||||
import tools.jackson.databind.ext.javatime.ser.LocalTimeSerializer;
|
||||
import tools.jackson.databind.module.SimpleModule;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
@ -29,26 +29,15 @@ public class YudaoJacksonAutoConfiguration {
|
||||
* 从 Builder 源头定制(关键:使用 *ByType,避免 handledType 要求)
|
||||
*/
|
||||
@Bean
|
||||
public Jackson2ObjectMapperBuilderCustomizer ldtEpochMillisCustomizer() {
|
||||
return builder -> builder
|
||||
// Long -> Number
|
||||
.serializerByType(Long.class, NumberSerializer.INSTANCE)
|
||||
.serializerByType(Long.TYPE, NumberSerializer.INSTANCE)
|
||||
// LocalDate / LocalTime
|
||||
.serializerByType(LocalDate.class, LocalDateSerializer.INSTANCE)
|
||||
.deserializerByType(LocalDate.class, LocalDateDeserializer.INSTANCE)
|
||||
.serializerByType(LocalTime.class, LocalTimeSerializer.INSTANCE)
|
||||
.deserializerByType(LocalTime.class, LocalTimeDeserializer.INSTANCE)
|
||||
// LocalDateTime < - > EpochMillis
|
||||
.serializerByType(LocalDateTime.class, TimestampLocalDateTimeSerializer.INSTANCE)
|
||||
.deserializerByType(LocalDateTime.class, TimestampLocalDateTimeDeserializer.INSTANCE);
|
||||
public JsonMapperBuilderCustomizer ldtEpochMillisCustomizer(JacksonModule timestampSupportModuleBean) {
|
||||
return builder -> builder.addModule(timestampSupportModuleBean);
|
||||
}
|
||||
|
||||
/**
|
||||
* 以 Bean 形式暴露 Module(Boot 会自动注册到所有 ObjectMapper)
|
||||
*/
|
||||
@Bean
|
||||
public Module timestampSupportModuleBean() {
|
||||
public JacksonModule timestampSupportModuleBean() {
|
||||
SimpleModule m = new SimpleModule("TimestampSupportModule");
|
||||
// Long -> Number,避免前端精度丢失
|
||||
m.addSerializer(Long.class, NumberSerializer.INSTANCE);
|
||||
|
||||
@ -14,10 +14,9 @@ import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations;
|
||||
import org.springframework.boot.webmvc.autoconfigure.WebMvcRegistrations;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.web.client.RestTemplateBuilder;
|
||||
import org.springframework.boot.restclient.RestTemplateBuilder;
|
||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.core.annotation.Order;
|
||||
@ -144,7 +143,7 @@ public class YudaoWebAutoConfiguration {
|
||||
/**
|
||||
* 创建 RestTemplate 实例
|
||||
*
|
||||
* @param restTemplateBuilder {@link RestTemplateAutoConfiguration#restTemplateBuilder}
|
||||
* @param restTemplateBuilder {@link RestTemplateBuilder#build}
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
|
||||
@ -15,7 +15,6 @@ import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
|
||||
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
|
||||
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
|
||||
import com.google.common.util.concurrent.UncheckedExecutionException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.validation.ConstraintViolation;
|
||||
@ -39,6 +38,7 @@ import org.springframework.web.method.annotation.MethodArgumentTypeMismatchExcep
|
||||
import org.springframework.web.multipart.MaxUploadSizeExceededException;
|
||||
import org.springframework.web.servlet.NoHandlerFoundException;
|
||||
import org.springframework.web.servlet.resource.NoResourceFoundException;
|
||||
import tools.jackson.databind.exc.InvalidFormatException;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@ -9,7 +9,7 @@ import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
|
||||
import org.springframework.boot.jackson.autoconfigure.JsonMapperBuilderCustomizer;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@ -37,17 +37,17 @@ public class YudaoXssAutoConfiguration implements WebMvcConfigurer {
|
||||
/**
|
||||
* 注册 Jackson 的序列化器,用于处理 json 类型参数的 xss 过滤
|
||||
*
|
||||
* @return Jackson2ObjectMapperBuilderCustomizer
|
||||
* @return JsonMapperBuilderCustomizer
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(name = "xssJacksonCustomizer")
|
||||
@ConditionalOnProperty(value = "yudao.xss.enable", havingValue = "true")
|
||||
public Jackson2ObjectMapperBuilderCustomizer xssJacksonCustomizer(XssProperties properties,
|
||||
PathMatcher pathMatcher,
|
||||
XssCleaner xssCleaner) {
|
||||
public JsonMapperBuilderCustomizer xssJacksonCustomizer(XssProperties properties,
|
||||
PathMatcher pathMatcher,
|
||||
XssCleaner xssCleaner) {
|
||||
// 在反序列化时进行 xss 过滤,可以替换使用 XssStringJsonSerializer,在序列化时进行处理
|
||||
return builder ->
|
||||
builder.deserializerByType(String.class, new XssStringJsonDeserializer(properties, pathMatcher, xssCleaner));
|
||||
return builder -> builder.addModule(new tools.jackson.databind.module.SimpleModule("XssStringModule")
|
||||
.addDeserializer(String.class, new XssStringJsonDeserializer(properties, pathMatcher, xssCleaner)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -3,16 +3,15 @@ package cn.iocoder.yudao.framework.xss.core.json;
|
||||
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
|
||||
import cn.iocoder.yudao.framework.xss.config.XssProperties;
|
||||
import cn.iocoder.yudao.framework.xss.core.clean.XssCleaner;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonToken;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.deser.std.StringDeserializer;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.util.PathMatcher;
|
||||
|
||||
import java.io.IOException;
|
||||
import tools.jackson.core.JacksonException;
|
||||
import tools.jackson.core.JsonParser;
|
||||
import tools.jackson.core.JsonToken;
|
||||
import tools.jackson.databind.DeserializationContext;
|
||||
import tools.jackson.databind.deser.jdk.StringDeserializer;
|
||||
|
||||
/**
|
||||
* XSS 过滤 jackson 反序列化器。
|
||||
@ -36,19 +35,19 @@ public class XssStringJsonDeserializer extends StringDeserializer {
|
||||
private final XssCleaner xssCleaner;
|
||||
|
||||
@Override
|
||||
public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
|
||||
public String deserialize(JsonParser p, DeserializationContext ctxt) throws JacksonException {
|
||||
// 1. 白名单 URL 的处理
|
||||
HttpServletRequest request = ServletUtils.getRequest();
|
||||
if (request != null) {
|
||||
String uri = ServletUtils.getRequest().getRequestURI();
|
||||
if (properties.getExcludeUrls().stream().anyMatch(excludeUrl -> pathMatcher.match(excludeUrl, uri))) {
|
||||
return p.getText();
|
||||
return p.getString();
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 真正使用 xssCleaner 进行过滤
|
||||
if (p.hasToken(JsonToken.VALUE_STRING)) {
|
||||
return xssCleaner.clean(p.getText());
|
||||
return xssCleaner.clean(p.getString());
|
||||
}
|
||||
JsonToken t = p.currentToken();
|
||||
// [databind#381]
|
||||
|
||||
@ -19,9 +19,9 @@
|
||||
国外:OpenAI、Ollama、Midjourney、StableDiffusion、Suno
|
||||
</description>
|
||||
<properties>
|
||||
<spring-ai.version>1.1.5</spring-ai.version>
|
||||
<spring-ai.version>2.0.0</spring-ai.version>
|
||||
<!-- https://mvnrepository.com/artifact/com.alibaba.cloud.ai/spring-ai-alibaba -->
|
||||
<alibaba-ai.version>1.1.2.2</alibaba-ai.version>
|
||||
<alibaba-ai.version>2.0.0-M1.1</alibaba-ai.version>
|
||||
<tinyflow.version>1.2.6</tinyflow.version>
|
||||
</properties>
|
||||
|
||||
@ -86,11 +86,6 @@
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-starter-model-azure-openai</artifactId>
|
||||
<version>${spring-ai.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-starter-model-anthropic</artifactId>
|
||||
@ -101,6 +96,11 @@
|
||||
<artifactId>spring-ai-starter-model-deepseek</artifactId>
|
||||
<version>${spring-ai.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-starter-model-google-genai</artifactId>
|
||||
<version>${spring-ai.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-starter-model-ollama</artifactId>
|
||||
@ -111,18 +111,6 @@
|
||||
<artifactId>spring-ai-starter-model-stability-ai</artifactId>
|
||||
<version>${spring-ai.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<!-- 智谱 GLM -->
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-starter-model-zhipuai</artifactId>
|
||||
<version>${spring-ai.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-starter-model-minimax</artifactId>
|
||||
<version>${spring-ai.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<!-- 通义千问 -->
|
||||
<groupId>com.alibaba.cloud.ai</groupId>
|
||||
@ -130,19 +118,6 @@
|
||||
<version>${alibaba-ai.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<!-- 文心一言 -->
|
||||
<groupId>org.springaicommunity</groupId>
|
||||
<artifactId>qianfan-spring-boot-starter</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<!-- 月之暗面 -->
|
||||
<groupId>org.springaicommunity</groupId>
|
||||
<artifactId>moonshot-spring-boot-starter</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 向量存储:https://db-engines.com/en/ranking/vector+dbms -->
|
||||
<dependency>
|
||||
<!-- Qdrant:https://qdrant.tech/ -->
|
||||
@ -211,12 +186,22 @@
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-mcp-server-common</artifactId>
|
||||
<version>${spring-ai.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<!-- 客户端 -->
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-starter-mcp-client</artifactId>
|
||||
<version>${spring-ai.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-mcp-client-common</artifactId>
|
||||
<version>${spring-ai.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- TinyFlow:AI 工作流 -->
|
||||
<dependency>
|
||||
@ -260,4 +245,4 @@
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
</project>
|
||||
|
||||
@ -12,7 +12,7 @@ import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatCo
|
||||
import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO;
|
||||
import cn.iocoder.yudao.module.ai.service.chat.AiChatConversationService;
|
||||
import cn.iocoder.yudao.module.ai.service.chat.AiChatMessageService;
|
||||
import com.fhs.core.trans.anno.TransMethodResult;
|
||||
import org.dromara.core.trans.anno.TransMethodResult;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
@ -2,9 +2,9 @@ package cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation;
|
||||
|
||||
import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
|
||||
import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO;
|
||||
import com.fhs.core.trans.anno.Trans;
|
||||
import com.fhs.core.trans.constant.TransType;
|
||||
import com.fhs.core.trans.vo.VO;
|
||||
import org.dromara.core.trans.anno.Trans;
|
||||
import org.dromara.core.trans.constant.TransType;
|
||||
import org.dromara.core.trans.vo.VO;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
|
||||
@ -10,7 +10,7 @@ import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatRole.AiChatRoleS
|
||||
import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatRole.AiChatRoleSaveReqVO;
|
||||
import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO;
|
||||
import cn.iocoder.yudao.module.ai.service.model.AiChatRoleService;
|
||||
import com.fhs.core.trans.anno.TransMethodResult;
|
||||
import org.dromara.core.trans.anno.TransMethodResult;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
package cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatRole;
|
||||
|
||||
import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
|
||||
import com.fhs.core.trans.anno.Trans;
|
||||
import com.fhs.core.trans.constant.TransType;
|
||||
import com.fhs.core.trans.vo.VO;
|
||||
import org.dromara.core.trans.anno.Trans;
|
||||
import org.dromara.core.trans.constant.TransType;
|
||||
import org.dromara.core.trans.vo.VO;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@ -64,4 +64,4 @@ public class AiChatRoleRespVO implements VO {
|
||||
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,7 +11,7 @@ import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
|
||||
import com.baomidou.mybatisplus.extension.handlers.Jackson3TypeHandler;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
@ -114,7 +114,7 @@ public class AiChatMessageDO extends BaseDO {
|
||||
/**
|
||||
* 联网搜索的网页内容数组
|
||||
*/
|
||||
@TableField(typeHandler = JacksonTypeHandler.class)
|
||||
@TableField(typeHandler = Jackson3TypeHandler.class)
|
||||
private List<AiWebSearchResponse.WebPage> webSearchPages;
|
||||
|
||||
/**
|
||||
|
||||
@ -10,7 +10,7 @@ import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
|
||||
import com.baomidou.mybatisplus.extension.handlers.Jackson3TypeHandler;
|
||||
import lombok.Data;
|
||||
import org.springframework.ai.openai.OpenAiImageOptions;
|
||||
import org.springframework.ai.stabilityai.api.StabilityAiImageOptions;
|
||||
@ -107,13 +107,13 @@ public class AiImageDO extends BaseDO {
|
||||
* 1. {@link OpenAiImageOptions}
|
||||
* 2. {@link StabilityAiImageOptions}
|
||||
*/
|
||||
@TableField(typeHandler = JacksonTypeHandler.class)
|
||||
@TableField(typeHandler = Jackson3TypeHandler.class)
|
||||
private Map<String, Object> options;
|
||||
|
||||
/**
|
||||
* mj buttons 按钮
|
||||
*/
|
||||
@TableField(typeHandler = JacksonTypeHandler.class)
|
||||
@TableField(typeHandler = Jackson3TypeHandler.class)
|
||||
private List<MidjourneyApi.Button> buttons;
|
||||
|
||||
/**
|
||||
|
||||
@ -8,7 +8,7 @@ import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
|
||||
import com.baomidou.mybatisplus.extension.handlers.Jackson3TypeHandler;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
@ -93,7 +93,7 @@ public class AiMusicDO extends BaseDO {
|
||||
/**
|
||||
* 音乐风格标签
|
||||
*/
|
||||
@TableField(typeHandler = JacksonTypeHandler.class)
|
||||
@TableField(typeHandler = Jackson3TypeHandler.class)
|
||||
private List<String> tags;
|
||||
|
||||
/**
|
||||
|
||||
@ -12,6 +12,7 @@ public interface ErrorCodeConstants {
|
||||
// ========== API 密钥 1-040-000-000 ==========
|
||||
ErrorCode API_KEY_NOT_EXISTS = new ErrorCode(1_040_000_000, "API 密钥不存在");
|
||||
ErrorCode API_KEY_DISABLE = new ErrorCode(1_040_000_001, "API 密钥已禁用!");
|
||||
ErrorCode API_CONFIG_PLACEHOLDER_NOT_RESOLVED = new ErrorCode(1_040_000_002, "AI 配置({})无法解析,请检查环境变量或配置项");
|
||||
|
||||
// ========== API 模型 1-040-001-000 ==========
|
||||
ErrorCode MODEL_NOT_EXISTS = new ErrorCode(1_040_001_000, "模型不存在!");
|
||||
|
||||
@ -6,29 +6,41 @@ import cn.iocoder.yudao.module.ai.framework.ai.core.model.AiModelFactory;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.AiModelFactoryImpl;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.baichuan.BaiChuanChatModel;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.doubao.DouBaoChatModel;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.gemini.GeminiChatModel;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.grok.GrokChatModel;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.hunyuan.HunYuanChatModel;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.midjourney.api.MidjourneyApi;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.minimax.MiniMaxChatModel;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.moonshot.MoonshotChatModel;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowApiConstants;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowChatModel;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.suno.api.SunoApi;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.xinghuo.XingHuoChatModel;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.yiyan.YiYanChatModel;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.zhipu.ZhiPuChatModel;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.AiWebSearchClient;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.bocha.AiBoChaWebSearchClient;
|
||||
import cn.iocoder.yudao.module.ai.tool.method.PersonService;
|
||||
import com.alibaba.cloud.ai.dashscope.api.DashScopeApi;
|
||||
import com.alibaba.cloud.ai.dashscope.api.DashScopeImageApi;
|
||||
import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel;
|
||||
import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions;
|
||||
import com.alibaba.cloud.ai.dashscope.embedding.text.DashScopeEmbeddingModel;
|
||||
import com.alibaba.cloud.ai.dashscope.embedding.text.DashScopeEmbeddingOptions;
|
||||
import com.alibaba.cloud.ai.dashscope.image.DashScopeImageModel;
|
||||
import com.alibaba.cloud.ai.dashscope.image.DashScopeImageOptions;
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.ai.chat.model.ChatModel;
|
||||
import org.springframework.ai.deepseek.DeepSeekChatModel;
|
||||
import org.springframework.ai.deepseek.DeepSeekChatOptions;
|
||||
import org.springframework.ai.deepseek.api.DeepSeekApi;
|
||||
import org.springframework.ai.document.MetadataMode;
|
||||
import org.springframework.ai.embedding.BatchingStrategy;
|
||||
import org.springframework.ai.embedding.TokenCountBatchingStrategy;
|
||||
import org.springframework.ai.model.tool.ToolCallingManager;
|
||||
import org.springframework.ai.openai.OpenAiChatModel;
|
||||
import org.springframework.ai.openai.OpenAiChatOptions;
|
||||
import org.springframework.ai.openai.api.OpenAiApi;
|
||||
import org.springframework.ai.retry.RetryUtils;
|
||||
import org.springframework.ai.support.ToolCallbacks;
|
||||
import org.springframework.ai.tokenizer.JTokkitTokenCountEstimator;
|
||||
import org.springframework.ai.tokenizer.TokenCountEstimator;
|
||||
@ -40,11 +52,11 @@ import org.springframework.ai.vectorstore.redis.autoconfigure.RedisVectorStorePr
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 芋道 AI 自动配置
|
||||
@ -75,31 +87,70 @@ public class AiAutoConfiguration {
|
||||
// ========== 各种 AI Client 创建 ==========
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(value = "yudao.ai.gemini.enable", havingValue = "true")
|
||||
public GeminiChatModel geminiChatModel(YudaoAiProperties yudaoAiProperties) {
|
||||
YudaoAiProperties.Gemini properties = yudaoAiProperties.getGemini();
|
||||
return buildGeminiChatClient(properties);
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(value = "spring.ai.dashscope.api-key")
|
||||
public DashScopeChatModel dashScopeChatModel(@Value("${spring.ai.dashscope.api-key}") String apiKey,
|
||||
ToolCallingManager toolCallingManager,
|
||||
ObservationRegistry observationRegistry) {
|
||||
return buildTongYiChatModel(apiKey, toolCallingManager, observationRegistry);
|
||||
}
|
||||
|
||||
public GeminiChatModel buildGeminiChatClient(YudaoAiProperties.Gemini properties) {
|
||||
if (StrUtil.isEmpty(properties.getModel())) {
|
||||
properties.setModel(GeminiChatModel.MODEL_DEFAULT);
|
||||
}
|
||||
OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
|
||||
.openAiApi(OpenAiApi.builder()
|
||||
.baseUrl(GeminiChatModel.BASE_URL)
|
||||
.completionsPath(GeminiChatModel.COMPLETE_PATH)
|
||||
.apiKey(properties.getApiKey())
|
||||
.build())
|
||||
.defaultOptions(OpenAiChatOptions.builder()
|
||||
.model(properties.getModel())
|
||||
.temperature(properties.getTemperature())
|
||||
.maxTokens(properties.getMaxTokens())
|
||||
.topP(properties.getTopP())
|
||||
.build())
|
||||
.toolCallingManager(getToolCallingManager())
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(value = "spring.ai.dashscope.api-key")
|
||||
public DashScopeImageModel dashScopeImageModel(@Value("${spring.ai.dashscope.api-key}") String apiKey,
|
||||
ObservationRegistry observationRegistry) {
|
||||
return buildTongYiImagesModel(apiKey, observationRegistry);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(value = "spring.ai.dashscope.api-key")
|
||||
public DashScopeEmbeddingModel dashScopeEmbeddingModel(@Value("${spring.ai.dashscope.api-key}") String apiKey,
|
||||
ObservationRegistry observationRegistry) {
|
||||
return buildTongYiEmbeddingModel(apiKey, null, observationRegistry);
|
||||
}
|
||||
|
||||
public static DashScopeChatModel buildTongYiChatModel(String apiKey) {
|
||||
return buildTongYiChatModel(apiKey, getToolCallingManager(), getObservationRegistry());
|
||||
}
|
||||
|
||||
private static DashScopeChatModel buildTongYiChatModel(String apiKey, ToolCallingManager toolCallingManager,
|
||||
ObservationRegistry observationRegistry) {
|
||||
DashScopeApi dashScopeApi = DashScopeApi.builder().apiKey(apiKey).build();
|
||||
DashScopeChatOptions options = DashScopeChatOptions.builder()
|
||||
.model(DashScopeApi.DEFAULT_CHAT_MODEL)
|
||||
.temperature(0.7)
|
||||
.build();
|
||||
return new GeminiChatModel(openAiChatModel);
|
||||
return new DashScopeChatModel(dashScopeApi, options, toolCallingManager, RetryUtils.DEFAULT_RETRY_TEMPLATE,
|
||||
observationRegistry);
|
||||
}
|
||||
|
||||
public static DashScopeImageModel buildTongYiImagesModel(String apiKey) {
|
||||
return buildTongYiImagesModel(apiKey, getObservationRegistry());
|
||||
}
|
||||
|
||||
private static DashScopeImageModel buildTongYiImagesModel(String apiKey, ObservationRegistry observationRegistry) {
|
||||
DashScopeImageApi dashScopeImageApi = DashScopeImageApi.builder().apiKey(apiKey).build();
|
||||
DashScopeImageOptions options = DashScopeImageOptions.builder()
|
||||
.model(DashScopeImageApi.DEFAULT_IMAGE_MODEL)
|
||||
.build();
|
||||
return new DashScopeImageModel(dashScopeImageApi, options, RetryUtils.DEFAULT_RETRY_TEMPLATE,
|
||||
observationRegistry);
|
||||
}
|
||||
|
||||
public static DashScopeEmbeddingModel buildTongYiEmbeddingModel(String apiKey, String model) {
|
||||
return buildTongYiEmbeddingModel(apiKey, model, getObservationRegistry());
|
||||
}
|
||||
|
||||
private static DashScopeEmbeddingModel buildTongYiEmbeddingModel(String apiKey, String model,
|
||||
ObservationRegistry observationRegistry) {
|
||||
DashScopeApi dashScopeApi = DashScopeApi.builder().apiKey(apiKey).build();
|
||||
DashScopeEmbeddingOptions options = DashScopeEmbeddingOptions.builder()
|
||||
.model(StrUtil.blankToDefault(model, DashScopeApi.DEFAULT_EMBEDDING_MODEL))
|
||||
.build();
|
||||
return new DashScopeEmbeddingModel(dashScopeApi, MetadataMode.EMBED, options, RetryUtils.DEFAULT_RETRY_TEMPLATE,
|
||||
observationRegistry);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ -113,19 +164,18 @@ public class AiAutoConfiguration {
|
||||
if (StrUtil.isEmpty(properties.getModel())) {
|
||||
properties.setModel(DouBaoChatModel.MODEL_DEFAULT);
|
||||
}
|
||||
OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
|
||||
.openAiApi(OpenAiApi.builder()
|
||||
DeepSeekChatModel openAiChatModel = DeepSeekChatModel.builder()
|
||||
.deepSeekApi(DeepSeekApi.builder()
|
||||
.baseUrl(DouBaoChatModel.BASE_URL)
|
||||
.completionsPath(DouBaoChatModel.COMPLETE_PATH)
|
||||
.apiKey(properties.getApiKey())
|
||||
.build())
|
||||
.defaultOptions(OpenAiChatOptions.builder()
|
||||
.options(DeepSeekChatOptions.builder()
|
||||
.model(properties.getModel())
|
||||
.temperature(properties.getTemperature())
|
||||
.maxTokens(properties.getMaxTokens())
|
||||
.topP(properties.getTopP())
|
||||
.build())
|
||||
.toolCallingManager(getToolCallingManager())
|
||||
.build();
|
||||
return new DouBaoChatModel(openAiChatModel);
|
||||
}
|
||||
@ -146,13 +196,12 @@ public class AiAutoConfiguration {
|
||||
.baseUrl(SiliconFlowApiConstants.DEFAULT_BASE_URL)
|
||||
.apiKey(properties.getApiKey())
|
||||
.build())
|
||||
.defaultOptions(DeepSeekChatOptions.builder()
|
||||
.options(DeepSeekChatOptions.builder()
|
||||
.model(properties.getModel())
|
||||
.temperature(properties.getTemperature())
|
||||
.maxTokens(properties.getMaxTokens())
|
||||
.topP(properties.getTopP())
|
||||
.build())
|
||||
.toolCallingManager(getToolCallingManager())
|
||||
.build();
|
||||
return new SiliconFlowChatModel(openAiChatModel);
|
||||
}
|
||||
@ -168,11 +217,8 @@ public class AiAutoConfiguration {
|
||||
if (StrUtil.isEmpty(properties.getModel())) {
|
||||
properties.setModel(HunYuanChatModel.MODEL_DEFAULT);
|
||||
}
|
||||
// 特殊:由于混元大模型不提供 deepseek,而是通过知识引擎,所以需要区分下 URL
|
||||
if (StrUtil.isEmpty(properties.getBaseUrl())) {
|
||||
properties.setBaseUrl(
|
||||
StrUtil.startWithIgnoreCase(properties.getModel(), "deepseek") ? HunYuanChatModel.DEEP_SEEK_BASE_URL
|
||||
: HunYuanChatModel.BASE_URL);
|
||||
properties.setBaseUrl(HunYuanChatModel.BASE_URL);
|
||||
}
|
||||
// 创建 DeepSeekChatModel、HunYuanChatModel 对象
|
||||
DeepSeekChatModel openAiChatModel = DeepSeekChatModel.builder()
|
||||
@ -181,13 +227,12 @@ public class AiAutoConfiguration {
|
||||
.completionsPath(HunYuanChatModel.COMPLETE_PATH)
|
||||
.apiKey(properties.getApiKey())
|
||||
.build())
|
||||
.defaultOptions(DeepSeekChatOptions.builder()
|
||||
.options(DeepSeekChatOptions.builder()
|
||||
.model(properties.getModel())
|
||||
.temperature(properties.getTemperature())
|
||||
.maxTokens(properties.getMaxTokens())
|
||||
.topP(properties.getTopP())
|
||||
.build())
|
||||
.toolCallingManager(getToolCallingManager())
|
||||
.build();
|
||||
return new HunYuanChatModel(openAiChatModel);
|
||||
}
|
||||
@ -203,25 +248,15 @@ public class AiAutoConfiguration {
|
||||
if (StrUtil.isEmpty(properties.getModel())) {
|
||||
properties.setModel(XingHuoChatModel.MODEL_DEFAULT);
|
||||
}
|
||||
OpenAiApi.Builder builder = OpenAiApi.builder()
|
||||
.baseUrl(XingHuoChatModel.BASE_URL_V1)
|
||||
.apiKey(properties.getAppKey() + ":" + properties.getSecretKey());
|
||||
if ("x1".equals(properties.getModel())) {
|
||||
builder.baseUrl(XingHuoChatModel.BASE_URL_V2)
|
||||
.completionsPath(XingHuoChatModel.BASE_COMPLETIONS_PATH_V2);
|
||||
}
|
||||
OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
|
||||
.openAiApi(builder.build())
|
||||
.defaultOptions(OpenAiChatOptions.builder()
|
||||
return XingHuoChatModel.builder()
|
||||
.apiKey(properties.getApiKey())
|
||||
.options(DeepSeekChatOptions.builder()
|
||||
.model(properties.getModel())
|
||||
.temperature(properties.getTemperature())
|
||||
.maxTokens(properties.getMaxTokens())
|
||||
.topP(properties.getTopP())
|
||||
.build())
|
||||
// TODO @芋艿:星火的 function call 有 bug,会报 ToolResponseMessage must have an id 错误!!!
|
||||
.toolCallingManager(getToolCallingManager())
|
||||
.build();
|
||||
return new XingHuoChatModel(openAiChatModel);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ -235,20 +270,118 @@ public class AiAutoConfiguration {
|
||||
if (StrUtil.isEmpty(properties.getModel())) {
|
||||
properties.setModel(BaiChuanChatModel.MODEL_DEFAULT);
|
||||
}
|
||||
OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
|
||||
.openAiApi(OpenAiApi.builder()
|
||||
DeepSeekChatModel deepSeekChatModel = DeepSeekChatModel.builder()
|
||||
.deepSeekApi(DeepSeekApi.builder()
|
||||
.baseUrl(BaiChuanChatModel.BASE_URL)
|
||||
.apiKey(properties.getApiKey())
|
||||
.completionsPath(BaiChuanChatModel.COMPLETE_PATH)
|
||||
.build())
|
||||
.defaultOptions(OpenAiChatOptions.builder()
|
||||
.options(DeepSeekChatOptions.builder()
|
||||
.model(properties.getModel())
|
||||
.temperature(properties.getTemperature())
|
||||
.maxTokens(properties.getMaxTokens())
|
||||
.topP(properties.getTopP())
|
||||
.build())
|
||||
.toolCallingManager(getToolCallingManager())
|
||||
.build();
|
||||
return new BaiChuanChatModel(openAiChatModel);
|
||||
return new BaiChuanChatModel(deepSeekChatModel);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(value = "yudao.ai.yiyan.enable", havingValue = "true")
|
||||
public YiYanChatModel yiYanChatClient(YudaoAiProperties yudaoAiProperties) {
|
||||
YudaoAiProperties.YiYan properties = yudaoAiProperties.getYiyan();
|
||||
return buildYiYanChatClient(properties);
|
||||
}
|
||||
|
||||
public YiYanChatModel buildYiYanChatClient(YudaoAiProperties.YiYan properties) {
|
||||
if (StrUtil.isEmpty(properties.getModel())) {
|
||||
properties.setModel(YiYanChatModel.MODEL_DEFAULT);
|
||||
}
|
||||
return new YiYanChatModel(buildDeepSeekCompatibleChatModel(
|
||||
StrUtil.blankToDefault(properties.getBaseUrl(), YiYanChatModel.BASE_URL),
|
||||
null, properties.getApiKey(), properties.getModel(), properties.getTemperature(),
|
||||
properties.getMaxTokens(), properties.getTopP()));
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(value = "yudao.ai.zhipu.enable", havingValue = "true")
|
||||
public ZhiPuChatModel zhiPuChatClient(YudaoAiProperties yudaoAiProperties) {
|
||||
YudaoAiProperties.ZhiPu properties = yudaoAiProperties.getZhipu();
|
||||
return buildZhiPuChatClient(properties);
|
||||
}
|
||||
|
||||
public ZhiPuChatModel buildZhiPuChatClient(YudaoAiProperties.ZhiPu properties) {
|
||||
if (StrUtil.isEmpty(properties.getModel())) {
|
||||
properties.setModel(ZhiPuChatModel.MODEL_DEFAULT);
|
||||
}
|
||||
DeepSeekChatModel deepSeekChatModel = DeepSeekChatModel.builder()
|
||||
.deepSeekApi(DeepSeekApi.builder()
|
||||
.baseUrl(StrUtil.blankToDefault(properties.getBaseUrl(), ZhiPuChatModel.BASE_URL))
|
||||
.apiKey(properties.getApiKey())
|
||||
.build())
|
||||
.options(DeepSeekChatOptions.builder()
|
||||
.model(properties.getModel())
|
||||
.temperature(properties.getTemperature())
|
||||
.maxTokens(properties.getMaxTokens())
|
||||
.topP(properties.getTopP())
|
||||
.build())
|
||||
.build();
|
||||
return new ZhiPuChatModel(deepSeekChatModel);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(value = "yudao.ai.minimax.enable", havingValue = "true")
|
||||
public MiniMaxChatModel miniMaxChatClient(YudaoAiProperties yudaoAiProperties) {
|
||||
YudaoAiProperties.MiniMax properties = yudaoAiProperties.getMinimax();
|
||||
return buildMiniMaxChatClient(properties);
|
||||
}
|
||||
|
||||
public MiniMaxChatModel buildMiniMaxChatClient(YudaoAiProperties.MiniMax properties) {
|
||||
if (StrUtil.isEmpty(properties.getModel())) {
|
||||
properties.setModel(MiniMaxChatModel.MODEL_DEFAULT);
|
||||
}
|
||||
return new MiniMaxChatModel(buildDeepSeekCompatibleChatModel(
|
||||
StrUtil.blankToDefault(properties.getBaseUrl(), MiniMaxChatModel.BASE_URL),
|
||||
null, properties.getApiKey(), properties.getModel(), properties.getTemperature(),
|
||||
properties.getMaxTokens(), properties.getTopP()));
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(value = "yudao.ai.moonshot.enable", havingValue = "true")
|
||||
public MoonshotChatModel moonshotChatClient(YudaoAiProperties yudaoAiProperties) {
|
||||
YudaoAiProperties.Moonshot properties = yudaoAiProperties.getMoonshot();
|
||||
return buildMoonshotChatClient(properties);
|
||||
}
|
||||
|
||||
public MoonshotChatModel buildMoonshotChatClient(YudaoAiProperties.Moonshot properties) {
|
||||
if (StrUtil.isEmpty(properties.getModel())) {
|
||||
properties.setModel(MoonshotChatModel.MODEL_DEFAULT);
|
||||
}
|
||||
return new MoonshotChatModel(buildDeepSeekCompatibleChatModel(
|
||||
StrUtil.blankToDefault(properties.getBaseUrl(), MoonshotChatModel.BASE_URL),
|
||||
MoonshotChatModel.COMPLETE_PATH, properties.getApiKey(), properties.getModel(),
|
||||
properties.getTemperature(), properties.getMaxTokens(), properties.getTopP()));
|
||||
}
|
||||
|
||||
private static DeepSeekChatModel buildDeepSeekCompatibleChatModel(String baseUrl, String completionsPath,
|
||||
String apiKey, String model,
|
||||
Double temperature, Integer maxTokens,
|
||||
Double topP) {
|
||||
DeepSeekApi.Builder apiBuilder = DeepSeekApi.builder()
|
||||
.baseUrl(baseUrl)
|
||||
.apiKey(apiKey);
|
||||
if (StrUtil.isNotEmpty(completionsPath)) {
|
||||
apiBuilder.completionsPath(completionsPath);
|
||||
}
|
||||
return DeepSeekChatModel.builder()
|
||||
.deepSeekApi(apiBuilder.build())
|
||||
.options(DeepSeekChatOptions.builder()
|
||||
.model(model)
|
||||
.temperature(temperature)
|
||||
.maxTokens(maxTokens)
|
||||
.topP(topP)
|
||||
.build())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ -269,21 +402,16 @@ public class AiAutoConfiguration {
|
||||
properties.setModel(GrokChatModel.MODEL_DEFAULT);
|
||||
}
|
||||
OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
|
||||
.openAiApi(OpenAiApi.builder()
|
||||
.baseUrl(Optional.ofNullable(properties.getBaseUrl())
|
||||
.orElse(GrokChatModel.BASE_URL))
|
||||
.completionsPath(GrokChatModel.COMPLETE_PATH)
|
||||
.options(OpenAiChatOptions.builder()
|
||||
.baseUrl(StrUtil.blankToDefault(properties.getBaseUrl(), GrokChatModel.BASE_URL))
|
||||
.apiKey(properties.getApiKey())
|
||||
.build())
|
||||
.defaultOptions(OpenAiChatOptions.builder()
|
||||
.model(properties.getModel())
|
||||
.temperature(properties.getTemperature())
|
||||
.maxTokens(properties.getMaxTokens())
|
||||
.topP(properties.getTopP())
|
||||
.build())
|
||||
.toolCallingManager(getToolCallingManager())
|
||||
.build();
|
||||
return new DouBaoChatModel(openAiChatModel);
|
||||
return new GrokChatModel(openAiChatModel);
|
||||
}
|
||||
|
||||
// ========== RAG 相关 ==========
|
||||
@ -302,6 +430,10 @@ public class AiAutoConfiguration {
|
||||
return SpringUtil.getBean(ToolCallingManager.class);
|
||||
}
|
||||
|
||||
private static ObservationRegistry getObservationRegistry() {
|
||||
return SpringUtil.getBean(ObservationRegistry.class);
|
||||
}
|
||||
|
||||
// ========== Web Search 相关 ==========
|
||||
|
||||
@Bean
|
||||
@ -320,4 +452,4 @@ public class AiAutoConfiguration {
|
||||
return List.of(ToolCallbacks.from(personService));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,11 +13,6 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
@Data
|
||||
public class YudaoAiProperties {
|
||||
|
||||
/**
|
||||
* 谷歌 Gemini
|
||||
*/
|
||||
private Gemini gemini;
|
||||
|
||||
/**
|
||||
* 字节豆包
|
||||
*/
|
||||
@ -43,6 +38,26 @@ public class YudaoAiProperties {
|
||||
*/
|
||||
private BaiChuan baichuan;
|
||||
|
||||
/**
|
||||
* 文心一言
|
||||
*/
|
||||
private YiYan yiyan;
|
||||
|
||||
/**
|
||||
* 智谱
|
||||
*/
|
||||
private ZhiPu zhipu;
|
||||
|
||||
/**
|
||||
* MiniMax
|
||||
*/
|
||||
private MiniMax minimax;
|
||||
|
||||
/**
|
||||
* 月之暗面
|
||||
*/
|
||||
private Moonshot moonshot;
|
||||
|
||||
/**
|
||||
* Midjourney 绘图
|
||||
*/
|
||||
@ -59,19 +74,6 @@ public class YudaoAiProperties {
|
||||
*/
|
||||
private WebSearch webSearch;
|
||||
|
||||
@Data
|
||||
public static class Gemini {
|
||||
|
||||
private String enable;
|
||||
private String apiKey;
|
||||
|
||||
private String model;
|
||||
private Double temperature;
|
||||
private Integer maxTokens;
|
||||
private Double topP;
|
||||
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class DouBao {
|
||||
|
||||
@ -116,9 +118,7 @@ public class YudaoAiProperties {
|
||||
public static class XingHuo {
|
||||
|
||||
private String enable;
|
||||
private String appId;
|
||||
private String appKey;
|
||||
private String secretKey;
|
||||
private String apiKey;
|
||||
|
||||
private String model;
|
||||
private Double temperature;
|
||||
@ -140,6 +140,62 @@ public class YudaoAiProperties {
|
||||
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class YiYan {
|
||||
|
||||
private String enable;
|
||||
private String baseUrl;
|
||||
private String apiKey;
|
||||
|
||||
private String model;
|
||||
private Double temperature;
|
||||
private Integer maxTokens;
|
||||
private Double topP;
|
||||
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class ZhiPu {
|
||||
|
||||
private String enable;
|
||||
private String baseUrl;
|
||||
private String apiKey;
|
||||
|
||||
private String model;
|
||||
private Double temperature;
|
||||
private Integer maxTokens;
|
||||
private Double topP;
|
||||
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class MiniMax {
|
||||
|
||||
private String enable;
|
||||
private String baseUrl;
|
||||
private String apiKey;
|
||||
|
||||
private String model;
|
||||
private Double temperature;
|
||||
private Integer maxTokens;
|
||||
private Double topP;
|
||||
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class Moonshot {
|
||||
|
||||
private String enable;
|
||||
private String baseUrl;
|
||||
private String apiKey;
|
||||
|
||||
private String model;
|
||||
private Double temperature;
|
||||
private Integer maxTokens;
|
||||
private Double topP;
|
||||
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class Midjourney {
|
||||
|
||||
|
||||
@ -8,52 +8,39 @@ import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.RuntimeUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.spring.SpringUtils;
|
||||
import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.config.AiAutoConfiguration;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.config.YudaoAiProperties;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.baichuan.BaiChuanChatModel;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.doubao.DouBaoChatModel;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.gemini.GeminiChatModel;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.hunyuan.HunYuanChatModel;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.midjourney.api.MidjourneyApi;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.minimax.MiniMaxChatModel;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.moonshot.MoonshotChatModel;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowApiConstants;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowChatModel;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowImageApi;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowImageModel;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.suno.api.SunoApi;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.xinghuo.XingHuoChatModel;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.yiyan.YiYanChatModel;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.zhipu.ZhiPuChatModel;
|
||||
import cn.iocoder.yudao.module.ai.util.AiUtils;
|
||||
import com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeChatAutoConfiguration;
|
||||
import com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeEmbeddingAutoConfiguration;
|
||||
import com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeImageAutoConfiguration;
|
||||
import com.alibaba.cloud.ai.dashscope.api.DashScopeApi;
|
||||
import com.alibaba.cloud.ai.dashscope.api.DashScopeImageApi;
|
||||
import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel;
|
||||
import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions;
|
||||
import com.alibaba.cloud.ai.dashscope.embedding.DashScopeEmbeddingModel;
|
||||
import com.alibaba.cloud.ai.dashscope.embedding.DashScopeEmbeddingOptions;
|
||||
import com.alibaba.cloud.ai.dashscope.embedding.text.DashScopeEmbeddingModel;
|
||||
import com.alibaba.cloud.ai.dashscope.image.DashScopeImageModel;
|
||||
import com.azure.ai.openai.OpenAIClientBuilder;
|
||||
import com.azure.core.credential.KeyCredential;
|
||||
import com.google.genai.Client;
|
||||
import com.google.genai.types.HttpOptions;
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
import io.milvus.client.MilvusServiceClient;
|
||||
import io.qdrant.client.QdrantClient;
|
||||
import io.qdrant.client.QdrantGrpcClient;
|
||||
import lombok.SneakyThrows;
|
||||
import org.springaicommunity.moonshot.MoonshotChatModel;
|
||||
import org.springaicommunity.moonshot.MoonshotChatOptions;
|
||||
import org.springaicommunity.moonshot.api.MoonshotApi;
|
||||
import org.springaicommunity.moonshot.autoconfigure.MoonshotChatAutoConfiguration;
|
||||
import org.springaicommunity.qianfan.QianFanChatModel;
|
||||
import org.springaicommunity.qianfan.QianFanEmbeddingModel;
|
||||
import org.springaicommunity.qianfan.QianFanEmbeddingOptions;
|
||||
import org.springaicommunity.qianfan.QianFanImageModel;
|
||||
import org.springaicommunity.qianfan.api.QianFanApi;
|
||||
import org.springaicommunity.qianfan.api.QianFanImageApi;
|
||||
import org.springaicommunity.qianfan.autoconfigure.QianFanChatAutoConfiguration;
|
||||
import org.springaicommunity.qianfan.autoconfigure.QianFanEmbeddingAutoConfiguration;
|
||||
import org.springframework.ai.azure.openai.AzureOpenAiChatModel;
|
||||
import org.springframework.ai.azure.openai.AzureOpenAiEmbeddingModel;
|
||||
import org.springframework.ai.anthropic.AnthropicChatModel;
|
||||
import org.springframework.ai.anthropic.AnthropicChatOptions;
|
||||
import org.springframework.ai.chat.model.ChatModel;
|
||||
import org.springframework.ai.deepseek.DeepSeekChatModel;
|
||||
import org.springframework.ai.deepseek.DeepSeekChatOptions;
|
||||
@ -62,41 +49,24 @@ import org.springframework.ai.document.MetadataMode;
|
||||
import org.springframework.ai.embedding.BatchingStrategy;
|
||||
import org.springframework.ai.embedding.EmbeddingModel;
|
||||
import org.springframework.ai.embedding.observation.EmbeddingModelObservationConvention;
|
||||
import org.springframework.ai.google.genai.GoogleGenAiChatModel;
|
||||
import org.springframework.ai.google.genai.GoogleGenAiChatOptions;
|
||||
import org.springframework.ai.image.ImageModel;
|
||||
import org.springframework.ai.minimax.MiniMaxChatModel;
|
||||
import org.springframework.ai.minimax.MiniMaxChatOptions;
|
||||
import org.springframework.ai.minimax.MiniMaxEmbeddingModel;
|
||||
import org.springframework.ai.minimax.MiniMaxEmbeddingOptions;
|
||||
import org.springframework.ai.minimax.api.MiniMaxApi;
|
||||
import org.springframework.ai.model.anthropic.autoconfigure.AnthropicChatAutoConfiguration;
|
||||
import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiChatAutoConfiguration;
|
||||
import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiEmbeddingAutoConfiguration;
|
||||
import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiEmbeddingProperties;
|
||||
import org.springframework.ai.model.deepseek.autoconfigure.DeepSeekChatAutoConfiguration;
|
||||
import org.springframework.ai.model.minimax.autoconfigure.MiniMaxChatAutoConfiguration;
|
||||
import org.springframework.ai.model.minimax.autoconfigure.MiniMaxEmbeddingAutoConfiguration;
|
||||
import org.springframework.ai.model.google.genai.autoconfigure.chat.GoogleGenAiChatAutoConfiguration;
|
||||
import org.springframework.ai.model.ollama.autoconfigure.OllamaChatAutoConfiguration;
|
||||
import org.springframework.ai.model.openai.autoconfigure.OpenAiChatAutoConfiguration;
|
||||
import org.springframework.ai.model.openai.autoconfigure.OpenAiEmbeddingAutoConfiguration;
|
||||
import org.springframework.ai.model.openai.autoconfigure.OpenAiImageAutoConfiguration;
|
||||
import org.springframework.ai.model.stabilityai.autoconfigure.StabilityAiImageAutoConfiguration;
|
||||
import org.springframework.ai.model.tool.ToolCallingManager;
|
||||
import org.springframework.ai.model.zhipuai.autoconfigure.ZhiPuAiChatAutoConfiguration;
|
||||
import org.springframework.ai.model.zhipuai.autoconfigure.ZhiPuAiEmbeddingAutoConfiguration;
|
||||
import org.springframework.ai.model.zhipuai.autoconfigure.ZhiPuAiImageAutoConfiguration;
|
||||
import org.springframework.ai.ollama.OllamaChatModel;
|
||||
import org.springframework.ai.ollama.OllamaEmbeddingModel;
|
||||
import org.springframework.ai.ollama.api.OllamaApi;
|
||||
import org.springframework.ai.ollama.api.OllamaEmbeddingOptions;
|
||||
import org.springframework.ai.openai.OpenAiChatModel;
|
||||
import org.springframework.ai.openai.OpenAiEmbeddingModel;
|
||||
import org.springframework.ai.openai.OpenAiEmbeddingOptions;
|
||||
import org.springframework.ai.openai.OpenAiImageModel;
|
||||
import org.springframework.ai.openai.api.OpenAiApi;
|
||||
import org.springframework.ai.openai.api.OpenAiImageApi;
|
||||
import org.springframework.ai.openai.api.common.OpenAiApiConstants;
|
||||
import org.springframework.ai.anthropic.AnthropicChatModel;
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi;
|
||||
import org.springframework.ai.openai.*;
|
||||
import org.springframework.ai.retry.RetryUtils;
|
||||
import org.springframework.ai.stabilityai.StabilityAiImageModel;
|
||||
import org.springframework.ai.stabilityai.api.StabilityAiApi;
|
||||
import org.springframework.ai.vectorstore.SimpleVectorStore;
|
||||
@ -114,24 +84,22 @@ import org.springframework.ai.vectorstore.qdrant.autoconfigure.QdrantVectorStore
|
||||
import org.springframework.ai.vectorstore.redis.RedisVectorStore;
|
||||
import org.springframework.ai.vectorstore.redis.autoconfigure.RedisVectorStoreAutoConfiguration;
|
||||
import org.springframework.ai.vectorstore.redis.autoconfigure.RedisVectorStoreProperties;
|
||||
import org.springframework.ai.zhipuai.*;
|
||||
import org.springframework.ai.zhipuai.api.ZhiPuAiApi;
|
||||
import org.springframework.ai.zhipuai.api.ZhiPuAiImageApi;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
|
||||
import org.springframework.web.client.RestClient;
|
||||
import redis.clients.jedis.JedisPooled;
|
||||
import org.springframework.boot.data.redis.autoconfigure.DataRedisProperties;
|
||||
import redis.clients.jedis.DefaultJedisClientConfig;
|
||||
import redis.clients.jedis.HostAndPort;
|
||||
import redis.clients.jedis.JedisClientConfig;
|
||||
import redis.clients.jedis.RedisClient;
|
||||
|
||||
import java.io.File;
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
|
||||
import static org.springframework.ai.retry.RetryUtils.DEFAULT_RETRY_TEMPLATE;
|
||||
|
||||
/**
|
||||
* AI Model 模型工厂的实现类
|
||||
@ -141,7 +109,9 @@ import static org.springframework.ai.retry.RetryUtils.DEFAULT_RETRY_TEMPLATE;
|
||||
public class AiModelFactoryImpl implements AiModelFactory {
|
||||
|
||||
@Override
|
||||
public ChatModel getOrCreateChatModel(AiPlatformEnum platform, String apiKey, String url) {
|
||||
public ChatModel getOrCreateChatModel(AiPlatformEnum platform, String rawApiKey, String rawUrl) {
|
||||
final String apiKey = resolveSpringPlaceholders(rawApiKey);
|
||||
final String url = resolveSpringPlaceholders(rawUrl);
|
||||
String cacheKey = buildClientCacheKey(ChatModel.class, platform, apiKey, url);
|
||||
return Singleton.get(cacheKey, (Func0<ChatModel>) () -> {
|
||||
// noinspection EnhancedSwitchMigration
|
||||
@ -175,11 +145,11 @@ public class AiModelFactoryImpl implements AiModelFactory {
|
||||
case ANTHROPIC:
|
||||
return buildAnthropicChatModel(apiKey, url);
|
||||
case GEMINI:
|
||||
return buildGeminiChatModel(apiKey);
|
||||
return buildGeminiChatModel(apiKey, url);
|
||||
case OLLAMA:
|
||||
return buildOllamaChatModel(url);
|
||||
case GROK:
|
||||
return buildGrokChatModel(apiKey,url);
|
||||
return buildGrokChatModel(apiKey, url);
|
||||
default:
|
||||
throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform));
|
||||
}
|
||||
@ -193,7 +163,7 @@ public class AiModelFactoryImpl implements AiModelFactory {
|
||||
case TONG_YI:
|
||||
return SpringUtil.getBean(DashScopeChatModel.class);
|
||||
case YI_YAN:
|
||||
return SpringUtil.getBean(QianFanChatModel.class);
|
||||
return SpringUtil.getBean(YiYanChatModel.class);
|
||||
case DEEP_SEEK:
|
||||
return SpringUtil.getBean(DeepSeekChatModel.class);
|
||||
case DOU_BAO:
|
||||
@ -203,7 +173,7 @@ public class AiModelFactoryImpl implements AiModelFactory {
|
||||
case SILICON_FLOW:
|
||||
return SpringUtil.getBean(SiliconFlowChatModel.class);
|
||||
case ZHI_PU:
|
||||
return SpringUtil.getBean(ZhiPuAiChatModel.class);
|
||||
return SpringUtil.getBean(ZhiPuChatModel.class);
|
||||
case MINI_MAX:
|
||||
return SpringUtil.getBean(MiniMaxChatModel.class);
|
||||
case MOONSHOT:
|
||||
@ -214,12 +184,10 @@ public class AiModelFactoryImpl implements AiModelFactory {
|
||||
return SpringUtil.getBean(BaiChuanChatModel.class);
|
||||
case OPENAI:
|
||||
return SpringUtil.getBean(OpenAiChatModel.class);
|
||||
case AZURE_OPENAI:
|
||||
return SpringUtil.getBean(AzureOpenAiChatModel.class);
|
||||
case ANTHROPIC:
|
||||
return SpringUtil.getBean(AnthropicChatModel.class);
|
||||
case GEMINI:
|
||||
return SpringUtil.getBean(GeminiChatModel.class);
|
||||
return SpringUtil.getBean(GoogleGenAiChatModel.class);
|
||||
case OLLAMA:
|
||||
return SpringUtil.getBean(OllamaChatModel.class);
|
||||
default:
|
||||
@ -233,10 +201,6 @@ public class AiModelFactoryImpl implements AiModelFactory {
|
||||
switch (platform) {
|
||||
case TONG_YI:
|
||||
return SpringUtil.getBean(DashScopeImageModel.class);
|
||||
case YI_YAN:
|
||||
return SpringUtil.getBean(QianFanImageModel.class);
|
||||
case ZHI_PU:
|
||||
return SpringUtil.getBean(ZhiPuAiImageModel.class);
|
||||
case SILICON_FLOW:
|
||||
return SpringUtil.getBean(SiliconFlowImageModel.class);
|
||||
case OPENAI:
|
||||
@ -249,19 +213,17 @@ public class AiModelFactoryImpl implements AiModelFactory {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageModel getOrCreateImageModel(AiPlatformEnum platform, String apiKey, String url) {
|
||||
public ImageModel getOrCreateImageModel(AiPlatformEnum platform, String rawApiKey, String rawUrl) {
|
||||
String apiKey = resolveSpringPlaceholders(rawApiKey);
|
||||
String url = resolveSpringPlaceholders(rawUrl);
|
||||
// noinspection EnhancedSwitchMigration
|
||||
switch (platform) {
|
||||
case TONG_YI:
|
||||
return buildTongYiImagesModel(apiKey);
|
||||
case YI_YAN:
|
||||
return buildQianFanImageModel(apiKey);
|
||||
case ZHI_PU:
|
||||
return buildZhiPuAiImageModel(apiKey, url);
|
||||
case OPENAI:
|
||||
return buildOpenAiImageModel(apiKey, url);
|
||||
case SILICON_FLOW:
|
||||
return buildSiliconFlowImageModel(apiKey,url);
|
||||
return buildSiliconFlowImageModel(apiKey, url);
|
||||
case STABLE_DIFFUSION:
|
||||
return buildStabilityAiImageModel(apiKey, url);
|
||||
default:
|
||||
@ -270,9 +232,11 @@ public class AiModelFactoryImpl implements AiModelFactory {
|
||||
}
|
||||
|
||||
@Override
|
||||
public MidjourneyApi getOrCreateMidjourneyApi(String apiKey, String url) {
|
||||
String cacheKey = buildClientCacheKey(MidjourneyApi.class, AiPlatformEnum.MIDJOURNEY.getPlatform(), apiKey,
|
||||
url);
|
||||
public MidjourneyApi getOrCreateMidjourneyApi(String rawApiKey, String rawUrl) {
|
||||
final String apiKey = resolveSpringPlaceholders(rawApiKey);
|
||||
final String url = resolveSpringPlaceholders(rawUrl);
|
||||
String cacheKey = buildClientCacheKey(MidjourneyApi.class, AiPlatformEnum.MIDJOURNEY.getPlatform(),
|
||||
apiKey, url);
|
||||
return Singleton.get(cacheKey, (Func0<MidjourneyApi>) () -> {
|
||||
YudaoAiProperties.Midjourney properties = SpringUtil.getBean(YudaoAiProperties.class)
|
||||
.getMidjourney();
|
||||
@ -281,25 +245,23 @@ public class AiModelFactoryImpl implements AiModelFactory {
|
||||
}
|
||||
|
||||
@Override
|
||||
public SunoApi getOrCreateSunoApi(String apiKey, String url) {
|
||||
public SunoApi getOrCreateSunoApi(String rawApiKey, String rawUrl) {
|
||||
final String apiKey = resolveSpringPlaceholders(rawApiKey);
|
||||
final String url = resolveSpringPlaceholders(rawUrl);
|
||||
String cacheKey = buildClientCacheKey(SunoApi.class, AiPlatformEnum.SUNO.getPlatform(), apiKey, url);
|
||||
return Singleton.get(cacheKey, (Func0<SunoApi>) () -> new SunoApi(url));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("EnhancedSwitchMigration")
|
||||
public EmbeddingModel getOrCreateEmbeddingModel(AiPlatformEnum platform, String apiKey, String url, String model) {
|
||||
public EmbeddingModel getOrCreateEmbeddingModel(AiPlatformEnum platform, String rawApiKey, String rawUrl, String model) {
|
||||
final String apiKey = resolveSpringPlaceholders(rawApiKey);
|
||||
final String url = resolveSpringPlaceholders(rawUrl);
|
||||
String cacheKey = buildClientCacheKey(EmbeddingModel.class, platform, apiKey, url, model);
|
||||
return Singleton.get(cacheKey, (Func0<EmbeddingModel>) () -> {
|
||||
switch (platform) {
|
||||
case TONG_YI:
|
||||
return buildTongYiEmbeddingModel(apiKey, model);
|
||||
case YI_YAN:
|
||||
return buildYiYanEmbeddingModel(apiKey, model);
|
||||
case ZHI_PU:
|
||||
return buildZhiPuEmbeddingModel(apiKey, url, model);
|
||||
case MINI_MAX:
|
||||
return buildMiniMaxEmbeddingModel(apiKey, url, model);
|
||||
case OPENAI:
|
||||
return buildOpenAiEmbeddingModel(apiKey, url, model);
|
||||
case AZURE_OPENAI:
|
||||
@ -341,56 +303,31 @@ public class AiModelFactoryImpl implements AiModelFactory {
|
||||
return StrUtil.format("{}#{}", clazz.getName(), ArrayUtil.join(params, "_"));
|
||||
}
|
||||
|
||||
private static String resolveSpringPlaceholders(String value) {
|
||||
// yml 配置的占位符由 Spring 自动解析;DB 里保存的 ${xxx} 需要在这里手动解析。
|
||||
return AiUtils.resolveSpringPlaceholders(value);
|
||||
}
|
||||
|
||||
// ========== 各种创建 spring-ai 客户端的方法 ==========
|
||||
|
||||
/**
|
||||
* 可参考 {@link DashScopeChatAutoConfiguration} 的 dashscopeChatModel 方法
|
||||
*/
|
||||
private static DashScopeChatModel buildTongYiChatModel(String key) {
|
||||
DashScopeApi dashScopeApi = DashScopeApi.builder().apiKey(key).build();
|
||||
DashScopeChatOptions options = DashScopeChatOptions.builder().withModel(DashScopeApi.DEFAULT_CHAT_MODEL)
|
||||
.withTemperature(0.7).build();
|
||||
return DashScopeChatModel.builder()
|
||||
.dashScopeApi(dashScopeApi)
|
||||
.defaultOptions(options)
|
||||
.toolCallingManager(getToolCallingManager())
|
||||
.build();
|
||||
return AiAutoConfiguration.buildTongYiChatModel(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 可参考 {@link DashScopeImageAutoConfiguration} 的 dashScopeImageModel 方法
|
||||
*/
|
||||
private static DashScopeImageModel buildTongYiImagesModel(String key) {
|
||||
DashScopeImageApi dashScopeImageApi = DashScopeImageApi.builder().apiKey(key).build();
|
||||
return DashScopeImageModel.builder()
|
||||
.dashScopeApi(dashScopeImageApi)
|
||||
.build();
|
||||
return AiAutoConfiguration.buildTongYiImagesModel(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 可参考 {@link QianFanChatAutoConfiguration} 的 qianFanChatModel 方法
|
||||
*/
|
||||
private static QianFanChatModel buildYiYanChatModel(String key) {
|
||||
// TODO spring ai qianfan 有 bug,无法使用 https://github.com/spring-ai-community/qianfan/issues/6
|
||||
List<String> keys = StrUtil.split(key, '|');
|
||||
Assert.equals(keys.size(), 2, "YiYanChatClient 的密钥需要 (appKey|secretKey) 格式");
|
||||
String appKey = keys.get(0);
|
||||
String secretKey = keys.get(1);
|
||||
QianFanApi qianFanApi = new QianFanApi(appKey, secretKey);
|
||||
return new QianFanChatModel(qianFanApi);
|
||||
}
|
||||
|
||||
/**
|
||||
* 可参考 {@link QianFanEmbeddingAutoConfiguration} 的 qianFanImageModel 方法
|
||||
*/
|
||||
private QianFanImageModel buildQianFanImageModel(String key) {
|
||||
// TODO spring ai qianfan 有 bug,无法使用 https://github.com/spring-ai-community/qianfan/issues/6
|
||||
List<String> keys = StrUtil.split(key, '|');
|
||||
Assert.equals(keys.size(), 2, "YiYanChatClient 的密钥需要 (appKey|secretKey) 格式");
|
||||
String appKey = keys.get(0);
|
||||
String secretKey = keys.get(1);
|
||||
QianFanImageApi qianFanApi = new QianFanImageApi(appKey, secretKey);
|
||||
return new QianFanImageModel(qianFanApi);
|
||||
private ChatModel buildYiYanChatModel(String apiKey) {
|
||||
YudaoAiProperties.YiYan properties = new YudaoAiProperties.YiYan()
|
||||
.setApiKey(apiKey);
|
||||
return new AiAutoConfiguration().buildYiYanChatClient(properties);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -402,8 +339,7 @@ public class AiModelFactoryImpl implements AiModelFactory {
|
||||
.temperature(0.7).build();
|
||||
return DeepSeekChatModel.builder()
|
||||
.deepSeekApi(deepSeekApi)
|
||||
.defaultOptions(options)
|
||||
.toolCallingManager(getToolCallingManager())
|
||||
.options(options)
|
||||
.build();
|
||||
}
|
||||
|
||||
@ -435,62 +371,38 @@ public class AiModelFactoryImpl implements AiModelFactory {
|
||||
}
|
||||
|
||||
/**
|
||||
* 可参考 {@link ZhiPuAiChatAutoConfiguration} 的 zhiPuAiChatModel 方法
|
||||
* 可参考 {@link AiAutoConfiguration#zhiPuChatClient(YudaoAiProperties)}
|
||||
*/
|
||||
private ZhiPuAiChatModel buildZhiPuChatModel(String apiKey, String url) {
|
||||
ZhiPuAiApi.Builder zhiPuAiApiBuilder = ZhiPuAiApi.builder().apiKey(apiKey);
|
||||
if (StrUtil.isNotEmpty(url)) {
|
||||
zhiPuAiApiBuilder.baseUrl(url);
|
||||
}
|
||||
ZhiPuAiChatOptions options = ZhiPuAiChatOptions.builder().model(ZhiPuAiApi.DEFAULT_CHAT_MODEL).temperature(0.7).build();
|
||||
return new ZhiPuAiChatModel(zhiPuAiApiBuilder.build(), options, getToolCallingManager(), DEFAULT_RETRY_TEMPLATE,
|
||||
getObservationRegistry().getIfAvailable());
|
||||
private ZhiPuChatModel buildZhiPuChatModel(String apiKey, String url) {
|
||||
YudaoAiProperties.ZhiPu properties = new YudaoAiProperties.ZhiPu()
|
||||
.setBaseUrl(url).setApiKey(apiKey);
|
||||
return new AiAutoConfiguration().buildZhiPuChatClient(properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* 可参考 {@link ZhiPuAiImageAutoConfiguration} 的 zhiPuAiImageModel 方法
|
||||
*/
|
||||
private ZhiPuAiImageModel buildZhiPuAiImageModel(String apiKey, String url) {
|
||||
ZhiPuAiImageApi zhiPuAiApi = StrUtil.isEmpty(url) ? new ZhiPuAiImageApi(apiKey)
|
||||
: new ZhiPuAiImageApi(url, apiKey, RestClient.builder());
|
||||
return new ZhiPuAiImageModel(zhiPuAiApi);
|
||||
}
|
||||
|
||||
/**
|
||||
* 可参考 {@link MiniMaxChatAutoConfiguration} 的 miniMaxChatModel 方法
|
||||
* 可参考 {@link AiAutoConfiguration#miniMaxChatClient(YudaoAiProperties)}
|
||||
*/
|
||||
private MiniMaxChatModel buildMiniMaxChatModel(String apiKey, String url) {
|
||||
MiniMaxApi miniMaxApi = StrUtil.isEmpty(url) ? new MiniMaxApi(apiKey)
|
||||
: new MiniMaxApi(url, apiKey);
|
||||
MiniMaxChatOptions options = MiniMaxChatOptions.builder().model(MiniMaxApi.DEFAULT_CHAT_MODEL).temperature(0.7).build();
|
||||
return new MiniMaxChatModel(miniMaxApi, options, getToolCallingManager(), DEFAULT_RETRY_TEMPLATE);
|
||||
YudaoAiProperties.MiniMax properties = new YudaoAiProperties.MiniMax()
|
||||
.setBaseUrl(url).setApiKey(apiKey);
|
||||
return new AiAutoConfiguration().buildMiniMaxChatClient(properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* 可参考 {@link MoonshotChatAutoConfiguration} 的 moonshotChatModel 方法
|
||||
* 可参考 {@link AiAutoConfiguration#moonshotChatClient(YudaoAiProperties)}
|
||||
*/
|
||||
private MoonshotChatModel buildMoonshotChatModel(String apiKey, String url) {
|
||||
MoonshotApi.Builder moonshotApiBuilder = MoonshotApi.builder()
|
||||
.apiKey(apiKey);
|
||||
if (StrUtil.isNotEmpty(url)) {
|
||||
moonshotApiBuilder.baseUrl(url);
|
||||
}
|
||||
MoonshotChatOptions options = MoonshotChatOptions.builder().model(MoonshotApi.DEFAULT_CHAT_MODEL).build();
|
||||
return MoonshotChatModel.builder()
|
||||
.moonshotApi(moonshotApiBuilder.build())
|
||||
.defaultOptions(options)
|
||||
.toolCallingManager(getToolCallingManager())
|
||||
.build();
|
||||
YudaoAiProperties.Moonshot properties = new YudaoAiProperties.Moonshot()
|
||||
.setBaseUrl(url).setApiKey(apiKey);
|
||||
return new AiAutoConfiguration().buildMoonshotChatClient(properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* 可参考 {@link AiAutoConfiguration#xingHuoChatClient(YudaoAiProperties)}
|
||||
*/
|
||||
private static XingHuoChatModel buildXingHuoChatModel(String key) {
|
||||
List<String> keys = StrUtil.split(key, '|');
|
||||
Assert.equals(keys.size(), 2, "XingHuoChatClient 的密钥需要 (appKey|secretKey) 格式");
|
||||
private static XingHuoChatModel buildXingHuoChatModel(String apiKey) {
|
||||
YudaoAiProperties.XingHuo properties = new YudaoAiProperties.XingHuo()
|
||||
.setAppKey(keys.get(0)).setSecretKey(keys.get(1));
|
||||
.setApiKey(apiKey).setModel(XingHuoChatModel.MODEL_DEFAULT);
|
||||
return new AiAutoConfiguration().buildXingHuoChatClient(properties);
|
||||
}
|
||||
|
||||
@ -507,58 +419,74 @@ public class AiModelFactoryImpl implements AiModelFactory {
|
||||
* 可参考 {@link OpenAiChatAutoConfiguration} 的 openAiChatModel 方法
|
||||
*/
|
||||
private static OpenAiChatModel buildOpenAiChatModel(String openAiToken, String url) {
|
||||
url = StrUtil.blankToDefault(url, OpenAiApiConstants.DEFAULT_BASE_URL);
|
||||
OpenAiApi openAiApi = OpenAiApi.builder().baseUrl(url).apiKey(openAiToken).build();
|
||||
return OpenAiChatModel.builder()
|
||||
.openAiApi(openAiApi)
|
||||
.toolCallingManager(getToolCallingManager())
|
||||
.options(buildOpenAiChatOptions(openAiToken, url).build())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 可参考 {@link AzureOpenAiChatAutoConfiguration}
|
||||
*/
|
||||
private static AzureOpenAiChatModel buildAzureOpenAiChatModel(String apiKey, String url) {
|
||||
// TODO @芋艿:使用前,请测试,暂时没密钥!!!
|
||||
OpenAIClientBuilder openAIClientBuilder = new OpenAIClientBuilder()
|
||||
.endpoint(url).credential(new KeyCredential(apiKey));
|
||||
return AzureOpenAiChatModel.builder()
|
||||
.openAIClientBuilder(openAIClientBuilder)
|
||||
.toolCallingManager(getToolCallingManager())
|
||||
private static OpenAiChatModel buildAzureOpenAiChatModel(String openAiToken, String url) {
|
||||
return OpenAiChatModel.builder()
|
||||
.options(buildOpenAiChatOptions(openAiToken, url)
|
||||
.azure(true)
|
||||
.build())
|
||||
.build();
|
||||
}
|
||||
|
||||
private static OpenAiChatOptions.Builder buildOpenAiChatOptions(String apiKey, String url) {
|
||||
OpenAiChatOptions.Builder optionsBuilder = OpenAiChatOptions.builder().apiKey(apiKey);
|
||||
if (StrUtil.isNotEmpty(url)) {
|
||||
optionsBuilder.baseUrl(url);
|
||||
}
|
||||
return optionsBuilder;
|
||||
}
|
||||
|
||||
/**
|
||||
* 可参考 {@link AnthropicChatAutoConfiguration} 的 anthropicApi 方法
|
||||
*/
|
||||
private static AnthropicChatModel buildAnthropicChatModel(String apiKey, String url) {
|
||||
AnthropicApi.Builder builder = AnthropicApi.builder().apiKey(apiKey);
|
||||
AnthropicChatOptions.Builder optionsBuilder = AnthropicChatOptions.builder().apiKey(apiKey);
|
||||
if (StrUtil.isNotEmpty(url)) {
|
||||
builder.baseUrl(url);
|
||||
optionsBuilder.baseUrl(url);
|
||||
}
|
||||
AnthropicApi anthropicApi = builder.build();
|
||||
return AnthropicChatModel.builder()
|
||||
.anthropicApi(anthropicApi)
|
||||
.toolCallingManager(getToolCallingManager())
|
||||
.options(optionsBuilder.build())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 可参考 {@link AiAutoConfiguration#buildGeminiChatClient(YudaoAiProperties.Gemini)}
|
||||
* 可参考 {@link GoogleGenAiChatAutoConfiguration} 的 googleGenAiChatModel 方法
|
||||
*/
|
||||
private static GeminiChatModel buildGeminiChatModel(String apiKey) {
|
||||
YudaoAiProperties.Gemini properties = SpringUtil.getBean(YudaoAiProperties.class)
|
||||
.getGemini().setApiKey(apiKey);
|
||||
return new AiAutoConfiguration().buildGeminiChatClient(properties);
|
||||
private static GoogleGenAiChatModel buildGeminiChatModel(String apiKey, String url) {
|
||||
Client.Builder clientBuilder = Client.builder().apiKey(apiKey);
|
||||
if (StrUtil.isNotBlank(url)) {
|
||||
clientBuilder.httpOptions(HttpOptions.builder()
|
||||
.baseUrl(url)
|
||||
// TeamOrouter 的 Gemini 原生协议使用 Authorization Bearer 鉴权
|
||||
.headers(Collections.singletonMap("Authorization", "Bearer " + apiKey))
|
||||
.build());
|
||||
}
|
||||
return GoogleGenAiChatModel.builder()
|
||||
.genAiClient(clientBuilder.build())
|
||||
.options(GoogleGenAiChatOptions.builder()
|
||||
.model("gemini-2.5-flash")
|
||||
.build())
|
||||
.toolCallingManager(SpringUtil.getBean(ToolCallingManager.class))
|
||||
.retryTemplate(RetryUtils.DEFAULT_RETRY_TEMPLATE)
|
||||
.observationRegistry(SpringUtil.getBean(ObservationRegistry.class))
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 可参考 {@link OpenAiImageAutoConfiguration} 的 openAiImageModel 方法
|
||||
*/
|
||||
private OpenAiImageModel buildOpenAiImageModel(String openAiToken, String url) {
|
||||
url = StrUtil.blankToDefault(url, OpenAiApiConstants.DEFAULT_BASE_URL);
|
||||
OpenAiImageApi openAiApi = OpenAiImageApi.builder().baseUrl(url).apiKey(openAiToken).build();
|
||||
return new OpenAiImageModel(openAiApi);
|
||||
OpenAiImageOptions.Builder optionsBuilder = OpenAiImageOptions.builder().apiKey(openAiToken);
|
||||
if (StrUtil.isNotEmpty(url)) {
|
||||
optionsBuilder.baseUrl(url);
|
||||
}
|
||||
return OpenAiImageModel.builder()
|
||||
.options(optionsBuilder.build())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -577,7 +505,6 @@ public class AiModelFactoryImpl implements AiModelFactory {
|
||||
OllamaApi ollamaApi = OllamaApi.builder().baseUrl(url).build();
|
||||
return OllamaChatModel.builder()
|
||||
.ollamaApi(ollamaApi)
|
||||
.toolCallingManager(getToolCallingManager())
|
||||
.build();
|
||||
}
|
||||
|
||||
@ -600,47 +527,10 @@ public class AiModelFactoryImpl implements AiModelFactory {
|
||||
// ========== 各种创建 EmbeddingModel 的方法 ==========
|
||||
|
||||
/**
|
||||
* 可参考 {@link DashScopeEmbeddingAutoConfiguration} 的 dashscopeEmbeddingModel 方法
|
||||
* 可参考 {@link DashScopeEmbeddingAutoConfiguration} 的 DashScopeEmbeddingModel 方法
|
||||
*/
|
||||
private DashScopeEmbeddingModel buildTongYiEmbeddingModel(String apiKey, String model) {
|
||||
DashScopeApi dashScopeApi = DashScopeApi.builder().apiKey(apiKey).build();
|
||||
DashScopeEmbeddingOptions dashScopeEmbeddingOptions = DashScopeEmbeddingOptions.builder().withModel(model).build();
|
||||
return new DashScopeEmbeddingModel(dashScopeApi, MetadataMode.EMBED, dashScopeEmbeddingOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* 可参考 {@link ZhiPuAiEmbeddingAutoConfiguration} 的 zhiPuAiEmbeddingModel 方法
|
||||
*/
|
||||
private ZhiPuAiEmbeddingModel buildZhiPuEmbeddingModel(String apiKey, String url, String model) {
|
||||
ZhiPuAiApi.Builder zhiPuAiApiBuilder = ZhiPuAiApi.builder().apiKey(apiKey);
|
||||
if (StrUtil.isNotEmpty(url)) {
|
||||
zhiPuAiApiBuilder.baseUrl(url);
|
||||
}
|
||||
ZhiPuAiEmbeddingOptions zhiPuAiEmbeddingOptions = ZhiPuAiEmbeddingOptions.builder().model(model).build();
|
||||
return new ZhiPuAiEmbeddingModel(zhiPuAiApiBuilder.build(), MetadataMode.EMBED, zhiPuAiEmbeddingOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* 可参考 {@link MiniMaxEmbeddingAutoConfiguration} 的 miniMaxEmbeddingModel 方法
|
||||
*/
|
||||
private EmbeddingModel buildMiniMaxEmbeddingModel(String apiKey, String url, String model) {
|
||||
MiniMaxApi miniMaxApi = StrUtil.isEmpty(url)? new MiniMaxApi(apiKey)
|
||||
: new MiniMaxApi(url, apiKey);
|
||||
MiniMaxEmbeddingOptions miniMaxEmbeddingOptions = MiniMaxEmbeddingOptions.builder().model(model).build();
|
||||
return new MiniMaxEmbeddingModel(miniMaxApi, MetadataMode.EMBED, miniMaxEmbeddingOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* 可参考 {@link QianFanEmbeddingAutoConfiguration} 的 qianFanEmbeddingModel 方法
|
||||
*/
|
||||
private QianFanEmbeddingModel buildYiYanEmbeddingModel(String key, String model) {
|
||||
List<String> keys = StrUtil.split(key, '|');
|
||||
Assert.equals(keys.size(), 2, "YiYanChatClient 的密钥需要 (appKey|secretKey) 格式");
|
||||
String appKey = keys.get(0);
|
||||
String secretKey = keys.get(1);
|
||||
QianFanApi qianFanApi = new QianFanApi(appKey, secretKey);
|
||||
QianFanEmbeddingOptions qianFanEmbeddingOptions = QianFanEmbeddingOptions.builder().model(model).build();
|
||||
return new QianFanEmbeddingModel(qianFanApi, MetadataMode.EMBED, qianFanEmbeddingOptions);
|
||||
return AiAutoConfiguration.buildTongYiEmbeddingModel(apiKey, model);
|
||||
}
|
||||
|
||||
private OllamaEmbeddingModel buildOllamaEmbeddingModel(String url, String model) {
|
||||
@ -648,7 +538,7 @@ public class AiModelFactoryImpl implements AiModelFactory {
|
||||
OllamaEmbeddingOptions ollamaOptions = OllamaEmbeddingOptions.builder().model(model).build();
|
||||
return OllamaEmbeddingModel.builder()
|
||||
.ollamaApi(ollamaApi)
|
||||
.defaultOptions(ollamaOptions)
|
||||
.options(ollamaOptions)
|
||||
.build();
|
||||
}
|
||||
|
||||
@ -656,25 +546,31 @@ public class AiModelFactoryImpl implements AiModelFactory {
|
||||
* 可参考 {@link OpenAiEmbeddingAutoConfiguration} 的 openAiEmbeddingModel 方法
|
||||
*/
|
||||
private OpenAiEmbeddingModel buildOpenAiEmbeddingModel(String openAiToken, String url, String model) {
|
||||
url = StrUtil.blankToDefault(url, OpenAiApiConstants.DEFAULT_BASE_URL);
|
||||
OpenAiApi openAiApi = OpenAiApi.builder().baseUrl(url).apiKey(openAiToken).build();
|
||||
OpenAiEmbeddingOptions openAiEmbeddingProperties = OpenAiEmbeddingOptions.builder().model(model).build();
|
||||
return new OpenAiEmbeddingModel(openAiApi, MetadataMode.EMBED, openAiEmbeddingProperties);
|
||||
OpenAiEmbeddingOptions.Builder optionsBuilder = OpenAiEmbeddingOptions.builder()
|
||||
.apiKey(openAiToken)
|
||||
.model(model);
|
||||
if (StrUtil.isNotEmpty(url)) {
|
||||
optionsBuilder.baseUrl(url);
|
||||
}
|
||||
return OpenAiEmbeddingModel.builder()
|
||||
.metadataMode(MetadataMode.EMBED)
|
||||
.options(optionsBuilder.build())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 可参考 {@link AzureOpenAiEmbeddingAutoConfiguration} 的 azureOpenAiEmbeddingModel 方法
|
||||
*/
|
||||
private AzureOpenAiEmbeddingModel buildAzureOpenAiEmbeddingModel(String apiKey, String url, String model) {
|
||||
// TODO @芋艿:手头暂时没密钥,使用建议再测试下
|
||||
AzureOpenAiEmbeddingAutoConfiguration azureOpenAiAutoConfiguration = new AzureOpenAiEmbeddingAutoConfiguration();
|
||||
// 创建 OpenAIClientBuilder 对象
|
||||
OpenAIClientBuilder openAIClientBuilder = new OpenAIClientBuilder()
|
||||
.endpoint(url).credential(new KeyCredential(apiKey));
|
||||
// 获取 AzureOpenAiChatProperties 对象
|
||||
AzureOpenAiEmbeddingProperties embeddingProperties = SpringUtil.getBean(AzureOpenAiEmbeddingProperties.class);
|
||||
return azureOpenAiAutoConfiguration.azureOpenAiEmbeddingModel(openAIClientBuilder, embeddingProperties,
|
||||
getObservationRegistry(), getEmbeddingModelObservationConvention());
|
||||
private OpenAiEmbeddingModel buildAzureOpenAiEmbeddingModel(String openAiToken, String url, String model) {
|
||||
OpenAiEmbeddingOptions.Builder optionsBuilder = OpenAiEmbeddingOptions.builder()
|
||||
.apiKey(openAiToken)
|
||||
.model(model)
|
||||
.deploymentName(model)
|
||||
.azure(true);
|
||||
if (StrUtil.isNotEmpty(url)) {
|
||||
optionsBuilder.baseUrl(url);
|
||||
}
|
||||
return OpenAiEmbeddingModel.builder()
|
||||
.metadataMode(MetadataMode.EMBED)
|
||||
.options(optionsBuilder.build())
|
||||
.build();
|
||||
}
|
||||
|
||||
// ========== 各种创建 VectorStore 的方法 ==========
|
||||
@ -737,13 +633,11 @@ public class AiModelFactoryImpl implements AiModelFactory {
|
||||
*/
|
||||
private RedisVectorStore buildRedisVectorStore(EmbeddingModel embeddingModel,
|
||||
Map<String, Class<?>> metadataFields) {
|
||||
// 创建 JedisPooled 对象
|
||||
RedisProperties redisProperties = SpringUtils.getBean(RedisProperties.class);
|
||||
JedisPooled jedisPooled = new JedisPooled(redisProperties.getHost(), redisProperties.getPort(),
|
||||
redisProperties.getUsername(), redisProperties.getPassword());
|
||||
// 创建 RedisClient 对象
|
||||
RedisClient redisClient = buildRedisClient();
|
||||
// 创建 RedisVectorStoreProperties 对象
|
||||
RedisVectorStoreProperties properties = SpringUtil.getBean(RedisVectorStoreProperties.class);
|
||||
RedisVectorStore redisVectorStore = RedisVectorStore.builder(jedisPooled, embeddingModel)
|
||||
RedisVectorStore redisVectorStore = RedisVectorStore.builder(redisClient, embeddingModel)
|
||||
.indexName(properties.getIndexName()).prefix(properties.getPrefix())
|
||||
.initializeSchema(properties.isInitializeSchema())
|
||||
.metadataFields(convertList(metadataFields.entrySet(), entry -> {
|
||||
@ -766,6 +660,43 @@ public class AiModelFactoryImpl implements AiModelFactory {
|
||||
return redisVectorStore;
|
||||
}
|
||||
|
||||
private RedisClient buildRedisClient() {
|
||||
DataRedisProperties redisProperties = SpringUtil.getBean(DataRedisProperties.class);
|
||||
Assert.isNull(redisProperties.getCluster(), "RedisVectorStore 暂不支持 Redis Cluster 模式");
|
||||
Assert.isNull(redisProperties.getSentinel(), "RedisVectorStore 暂不支持 Redis Sentinel 模式");
|
||||
Assert.isNull(redisProperties.getMasterreplica(), "RedisVectorStore 暂不支持 Redis Master-Replica 模式");
|
||||
if (StrUtil.isNotEmpty(redisProperties.getUrl())) {
|
||||
return RedisClient.create(redisProperties.getUrl());
|
||||
}
|
||||
DefaultJedisClientConfig.Builder clientConfigBuilder = DefaultJedisClientConfig.builder()
|
||||
.ssl(redisProperties.getSsl().isEnabled())
|
||||
.database(redisProperties.getDatabase());
|
||||
if (StrUtil.isNotEmpty(redisProperties.getUsername())) {
|
||||
clientConfigBuilder.user(redisProperties.getUsername());
|
||||
}
|
||||
if (StrUtil.isNotEmpty(redisProperties.getPassword())) {
|
||||
clientConfigBuilder.password(redisProperties.getPassword());
|
||||
}
|
||||
if (StrUtil.isNotEmpty(redisProperties.getClientName())) {
|
||||
clientConfigBuilder.clientName(redisProperties.getClientName());
|
||||
}
|
||||
if (redisProperties.getTimeout() != null) {
|
||||
clientConfigBuilder.socketTimeoutMillis(toMillis(redisProperties.getTimeout()));
|
||||
}
|
||||
if (redisProperties.getConnectTimeout() != null) {
|
||||
clientConfigBuilder.connectionTimeoutMillis(toMillis(redisProperties.getConnectTimeout()));
|
||||
}
|
||||
JedisClientConfig clientConfig = clientConfigBuilder.build();
|
||||
return RedisClient.builder()
|
||||
.hostAndPort(new HostAndPort(redisProperties.getHost(), redisProperties.getPort()))
|
||||
.clientConfig(clientConfig)
|
||||
.build();
|
||||
}
|
||||
|
||||
private static int toMillis(Duration duration) {
|
||||
return Math.toIntExact(duration.toMillis());
|
||||
}
|
||||
|
||||
/**
|
||||
* 参考 {@link MilvusVectorStoreAutoConfiguration} 的 vectorStore 方法
|
||||
*/
|
||||
@ -827,10 +758,6 @@ public class AiModelFactoryImpl implements AiModelFactory {
|
||||
return SpringUtil.getBean(BatchingStrategy.class);
|
||||
}
|
||||
|
||||
private static ToolCallingManager getToolCallingManager() {
|
||||
return SpringUtil.getBean(ToolCallingManager.class);
|
||||
}
|
||||
|
||||
private static ObjectProvider<EmbeddingModelObservationConvention> getEmbeddingModelObservationConvention() {
|
||||
return new ObjectProvider<>() {
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@ import org.springframework.ai.chat.model.ChatModel;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.ChatOptions;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.openai.OpenAiChatModel;
|
||||
import org.springframework.ai.deepseek.DeepSeekChatModel;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
/**
|
||||
@ -20,26 +20,35 @@ public class BaiChuanChatModel implements ChatModel {
|
||||
|
||||
public static final String BASE_URL = "https://api.baichuan-ai.com";
|
||||
|
||||
public static final String MODEL_DEFAULT = "Baichuan4-Turbo";
|
||||
public static final String COMPLETE_PATH = "/v1/chat/completions";
|
||||
|
||||
public static final String MODEL_DEFAULT = "Baichuan-M3";
|
||||
|
||||
/**
|
||||
* 兼容 OpenAI 接口,进行复用
|
||||
* 兼容 OpenAI 接口,复用 DeepSeek 客户端
|
||||
*/
|
||||
private final OpenAiChatModel openAiChatModel;
|
||||
private final DeepSeekChatModel deepSeekChatModel;
|
||||
|
||||
@Override
|
||||
public ChatResponse call(Prompt prompt) {
|
||||
return openAiChatModel.call(prompt);
|
||||
return deepSeekChatModel.call(prompt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<ChatResponse> stream(Prompt prompt) {
|
||||
return openAiChatModel.stream(prompt);
|
||||
return deepSeekChatModel.stream(prompt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChatOptions getOptions() {
|
||||
return deepSeekChatModel.getOptions();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated(forRemoval = true)
|
||||
@SuppressWarnings("removal")
|
||||
public ChatOptions getDefaultOptions() {
|
||||
return openAiChatModel.getDefaultOptions();
|
||||
return getOptions();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -20,7 +20,7 @@ public class DouBaoChatModel implements ChatModel {
|
||||
public static final String BASE_URL = "https://ark.cn-beijing.volces.com/api";
|
||||
public static final String COMPLETE_PATH = "/v3/chat/completions";
|
||||
|
||||
public static final String MODEL_DEFAULT = "doubao-1-5-lite-32k-250115";
|
||||
public static final String MODEL_DEFAULT = "doubao-seed-2-1-turbo-260628";
|
||||
|
||||
/**
|
||||
* 兼容 OpenAI 接口,进行复用
|
||||
@ -38,8 +38,15 @@ public class DouBaoChatModel implements ChatModel {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChatOptions getOptions() {
|
||||
return openAiChatModel.getOptions();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated(forRemoval = true)
|
||||
@SuppressWarnings("removal")
|
||||
public ChatOptions getDefaultOptions() {
|
||||
return openAiChatModel.getDefaultOptions();
|
||||
return getOptions();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,46 +0,0 @@
|
||||
package cn.iocoder.yudao.module.ai.framework.ai.core.model.gemini;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.ai.chat.model.ChatModel;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.ChatOptions;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.openai.OpenAiChatModel;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
/**
|
||||
* 谷歌 Gemini {@link ChatModel} 实现类,基于 Google AI Studio 提供的 <a href="https://ai.google.dev/gemini-api/docs/openai">OpenAI 兼容方案</a>
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class GeminiChatModel implements ChatModel {
|
||||
|
||||
public static final String BASE_URL = "https://generativelanguage.googleapis.com/v1beta/openai/";
|
||||
public static final String COMPLETE_PATH = "/chat/completions";
|
||||
|
||||
public static final String MODEL_DEFAULT = "gemini-2.5-flash";
|
||||
|
||||
/**
|
||||
* 兼容 OpenAI 接口,进行复用
|
||||
*/
|
||||
private final OpenAiChatModel openAiChatModel;
|
||||
|
||||
@Override
|
||||
public ChatResponse call(Prompt prompt) {
|
||||
return openAiChatModel.call(prompt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<ChatResponse> stream(Prompt prompt) {
|
||||
return openAiChatModel.stream(prompt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChatOptions getDefaultOptions() {
|
||||
return openAiChatModel.getDefaultOptions();
|
||||
}
|
||||
|
||||
}
|
||||
@ -37,8 +37,15 @@ public class GrokChatModel implements ChatModel {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChatOptions getOptions() {
|
||||
return openAiChatModel.getOptions();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated(forRemoval = true)
|
||||
@SuppressWarnings("removal")
|
||||
public ChatOptions getDefaultOptions() {
|
||||
return openAiChatModel.getDefaultOptions();
|
||||
return getOptions();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -9,10 +9,9 @@ import org.springframework.ai.chat.prompt.Prompt;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
/**
|
||||
* 腾云混元 {@link ChatModel} 实现类
|
||||
* 腾讯混元 {@link ChatModel} 实现类
|
||||
*
|
||||
* 1. 混元大模型:基于 <a href="https://cloud.tencent.com/document/product/1729/111007">知识引擎原子能力</a> 实现
|
||||
* 2. 知识引擎原子能力:基于 <a href="https://cloud.tencent.com/document/product/1772/115969">知识引擎原子能力</a> 实现
|
||||
* 基于 <a href="https://cloud.tencent.com/document/product/1823/132252">TokenHub OpenAI 兼容接口</a> 实现
|
||||
*
|
||||
* @author fansili
|
||||
*/
|
||||
@ -20,14 +19,10 @@ import reactor.core.publisher.Flux;
|
||||
@RequiredArgsConstructor
|
||||
public class HunYuanChatModel implements ChatModel {
|
||||
|
||||
public static final String BASE_URL = "https://api.hunyuan.cloud.tencent.com";
|
||||
public static final String BASE_URL = "https://tokenhub.tencentmaas.com";
|
||||
public static final String COMPLETE_PATH = "/v1/chat/completions";
|
||||
|
||||
public static final String MODEL_DEFAULT = "hunyuan-turbo";
|
||||
|
||||
public static final String DEEP_SEEK_BASE_URL = "https://api.lkeap.cloud.tencent.com";
|
||||
|
||||
public static final String DEEP_SEEK_MODEL_DEFAULT = "deepseek-v3";
|
||||
public static final String MODEL_DEFAULT = "hy3-preview";
|
||||
|
||||
/**
|
||||
* 兼容 OpenAI 接口,进行复用
|
||||
@ -45,8 +40,15 @@ public class HunYuanChatModel implements ChatModel {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChatOptions getOptions() {
|
||||
return openAiChatModel.getOptions();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated(forRemoval = true)
|
||||
@SuppressWarnings("removal")
|
||||
public ChatOptions getDefaultOptions() {
|
||||
return openAiChatModel.getDefaultOptions();
|
||||
return getOptions();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,52 @@
|
||||
package cn.iocoder.yudao.module.ai.framework.ai.core.model.minimax;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.ai.chat.model.ChatModel;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.ChatOptions;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.deepseek.DeepSeekChatModel;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
/**
|
||||
* MiniMax {@link ChatModel} 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class MiniMaxChatModel implements ChatModel {
|
||||
|
||||
public static final String BASE_URL = "https://api.minimaxi.com/v1";
|
||||
|
||||
public static final String MODEL_DEFAULT = "MiniMax-M3";
|
||||
|
||||
/**
|
||||
* 兼容 OpenAI 接口,复用 DeepSeek 客户端
|
||||
*/
|
||||
private final DeepSeekChatModel deepSeekChatModel;
|
||||
|
||||
@Override
|
||||
public ChatResponse call(Prompt prompt) {
|
||||
return deepSeekChatModel.call(prompt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<ChatResponse> stream(Prompt prompt) {
|
||||
return deepSeekChatModel.stream(prompt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChatOptions getOptions() {
|
||||
return deepSeekChatModel.getOptions();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated(forRemoval = true)
|
||||
@SuppressWarnings("removal")
|
||||
public ChatOptions getDefaultOptions() {
|
||||
return getOptions();
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,54 @@
|
||||
package cn.iocoder.yudao.module.ai.framework.ai.core.model.moonshot;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.ai.chat.model.ChatModel;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.ChatOptions;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.deepseek.DeepSeekChatModel;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
/**
|
||||
* 月之暗面 {@link ChatModel} 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class MoonshotChatModel implements ChatModel {
|
||||
|
||||
public static final String BASE_URL = "https://api.moonshot.cn";
|
||||
|
||||
public static final String COMPLETE_PATH = "/v1/chat/completions";
|
||||
|
||||
public static final String MODEL_DEFAULT = "kimi-k2.6";
|
||||
|
||||
/**
|
||||
* 兼容 OpenAI 接口,复用 DeepSeek 客户端
|
||||
*/
|
||||
private final DeepSeekChatModel deepSeekChatModel;
|
||||
|
||||
@Override
|
||||
public ChatResponse call(Prompt prompt) {
|
||||
return deepSeekChatModel.call(prompt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<ChatResponse> stream(Prompt prompt) {
|
||||
return deepSeekChatModel.stream(prompt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChatOptions getOptions() {
|
||||
return deepSeekChatModel.getOptions();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated(forRemoval = true)
|
||||
@SuppressWarnings("removal")
|
||||
public ChatOptions getDefaultOptions() {
|
||||
return getOptions();
|
||||
}
|
||||
|
||||
}
|
||||
@ -25,7 +25,7 @@ public final class SiliconFlowApiConstants {
|
||||
|
||||
public static final String DEFAULT_BASE_URL = "https://api.siliconflow.cn";
|
||||
|
||||
public static final String MODEL_DEFAULT = "deepseek-ai/DeepSeek-R1-Distill-Qwen-7B";
|
||||
public static final String MODEL_DEFAULT = "deepseek-ai/DeepSeek-V4-Pro";
|
||||
|
||||
public static final String DEFAULT_IMAGE_MODEL = "Kwai-Kolors/Kolors";
|
||||
|
||||
|
||||
@ -6,7 +6,6 @@ import org.springframework.ai.chat.model.ChatModel;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.ChatOptions;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.openai.OpenAiChatModel;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
/**
|
||||
@ -36,8 +35,15 @@ public class SiliconFlowChatModel implements ChatModel {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChatOptions getOptions() {
|
||||
return openAiChatModel.getOptions();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated(forRemoval = true)
|
||||
@SuppressWarnings("removal")
|
||||
public ChatOptions getDefaultOptions() {
|
||||
return openAiChatModel.getDefaultOptions();
|
||||
return getOptions();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -21,18 +21,14 @@ import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.springframework.ai.model.ApiKey;
|
||||
import org.springframework.ai.model.NoopApiKey;
|
||||
import org.springframework.ai.model.SimpleApiKey;
|
||||
import org.springframework.ai.openai.api.OpenAiImageApi;
|
||||
import org.springframework.ai.retry.RetryUtils;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.client.ResponseErrorHandler;
|
||||
import org.springframework.web.client.RestClient;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 硅基流动 Image API
|
||||
*
|
||||
@ -58,15 +54,15 @@ public class SiliconFlowImageApi {
|
||||
|
||||
public SiliconFlowImageApi(String baseUrl, String apiKey, RestClient.Builder restClientBuilder,
|
||||
ResponseErrorHandler responseErrorHandler) {
|
||||
this(baseUrl, apiKey, CollectionUtils.toMultiValueMap(Map.of()), restClientBuilder, responseErrorHandler);
|
||||
this(baseUrl, apiKey, new HttpHeaders(), restClientBuilder, responseErrorHandler);
|
||||
}
|
||||
|
||||
public SiliconFlowImageApi(String baseUrl, String apiKey, MultiValueMap<String, String> headers,
|
||||
public SiliconFlowImageApi(String baseUrl, String apiKey, HttpHeaders headers,
|
||||
RestClient.Builder restClientBuilder, ResponseErrorHandler responseErrorHandler) {
|
||||
this(baseUrl, new SimpleApiKey(apiKey), headers, restClientBuilder, responseErrorHandler);
|
||||
}
|
||||
|
||||
public SiliconFlowImageApi(String baseUrl, ApiKey apiKey, MultiValueMap<String, String> headers,
|
||||
public SiliconFlowImageApi(String baseUrl, ApiKey apiKey, HttpHeaders headers,
|
||||
RestClient.Builder restClientBuilder, ResponseErrorHandler responseErrorHandler) {
|
||||
|
||||
// @formatter:off
|
||||
@ -83,7 +79,7 @@ public class SiliconFlowImageApi {
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
public ResponseEntity<OpenAiImageApi.OpenAiImageResponse> createImage(SiliconflowImageRequest siliconflowImageRequest) {
|
||||
public ResponseEntity<SiliconFlowImageResponse> createImage(SiliconflowImageRequest siliconflowImageRequest) {
|
||||
Assert.notNull(siliconflowImageRequest, "Image request cannot be null.");
|
||||
Assert.hasLength(siliconflowImageRequest.prompt(), "Prompt cannot be empty.");
|
||||
|
||||
@ -91,7 +87,7 @@ public class SiliconFlowImageApi {
|
||||
.uri("v1/images/generations")
|
||||
.body(siliconflowImageRequest)
|
||||
.retrieve()
|
||||
.toEntity(OpenAiImageApi.OpenAiImageResponse.class);
|
||||
.toEntity(SiliconFlowImageResponse.class);
|
||||
}
|
||||
|
||||
|
||||
@ -112,4 +108,15 @@ public class SiliconFlowImageApi {
|
||||
}
|
||||
}
|
||||
|
||||
public record SiliconFlowImageResponse(
|
||||
@JsonProperty("created") Long created,
|
||||
@JsonProperty("data") java.util.List<Entry> data) {
|
||||
|
||||
public record Entry(
|
||||
@JsonProperty("url") String url,
|
||||
@JsonProperty("b64_json") String b64Json,
|
||||
@JsonProperty("revised_prompt") String revisedPrompt) {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -27,12 +27,11 @@ import org.springframework.ai.image.observation.ImageModelObservationConvention;
|
||||
import org.springframework.ai.image.observation.ImageModelObservationDocumentation;
|
||||
import org.springframework.ai.model.ModelOptionsUtils;
|
||||
import org.springframework.ai.openai.OpenAiImageModel;
|
||||
import org.springframework.ai.openai.api.OpenAiImageApi;
|
||||
import org.springframework.ai.openai.metadata.OpenAiImageGenerationMetadata;
|
||||
import org.springframework.ai.retry.RetryUtils;
|
||||
import org.springframework.core.retry.RetryTemplate;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.retry.support.RetryTemplate;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.List;
|
||||
@ -71,7 +70,7 @@ public class SiliconFlowImageModel implements ImageModel {
|
||||
|
||||
public SiliconFlowImageModel(SiliconFlowImageApi siliconFlowImageApi, SiliconFlowImageOptions options, RetryTemplate retryTemplate,
|
||||
ObservationRegistry observationRegistry) {
|
||||
Assert.notNull(siliconFlowImageApi, "OpenAiImageApi must not be null");
|
||||
Assert.notNull(siliconFlowImageApi, "SiliconFlowImageApi must not be null");
|
||||
Assert.notNull(options, "options must not be null");
|
||||
Assert.notNull(retryTemplate, "retryTemplate must not be null");
|
||||
Assert.notNull(observationRegistry, "observationRegistry must not be null");
|
||||
@ -96,8 +95,8 @@ public class SiliconFlowImageModel implements ImageModel {
|
||||
.observation(this.observationConvention, DEFAULT_OBSERVATION_CONVENTION, () -> observationContext,
|
||||
this.observationRegistry)
|
||||
.observe(() -> {
|
||||
ResponseEntity<OpenAiImageApi.OpenAiImageResponse> imageResponseEntity = this.retryTemplate
|
||||
.execute(ctx -> this.siliconFlowImageApi.createImage(imageRequest));
|
||||
ResponseEntity<SiliconFlowImageApi.SiliconFlowImageResponse> imageResponseEntity = RetryUtils.execute(
|
||||
this.retryTemplate, () -> this.siliconFlowImageApi.createImage(imageRequest));
|
||||
|
||||
ImageResponse imageResponse = convertResponse(imageResponseEntity, imageRequest);
|
||||
|
||||
@ -109,17 +108,22 @@ public class SiliconFlowImageModel implements ImageModel {
|
||||
|
||||
private SiliconFlowImageApi.SiliconflowImageRequest createRequest(ImagePrompt imagePrompt,
|
||||
SiliconFlowImageOptions requestImageOptions) {
|
||||
String instructions = imagePrompt.getInstructions().get(0).getText();
|
||||
String instructions = imagePrompt.getInstructions().getFirst().getText();
|
||||
|
||||
SiliconFlowImageApi.SiliconflowImageRequest imageRequest = new SiliconFlowImageApi.SiliconflowImageRequest(instructions,
|
||||
SiliconFlowApiConstants.DEFAULT_IMAGE_MODEL);
|
||||
|
||||
return ModelOptionsUtils.merge(requestImageOptions, imageRequest, SiliconFlowImageApi.SiliconflowImageRequest.class);
|
||||
return new SiliconFlowImageApi.SiliconflowImageRequest(
|
||||
instructions,
|
||||
ModelOptionsUtils.mergeOption(requestImageOptions.getModel(), SiliconFlowApiConstants.DEFAULT_IMAGE_MODEL),
|
||||
requestImageOptions.getN(),
|
||||
requestImageOptions.getNegativePrompt(),
|
||||
requestImageOptions.getSeed(),
|
||||
requestImageOptions.getNumInferenceSteps(),
|
||||
requestImageOptions.getGuidanceScale(),
|
||||
requestImageOptions.getImage());
|
||||
}
|
||||
|
||||
private ImageResponse convertResponse(ResponseEntity<OpenAiImageApi.OpenAiImageResponse> imageResponseEntity,
|
||||
private ImageResponse convertResponse(ResponseEntity<SiliconFlowImageApi.SiliconFlowImageResponse> imageResponseEntity,
|
||||
SiliconFlowImageApi.SiliconflowImageRequest siliconflowImageRequest) {
|
||||
OpenAiImageApi.OpenAiImageResponse imageApiResponse = imageResponseEntity.getBody();
|
||||
SiliconFlowImageApi.SiliconFlowImageResponse imageApiResponse = imageResponseEntity.getBody();
|
||||
if (imageApiResponse == null) {
|
||||
logger.warn("No image response returned for request: {}", siliconflowImageRequest);
|
||||
return new ImageResponse(List.of());
|
||||
@ -136,12 +140,17 @@ public class SiliconFlowImageModel implements ImageModel {
|
||||
}
|
||||
|
||||
private SiliconFlowImageOptions mergeOptions(@Nullable ImageOptions runtimeOptions, SiliconFlowImageOptions defaultOptions) {
|
||||
var runtimeOptionsForProvider = ModelOptionsUtils.copyToTarget(runtimeOptions, ImageOptions.class,
|
||||
SiliconFlowImageOptions.class);
|
||||
|
||||
if (runtimeOptionsForProvider == null) {
|
||||
if (runtimeOptions == null) {
|
||||
return defaultOptions;
|
||||
}
|
||||
SiliconFlowImageOptions runtimeOptionsForProvider = runtimeOptions instanceof SiliconFlowImageOptions siliconFlowImageOptions
|
||||
? siliconFlowImageOptions
|
||||
: SiliconFlowImageOptions.builder()
|
||||
.model(runtimeOptions.getModel())
|
||||
.batchSize(runtimeOptions.getN())
|
||||
.width(runtimeOptions.getWidth())
|
||||
.height(runtimeOptions.getHeight())
|
||||
.build();
|
||||
|
||||
return SiliconFlowImageOptions.builder()
|
||||
// Handle portable image options
|
||||
|
||||
@ -1,11 +1,14 @@
|
||||
package cn.iocoder.yudao.module.ai.framework.ai.core.model.xinghuo;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.ai.chat.model.ChatModel;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.ChatOptions;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.deepseek.DeepSeekChatModel;
|
||||
import org.springframework.ai.deepseek.DeepSeekChatOptions;
|
||||
import org.springframework.ai.deepseek.api.DeepSeekApi;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
/**
|
||||
@ -14,37 +17,144 @@ import reactor.core.publisher.Flux;
|
||||
* @author fansili
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class XingHuoChatModel implements ChatModel {
|
||||
|
||||
public static final String BASE_URL_V1 = "https://spark-api-open.xf-yun.com";
|
||||
|
||||
public static final String BASE_URL_V2 = "https://spark-api-open.xf-yun.com";
|
||||
public static final String BASE_COMPLETIONS_PATH_V2 = "/v2/chat/completions";
|
||||
/**
|
||||
* 星火 X2
|
||||
*
|
||||
* @see <a href="https://spark-api-open.xf-yun.com/x2/chat/completions">接口地址</a>
|
||||
*/
|
||||
public static final String MODEL_X2 = "x2";
|
||||
|
||||
/**
|
||||
* 已知模型名列表:x1、4.0Ultra、generalv3.5、max-32k、generalv3、pro-128k、lite
|
||||
* 星火 X2 Flash
|
||||
*
|
||||
* @see <a href="https://spark-api-open.xf-yun.com/agent/v1/chat/completions">接口地址</a>
|
||||
*/
|
||||
public static final String MODEL_DEFAULT = "4.0Ultra";
|
||||
public static final String MODEL_X2_FLASH = "x2-flash";
|
||||
|
||||
private static final String BASE_URL_X2 = "https://spark-api-open.xf-yun.com/x2";
|
||||
|
||||
private static final String BASE_URL_X2_FLASH = "https://spark-api-open.xf-yun.com/agent/v1";
|
||||
|
||||
public static final String MODEL_DEFAULT = MODEL_X2_FLASH;
|
||||
|
||||
private static String getBaseUrl(String model) {
|
||||
if (MODEL_X2_FLASH.equals(model)) {
|
||||
return BASE_URL_X2_FLASH;
|
||||
}
|
||||
return BASE_URL_X2;
|
||||
}
|
||||
|
||||
private final String apiKey;
|
||||
|
||||
private final DeepSeekChatOptions options;
|
||||
|
||||
/**
|
||||
* v1 兼容 OpenAI 接口,进行复用
|
||||
* 兼容 OpenAI 接口,进行复用
|
||||
*/
|
||||
private final ChatModel openAiChatModelV1;
|
||||
private final ChatModel chatModelX2;
|
||||
|
||||
private final ChatModel chatModelX2Flash;
|
||||
|
||||
private XingHuoChatModel(String apiKey, DeepSeekChatOptions options) {
|
||||
this.apiKey = apiKey;
|
||||
this.options = options;
|
||||
this.chatModelX2 = buildChatModel(MODEL_X2);
|
||||
this.chatModelX2Flash = buildChatModel(MODEL_X2_FLASH);
|
||||
}
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChatResponse call(Prompt prompt) {
|
||||
return openAiChatModelV1.call(prompt);
|
||||
return getChatModel(prompt).call(buildApiPrompt(prompt));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<ChatResponse> stream(Prompt prompt) {
|
||||
return openAiChatModelV1.stream(prompt);
|
||||
return getChatModel(prompt).stream(buildApiPrompt(prompt));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChatOptions getOptions() {
|
||||
return options;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated(forRemoval = true)
|
||||
@SuppressWarnings("removal")
|
||||
public ChatOptions getDefaultOptions() {
|
||||
return openAiChatModelV1.getDefaultOptions();
|
||||
return getOptions();
|
||||
}
|
||||
|
||||
private ChatModel getChatModel(Prompt prompt) {
|
||||
String model = options.getModel();
|
||||
ChatOptions options = prompt.getOptions();
|
||||
if (options != null && isBusinessModel(options.getModel())) {
|
||||
model = options.getModel();
|
||||
}
|
||||
return getChatModel(model);
|
||||
}
|
||||
|
||||
private ChatModel getChatModel(String model) {
|
||||
if (MODEL_X2_FLASH.equals(model)) {
|
||||
return chatModelX2Flash;
|
||||
}
|
||||
return chatModelX2;
|
||||
}
|
||||
|
||||
private ChatModel buildChatModel(String model) {
|
||||
return DeepSeekChatModel.builder()
|
||||
.deepSeekApi(DeepSeekApi.builder()
|
||||
.baseUrl(getBaseUrl(model))
|
||||
.apiKey(apiKey)
|
||||
.build())
|
||||
.options(options.mutate().model("spark-x").build())
|
||||
.build();
|
||||
}
|
||||
|
||||
private static Prompt buildApiPrompt(Prompt prompt) {
|
||||
ChatOptions options = prompt.getOptions();
|
||||
if (options == null) {
|
||||
return prompt;
|
||||
}
|
||||
return Prompt.builder()
|
||||
.messages(prompt.getInstructions())
|
||||
.chatOptions(options.mutate().model("spark-x").build())
|
||||
.build();
|
||||
}
|
||||
|
||||
private static boolean isBusinessModel(String model) {
|
||||
return MODEL_X2.equals(model) || MODEL_X2_FLASH.equals(model);
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
|
||||
private String apiKey;
|
||||
|
||||
private DeepSeekChatOptions options;
|
||||
|
||||
public Builder apiKey(String apiKey) {
|
||||
this.apiKey = apiKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder options(DeepSeekChatOptions options) {
|
||||
this.options = options;
|
||||
return this;
|
||||
}
|
||||
|
||||
public XingHuoChatModel build() {
|
||||
DeepSeekChatOptions options = this.options != null ? this.options : DeepSeekChatOptions.builder().build();
|
||||
if (StrUtil.isEmpty(options.getModel())) {
|
||||
options = options.mutate().model(MODEL_DEFAULT).build();
|
||||
}
|
||||
return new XingHuoChatModel(apiKey, options);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,52 @@
|
||||
package cn.iocoder.yudao.module.ai.framework.ai.core.model.yiyan;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.ai.chat.model.ChatModel;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.ChatOptions;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.deepseek.DeepSeekChatModel;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
/**
|
||||
* 文心一言 {@link ChatModel} 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class YiYanChatModel implements ChatModel {
|
||||
|
||||
public static final String BASE_URL = "https://qianfan.baidubce.com/v2";
|
||||
|
||||
public static final String MODEL_DEFAULT = "ernie-5.1";
|
||||
|
||||
/**
|
||||
* 兼容 OpenAI 接口,复用 DeepSeek 客户端
|
||||
*/
|
||||
private final DeepSeekChatModel deepSeekChatModel;
|
||||
|
||||
@Override
|
||||
public ChatResponse call(Prompt prompt) {
|
||||
return deepSeekChatModel.call(prompt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<ChatResponse> stream(Prompt prompt) {
|
||||
return deepSeekChatModel.stream(prompt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChatOptions getOptions() {
|
||||
return deepSeekChatModel.getOptions();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated(forRemoval = true)
|
||||
@SuppressWarnings("removal")
|
||||
public ChatOptions getDefaultOptions() {
|
||||
return getOptions();
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,52 @@
|
||||
package cn.iocoder.yudao.module.ai.framework.ai.core.model.zhipu;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.ai.chat.model.ChatModel;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.ChatOptions;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.deepseek.DeepSeekChatModel;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
/**
|
||||
* 智谱 {@link ChatModel} 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class ZhiPuChatModel implements ChatModel {
|
||||
|
||||
public static final String BASE_URL = "https://open.bigmodel.cn/api/paas/v4";
|
||||
|
||||
public static final String MODEL_DEFAULT = "glm-5.2";
|
||||
|
||||
/**
|
||||
* 兼容 OpenAI 接口,复用 DeepSeek 客户端
|
||||
*/
|
||||
private final DeepSeekChatModel deepSeekChatModel;
|
||||
|
||||
@Override
|
||||
public ChatResponse call(Prompt prompt) {
|
||||
return deepSeekChatModel.call(prompt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<ChatResponse> stream(Prompt prompt) {
|
||||
return deepSeekChatModel.stream(prompt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChatOptions getOptions() {
|
||||
return deepSeekChatModel.getOptions();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated(forRemoval = true)
|
||||
@SuppressWarnings("removal")
|
||||
public ChatOptions getDefaultOptions() {
|
||||
return getOptions();
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,26 +1,25 @@
|
||||
package cn.iocoder.yudao.module.ai.framework.security.config;
|
||||
|
||||
import cn.iocoder.yudao.framework.security.config.AuthorizeRequestsCustomizer;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerSseProperties;
|
||||
import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerStreamableHttpProperties;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* AI 模块的 Security 配置
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false, value = "aiSecurityConfiguration")
|
||||
public class SecurityConfiguration {
|
||||
|
||||
@Resource
|
||||
private Optional<McpServerSseProperties> mcpServerSseProperties;
|
||||
@Resource
|
||||
private Optional<McpServerStreamableHttpProperties> mcpServerStreamableHttpProperties;
|
||||
@Value("${spring.ai.mcp.server.sse-endpoint:/sse}")
|
||||
private String mcpSseEndpoint;
|
||||
@Value("${spring.ai.mcp.server.sse-message-endpoint:/mcp/message}")
|
||||
private String mcpSseMessageEndpoint;
|
||||
@Value("${spring.ai.mcp.server.streamable-http-endpoint:/mcp}")
|
||||
private String mcpStreamableHttpEndpoint;
|
||||
|
||||
@Bean("aiAuthorizeRequestsCustomizer")
|
||||
public AuthorizeRequestsCustomizer authorizeRequestsCustomizer() {
|
||||
@ -28,12 +27,15 @@ public class SecurityConfiguration {
|
||||
|
||||
@Override
|
||||
public void customize(AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry registry) {
|
||||
mcpServerSseProperties.ifPresent(properties -> {
|
||||
registry.requestMatchers(properties.getSseEndpoint()).permitAll();
|
||||
registry.requestMatchers(properties.getSseMessageEndpoint()).permitAll();
|
||||
});
|
||||
mcpServerStreamableHttpProperties.ifPresent(properties ->
|
||||
registry.requestMatchers(properties.getMcpEndpoint()).permitAll());
|
||||
if (StrUtil.isNotBlank(mcpSseEndpoint)) {
|
||||
registry.requestMatchers(mcpSseEndpoint).permitAll();
|
||||
}
|
||||
if (StrUtil.isNotBlank(mcpSseMessageEndpoint)) {
|
||||
registry.requestMatchers(mcpSseMessageEndpoint).permitAll();
|
||||
}
|
||||
if (StrUtil.isNotBlank(mcpStreamableHttpEndpoint)) {
|
||||
registry.requestMatchers(mcpStreamableHttpEndpoint).permitAll();
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@ -49,10 +49,10 @@ import org.springframework.ai.chat.model.StreamingChatModel;
|
||||
import org.springframework.ai.chat.prompt.ChatOptions;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.mcp.SyncMcpToolCallbackProvider;
|
||||
import org.springframework.ai.mcp.client.common.autoconfigure.properties.McpClientCommonProperties;
|
||||
import org.springframework.ai.tool.ToolCallback;
|
||||
import org.springframework.ai.tool.resolution.ToolCallbackResolver;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import reactor.core.publisher.Flux;
|
||||
@ -130,9 +130,8 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
|
||||
@Autowired(required = false) // 由于 yudao.ai.mcp.client.enable 配置项,可以关闭 McpSyncClient 的功能,所以这里只能不强制注入
|
||||
private List<McpSyncClient> mcpClients;
|
||||
|
||||
@SuppressWarnings("SpringJavaAutowiredFieldsWarningInspection")
|
||||
@Autowired(required = false) // 由于 yudao.ai.mcp.client.enable 配置项,可以关闭 McpSyncClient 的功能,所以这里只能不强制注入
|
||||
private McpClientCommonProperties mcpClientCommonProperties;
|
||||
@Value("${spring.ai.mcp.client.name:mcp}")
|
||||
private String mcpClientName;
|
||||
|
||||
@Resource
|
||||
private ToolCallbackResolver toolCallbackResolver;
|
||||
@ -410,13 +409,16 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
|
||||
if (CollUtil.isNotEmpty(mcpClients) && CollUtil.isNotEmpty(chatRole.getMcpClientNames())) {
|
||||
chatRole.getMcpClientNames().forEach(mcpClientName -> {
|
||||
// 2.1 标准化名字,参考 McpClientAutoConfiguration 的 connectedClientName 方法
|
||||
String finalMcpClientName = mcpClientCommonProperties.getName() + " - " + mcpClientName;
|
||||
String finalMcpClientName = this.mcpClientName + " - " + mcpClientName;
|
||||
// 2.2 匹配对应的 McpSyncClient
|
||||
mcpClients.forEach(mcpClient -> {
|
||||
if (ObjUtil.notEqual(mcpClient.getClientInfo().name(), finalMcpClientName)) {
|
||||
return;
|
||||
}
|
||||
ToolCallback[] mcpToolCallBacks = new SyncMcpToolCallbackProvider(mcpClient).getToolCallbacks();
|
||||
ToolCallback[] mcpToolCallBacks = SyncMcpToolCallbackProvider.builder()
|
||||
.mcpClients(mcpClient)
|
||||
.build()
|
||||
.getToolCallbacks();
|
||||
CollUtil.addAll(toolCallbacks, mcpToolCallBacks);
|
||||
});
|
||||
});
|
||||
@ -539,7 +541,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
|
||||
public void deleteChatMessageByConversationId(Long conversationId, Long userId) {
|
||||
// 1. 校验消息存在
|
||||
List<AiChatMessageDO> messages = chatMessageMapper.selectListByConversationId(conversationId);
|
||||
if (CollUtil.isEmpty(messages) || ObjUtil.notEqual(messages.get(0).getUserId(), userId)) {
|
||||
if (CollUtil.isEmpty(messages) || ObjUtil.notEqual(messages.getFirst().getUserId(), userId)) {
|
||||
throw exception(CHAT_MESSAGE_NOT_EXIST);
|
||||
}
|
||||
// 2. 执行删除
|
||||
|
||||
@ -29,14 +29,12 @@ import cn.iocoder.yudao.module.infra.api.file.FileApi;
|
||||
import com.alibaba.cloud.ai.dashscope.image.DashScopeImageOptions;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springaicommunity.qianfan.QianFanImageOptions;
|
||||
import org.springframework.ai.image.ImageModel;
|
||||
import org.springframework.ai.image.ImageOptions;
|
||||
import org.springframework.ai.image.ImagePrompt;
|
||||
import org.springframework.ai.image.ImageResponse;
|
||||
import org.springframework.ai.openai.OpenAiImageOptions;
|
||||
import org.springframework.ai.stabilityai.api.StabilityAiImageOptions;
|
||||
import org.springframework.ai.zhipuai.ZhiPuAiImageOptions;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@ -168,15 +166,6 @@ public class AiImageServiceImpl implements AiImageService {
|
||||
.model(model.getModel()).n(1)
|
||||
.height(draw.getHeight()).width(draw.getWidth())
|
||||
.build();
|
||||
} else if (ObjUtil.equal(model.getPlatform(), AiPlatformEnum.YI_YAN.getPlatform())) {
|
||||
return QianFanImageOptions.builder()
|
||||
.model(model.getModel()).N(1)
|
||||
.height(draw.getHeight()).width(draw.getWidth())
|
||||
.build();
|
||||
} else if (ObjUtil.equal(model.getPlatform(), AiPlatformEnum.ZHI_PU.getPlatform())) {
|
||||
return ZhiPuAiImageOptions.builder()
|
||||
.model(model.getModel())
|
||||
.build();
|
||||
}
|
||||
throw new IllegalArgumentException("不支持的 AI 平台:" + model.getPlatform());
|
||||
}
|
||||
|
||||
@ -166,7 +166,7 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService
|
||||
segmentMapper.deleteByIds(convertList(segments, AiKnowledgeSegmentDO::getId));
|
||||
|
||||
// 3. 删除向量存储中的段落
|
||||
VectorStore vectorStore = getVectorStoreById(segments.get(0).getKnowledgeId());
|
||||
VectorStore vectorStore = getVectorStoreById(segments.getFirst().getKnowledgeId());
|
||||
vectorStore.delete(convertList(segments, AiKnowledgeSegmentDO::getVectorId));
|
||||
}
|
||||
|
||||
@ -299,7 +299,7 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService
|
||||
// 2. Rerank 重排序
|
||||
if (rerankModel != null) {
|
||||
RerankResponse rerankResponse = rerankModel.call(new RerankRequest(reqBO.getContent(), documents,
|
||||
DashScopeRerankOptions.builder().withTopN(topK).build()));
|
||||
DashScopeRerankOptions.builder().topN(topK).build()));
|
||||
documents = convertList(rerankResponse.getResults(),
|
||||
documentWithScore -> documentWithScore.getScore() >= similarityThreshold
|
||||
? documentWithScore.getOutput() : null);
|
||||
|
||||
@ -198,4 +198,4 @@ public class AiModelServiceImpl implements AiModelService {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,23 +8,24 @@ import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
|
||||
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
|
||||
import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum;
|
||||
import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions;
|
||||
import org.springaicommunity.moonshot.MoonshotChatOptions;
|
||||
import org.springaicommunity.qianfan.QianFanChatOptions;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import org.springframework.ai.anthropic.AnthropicChatOptions;
|
||||
import org.springframework.ai.azure.openai.AzureOpenAiChatOptions;
|
||||
import org.springframework.ai.chat.messages.*;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.ChatOptions;
|
||||
import org.springframework.ai.deepseek.DeepSeekAssistantMessage;
|
||||
import org.springframework.ai.deepseek.DeepSeekChatOptions;
|
||||
import org.springframework.ai.minimax.MiniMaxChatOptions;
|
||||
import org.springframework.ai.google.genai.GoogleGenAiChatOptions;
|
||||
import org.springframework.ai.ollama.api.OllamaChatOptions;
|
||||
import org.springframework.ai.openai.OpenAiChatOptions;
|
||||
import org.springframework.ai.tool.ToolCallback;
|
||||
import org.springframework.ai.zhipuai.ZhiPuAiChatOptions;
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.API_CONFIG_PLACEHOLDER_NOT_RESOLVED;
|
||||
|
||||
/**
|
||||
* Spring AI 工具类
|
||||
*
|
||||
@ -35,6 +36,34 @@ public class AiUtils {
|
||||
public static final String TOOL_CONTEXT_LOGIN_USER = "LOGIN_USER";
|
||||
public static final String TOOL_CONTEXT_TENANT_ID = "TENANT_ID";
|
||||
|
||||
/**
|
||||
* 解析 DB 等动态配置里的 Spring 占位符,例如 ${OPENAI_API_KEY}
|
||||
*
|
||||
* @param value 待解析的配置值
|
||||
* @return 解析后的配置值
|
||||
*/
|
||||
public static String resolveSpringPlaceholders(String value) {
|
||||
if (StrUtil.isBlank(value) || !StrUtil.contains(value, "${")) {
|
||||
return value;
|
||||
}
|
||||
try {
|
||||
return SpringUtil.getBean(Environment.class).resolveRequiredPlaceholders(value);
|
||||
} catch (IllegalArgumentException ex) {
|
||||
throw exception(API_CONFIG_PLACEHOLDER_NOT_RESOLVED, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验 API Key,避免集成测试使用默认占位值发起调用。
|
||||
*
|
||||
* @param apiKey API Key
|
||||
*/
|
||||
public static void validateApiKey(String apiKey) {
|
||||
if (StrUtil.isBlank(apiKey) || "sk-xxxx".equals(apiKey)) {
|
||||
throw new IllegalStateException("apiKey 不能为空");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通义千问支持多模态的模型
|
||||
*
|
||||
@ -42,7 +71,8 @@ public class AiUtils {
|
||||
* @see <a href="https://help.aliyun.com/zh/model-studio/error-code#error-url">必须开启 withMultiModel 参数</a>
|
||||
*/
|
||||
public static final Set<String> TONG_YI_MULTI_MODELS = SetUtils.asSet(
|
||||
// qwen3.5 / 3.6 系列(统一多模态主干)
|
||||
// qwen3.5 / 3.6 / 3.7 系列(统一多模态主干)
|
||||
"qwen3.7-max", "qwen3.7-plus", "qwen3.7-flash",
|
||||
"qwen3.6-plus", "qwen3.6-flash",
|
||||
"qwen3.5-plus", "qwen3.5-flash",
|
||||
// qwen-vl 视觉理解
|
||||
@ -72,32 +102,28 @@ public class AiUtils {
|
||||
.enableThinking(true) // TODO 芋艿:默认都开启 thinking 模式,后续可以让用户配置
|
||||
.multiModel(TONG_YI_MULTI_MODELS.contains(model)) // 是否多模态模型
|
||||
.toolCallbacks(toolCallbacks).toolContext(toolContext).build();
|
||||
case YI_YAN:
|
||||
return QianFanChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens).build();
|
||||
case DEEP_SEEK:
|
||||
case DOU_BAO: // 复用 DeepSeek 客户端
|
||||
case HUN_YUAN: // 复用 DeepSeek 客户端
|
||||
case SILICON_FLOW: // 复用 DeepSeek 客户端
|
||||
case YI_YAN: // 复用 DeepSeek 客户端
|
||||
case ZHI_PU: // 复用 DeepSeek 客户端
|
||||
case XING_HUO: // 复用 DeepSeek 客户端
|
||||
case MINI_MAX: // 复用 DeepSeek 客户端
|
||||
case MOONSHOT: // 复用 DeepSeek 客户端
|
||||
case BAI_CHUAN: // 复用 DeepSeek 客户端
|
||||
return DeepSeekChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens)
|
||||
.toolCallbacks(toolCallbacks).toolContext(toolContext).build();
|
||||
case ZHI_PU:
|
||||
return ZhiPuAiChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens)
|
||||
.toolCallbacks(toolCallbacks).toolContext(toolContext).build();
|
||||
case MINI_MAX:
|
||||
return MiniMaxChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens)
|
||||
.toolCallbacks(toolCallbacks).toolContext(toolContext).build();
|
||||
case MOONSHOT:
|
||||
return MoonshotChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens)
|
||||
.toolCallbacks(toolCallbacks).toolContext(toolContext).build();
|
||||
case OPENAI:
|
||||
case GEMINI: // 复用 OpenAI 客户端
|
||||
case BAI_CHUAN: // 复用 OpenAI 客户端
|
||||
case GROK: // 复用 OpenAI 客户端
|
||||
return OpenAiChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens)
|
||||
.toolCallbacks(toolCallbacks).toolContext(toolContext).build();
|
||||
case GEMINI:
|
||||
return GoogleGenAiChatOptions.builder().model(model).temperature(temperature).maxOutputTokens(maxTokens)
|
||||
.toolCallbacks(toolCallbacks).toolContext(toolContext).build();
|
||||
case AZURE_OPENAI:
|
||||
return AzureOpenAiChatOptions.builder().deploymentName(model).temperature(temperature).maxTokens(maxTokens)
|
||||
return OpenAiChatOptions.builder().model(model).deploymentName(model).azure(true)
|
||||
.temperature(temperature).maxTokens(maxTokens)
|
||||
.toolCallbacks(toolCallbacks).toolContext(toolContext).build();
|
||||
case ANTHROPIC:
|
||||
return AnthropicChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens)
|
||||
@ -159,4 +185,4 @@ public class AiUtils {
|
||||
return MapUtil.getStr(output.getMetadata(), "reasoningContent");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,29 @@
|
||||
package org.springframework.ai.model.tool;
|
||||
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.ChatOptions;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* TODO 芋艿:spring-ai-alibaba 2.0.0-M1.1 仍依赖旧的 Spring AI ToolExecutionEligibilityPredicate,
|
||||
* 临时补齐;升级到兼容 Spring AI 2.0.0 的 spring-ai-alibaba 版本后,删除本包下的 shim。
|
||||
*/
|
||||
public class DefaultToolExecutionEligibilityPredicate implements ToolExecutionEligibilityPredicate {
|
||||
|
||||
@Override
|
||||
public boolean test(ChatOptions promptOptions, ChatResponse chatResponse) {
|
||||
return isInternalToolExecutionEnabled(promptOptions) && chatResponse != null && chatResponse.hasToolCalls();
|
||||
}
|
||||
|
||||
private static boolean isInternalToolExecutionEnabled(ChatOptions promptOptions) {
|
||||
try {
|
||||
Method method = promptOptions.getClass().getMethod("getInternalToolExecutionEnabled");
|
||||
Object result = method.invoke(promptOptions);
|
||||
return result == null || Boolean.TRUE.equals(result);
|
||||
} catch (ReflectiveOperationException | SecurityException ignored) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
package org.springframework.ai.model.tool;
|
||||
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.ChatOptions;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.function.BiPredicate;
|
||||
|
||||
/**
|
||||
* TODO 芋艿:spring-ai-alibaba 2.0.0-M1.1 仍依赖旧的 Spring AI ToolExecutionEligibilityPredicate,
|
||||
* 临时补齐;升级到兼容 Spring AI 2.0.0 的 spring-ai-alibaba 版本后,删除本包下的 shim。
|
||||
*/
|
||||
public interface ToolExecutionEligibilityPredicate extends BiPredicate<ChatOptions, ChatResponse> {
|
||||
|
||||
default boolean isToolExecutionRequired(ChatOptions promptOptions, ChatResponse chatResponse) {
|
||||
Assert.notNull(promptOptions, "promptOptions cannot be null");
|
||||
Assert.notNull(chatResponse, "chatResponse cannot be null");
|
||||
return test(promptOptions, chatResponse);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,10 +1,10 @@
|
||||
package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
|
||||
|
||||
import cn.hutool.system.SystemUtil;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.ai.anthropic.AnthropicChatModel;
|
||||
import org.springframework.ai.anthropic.AnthropicChatOptions;
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi;
|
||||
import org.springframework.ai.chat.messages.Message;
|
||||
import org.springframework.ai.chat.messages.SystemMessage;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
@ -14,6 +14,9 @@ import reactor.core.publisher.Flux;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.iocoder.yudao.module.ai.util.AiUtils.validateApiKey;
|
||||
|
||||
/**
|
||||
* {@link AnthropicChatModel} 集成测试类
|
||||
@ -22,13 +25,18 @@ import java.util.List;
|
||||
*/
|
||||
public class AnthropicChatModelTest {
|
||||
|
||||
private static final String BASE_URL = SystemUtil.get("ANTHROPIC_BASE_URL",
|
||||
"https://api.teamorouter.com");
|
||||
private static final String API_KEY = SystemUtil.get("ANTHROPIC_API_KEY",
|
||||
"sk-xxxx"); // 按需改成你的 Anthropic API Key
|
||||
private static final String MODEL = SystemUtil.get("ANTHROPIC_MODEL",
|
||||
"claude-sonnet-4-6");
|
||||
|
||||
private final AnthropicChatModel chatModel = AnthropicChatModel.builder()
|
||||
.anthropicApi(AnthropicApi.builder()
|
||||
.apiKey("sk-muubv7cXeLw0Etgs743f365cD5Ea44429946Fa7e672d8942")
|
||||
.baseUrl("https://aihubmix.com")
|
||||
.build())
|
||||
.defaultOptions(AnthropicChatOptions.builder()
|
||||
.model(AnthropicApi.ChatModel.CLAUDE_SONNET_4_5)
|
||||
.options(AnthropicChatOptions.builder()
|
||||
.baseUrl(BASE_URL)
|
||||
.apiKey(API_KEY)
|
||||
.model(MODEL)
|
||||
.temperature(0.7)
|
||||
.maxTokens(4096)
|
||||
.build())
|
||||
@ -37,6 +45,7 @@ public class AnthropicChatModelTest {
|
||||
@Test
|
||||
@Disabled
|
||||
public void testCall() {
|
||||
validateApiKey(API_KEY);
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
|
||||
@ -46,11 +55,13 @@ public class AnthropicChatModelTest {
|
||||
ChatResponse response = chatModel.call(new Prompt(messages));
|
||||
// 打印结果
|
||||
System.out.println(response);
|
||||
System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testStream() {
|
||||
validateApiKey(API_KEY);
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
|
||||
@ -59,20 +70,25 @@ public class AnthropicChatModelTest {
|
||||
// 调用
|
||||
Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages));
|
||||
// 打印结果
|
||||
flux.doOnNext(System.out::println).then().block();
|
||||
flux.doOnNext(response -> {
|
||||
// System.out.println(response);
|
||||
System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
|
||||
}).then().block();
|
||||
}
|
||||
|
||||
// TODO @芋艿:需要等 spring ai 升级:https://github.com/spring-projects/spring-ai/pull/2800
|
||||
@Test
|
||||
@Disabled
|
||||
public void testStream_thinking() {
|
||||
validateApiKey(API_KEY);
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new UserMessage("thkinking 下,1+1 为什么等于 2 "));
|
||||
AnthropicChatOptions options = AnthropicChatOptions.builder()
|
||||
.model(AnthropicApi.ChatModel.CLAUDE_SONNET_4_5)
|
||||
.thinking(AnthropicApi.ThinkingType.ENABLED, 3096)
|
||||
.temperature(1D)
|
||||
.baseUrl(BASE_URL)
|
||||
.apiKey(API_KEY)
|
||||
.model(MODEL)
|
||||
.thinkingEnabled(1024) // https://platform.claude.com/docs/en/build-with-claude/extended-thinking
|
||||
.maxTokens(4096)
|
||||
.build();
|
||||
|
||||
// 调用
|
||||
@ -80,7 +96,7 @@ public class AnthropicChatModelTest {
|
||||
// 打印结果
|
||||
flux.doOnNext(response -> {
|
||||
// System.out.println(response);
|
||||
System.out.println(response.getResult());
|
||||
System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
|
||||
}).then().block();
|
||||
}
|
||||
|
||||
|
||||
@ -1,44 +1,52 @@
|
||||
package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
|
||||
|
||||
import com.azure.ai.openai.OpenAIClientBuilder;
|
||||
import com.azure.core.credential.AzureKeyCredential;
|
||||
import cn.hutool.system.SystemUtil;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.ai.azure.openai.AzureOpenAiChatModel;
|
||||
import org.springframework.ai.azure.openai.AzureOpenAiChatOptions;
|
||||
import org.springframework.ai.chat.messages.Message;
|
||||
import org.springframework.ai.chat.messages.SystemMessage;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.openai.OpenAiChatModel;
|
||||
import org.springframework.ai.openai.OpenAiChatOptions;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiChatProperties.DEFAULT_DEPLOYMENT_NAME;
|
||||
import static cn.iocoder.yudao.module.ai.util.AiUtils.validateApiKey;
|
||||
|
||||
/**
|
||||
* {@link AzureOpenAiChatModel} 集成测试
|
||||
* Azure OpenAI 集成测试
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class AzureOpenAIChatModelTests {
|
||||
|
||||
// TODO @芋艿:晚点在调整
|
||||
private final OpenAIClientBuilder openAiApi = new OpenAIClientBuilder()
|
||||
.endpoint("https://eastusprejade.openai.azure.com")
|
||||
.credential(new AzureKeyCredential("xxx"));
|
||||
private final AzureOpenAiChatModel chatModel = AzureOpenAiChatModel.builder()
|
||||
.openAIClientBuilder(openAiApi)
|
||||
.defaultOptions(AzureOpenAiChatOptions.builder()
|
||||
.deploymentName(DEFAULT_DEPLOYMENT_NAME)
|
||||
private static final String BASE_URL = SystemUtil.get("AZURE_OPENAI_BASE_URL",
|
||||
"https://xxx.openai.azure.com");
|
||||
private static final String API_KEY = SystemUtil.get("AZURE_OPENAI_API_KEY",
|
||||
"sk-xxxx"); // 按需改成你的 Azure OpenAI API Key
|
||||
private static final String DEPLOYMENT_NAME = SystemUtil.get("AZURE_OPENAI_DEPLOYMENT_NAME",
|
||||
"gpt-5.4"); // Azure 上创建的模型部署名称
|
||||
|
||||
private final OpenAiChatModel chatModel = OpenAiChatModel.builder()
|
||||
.options(OpenAiChatOptions.builder()
|
||||
.baseUrl(BASE_URL)
|
||||
.apiKey(API_KEY)
|
||||
.model(DEPLOYMENT_NAME)
|
||||
.microsoftFoundry(true)
|
||||
.deploymentName(DEPLOYMENT_NAME)
|
||||
.temperature(0.7)
|
||||
.build())
|
||||
.build();
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testCall() {
|
||||
validateApiKey(API_KEY);
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
|
||||
@ -48,12 +56,13 @@ public class AzureOpenAIChatModelTests {
|
||||
ChatResponse response = chatModel.call(new Prompt(messages));
|
||||
// 打印结果
|
||||
System.out.println(response);
|
||||
System.out.println(response.getResult().getOutput());
|
||||
System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testStream() {
|
||||
validateApiKey(API_KEY);
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
|
||||
@ -64,7 +73,7 @@ public class AzureOpenAIChatModelTests {
|
||||
// 打印结果
|
||||
flux.doOnNext(response -> {
|
||||
// System.out.println(response);
|
||||
System.out.println(response.getResult().getOutput());
|
||||
System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
|
||||
}).then().block();
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
|
||||
|
||||
import cn.hutool.system.SystemUtil;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.baichuan.BaiChuanChatModel;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@ -8,13 +9,16 @@ import org.springframework.ai.chat.messages.SystemMessage;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.openai.OpenAiChatModel;
|
||||
import org.springframework.ai.openai.OpenAiChatOptions;
|
||||
import org.springframework.ai.openai.api.OpenAiApi;
|
||||
import org.springframework.ai.deepseek.DeepSeekChatModel;
|
||||
import org.springframework.ai.deepseek.DeepSeekChatOptions;
|
||||
import org.springframework.ai.deepseek.api.DeepSeekApi;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.iocoder.yudao.module.ai.util.AiUtils.validateApiKey;
|
||||
|
||||
/**
|
||||
* {@link BaiChuanChatModel} 集成测试
|
||||
@ -23,22 +27,27 @@ import java.util.List;
|
||||
*/
|
||||
public class BaiChuanChatModelTests {
|
||||
|
||||
private final OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
|
||||
.openAiApi(OpenAiApi.builder()
|
||||
private static final String API_KEY = SystemUtil.get("BAICHUAN_API_KEY",
|
||||
"sk-xxxx"); // 按需改成你的百川 API Key
|
||||
private static final String MODEL = SystemUtil.get("BAICHUAN_MODEL",
|
||||
BaiChuanChatModel.MODEL_DEFAULT);
|
||||
|
||||
private final BaiChuanChatModel chatModel = new BaiChuanChatModel(DeepSeekChatModel.builder()
|
||||
.deepSeekApi(DeepSeekApi.builder()
|
||||
.baseUrl(BaiChuanChatModel.BASE_URL)
|
||||
.apiKey("sk-61b6766a94c70786ed02673f5e16af3c") // apiKey
|
||||
.completionsPath(BaiChuanChatModel.COMPLETE_PATH)
|
||||
.apiKey(API_KEY)
|
||||
.build())
|
||||
.defaultOptions(OpenAiChatOptions.builder()
|
||||
.model("Baichuan4-Turbo") // 模型(https://platform.baichuan-ai.com/docs/api)
|
||||
.options(DeepSeekChatOptions.builder()
|
||||
.model(MODEL)
|
||||
.temperature(0.7)
|
||||
.build())
|
||||
.build();
|
||||
|
||||
private final BaiChuanChatModel chatModel = new BaiChuanChatModel(openAiChatModel);
|
||||
.build());
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testCall() {
|
||||
validateApiKey(API_KEY);
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
|
||||
@ -48,11 +57,13 @@ public class BaiChuanChatModelTests {
|
||||
ChatResponse response = chatModel.call(new Prompt(messages));
|
||||
// 打印结果
|
||||
System.out.println(response);
|
||||
System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testStream() {
|
||||
validateApiKey(API_KEY);
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
|
||||
@ -61,7 +72,10 @@ public class BaiChuanChatModelTests {
|
||||
// 调用
|
||||
Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages));
|
||||
// 打印结果
|
||||
flux.doOnNext(System.out::println).then().block();
|
||||
flux.doOnNext(response -> {
|
||||
// System.out.println(response);
|
||||
System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
|
||||
}).then().block();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -8,11 +8,12 @@ import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.openai.OpenAiChatModel;
|
||||
import org.springframework.ai.openai.api.OpenAiApi;
|
||||
import org.springframework.ai.openai.OpenAiChatOptions;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 基于 {@link OpenAiChatModel} 集成 Coze 测试
|
||||
@ -22,7 +23,7 @@ import java.util.List;
|
||||
public class CozeChatModelTests {
|
||||
|
||||
private final OpenAiChatModel chatModel = OpenAiChatModel.builder()
|
||||
.openAiApi(OpenAiApi.builder()
|
||||
.options(OpenAiChatOptions.builder()
|
||||
.baseUrl("http://127.0.0.1:3000")
|
||||
.apiKey("app-4hy2d7fJauSbrKbzTKX1afuP") // apiKey
|
||||
.build())
|
||||
@ -40,7 +41,7 @@ public class CozeChatModelTests {
|
||||
ChatResponse response = chatModel.call(new Prompt(messages));
|
||||
// 打印结果
|
||||
System.out.println(response);
|
||||
System.out.println(response.getResult().getOutput());
|
||||
System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -56,7 +57,7 @@ public class CozeChatModelTests {
|
||||
// 打印结果
|
||||
flux.doOnNext(response -> {
|
||||
// System.out.println(response);
|
||||
System.out.println(response.getResult().getOutput());
|
||||
System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
|
||||
}).then().block();
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
|
||||
|
||||
import cn.hutool.system.SystemUtil;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.ai.chat.messages.Message;
|
||||
@ -14,6 +15,9 @@ import reactor.core.publisher.Flux;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.iocoder.yudao.module.ai.util.AiUtils.validateApiKey;
|
||||
|
||||
/**
|
||||
* {@link DeepSeekChatModel} 集成测试
|
||||
@ -22,12 +26,17 @@ import java.util.List;
|
||||
*/
|
||||
public class DeepSeekChatModelTests {
|
||||
|
||||
private static final String API_KEY = SystemUtil.get("DEEPSEEK_API_KEY",
|
||||
"sk-xxxx");
|
||||
private static final String MODEL = SystemUtil.get("DEEPSEEK_MODEL",
|
||||
"deepseek-v4-flash");
|
||||
|
||||
private final DeepSeekChatModel chatModel = DeepSeekChatModel.builder()
|
||||
.deepSeekApi(DeepSeekApi.builder()
|
||||
.apiKey("sk-eaf4172a057344dd9bc64b1f806b6axx") // apiKey
|
||||
.apiKey(API_KEY) // apiKey
|
||||
.build())
|
||||
.defaultOptions(DeepSeekChatOptions.builder()
|
||||
.model("deepseek-chat") // 模型
|
||||
.options(DeepSeekChatOptions.builder()
|
||||
.model(MODEL) // 模型
|
||||
.temperature(0.7)
|
||||
.build())
|
||||
.build();
|
||||
@ -35,6 +44,7 @@ public class DeepSeekChatModelTests {
|
||||
@Test
|
||||
@Disabled
|
||||
public void testCall() {
|
||||
validateApiKey(API_KEY);
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
|
||||
@ -43,12 +53,13 @@ public class DeepSeekChatModelTests {
|
||||
// 调用
|
||||
ChatResponse response = chatModel.call(new Prompt(messages));
|
||||
// 打印结果
|
||||
System.out.println(response);
|
||||
System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testStream() {
|
||||
validateApiKey(API_KEY);
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
|
||||
@ -63,11 +74,12 @@ public class DeepSeekChatModelTests {
|
||||
@Test
|
||||
@Disabled
|
||||
public void testStream_thinking() {
|
||||
validateApiKey(API_KEY);
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new UserMessage("详细分析下,如何设计一个电商系统?"));
|
||||
DeepSeekChatOptions options = DeepSeekChatOptions.builder()
|
||||
.model("deepseek-reasoner")
|
||||
.model(MODEL)
|
||||
.build();
|
||||
|
||||
// 调用
|
||||
|
||||
@ -8,11 +8,12 @@ import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.openai.OpenAiChatModel;
|
||||
import org.springframework.ai.openai.api.OpenAiApi;
|
||||
import org.springframework.ai.openai.OpenAiChatOptions;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 基于 {@link OpenAiChatModel} 集成 Dify 测试
|
||||
@ -22,7 +23,7 @@ import java.util.List;
|
||||
public class DifyChatModelTests {
|
||||
|
||||
private final OpenAiChatModel chatModel = OpenAiChatModel.builder()
|
||||
.openAiApi(OpenAiApi.builder()
|
||||
.options(OpenAiChatOptions.builder()
|
||||
.baseUrl("http://127.0.0.1:3000")
|
||||
.apiKey("app-4hy2d7fJauSbrKbzTKX1afuP") // apiKey
|
||||
.build())
|
||||
@ -40,7 +41,7 @@ public class DifyChatModelTests {
|
||||
ChatResponse response = chatModel.call(new Prompt(messages));
|
||||
// 打印结果
|
||||
System.out.println(response);
|
||||
System.out.println(response.getResult().getOutput());
|
||||
System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -56,7 +57,7 @@ public class DifyChatModelTests {
|
||||
// 打印结果
|
||||
flux.doOnNext(response -> {
|
||||
// System.out.println(response);
|
||||
System.out.println(response.getResult().getOutput());
|
||||
System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
|
||||
}).then().block();
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
|
||||
|
||||
import cn.hutool.system.SystemUtil;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.doubao.DouBaoChatModel;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@ -15,6 +16,9 @@ import reactor.core.publisher.Flux;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.iocoder.yudao.module.ai.util.AiUtils.validateApiKey;
|
||||
|
||||
/**
|
||||
* {@link DouBaoChatModel} 集成测试
|
||||
@ -23,19 +27,19 @@ import java.util.List;
|
||||
*/
|
||||
public class DouBaoChatModelTests {
|
||||
|
||||
/**
|
||||
* 相比 OpenAIChatModel 来说,DeepSeekChatModel 可以兼容豆包的 thinking 能力!
|
||||
*/
|
||||
private static final String API_KEY = SystemUtil.get("DOUBAO_API_KEY",
|
||||
"sk-xxxx"); // 按需改成你的豆包 API Key
|
||||
private static final String MODEL = SystemUtil.get("DOUBAO_MODEL",
|
||||
DouBaoChatModel.MODEL_DEFAULT);
|
||||
|
||||
private final DeepSeekChatModel openAiChatModel = DeepSeekChatModel.builder()
|
||||
.deepSeekApi(DeepSeekApi.builder()
|
||||
.baseUrl(DouBaoChatModel.BASE_URL)
|
||||
.completionsPath(DouBaoChatModel.COMPLETE_PATH)
|
||||
.apiKey("5c1b5747-26d2-4ebd-a4e0-dd0e8d8b4272") // apiKey
|
||||
.apiKey(API_KEY)
|
||||
.build())
|
||||
.defaultOptions(DeepSeekChatOptions.builder()
|
||||
.model("doubao-1-5-lite-32k-250115") // 模型(doubao)
|
||||
// .model("doubao-seed-1-6-thinking-250715") // 模型(doubao)
|
||||
// .model("deepseek-r1-250120") // 模型(deepseek)
|
||||
.options(DeepSeekChatOptions.builder()
|
||||
.model(MODEL)
|
||||
.temperature(0.7)
|
||||
.build())
|
||||
.build();
|
||||
@ -45,6 +49,7 @@ public class DouBaoChatModelTests {
|
||||
@Test
|
||||
@Disabled
|
||||
public void testCall() {
|
||||
validateApiKey(API_KEY);
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
|
||||
@ -54,11 +59,13 @@ public class DouBaoChatModelTests {
|
||||
ChatResponse response = chatModel.call(new Prompt(messages));
|
||||
// 打印结果
|
||||
System.out.println(response);
|
||||
System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testStream() {
|
||||
validateApiKey(API_KEY);
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
|
||||
@ -67,25 +74,26 @@ public class DouBaoChatModelTests {
|
||||
// 调用
|
||||
Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages));
|
||||
// 打印结果
|
||||
flux.doOnNext(System.out::println).then().block();
|
||||
flux.doOnNext(response -> {
|
||||
// System.out.println(response);
|
||||
System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
|
||||
}).then().block();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testStream_thinking() {
|
||||
validateApiKey(API_KEY);
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new UserMessage("详细分析下,如何设计一个电商系统?"));
|
||||
DeepSeekChatOptions options = DeepSeekChatOptions.builder()
|
||||
.model("doubao-seed-1-6-thinking-250715")
|
||||
.build();
|
||||
|
||||
// 调用
|
||||
Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages, options));
|
||||
Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages));
|
||||
// 打印结果
|
||||
flux.doOnNext(response -> {
|
||||
// System.out.println(response);
|
||||
System.out.println(response.getResult().getOutput());
|
||||
System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
|
||||
}).then().block();
|
||||
}
|
||||
|
||||
|
||||
@ -8,11 +8,12 @@ import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.openai.OpenAiChatModel;
|
||||
import org.springframework.ai.openai.api.OpenAiApi;
|
||||
import org.springframework.ai.openai.OpenAiChatOptions;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 基于 {@link OpenAiChatModel} 集成 FastGPT 测试
|
||||
@ -22,7 +23,7 @@ import java.util.List;
|
||||
public class FastGPTChatModelTests {
|
||||
|
||||
private final OpenAiChatModel chatModel = OpenAiChatModel.builder()
|
||||
.openAiApi(OpenAiApi.builder()
|
||||
.options(OpenAiChatOptions.builder()
|
||||
.baseUrl("https://cloud.fastgpt.cn/api")
|
||||
.apiKey("fastgpt-aqcc61kFtF8CeaglnGAfQOCIDWwjGdJVJHv6hIlMo28otFlva2aZNK") // apiKey
|
||||
.build())
|
||||
@ -40,7 +41,7 @@ public class FastGPTChatModelTests {
|
||||
ChatResponse response = chatModel.call(new Prompt(messages));
|
||||
// 打印结果
|
||||
System.out.println(response);
|
||||
System.out.println(response.getResult().getOutput());
|
||||
System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -56,7 +57,7 @@ public class FastGPTChatModelTests {
|
||||
// 打印结果
|
||||
flux.doOnNext(response -> {
|
||||
// System.out.println(response);
|
||||
System.out.println(response.getResult().getOutput());
|
||||
System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
|
||||
}).then().block();
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
|
||||
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.gemini.GeminiChatModel;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.system.SystemUtil;
|
||||
import com.google.genai.Client;
|
||||
import com.google.genai.types.HttpOptions;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.ai.chat.messages.Message;
|
||||
@ -8,38 +11,48 @@ import org.springframework.ai.chat.messages.SystemMessage;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.openai.OpenAiChatModel;
|
||||
import org.springframework.ai.openai.OpenAiChatOptions;
|
||||
import org.springframework.ai.openai.api.OpenAiApi;
|
||||
import org.springframework.ai.google.genai.GoogleGenAiChatModel;
|
||||
import org.springframework.ai.google.genai.GoogleGenAiChatOptions;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.iocoder.yudao.module.ai.util.AiUtils.validateApiKey;
|
||||
|
||||
/**
|
||||
* {@link GeminiChatModel} 集成测试
|
||||
* {@link GoogleGenAiChatModel} 集成测试
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class GeminiChatModelTests {
|
||||
|
||||
private final OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
|
||||
.openAiApi(OpenAiApi.builder()
|
||||
.baseUrl(GeminiChatModel.BASE_URL)
|
||||
.completionsPath(GeminiChatModel.COMPLETE_PATH)
|
||||
.apiKey("AIzaSyAVoBxgoFvvte820vEQMma2LKBnC98bqMQ")
|
||||
.build())
|
||||
.defaultOptions(OpenAiChatOptions.builder()
|
||||
.model(GeminiChatModel.MODEL_DEFAULT) // 模型
|
||||
.temperature(0.7)
|
||||
.build())
|
||||
.build();
|
||||
private static final String BASE_URL = SystemUtil.get("GEMINI_BASE_URL");
|
||||
private static final String API_KEY = SystemUtil.get("GEMINI_API_KEY",
|
||||
"sk-xxxx"); // 按需改成你的 Gemini API Key
|
||||
private static final String MODEL = "gemini-3.5-flash";
|
||||
|
||||
private final GeminiChatModel chatModel = new GeminiChatModel(openAiChatModel);
|
||||
private final GoogleGenAiChatModel chatModel = GoogleGenAiChatModel.builder()
|
||||
.genAiClient(buildClient())
|
||||
.options(GoogleGenAiChatOptions.builder()
|
||||
.model(MODEL) // 模型
|
||||
.temperature(0.7)
|
||||
.build())
|
||||
.build();
|
||||
|
||||
private static Client buildClient() {
|
||||
Client.Builder builder = Client.builder().apiKey(API_KEY);
|
||||
if (StrUtil.isNotBlank(BASE_URL)) {
|
||||
builder.httpOptions(HttpOptions.builder().baseUrl(BASE_URL).build());
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testCall() {
|
||||
validateApiKey(API_KEY);
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
|
||||
@ -49,11 +62,13 @@ public class GeminiChatModelTests {
|
||||
ChatResponse response = chatModel.call(new Prompt(messages));
|
||||
// 打印结果
|
||||
System.out.println(response);
|
||||
System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testStream() {
|
||||
validateApiKey(API_KEY);
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
|
||||
@ -62,7 +77,32 @@ public class GeminiChatModelTests {
|
||||
// 调用
|
||||
Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages));
|
||||
// 打印结果
|
||||
flux.doOnNext(System.out::println).then().block();
|
||||
flux.doOnNext(response -> {
|
||||
System.out.println(response);
|
||||
// System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
|
||||
}).then().block();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testStream_thinking() {
|
||||
validateApiKey(API_KEY);
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new UserMessage("详细分析下,如何设计一个电商系统?"));
|
||||
GoogleGenAiChatOptions options = GoogleGenAiChatOptions.builder()
|
||||
.model(MODEL)
|
||||
.thinkingBudget(1024)
|
||||
.includeThoughts(true)
|
||||
.build();
|
||||
|
||||
// 调用
|
||||
Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages, options));
|
||||
// 打印结果
|
||||
flux.doOnNext(response -> {
|
||||
System.out.println(response);
|
||||
// System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
|
||||
}).then().block();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
|
||||
|
||||
import cn.hutool.system.SystemUtil;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.hunyuan.HunYuanChatModel;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@ -15,6 +16,9 @@ import reactor.core.publisher.Flux;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.iocoder.yudao.module.ai.util.AiUtils.validateApiKey;
|
||||
|
||||
/**
|
||||
* {@link HunYuanChatModel} 集成测试
|
||||
@ -23,14 +27,19 @@ import java.util.List;
|
||||
*/
|
||||
public class HunYuanChatModelTests {
|
||||
|
||||
private static final String API_KEY = SystemUtil.get("HUNYUAN_API_KEY",
|
||||
"sk-xxxx");
|
||||
private static final String MODEL = SystemUtil.get("HUNYUAN_MODEL",
|
||||
HunYuanChatModel.MODEL_DEFAULT);
|
||||
|
||||
private final DeepSeekChatModel openAiChatModel = DeepSeekChatModel.builder()
|
||||
.deepSeekApi(DeepSeekApi.builder()
|
||||
.baseUrl(HunYuanChatModel.BASE_URL)
|
||||
.completionsPath(HunYuanChatModel.COMPLETE_PATH)
|
||||
.apiKey("sk-abc") // apiKey
|
||||
.apiKey(API_KEY) // apiKey
|
||||
.build())
|
||||
.defaultOptions(DeepSeekChatOptions.builder()
|
||||
.model(HunYuanChatModel.MODEL_DEFAULT) // 模型
|
||||
.options(DeepSeekChatOptions.builder()
|
||||
.model(MODEL) // 模型
|
||||
.temperature(0.7)
|
||||
.build())
|
||||
.build();
|
||||
@ -40,6 +49,7 @@ public class HunYuanChatModelTests {
|
||||
@Test
|
||||
@Disabled
|
||||
public void testCall() {
|
||||
validateApiKey(API_KEY);
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
|
||||
@ -48,12 +58,13 @@ public class HunYuanChatModelTests {
|
||||
// 调用
|
||||
ChatResponse response = chatModel.call(new Prompt(messages));
|
||||
// 打印结果
|
||||
System.out.println(response);
|
||||
System.out.println(Objects.requireNonNull(response.getResult()).getOutput());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testStream() {
|
||||
validateApiKey(API_KEY);
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
|
||||
@ -68,12 +79,12 @@ public class HunYuanChatModelTests {
|
||||
@Test
|
||||
@Disabled
|
||||
public void testStream_thinking() {
|
||||
validateApiKey(API_KEY);
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new UserMessage("详细分析下,如何设计一个电商系统?"));
|
||||
DeepSeekChatOptions options = DeepSeekChatOptions.builder()
|
||||
.model("hunyuan-a13b")
|
||||
// .model("hunyuan-turbos-latest")
|
||||
.model(MODEL)
|
||||
.build();
|
||||
|
||||
// 调用
|
||||
@ -85,66 +96,4 @@ public class HunYuanChatModelTests {
|
||||
}).then().block();
|
||||
}
|
||||
|
||||
private final DeepSeekChatModel deepSeekOpenAiChatModel = DeepSeekChatModel.builder()
|
||||
.deepSeekApi(DeepSeekApi.builder()
|
||||
.baseUrl(HunYuanChatModel.DEEP_SEEK_BASE_URL)
|
||||
.completionsPath(HunYuanChatModel.COMPLETE_PATH)
|
||||
.apiKey("sk-abc") // apiKey
|
||||
.build())
|
||||
.defaultOptions(DeepSeekChatOptions.builder()
|
||||
// .model(HunYuanChatModel.DEEP_SEEK_MODEL_DEFAULT) // 模型("deepseek-v3")
|
||||
.model("deepseek-r1") // 模型("deepseek-r1")
|
||||
.temperature(0.7)
|
||||
.build())
|
||||
.build();
|
||||
|
||||
private final HunYuanChatModel deepSeekChatModel = new HunYuanChatModel(deepSeekOpenAiChatModel);
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testCall_deepseek() {
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
|
||||
messages.add(new UserMessage("1 + 1 = ?"));
|
||||
|
||||
// 调用
|
||||
ChatResponse response = deepSeekChatModel.call(new Prompt(messages));
|
||||
// 打印结果
|
||||
System.out.println(response);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testStream_deepseek() {
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
|
||||
messages.add(new UserMessage("1 + 1 = ?"));
|
||||
|
||||
// 调用
|
||||
Flux<ChatResponse> flux = deepSeekChatModel.stream(new Prompt(messages));
|
||||
// 打印结果
|
||||
flux.doOnNext(System.out::println).then().block();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testStream_deepseek_thinking() {
|
||||
// 准备参数
|
||||
List<Message> messages = new ArrayList<>();
|
||||
messages.add(new UserMessage("详细分析下,如何设计一个电商系统?"));
|
||||
DeepSeekChatOptions options = DeepSeekChatOptions.builder()
|
||||
.model("deepseek-r1")
|
||||
.build();
|
||||
|
||||
// 调用
|
||||
Flux<ChatResponse> flux = deepSeekChatModel.stream(new Prompt(messages, options));
|
||||
// 打印结果
|
||||
flux.doOnNext(response -> {
|
||||
// System.out.println(response);
|
||||
System.out.println(response.getResult().getOutput());
|
||||
}).then().block();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -27,7 +27,7 @@ public class LlamaChatModelTests {
|
||||
.ollamaApi(OllamaApi.builder()
|
||||
.baseUrl("http://127.0.0.1:11434") // Ollama 服务地址
|
||||
.build())
|
||||
.defaultOptions(OllamaChatOptions.builder()
|
||||
.options(OllamaChatOptions.builder()
|
||||
.model(OllamaModel.LLAMA3.getName()) // 模型
|
||||
.build())
|
||||
.build();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user