Compare commits
41 Commits
cursor/gen
...
cursor/bc-
Author | SHA1 | Date | |
---|---|---|---|
![]() |
79a815b6f3 | ||
![]() |
3f9c3661a8 | ||
![]() |
adece67532 | ||
![]() |
ea5b12f21e | ||
![]() |
39ecf5ebe5 | ||
![]() |
750709d706 | ||
![]() |
3d0eb77148 | ||
![]() |
c789418a7b | ||
![]() |
e50250449a | ||
![]() |
c96f6bb360 | ||
![]() |
af94536a06 | ||
![]() |
a9c7b584cc | ||
![]() |
a54e743a88 | ||
![]() |
64516b2210 | ||
![]() |
e7c9e3dc23 | ||
![]() |
7a8c37d7ac | ||
![]() |
432609b6fa | ||
![]() |
520fb79e2a | ||
![]() |
9b860d9ae5 | ||
![]() |
285da13989 | ||
![]() |
b1d439abf8 | ||
![]() |
b81b8e7aff | ||
![]() |
7a5e28d08a | ||
![]() |
4d5e501a41 | ||
![]() |
876659b01d | ||
![]() |
569ff42e6f | ||
![]() |
bda357508a | ||
![]() |
7fc522938d | ||
![]() |
563985dcfc | ||
![]() |
d8e1610495 | ||
![]() |
eca9307344 | ||
![]() |
a690184524 | ||
![]() |
2ee1e15101 | ||
![]() |
e6fecd8efe | ||
![]() |
94e280eb34 | ||
![]() |
ec5281f2e5 | ||
![]() |
6ac8fa28a7 | ||
![]() |
cac82a13a7 | ||
![]() |
9fc4a4061f | ||
![]() |
a5ad8bb708 | ||
![]() |
3b2a3dd0ea |
521
README.md
521
README.md
@@ -1,244 +1,391 @@
|
|||||||
# 🚀 芋道 ruoyi-vue-pro
|
<p align="center">
|
||||||
|
<img src="https://img.shields.io/badge/Spring%20Boot-2.7.18-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"/>
|
||||||
|
</p>
|
||||||
|
|
||||||
<div align="center">
|
**严肃声明:现在、未来都不会有商业版本,所有代码全部开源!!**
|
||||||
|
|
||||||

|
**「我喜欢写代码,乐此不疲」**
|
||||||

|
**「我喜欢做开源,以此为乐」**
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
**基于 Spring Boot + Vue 的企业级快速开发平台**
|
我 🐶 在上海艰苦奋斗,早中晚在 top3 大厂认真搬砖,夜里为开源做贡献。
|
||||||
|
|
||||||
**🔥 100% 开源免费,个人与企业可免费使用**
|
如果这个项目让你有所收获,记得 Star 关注哦,这对我是非常不错的鼓励与支持。
|
||||||
|
|
||||||
[在线演示](http://dashboard-vue3.yudao.iocoder.cn) | [快速开始](https://doc.iocoder.cn/quick-start/) | [开发文档](https://doc.iocoder.cn/) | [视频教程](https://doc.iocoder.cn/video/)
|
## 🐶 新手必读
|
||||||
|
|
||||||
</div>
|
* 演示地址【Vue3 + element-plus】:<http://dashboard-vue3.yudao.iocoder.cn>
|
||||||
|
* 演示地址【Vue3 + vben(ant-design-vue)】:<http://dashboard-vben.yudao.iocoder.cn>
|
||||||
|
* 演示地址【Vue2 + element-ui】:<http://dashboard.yudao.iocoder.cn>
|
||||||
|
* 启动文档:<https://doc.iocoder.cn/quick-start/>
|
||||||
|
* 视频教程:<https://doc.iocoder.cn/video/>
|
||||||
|
|
||||||
## ✨ 特性
|
## 🐰 版本说明
|
||||||
|
|
||||||
- 🎯 **开箱即用** - 基于最新技术栈,提供完整的前后端解决方案
|
| 版本 | JDK 8 + Spring Boot 2.7 | JDK 17/21 + Spring Boot 3.2 |
|
||||||
- 🔐 **权限管理** - 完善的权限认证体系,支持多租户、多终端
|
|---------------------------------------------------------------------|---------------------------------------------------------------------------|---------------------------------------------------------------------------------------|
|
||||||
- 🔄 **工作流程** - 集成 Flowable,支持可视化流程设计
|
| 【完整版】[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/) 分支 |
|
||||||
- 🛒 **业务模块** - 内置 CRM、ERP、商城、会员等业务系统
|
| 【精简版】[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/) 分支 |
|
||||||
- 🎨 **多端支持** - 支持 PC、移动端、小程序
|
|
||||||
- 🚀 **高性能** - Redis 缓存、MySQL 读写分离、分布式架构
|
|
||||||
- 📊 **数据报表** - 支持报表设计器、大屏设计器
|
|
||||||
- 🤖 **AI 集成** - 支持主流 AI 大模型接入
|
|
||||||
|
|
||||||
## 🎯 在线演示
|
* 【完整版】:包括系统功能、基础设施、会员中心、数据报表、工作流程、商城系统、微信公众号、CRM、ERP 等功能
|
||||||
|
* 【精简版】:只包括系统功能、基础设施功能,不包括会员中心、数据报表、工作流程、商城系统、微信公众号、CRM、ERP 等功能
|
||||||
|
|
||||||
| 版本 | 地址 | 账号 |
|
可参考 [《迁移文档》](https://doc.iocoder.cn/migrate-module/) ,只需要 5-10 分钟,即可将【完整版】按需迁移到【精简版】
|
||||||
|------|------|------|
|
|
||||||
| Vue3 + Element Plus | [dashboard-vue3.yudao.iocoder.cn](http://dashboard-vue3.yudao.iocoder.cn) | admin/admin123 |
|
|
||||||
| Vue3 + Ant Design | [dashboard-vben.yudao.iocoder.cn](http://dashboard-vben.yudao.iocoder.cn) | admin/admin123 |
|
|
||||||
| Vue2 + Element UI | [dashboard.yudao.iocoder.cn](http://dashboard.yudao.iocoder.cn) | admin/admin123 |
|
|
||||||
|
|
||||||
## 🏗️ 项目结构
|
## 🐯 平台简介
|
||||||
|
|
||||||
```
|
**芋道**,以开发者为中心,打造中国第一流的快速开发平台,全部开源,个人与企业可 100% 免费使用。
|
||||||
ruoyi-vue-pro/
|
|
||||||
├── yudao-dependencies/ # Maven 依赖管理
|
|
||||||
├── yudao-framework/ # 框架核心
|
|
||||||
├── yudao-server/ # 服务启动器
|
|
||||||
├── yudao-module-system/ # 系统管理模块
|
|
||||||
├── yudao-module-infra/ # 基础设施模块
|
|
||||||
├── yudao-module-bpm/ # 工作流程模块
|
|
||||||
├── yudao-module-pay/ # 支付系统模块
|
|
||||||
├── yudao-module-mall/ # 商城系统模块
|
|
||||||
├── yudao-module-crm/ # CRM 客户管理模块
|
|
||||||
├── yudao-module-erp/ # ERP 企业资源计划模块
|
|
||||||
├── yudao-module-ai/ # AI 大模型模块
|
|
||||||
├── yudao-module-member/ # 会员中心模块
|
|
||||||
├── yudao-module-mp/ # 微信公众号模块
|
|
||||||
├── yudao-module-report/ # 数据报表模块
|
|
||||||
└── yudao-ui/ # 前端项目
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🛠️ 技术栈
|
> 有任何问题,或者想要的功能,可以在 _Issues_ 中提给艿艿。
|
||||||
|
>
|
||||||
|
> 😜 给项目点点 Star 吧,这对我们真的很重要!
|
||||||
|
|
||||||
### 后端技术
|

|
||||||
- **核心框架**: Spring Boot 2.7.18
|
|
||||||
- **安全框架**: Spring Security + JWT
|
|
||||||
- **持久层**: MyBatis Plus + Druid
|
|
||||||
- **数据库**: MySQL 5.7/8.0+
|
|
||||||
- **缓存**: Redis + Redisson
|
|
||||||
- **工作流**: Flowable
|
|
||||||
- **任务调度**: Quartz
|
|
||||||
- **接口文档**: Swagger3 + Knife4j
|
|
||||||
|
|
||||||
### 前端技术
|
* Java 后端:`master` 分支为 JDK 8 + Spring Boot 2.7,`master-jdk17` 分支为 JDK 17/21 + Spring Boot 3.2
|
||||||
- **框架**: Vue 3.x / Vue 2.x
|
* 管理后台的电脑端:Vue3 提供 `element-plus`、`vben(ant-design-vue)` 两个版本,Vue2 提供 `element-ui` 版本
|
||||||
- **UI 组件**: Element Plus / Ant Design Vue / Element UI
|
* 管理后台的移动端:采用 `uni-app` 方案,一份代码多终端适配,同时支持 APP、小程序、H5!
|
||||||
- **构建工具**: Vite / Webpack
|
* 后端采用 Spring Boot 多模块架构、MySQL + MyBatis Plus、Redis + Redisson
|
||||||
- **路由**: Vue Router
|
* 数据库可使用 MySQL、Oracle、PostgreSQL、SQL Server、MariaDB、国产达梦 DM、TiDB 等
|
||||||
- **状态管理**: Pinia / Vuex
|
* 消息队列可使用 Event、Redis、RabbitMQ、Kafka、RocketMQ 等
|
||||||
- **HTTP 客户端**: Axios
|
* 权限认证使用 Spring Security & Token & Redis,支持多终端、多种用户的认证系统,支持 SSO 单点登录
|
||||||
|
* 支持加载动态权限菜单,按钮级别权限控制,Redis 缓存提升性能
|
||||||
|
* 支持 SaaS 多租户,可自定义每个租户的权限,提供透明化的多租户底层封装
|
||||||
|
* 工作流使用 Flowable,支持动态表单、在线设计流程、会签 / 或签、多种任务分配方式
|
||||||
|
* 高效率开发,使用代码生成器可以一键生成 Java、Vue 前后端代码、SQL 脚本、接口文档,支持单表、树表、主子表
|
||||||
|
* 实时通信,采用 Spring WebSocket 实现,内置 Token 身份校验,支持 WebSocket 集群
|
||||||
|
* 集成微信小程序、微信公众号、企业微信、钉钉等三方登陆,集成支付宝、微信等支付与退款
|
||||||
|
* 集成阿里云、腾讯云等短信渠道,集成 MinIO、阿里云、腾讯云、七牛云等云存储服务
|
||||||
|
* 集成报表设计器、大屏设计器,通过拖拽即可生成酷炫的报表与大屏
|
||||||
|
|
||||||
### 开发工具
|
## 🐳 项目关系
|
||||||
- **IDE**: IntelliJ IDEA / VS Code
|
|
||||||
- **版本控制**: Git
|
|
||||||
- **项目管理**: Maven
|
|
||||||
- **代码规范**: ESLint + Prettier
|
|
||||||
- **API 测试**: Postman / ApiPost
|
|
||||||
|
|
||||||
## 🚀 快速开始
|

|
||||||
|
|
||||||
### 环境要求
|
三个项目的功能对比,可见社区共同整理的 [国产开源项目对比](https://www.yuque.com/xiatian-bsgny/lm0ec1/wqf8mn) 表格。
|
||||||
|
|
||||||
- JDK 8 或 JDK 17/21
|
### 后端项目
|
||||||
- MySQL 5.7+
|
|
||||||
- Redis 3.0+
|
|
||||||
- Maven 3.6+
|
|
||||||
- Node.js 16+
|
|
||||||
|
|
||||||
### 后端启动
|
| 项目 | Star | 简介 |
|
||||||
|
|-----------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------|
|
||||||
|
| [ruoyi-vue-pro](https://gitee.com/zhijiantianya/ruoyi-vue-pro) | [](https://gitee.com/zhijiantianya/ruoyi-vue-pro) [](https://github.com/YunaiV/ruoyi-vue-pro) | 基于 Spring Boot 多模块架构 |
|
||||||
|
| [yudao-cloud](https://gitee.com/zhijiantianya/yudao-cloud) | [](https://gitee.com/zhijiantianya/yudao-cloud) [](https://github.com/YunaiV/yudao-cloud) | 基于 Spring Cloud 微服务架构 |
|
||||||
|
| [Spring-Boot-Labs](https://gitee.com/yudaocode/SpringBoot-Labs) | [](https://gitee.com/zhijiantianya/yudao-cloud) [](https://github.com/yudaocode/SpringBoot-Labs) | 系统学习 Spring Boot & Cloud 专栏 |
|
||||||
|
|
||||||
1. **克隆项目**
|
### 前端项目
|
||||||
```bash
|
|
||||||
git clone https://github.com/YunaiV/ruoyi-vue-pro.git
|
|
||||||
cd ruoyi-vue-pro
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **创建数据库**
|
| 项目 | Star | 简介 |
|
||||||
```bash
|
|----------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------|
|
||||||
# 创建数据库
|
| [yudao-ui-admin-vue3](https://gitee.com/yudaocode/yudao-ui-admin-vue3) | [](https://gitee.com/yudaocode/yudao-ui-admin-vue3) [](https://github.com/yudaocode/yudao-ui-admin-vue3) | 基于 Vue3 + element-plus 实现的管理后台 |
|
||||||
mysql -u root -p
|
| [yudao-ui-admin-vben](https://gitee.com/yudaocode/yudao-ui-admin-vben) | [](https://gitee.com/yudaocode/yudao-ui-admin-vben) [](https://github.com/yudaocode/yudao-ui-admin-vben) | 基于 Vue3 + vben(ant-design-vue) 实现的管理后台 |
|
||||||
CREATE DATABASE ruoyi_vue_pro DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
| [yudao-mall-uniapp](https://gitee.com/yudaocode/yudao-mall-uniapp) | [](https://gitee.com/yudaocode/yudao-mall-uniapp) [](https://github.com/yudaocode/yudao-mall-uniapp) | 基于 uni-app 实现的商城小程序 |
|
||||||
|
| [yudao-ui-admin-vue2](https://gitee.com/yudaocode/yudao-ui-admin-vue2) | [](https://gitee.com/yudaocode/yudao-ui-admin-vue2) [](https://github.com/yudaocode/yudao-ui-admin-vue2) | 基于 Vue2 + element-ui 实现的管理后台 |
|
||||||
|
| [yudao-ui-admin-uniapp](https://gitee.com/yudaocode/yudao-ui-admin-uniapp) | [](https://gitee.com/yudaocode/yudao-ui-admin-uniapp) [](https://github.com/yudaocode/yudao-ui-admin-uniapp) | 基于 Vue2 + element-ui 实现的管理后台 |
|
||||||
|
| [yudao-ui-go-view](https://gitee.com/yudaocode/yudao-ui-go-view) | [](https://gitee.com/yudaocode/yudao-ui-go-view) [](https://github.com/yudaocode/yudao-ui-go-view) | 基于 Vue3 + naive-ui 实现的大屏报表 |
|
||||||
|
|
||||||
# 导入初始化脚本
|
## 😎 开源协议
|
||||||
source sql/mysql/ruoyi-vue-pro.sql
|
|
||||||
source sql/mysql/quartz.sql
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **修改配置**
|
**为什么推荐使用本项目?**
|
||||||
```yaml
|
|
||||||
# application-local.yaml
|
|
||||||
spring:
|
|
||||||
datasource:
|
|
||||||
url: jdbc:mysql://127.0.0.1:3306/ruoyi_vue_pro
|
|
||||||
username: root
|
|
||||||
password: 123456
|
|
||||||
data:
|
|
||||||
redis:
|
|
||||||
host: 127.0.0.1
|
|
||||||
port: 6379
|
|
||||||
password: # Redis 密码,没有可不填
|
|
||||||
```
|
|
||||||
|
|
||||||
4. **启动项目**
|
① 本项目采用比 Apache 2.0 更宽松的 [MIT License](https://gitee.com/zhijiantianya/ruoyi-vue-pro/blob/master/LICENSE) 开源协议,个人与企业可 100% 免费使用,不用保留类作者、Copyright 信息。
|
||||||
```bash
|
|
||||||
mvn clean install
|
|
||||||
cd yudao-server
|
|
||||||
mvn spring-boot:run
|
|
||||||
```
|
|
||||||
|
|
||||||
### 前端启动
|
② 代码全部开源,不会像其他项目一样,只开源部分代码,让你无法了解整个项目的架构设计。[国产开源项目对比](https://www.yuque.com/xiatian-bsgny/lm0ec1/wqf8mn)
|
||||||
|
|
||||||
```bash
|

|
||||||
# 进入前端目录(以 Vue3 版本为例)
|
|
||||||
cd yudao-ui/yudao-ui-admin-vue3
|
|
||||||
|
|
||||||
# 安装依赖
|
③ 代码整洁、架构整洁,遵循《阿里巴巴 Java 开发手册》规范,代码注释详细,113770 行 Java 代码,42462 行代码注释。
|
||||||
npm install
|
|
||||||
|
|
||||||
# 启动开发服务器
|
## 🤝 项目外包
|
||||||
npm run dev
|
|
||||||
|
|
||||||
# 访问地址:http://localhost:80
|
我们也是接外包滴,如果你有项目想要外包,可以微信联系【**Aix9975**】。
|
||||||
```
|
|
||||||
|
|
||||||
## 📚 核心功能
|
团队包含专业的项目经理、架构师、前端工程师、后端工程师、测试工程师、运维工程师,可以提供全流程的外包服务。
|
||||||
|
|
||||||
### 🔐 系统管理
|
项目可以是商城、SCRM 系统、OA 系统、物流系统、ERP 系统、CMS 系统、HIS 系统、支付系统、IM 聊天、微信公众号、微信小程序等等。
|
||||||
- **用户管理**: 用户信息维护、角色分配
|
|
||||||
- **角色管理**: 角色权限分配、数据权限设置
|
|
||||||
- **菜单管理**: 菜单配置、按钮权限控制
|
|
||||||
- **部门管理**: 组织架构管理、数据权限划分
|
|
||||||
- **租户管理**: SaaS 多租户支持
|
|
||||||
- **字典管理**: 系统字典维护
|
|
||||||
- **操作日志**: 系统操作记录追踪
|
|
||||||
|
|
||||||
### 📊 工作流程
|
## 🐼 内置功能
|
||||||
- **流程设计**: 可视化流程设计器
|
|
||||||
- **表单设计**: 动态表单构建器
|
|
||||||
- **流程实例**: 流程发起、审批、监控
|
|
||||||
- **任务处理**: 待办事项、已办查询
|
|
||||||
- **流程监控**: 流程实例监控、统计分析
|
|
||||||
|
|
||||||
### 💰 支付系统
|
系统内置多种多种业务功能,可以用于快速你的业务系统:
|
||||||
- **支付渠道**: 支付宝、微信支付接入
|
|
||||||
- **支付订单**: 支付订单管理、退款处理
|
|
||||||
- **商户管理**: 商户信息、应用配置
|
|
||||||
- **对账管理**: 交易对账、财务报表
|
|
||||||
|
|
||||||
### 🛍️ 商城系统
|

|
||||||
- **商品管理**: 商品信息、分类、品牌管理
|
|
||||||
- **订单管理**: 订单处理、物流跟踪
|
|
||||||
- **促销活动**: 优惠券、拼团、秒杀
|
|
||||||
- **会员系统**: 会员等级、积分、成长值
|
|
||||||
|
|
||||||
### 🏢 CRM 系统
|
* 通用模块(必选):系统功能、基础设施
|
||||||
- **客户管理**: 客户信息、跟进记录
|
* 通用模块(可选):工作流程、支付系统、数据报表、会员中心
|
||||||
- **商机管理**: 销售机会、转化追踪
|
* 业务系统(按需):ERP 系统、CRM 系统、商城系统、微信公众号、AI 大模型
|
||||||
- **合同管理**: 合同签署、执行监控
|
|
||||||
- **回款管理**: 回款计划、到账记录
|
|
||||||
|
|
||||||
### 📦 ERP 系统
|
> 友情提示:本项目基于 RuoYi-Vue 修改,**重构优化**后端的代码,**美化**前端的界面。
|
||||||
- **采购管理**: 采购订单、供应商管理
|
>
|
||||||
- **销售管理**: 销售订单、客户管理
|
> * 额外新增的功能,我们使用 🚀 标记。
|
||||||
- **库存管理**: 出入库、库存盘点
|
> * 重新实现的功能,我们使用 ⭐️ 标记。
|
||||||
- **财务管理**: 应收应付、财务报表
|
|
||||||
|
|
||||||
## 🌟 开源协议
|
🙂 所有功能,都通过 **单元测试** 保证高质量。
|
||||||
|
|
||||||
本项目基于 [MIT License](./LICENSE) 开源协议,您可以:
|
### 系统功能
|
||||||
|
|
||||||
✅ **商业使用** - 可用于商业项目
|
| | 功能 | 描述 |
|
||||||
✅ **自由修改** - 可自由修改代码
|
|-----|-------|---------------------------------|
|
||||||
✅ **自由分发** - 可自由分享给他人
|
| | 用户管理 | 用户是系统操作者,该功能主要完成系统用户配置 |
|
||||||
✅ **私用** - 可用于个人项目
|
| ⭐️ | 在线用户 | 当前系统中活跃用户状态监控,支持手动踢下线 |
|
||||||
|
| | 角色管理 | 角色菜单权限分配、设置角色按机构进行数据范围权限划分 |
|
||||||
|
| | 菜单管理 | 配置系统菜单、操作权限、按钮权限标识等,本地缓存提供性能 |
|
||||||
|
| | 部门管理 | 配置系统组织机构(公司、部门、小组),树结构展现支持数据权限 |
|
||||||
|
| | 岗位管理 | 配置系统用户所属担任职务 |
|
||||||
|
| 🚀 | 租户管理 | 配置系统租户,支持 SaaS 场景下的多租户功能 |
|
||||||
|
| 🚀 | 租户套餐 | 配置租户套餐,自定每个租户的菜单、操作、按钮的权限 |
|
||||||
|
| | 字典管理 | 对系统中经常使用的一些较为固定的数据进行维护 |
|
||||||
|
| 🚀 | 短信管理 | 短信渠道、短息模板、短信日志,对接阿里云、腾讯云等主流短信平台 |
|
||||||
|
| 🚀 | 邮件管理 | 邮箱账号、邮件模版、邮件发送日志,支持所有邮件平台 |
|
||||||
|
| 🚀 | 站内信 | 系统内的消息通知,提供站内信模版、站内信消息 |
|
||||||
|
| 🚀 | 操作日志 | 系统正常操作日志记录和查询,集成 Swagger 生成日志内容 |
|
||||||
|
| ⭐️ | 登录日志 | 系统登录日志记录查询,包含登录异常 |
|
||||||
|
| 🚀 | 错误码管理 | 系统所有错误码的管理,可在线修改错误提示,无需重启服务 |
|
||||||
|
| | 通知公告 | 系统通知公告信息发布维护 |
|
||||||
|
| 🚀 | 敏感词 | 配置系统敏感词,支持标签分组 |
|
||||||
|
| 🚀 | 应用管理 | 管理 SSO 单点登录的应用,支持多种 OAuth2 授权方式 |
|
||||||
|
| 🚀 | 地区管理 | 展示省份、城市、区镇等城市信息,支持 IP 对应城市 |
|
||||||
|
|
||||||
## 🤝 参与贡献
|

|
||||||
|
|
||||||
欢迎各种形式的贡献,包括但不限于:
|
### 工作流程
|
||||||
|
|
||||||
- 🐛 报告 Bug
|

|
||||||
- 💡 提出新功能建议
|
|
||||||
- 📝 完善文档
|
|
||||||
- 🔧 提交代码
|
|
||||||
|
|
||||||
### 贡献流程
|
基于 Flowable 构建,可支持信创(国产)数据库,满足中国特色流程操作:
|
||||||
|
|
||||||
1. Fork 本项目
|
| BPMN 设计器 | 钉钉/飞书设计器 |
|
||||||
2. 创建特性分支: `git checkout -b feature/AmazingFeature`
|
|------------------------------|--------------------------------|
|
||||||
3. 提交更改: `git commit -m 'Add some AmazingFeature'`
|
|  |  |
|
||||||
4. 推送到分支: `git push origin feature/AmazingFeature`
|
|
||||||
5. 提交 Pull Request
|
|
||||||
|
|
||||||
## 📞 联系我们
|
> 历经头部企业生产验证,工作流引擎须标配仿钉钉/飞书 + BPMN 双设计器!!!
|
||||||
|
>
|
||||||
|
> 前者支持轻量配置简单流程,后者实现复杂场景深度编排
|
||||||
|
|
||||||
- 📧 **邮箱**: yunai@iocoder.cn
|
| 功能列表 | 功能描述 | 是否完成 |
|
||||||
- 💬 **微信**: Aix9975(项目外包合作)
|
|------------|-------------------------------------------------------------------------------------|------|
|
||||||
- 🌐 **官网**: [https://www.iocoder.cn](https://www.iocoder.cn)
|
| SIMPLE 设计器 | 仿钉钉/飞书设计器,支持拖拽搭建表单流程,10 分钟快速完成审批流程配置 | ✅ |
|
||||||
- 📖 **文档**: [https://doc.iocoder.cn](https://doc.iocoder.cn)
|
| BPMN 设计器 | 基于 BPMN 标准开发,适配复杂业务场景,满足多层级审批及流程自动化需求 | ✅ |
|
||||||
|
| 会签 | 同一个审批节点设置多个人(如 A、B、C 三人,三人会同时收到待办任务),需全部同意之后,审批才可到下一审批节点 | ✅ |
|
||||||
|
| 或签 | 同一个审批节点设置多个人,任意一个人处理后,就能进入下一个节点 | ✅ |
|
||||||
|
| 依次审批 | (顺序会签)同一个审批节点设置多个人(如 A、B、C 三人),三人按顺序依次收到待办,即 A 先审批,A 提交后 B 才能审批,需全部同意之后,审批才可到下一审批节点 | ✅ |
|
||||||
|
| 抄送 | 将审批结果通知给抄送人,同一个审批默认排重,不重复抄送给同一人 | ✅ |
|
||||||
|
| 驳回 | (退回)将审批重置发送给某节点,重新审批。可驳回至发起人、上一节点、任意节点 | ✅ |
|
||||||
|
| 转办 | A 转给其 B 审批,B 审批后,进入下一节点 | ✅ |
|
||||||
|
| 委派 | A 转给其 B 审批,B 审批后,转给 A,A 继续审批后进入下一节点 | ✅ |
|
||||||
|
| 加签 | 允许当前审批人根据需要,自行增加当前节点的审批人,支持向前、向后加签 | ✅ |
|
||||||
|
| 减签 | (取消加签)在当前审批人操作之前,减少审批人 | ✅ |
|
||||||
|
| 撤销 | (取消流程)流程发起人,可以对流程进行撤销处理 | ✅ |
|
||||||
|
| 终止 | 系统管理员,在任意节点终止流程实例 | ✅ |
|
||||||
|
| 表单权限 | 支持拖拉拽配置表单,每个审批节点可配置只读、编辑、隐藏权限 | ✅ |
|
||||||
|
| 超时审批 | 配置超时审批时间,超时后自动触发审批通过、不通过、驳回等操作 | ✅ |
|
||||||
|
| 自动提醒 | 配置提醒时间,到达时间后自动触发短信、邮箱、站内信等通知提醒,支持自定义重复提醒频次 | ✅ |
|
||||||
|
| 父子流程 | 主流程设置子流程节点,子流程节点会自动触发子流程。子流程结束后,主流程才会执行(继续往下下执行),支持同步子流程、异步子流程 | ✅ |
|
||||||
|
| 条件分支 | (排它分支)用于在流程中实现决策,即根据条件选择一个分支执行 | ✅ |
|
||||||
|
| 并行分支 | 允许将流程分成多条分支,不进行条件判断,所有分支都会执行 | ✅ |
|
||||||
|
| 包容分支 | (条件分支 + 并行分支的结合体)允许基于条件选择多条分支执行,但如果没有任何一个分支满足条件,则可以选择默认分支 | ✅ |
|
||||||
|
| 路由分支 | 根据条件选择一个分支执行(重定向到指定配置节点),也可以选择默认分支执行(继续往下执行) | ✅ |
|
||||||
|
| 触发节点 | 执行到该节点,触发 HTTP 请求、HTTP 回调、更新数据、删除数据等 | ✅ |
|
||||||
|
| 延迟节点 | 执行到该节点,审批等待一段时间再执行,支持固定时长、固定日期等 | ✅ |
|
||||||
|
| 拓展设置 | 流程前置/后置通知,节点(任务)前置、后置通知,流程报表,自动审批去重,自定流程编号、标题、摘要,流程报表等 | ✅ |
|
||||||
|
|
||||||
## ⭐ Star History
|
### 支付系统
|
||||||
|
|
||||||
如果这个项目对您有帮助,请给我们一个 ⭐️ Star!您的支持是我们前进的动力。
|
| | 功能 | 描述 |
|
||||||
|
|-----|------|---------------------------|
|
||||||
|
| 🚀 | 应用信息 | 配置商户的应用信息,对接支付宝、微信等多个支付渠道 |
|
||||||
|
| 🚀 | 支付订单 | 查看用户发起的支付宝、微信等的【支付】订单 |
|
||||||
|
| 🚀 | 退款订单 | 查看用户发起的支付宝、微信等的【退款】订单 |
|
||||||
|
| 🚀 | 回调通知 | 查看支付回调业务的【支付】【退款】的通知结果 |
|
||||||
|
| 🚀 | 接入示例 | 提供接入支付系统的【支付】【退款】的功能实战 |
|
||||||
|
|
||||||
[](https://star-history.com/#YunaiV/ruoyi-vue-pro&Date)
|
### 基础设施
|
||||||
|
|
||||||
## 📄 许可证
|
| | 功能 | 描述 |
|
||||||
|
|-----|-----------|----------------------------------------------|
|
||||||
|
| 🚀 | 代码生成 | 前后端代码的生成(Java、Vue、SQL、单元测试),支持 CRUD 下载 |
|
||||||
|
| 🚀 | 系统接口 | 基于 Swagger 自动生成相关的 RESTful API 接口文档 |
|
||||||
|
| 🚀 | 数据库文档 | 基于 Screw 自动生成数据库文档,支持导出 Word、HTML、MD 格式 |
|
||||||
|
| | 表单构建 | 拖动表单元素生成相应的 HTML 代码,支持导出 JSON、Vue 文件 |
|
||||||
|
| 🚀 | 配置管理 | 对系统动态配置常用参数,支持 SpringBoot 加载 |
|
||||||
|
| ⭐️ | 定时任务 | 在线(添加、修改、删除)任务调度包含执行结果日志 |
|
||||||
|
| 🚀 | 文件服务 | 支持将文件存储到 S3(MinIO、阿里云、腾讯云、七牛云)、本地、FTP、数据库等 |
|
||||||
|
| 🚀 | WebSocket | 提供 WebSocket 接入示例,支持一对一、一对多发送方式 |
|
||||||
|
| 🚀 | API 日志 | 包括 RESTful API 访问日志、异常日志两部分,方便排查 API 相关的问题 |
|
||||||
|
| | MySQL 监控 | 监视当前系统数据库连接池状态,可进行分析SQL找出系统性能瓶颈 |
|
||||||
|
| | Redis 监控 | 监控 Redis 数据库的使用情况,使用的 Redis Key 管理 |
|
||||||
|
| 🚀 | 消息队列 | 基于 Redis 实现消息队列,Stream 提供集群消费,Pub/Sub 提供广播消费 |
|
||||||
|
| 🚀 | Java 监控 | 基于 Spring Boot Admin 实现 Java 应用的监控 |
|
||||||
|
| 🚀 | 链路追踪 | 接入 SkyWalking 组件,实现链路追踪 |
|
||||||
|
| 🚀 | 日志中心 | 接入 SkyWalking 组件,实现日志中心 |
|
||||||
|
| 🚀 | 服务保障 | 基于 Redis 实现分布式锁、幂等、限流功能,满足高并发场景 |
|
||||||
|
| 🚀 | 日志服务 | 轻量级日志中心,查看远程服务器的日志 |
|
||||||
|
| 🚀 | 单元测试 | 基于 JUnit + Mockito 实现单元测试,保证功能的正确性、代码的质量等 |
|
||||||
|
|
||||||
本项目采用 MIT 许可证。详情请参见 [LICENSE](./LICENSE) 文件。
|

|
||||||
|
|
||||||
---
|
### 数据报表
|
||||||
|
|
||||||
<div align="center">
|
| | 功能 | 描述 |
|
||||||
|
|-----|-------|--------------------|
|
||||||
|
| 🚀 | 报表设计器 | 支持数据报表、图形报表、打印设计等 |
|
||||||
|
| 🚀 | 大屏设计器 | 拖拽生成数据大屏,内置几十种图表组件 |
|
||||||
|
|
||||||
**让开发更简单,让创业更轻松** 🚀
|
### 微信公众号
|
||||||
|
|
||||||
Made with ❤️ by [芋道源码](https://www.iocoder.cn)
|
| | 功能 | 描述 |
|
||||||
|
|-----|--------|-------------------------------|
|
||||||
|
| 🚀 | 账号管理 | 配置接入的微信公众号,可支持多个公众号 |
|
||||||
|
| 🚀 | 数据统计 | 统计公众号的用户增减、累计用户、消息概况、接口分析等数据 |
|
||||||
|
| 🚀 | 粉丝管理 | 查看已关注、取关的粉丝列表,可对粉丝进行同步、打标签等操作 |
|
||||||
|
| 🚀 | 消息管理 | 查看粉丝发送的消息列表,可主动回复粉丝消息 |
|
||||||
|
| 🚀 | 自动回复 | 自动回复粉丝发送的消息,支持关注回复、消息回复、关键字回复 |
|
||||||
|
| 🚀 | 标签管理 | 对公众号的标签进行创建、查询、修改、删除等操作 |
|
||||||
|
| 🚀 | 菜单管理 | 自定义公众号的菜单,也可以从公众号同步菜单 |
|
||||||
|
| 🚀 | 素材管理 | 管理公众号的图片、语音、视频等素材,支持在线播放语音、视频 |
|
||||||
|
| 🚀 | 图文草稿箱 | 新增常用的图文素材到草稿箱,可发布到公众号 |
|
||||||
|
| 🚀 | 图文发表记录 | 查看已发布成功的图文素材,支持删除操作 |
|
||||||
|
|
||||||
</div>
|
### 商城系统
|
||||||
|
|
||||||
|
演示地址:<https://doc.iocoder.cn/mall-preview/>
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 会员中心
|
||||||
|
|
||||||
|
| | 功能 | 描述 |
|
||||||
|
|-----|------|----------------------------------|
|
||||||
|
| 🚀 | 会员管理 | 会员是 C 端的消费者,该功能用于会员的搜索与管理 |
|
||||||
|
| 🚀 | 会员标签 | 对会员的标签进行创建、查询、修改、删除等操作 |
|
||||||
|
| 🚀 | 会员等级 | 对会员的等级、成长值进行管理,可用于订单折扣等会员权益 |
|
||||||
|
| 🚀 | 会员分组 | 对会员进行分组,用于用户画像、内容推送等运营手段 |
|
||||||
|
| 🚀 | 积分签到 | 回馈给签到、消费等行为的积分,会员可订单抵现、积分兑换等途径消耗 |
|
||||||
|
|
||||||
|
### ERP 系统
|
||||||
|
|
||||||
|
演示地址:<https://doc.iocoder.cn/erp-preview/>
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### CRM 系统
|
||||||
|
|
||||||
|
演示地址:<https://doc.iocoder.cn/crm-preview/>
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### AI 大模型
|
||||||
|
|
||||||
|
演示地址:<https://doc.iocoder.cn/ai-preview/>
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 🐨 技术栈
|
||||||
|
|
||||||
|
### 模块
|
||||||
|
|
||||||
|
| 项目 | 说明 |
|
||||||
|
|-----------------------|--------------------|
|
||||||
|
| `yudao-dependencies` | Maven 依赖版本管理 |
|
||||||
|
| `yudao-framework` | Java 框架拓展 |
|
||||||
|
| `yudao-server` | 管理后台 + 用户 APP 的服务端 |
|
||||||
|
| `yudao-module-system` | 系统功能的 Module 模块 |
|
||||||
|
| `yudao-module-member` | 会员中心的 Module 模块 |
|
||||||
|
| `yudao-module-infra` | 基础设施的 Module 模块 |
|
||||||
|
| `yudao-module-bpm` | 工作流程的 Module 模块 |
|
||||||
|
| `yudao-module-pay` | 支付系统的 Module 模块 |
|
||||||
|
| `yudao-module-mall` | 商城系统的 Module 模块 |
|
||||||
|
| `yudao-module-erp` | ERP 系统的 Module 模块 |
|
||||||
|
| `yudao-module-crm` | CRM 系统的 Module 模块 |
|
||||||
|
| `yudao-module-ai` | AI 大模型的 Module 模块 |
|
||||||
|
| `yudao-module-mp` | 微信公众号的 Module 模块 |
|
||||||
|
| `yudao-module-report` | 大屏报表 Module 模块 |
|
||||||
|
|
||||||
|
### 框架
|
||||||
|
|
||||||
|
| 框架 | 说明 | 版本 | 学习指南 |
|
||||||
|
|---------------------------------------------------------------------------------------------|------------------|----------------|----------------------------------------------------------------|
|
||||||
|
| [Spring Boot](https://spring.io/projects/spring-boot) | 应用开发框架 | 2.7.18 | [文档](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.23 | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) |
|
||||||
|
| [MyBatis Plus](https://mp.baomidou.com/) | MyBatis 增强工具包 | 3.5.7 | [文档](http://www.iocoder.cn/Spring-Boot/MyBatis/?yudao) |
|
||||||
|
| [Dynamic Datasource](https://dynamic-datasource.com/) | 动态数据源 | 3.6.1 | [文档](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.32.0 | [文档](http://www.iocoder.cn/Spring-Boot/Redis/?yudao) |
|
||||||
|
| [Spring MVC](https://github.com/spring-projects/spring-framework/tree/master/spring-webmvc) | MVC 框架 | 5.3.24 | [文档](http://www.iocoder.cn/SpringMVC/MVC/?yudao) |
|
||||||
|
| [Spring Security](https://github.com/spring-projects/spring-security) | Spring 安全框架 | 5.7.11 | [文档](http://www.iocoder.cn/Spring-Boot/Spring-Security/?yudao) |
|
||||||
|
| [Hibernate Validator](https://github.com/hibernate/hibernate-validator) | 参数校验组件 | 6.2.5 | [文档](http://www.iocoder.cn/Spring-Boot/Validation/?yudao) |
|
||||||
|
| [Flowable](https://github.com/flowable/flowable-engine) | 工作流引擎 | 6.8.0 | [文档](https://doc.iocoder.cn/bpm/) |
|
||||||
|
| [Quartz](https://github.com/quartz-scheduler) | 任务调度组件 | 2.3.2 | [文档](http://www.iocoder.cn/Spring-Boot/Job/?yudao) |
|
||||||
|
| [Springdoc](https://springdoc.org/) | Swagger 文档 | 1.7.0 | [文档](http://www.iocoder.cn/Spring-Boot/Swagger/?yudao) |
|
||||||
|
| [SkyWalking](https://skywalking.apache.org/) | 分布式应用追踪系统 | 8.12.0 | [文档](http://www.iocoder.cn/Spring-Boot/SkyWalking/?yudao) |
|
||||||
|
| [Spring Boot Admin](https://github.com/codecentric/spring-boot-admin) | Spring Boot 监控平台 | 2.7.10 | [文档](http://www.iocoder.cn/Spring-Boot/Admin/?yudao) |
|
||||||
|
| [Jackson](https://github.com/FasterXML/jackson) | JSON 工具库 | 2.13.5 | |
|
||||||
|
| [MapStruct](https://mapstruct.org/) | Java Bean 转换 | 1.6.3 | [文档](http://www.iocoder.cn/Spring-Boot/MapStruct/?yudao) |
|
||||||
|
| [Lombok](https://projectlombok.org/) | 消除冗长的 Java 代码 | 1.18.34 | [文档](http://www.iocoder.cn/Spring-Boot/Lombok/?yudao) |
|
||||||
|
| [JUnit](https://junit.org/junit5/) | Java 单元测试框架 | 5.8.2 | - |
|
||||||
|
| [Mockito](https://github.com/mockito/mockito) | Java Mock 框架 | 4.8.0 | - |
|
||||||
|
|
||||||
|
## 🐷 演示图
|
||||||
|
|
||||||
|
### 系统功能
|
||||||
|
|
||||||
|
| 模块 | biu | biu | biu |
|
||||||
|
|----------|-----------------------------|---------------------------|--------------------------|
|
||||||
|
| 登录 & 首页 |  |  |  |
|
||||||
|
| 用户 & 应用 |  |  |  |
|
||||||
|
| 租户 & 套餐 |  |  | - |
|
||||||
|
| 部门 & 岗位 |  |  | - |
|
||||||
|
| 菜单 & 角色 |  |  | - |
|
||||||
|
| 审计日志 |  |  | - |
|
||||||
|
| 短信 |  |  |  |
|
||||||
|
| 字典 & 敏感词 |  |  |  |
|
||||||
|
| 错误码 & 通知 |  |  | - |
|
||||||
|
|
||||||
|
### 工作流程
|
||||||
|
|
||||||
|
| 模块 | biu | biu | biu |
|
||||||
|
|---------|---------------------------------|---------------------------------|---------------------------------|
|
||||||
|
| 流程模型 |  |  |  |
|
||||||
|
| 表单 & 分组 |  |  | - |
|
||||||
|
| 我的流程 |  |  |  |
|
||||||
|
| 待办 & 已办 |  |  |  |
|
||||||
|
| OA 请假 |  |  |  |
|
||||||
|
|
||||||
|
### 基础设施
|
||||||
|
|
||||||
|
| 模块 | biu | biu | biu |
|
||||||
|
|---------------|-------------------------------|-----------------------------|---------------------------|
|
||||||
|
| 代码生成 |  |  | - |
|
||||||
|
| 文档 |  |  | - |
|
||||||
|
| 文件 & 配置 |  |  |  |
|
||||||
|
| 定时任务 |  |  | - |
|
||||||
|
| API 日志 |  |  | - |
|
||||||
|
| MySQL & Redis |  |  | - |
|
||||||
|
| 监控平台 |  |  |  |
|
||||||
|
|
||||||
|
### 支付系统
|
||||||
|
|
||||||
|
| 模块 | biu | biu | biu |
|
||||||
|
|---------|---------------------------|---------------------------------|---------------------------------|
|
||||||
|
| 商家 & 应用 |  |  |  |
|
||||||
|
| 支付 & 退款 |  |  | --- |
|
||||||
|
### 数据报表
|
||||||
|
|
||||||
|
| 模块 | biu | biu | biu |
|
||||||
|
|-------|---------------------------------|---------------------------------|---------------------------------------|
|
||||||
|
| 报表设计器 |  |  |  |
|
||||||
|
| 大屏设计器 |  |  |  |
|
||||||
|
|
||||||
|
### 移动端(管理后台)
|
||||||
|
|
||||||
|
| biu | biu | biu |
|
||||||
|
|----------------------------------|----------------------------------|----------------------------------|
|
||||||
|
|  |  |  |
|
||||||
|
|  |  |  |
|
||||||
|
|  |  |  |
|
||||||
|
|
||||||
|
目前已经实现登录、我的、工作台、编辑资料、头像修改、密码修改、常见问题、关于我们等基础功能。
|
||||||
|
7
pom.xml
7
pom.xml
@@ -88,6 +88,13 @@
|
|||||||
<artifactId>lombok</artifactId>
|
<artifactId>lombok</artifactId>
|
||||||
<version>${lombok.version}</version>
|
<version>${lombok.version}</version>
|
||||||
</path>
|
</path>
|
||||||
|
<path>
|
||||||
|
<!-- 确保 Lombok 生成的 getter/setter 方法能被 MapStruct 正确识别,
|
||||||
|
避免出现 No property named “xxx" exists 的编译错误 -->
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok-mapstruct-binding</artifactId>
|
||||||
|
<version>0.2.0</version>
|
||||||
|
</path>
|
||||||
<path>
|
<path>
|
||||||
<groupId>org.mapstruct</groupId>
|
<groupId>org.mapstruct</groupId>
|
||||||
<artifactId>mapstruct-processor</artifactId>
|
<artifactId>mapstruct-processor</artifactId>
|
||||||
|
@@ -1,253 +1,208 @@
|
|||||||
-- ----------------------------
|
-- https://github.com/quartz-scheduler/quartz/blob/main/quartz/src/main/resources/org/quartz/impl/jdbcjobstore/tables_postgres.sql
|
||||||
-- qrtz_blob_triggers
|
-- Thanks to Patrick Lightbody for submitting this...
|
||||||
-- ----------------------------
|
--
|
||||||
CREATE TABLE qrtz_blob_triggers
|
-- In your Quartz properties file, you'll need to set
|
||||||
|
-- org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS;
|
||||||
|
DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS;
|
||||||
|
DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE;
|
||||||
|
DROP TABLE IF EXISTS QRTZ_LOCKS;
|
||||||
|
DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS;
|
||||||
|
DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS;
|
||||||
|
DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS;
|
||||||
|
DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS;
|
||||||
|
DROP TABLE IF EXISTS QRTZ_TRIGGERS;
|
||||||
|
DROP TABLE IF EXISTS QRTZ_JOB_DETAILS;
|
||||||
|
DROP TABLE IF EXISTS QRTZ_CALENDARS;
|
||||||
|
|
||||||
|
CREATE TABLE QRTZ_JOB_DETAILS
|
||||||
(
|
(
|
||||||
sched_name varchar(120) NOT NULL,
|
SCHED_NAME VARCHAR(120) NOT NULL,
|
||||||
trigger_name varchar(190) NOT NULL,
|
JOB_NAME VARCHAR(200) NOT NULL,
|
||||||
trigger_group varchar(190) NOT NULL,
|
JOB_GROUP VARCHAR(200) NOT NULL,
|
||||||
blob_data bytea NULL,
|
DESCRIPTION VARCHAR(250) NULL,
|
||||||
PRIMARY KEY (sched_name, trigger_name, trigger_group)
|
JOB_CLASS_NAME VARCHAR(250) NOT NULL,
|
||||||
|
IS_DURABLE BOOL NOT NULL,
|
||||||
|
IS_NONCONCURRENT BOOL NOT NULL,
|
||||||
|
IS_UPDATE_DATA BOOL NOT NULL,
|
||||||
|
REQUESTS_RECOVERY BOOL NOT NULL,
|
||||||
|
JOB_DATA BYTEA NULL,
|
||||||
|
PRIMARY KEY (SCHED_NAME, JOB_NAME, JOB_GROUP)
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE INDEX idx_qrtz_blob_triggers_sched_name ON qrtz_blob_triggers (sched_name, trigger_name, trigger_group);
|
CREATE TABLE QRTZ_TRIGGERS
|
||||||
|
|
||||||
-- ----------------------------
|
|
||||||
-- qrtz_calendars
|
|
||||||
-- ----------------------------
|
|
||||||
CREATE TABLE qrtz_calendars
|
|
||||||
(
|
(
|
||||||
sched_name varchar(120) NOT NULL,
|
SCHED_NAME VARCHAR(120) NOT NULL,
|
||||||
calendar_name varchar(190) NOT NULL,
|
TRIGGER_NAME VARCHAR(200) NOT NULL,
|
||||||
calendar bytea NOT NULL,
|
TRIGGER_GROUP VARCHAR(200) NOT NULL,
|
||||||
PRIMARY KEY (sched_name, calendar_name)
|
JOB_NAME VARCHAR(200) NOT NULL,
|
||||||
|
JOB_GROUP VARCHAR(200) NOT NULL,
|
||||||
|
DESCRIPTION VARCHAR(250) NULL,
|
||||||
|
NEXT_FIRE_TIME BIGINT NULL,
|
||||||
|
PREV_FIRE_TIME BIGINT NULL,
|
||||||
|
PRIORITY INTEGER NULL,
|
||||||
|
TRIGGER_STATE VARCHAR(16) NOT NULL,
|
||||||
|
TRIGGER_TYPE VARCHAR(8) NOT NULL,
|
||||||
|
START_TIME BIGINT NOT NULL,
|
||||||
|
END_TIME BIGINT NULL,
|
||||||
|
CALENDAR_NAME VARCHAR(200) NULL,
|
||||||
|
MISFIRE_INSTR SMALLINT NULL,
|
||||||
|
JOB_DATA BYTEA NULL,
|
||||||
|
PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP),
|
||||||
|
FOREIGN KEY (SCHED_NAME, JOB_NAME, JOB_GROUP)
|
||||||
|
REFERENCES QRTZ_JOB_DETAILS (SCHED_NAME, JOB_NAME, JOB_GROUP)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE QRTZ_SIMPLE_TRIGGERS
|
||||||
|
(
|
||||||
|
SCHED_NAME VARCHAR(120) NOT NULL,
|
||||||
|
TRIGGER_NAME VARCHAR(200) NOT NULL,
|
||||||
|
TRIGGER_GROUP VARCHAR(200) NOT NULL,
|
||||||
|
REPEAT_COUNT BIGINT NOT NULL,
|
||||||
|
REPEAT_INTERVAL BIGINT NOT NULL,
|
||||||
|
TIMES_TRIGGERED BIGINT NOT NULL,
|
||||||
|
PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP),
|
||||||
|
FOREIGN KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP)
|
||||||
|
REFERENCES QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE QRTZ_CRON_TRIGGERS
|
||||||
|
(
|
||||||
|
SCHED_NAME VARCHAR(120) NOT NULL,
|
||||||
|
TRIGGER_NAME VARCHAR(200) NOT NULL,
|
||||||
|
TRIGGER_GROUP VARCHAR(200) NOT NULL,
|
||||||
|
CRON_EXPRESSION VARCHAR(120) NOT NULL,
|
||||||
|
TIME_ZONE_ID VARCHAR(80),
|
||||||
|
PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP),
|
||||||
|
FOREIGN KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP)
|
||||||
|
REFERENCES QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE QRTZ_SIMPROP_TRIGGERS
|
||||||
|
(
|
||||||
|
SCHED_NAME VARCHAR(120) NOT NULL,
|
||||||
|
TRIGGER_NAME VARCHAR(200) NOT NULL,
|
||||||
|
TRIGGER_GROUP VARCHAR(200) NOT NULL,
|
||||||
|
STR_PROP_1 VARCHAR(512) NULL,
|
||||||
|
STR_PROP_2 VARCHAR(512) NULL,
|
||||||
|
STR_PROP_3 VARCHAR(512) NULL,
|
||||||
|
INT_PROP_1 INT NULL,
|
||||||
|
INT_PROP_2 INT NULL,
|
||||||
|
LONG_PROP_1 BIGINT NULL,
|
||||||
|
LONG_PROP_2 BIGINT NULL,
|
||||||
|
DEC_PROP_1 NUMERIC(13, 4) NULL,
|
||||||
|
DEC_PROP_2 NUMERIC(13, 4) NULL,
|
||||||
|
BOOL_PROP_1 BOOL NULL,
|
||||||
|
BOOL_PROP_2 BOOL NULL,
|
||||||
|
PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP),
|
||||||
|
FOREIGN KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP)
|
||||||
|
REFERENCES QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE QRTZ_BLOB_TRIGGERS
|
||||||
|
(
|
||||||
|
SCHED_NAME VARCHAR(120) NOT NULL,
|
||||||
|
TRIGGER_NAME VARCHAR(200) NOT NULL,
|
||||||
|
TRIGGER_GROUP VARCHAR(200) NOT NULL,
|
||||||
|
BLOB_DATA BYTEA NULL,
|
||||||
|
PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP),
|
||||||
|
FOREIGN KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP)
|
||||||
|
REFERENCES QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE QRTZ_CALENDARS
|
||||||
|
(
|
||||||
|
SCHED_NAME VARCHAR(120) NOT NULL,
|
||||||
|
CALENDAR_NAME VARCHAR(200) NOT NULL,
|
||||||
|
CALENDAR BYTEA NOT NULL,
|
||||||
|
PRIMARY KEY (SCHED_NAME, CALENDAR_NAME)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
-- ----------------------------
|
CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS
|
||||||
-- qrtz_cron_triggers
|
|
||||||
-- ----------------------------
|
|
||||||
CREATE TABLE qrtz_cron_triggers
|
|
||||||
(
|
(
|
||||||
sched_name varchar(120) NOT NULL,
|
SCHED_NAME VARCHAR(120) NOT NULL,
|
||||||
trigger_name varchar(190) NOT NULL,
|
TRIGGER_GROUP VARCHAR(200) NOT NULL,
|
||||||
trigger_group varchar(190) NOT NULL,
|
PRIMARY KEY (SCHED_NAME, TRIGGER_GROUP)
|
||||||
cron_expression varchar(120) NOT NULL,
|
|
||||||
time_zone_id varchar(80) NULL DEFAULT NULL,
|
|
||||||
PRIMARY KEY (sched_name, trigger_name, trigger_group)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
-- @formatter:off
|
CREATE TABLE QRTZ_FIRED_TRIGGERS
|
||||||
BEGIN;
|
|
||||||
COMMIT;
|
|
||||||
-- @formatter:on
|
|
||||||
|
|
||||||
-- ----------------------------
|
|
||||||
-- qrtz_fired_triggers
|
|
||||||
-- ----------------------------
|
|
||||||
CREATE TABLE qrtz_fired_triggers
|
|
||||||
(
|
(
|
||||||
sched_name varchar(120) NOT NULL,
|
SCHED_NAME VARCHAR(120) NOT NULL,
|
||||||
entry_id varchar(95) NOT NULL,
|
ENTRY_ID VARCHAR(95) NOT NULL,
|
||||||
trigger_name varchar(190) NOT NULL,
|
TRIGGER_NAME VARCHAR(200) NOT NULL,
|
||||||
trigger_group varchar(190) NOT NULL,
|
TRIGGER_GROUP VARCHAR(200) NOT NULL,
|
||||||
instance_name varchar(190) NOT NULL,
|
INSTANCE_NAME VARCHAR(200) NOT NULL,
|
||||||
fired_time int8 NOT NULL,
|
FIRED_TIME BIGINT NOT NULL,
|
||||||
sched_time int8 NOT NULL,
|
SCHED_TIME BIGINT NOT NULL,
|
||||||
priority int4 NOT NULL,
|
PRIORITY INTEGER NOT NULL,
|
||||||
state varchar(16) NOT NULL,
|
STATE VARCHAR(16) NOT NULL,
|
||||||
job_name varchar(190) NULL DEFAULT NULL,
|
JOB_NAME VARCHAR(200) NULL,
|
||||||
job_group varchar(190) NULL DEFAULT NULL,
|
JOB_GROUP VARCHAR(200) NULL,
|
||||||
is_nonconcurrent varchar(1) NULL DEFAULT NULL,
|
IS_NONCONCURRENT BOOL NULL,
|
||||||
requests_recovery varchar(1) NULL DEFAULT NULL,
|
REQUESTS_RECOVERY BOOL NULL,
|
||||||
PRIMARY KEY (sched_name, entry_id)
|
PRIMARY KEY (SCHED_NAME, ENTRY_ID)
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE INDEX idx_qrtz_ft_trig_inst_name ON qrtz_fired_triggers (sched_name, instance_name);
|
CREATE TABLE QRTZ_SCHEDULER_STATE
|
||||||
CREATE INDEX idx_qrtz_ft_inst_job_req_rcvry ON qrtz_fired_triggers (sched_name, instance_name, requests_recovery);
|
|
||||||
CREATE INDEX idx_qrtz_ft_j_g ON qrtz_fired_triggers (sched_name, job_name, job_group);
|
|
||||||
CREATE INDEX idx_qrtz_ft_jg ON qrtz_fired_triggers (sched_name, job_group);
|
|
||||||
CREATE INDEX idx_qrtz_ft_t_g ON qrtz_fired_triggers (sched_name, trigger_name, trigger_group);
|
|
||||||
CREATE INDEX idx_qrtz_ft_tg ON qrtz_fired_triggers (sched_name, trigger_group);
|
|
||||||
|
|
||||||
-- ----------------------------
|
|
||||||
-- qrtz_job_details
|
|
||||||
-- ----------------------------
|
|
||||||
CREATE TABLE qrtz_job_details
|
|
||||||
(
|
(
|
||||||
sched_name varchar(120) NOT NULL,
|
SCHED_NAME VARCHAR(120) NOT NULL,
|
||||||
job_name varchar(190) NOT NULL,
|
INSTANCE_NAME VARCHAR(200) NOT NULL,
|
||||||
job_group varchar(190) NOT NULL,
|
LAST_CHECKIN_TIME BIGINT NOT NULL,
|
||||||
description varchar(250) NULL DEFAULT NULL,
|
CHECKIN_INTERVAL BIGINT NOT NULL,
|
||||||
job_class_name varchar(250) NOT NULL,
|
PRIMARY KEY (SCHED_NAME, INSTANCE_NAME)
|
||||||
is_durable varchar(1) NOT NULL,
|
|
||||||
is_nonconcurrent varchar(1) NOT NULL,
|
|
||||||
is_update_data varchar(1) NOT NULL,
|
|
||||||
requests_recovery varchar(1) NOT NULL,
|
|
||||||
job_data bytea NULL,
|
|
||||||
PRIMARY KEY (sched_name, job_name, job_group)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE INDEX idx_qrtz_j_req_recovery ON qrtz_job_details (sched_name, requests_recovery);
|
CREATE TABLE QRTZ_LOCKS
|
||||||
CREATE INDEX idx_qrtz_j_grp ON qrtz_job_details (sched_name, job_group);
|
|
||||||
|
|
||||||
-- @formatter:off
|
|
||||||
BEGIN;
|
|
||||||
COMMIT;
|
|
||||||
-- @formatter:on
|
|
||||||
|
|
||||||
-- ----------------------------
|
|
||||||
-- qrtz_locks
|
|
||||||
-- ----------------------------
|
|
||||||
CREATE TABLE qrtz_locks
|
|
||||||
(
|
(
|
||||||
sched_name varchar(120) NOT NULL,
|
SCHED_NAME VARCHAR(120) NOT NULL,
|
||||||
lock_name varchar(40) NOT NULL,
|
LOCK_NAME VARCHAR(40) NOT NULL,
|
||||||
PRIMARY KEY (sched_name, lock_name)
|
PRIMARY KEY (SCHED_NAME, LOCK_NAME)
|
||||||
);
|
);
|
||||||
|
|
||||||
-- @formatter:off
|
CREATE INDEX IDX_QRTZ_J_REQ_RECOVERY
|
||||||
BEGIN;
|
ON QRTZ_JOB_DETAILS (SCHED_NAME, REQUESTS_RECOVERY);
|
||||||
COMMIT;
|
CREATE INDEX IDX_QRTZ_J_GRP
|
||||||
-- @formatter:on
|
ON QRTZ_JOB_DETAILS (SCHED_NAME, JOB_GROUP);
|
||||||
|
|
||||||
-- ----------------------------
|
CREATE INDEX IDX_QRTZ_T_J
|
||||||
-- qrtz_paused_trigger_grps
|
ON QRTZ_TRIGGERS (SCHED_NAME, JOB_NAME, JOB_GROUP);
|
||||||
-- ----------------------------
|
CREATE INDEX IDX_QRTZ_T_JG
|
||||||
CREATE TABLE qrtz_paused_trigger_grps
|
ON QRTZ_TRIGGERS (SCHED_NAME, JOB_GROUP);
|
||||||
(
|
CREATE INDEX IDX_QRTZ_T_C
|
||||||
sched_name varchar(120) NOT NULL,
|
ON QRTZ_TRIGGERS (SCHED_NAME, CALENDAR_NAME);
|
||||||
trigger_group varchar(190) NOT NULL,
|
CREATE INDEX IDX_QRTZ_T_G
|
||||||
PRIMARY KEY (sched_name, trigger_group)
|
ON QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_GROUP);
|
||||||
);
|
CREATE INDEX IDX_QRTZ_T_STATE
|
||||||
|
ON QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_STATE);
|
||||||
|
CREATE INDEX IDX_QRTZ_T_N_STATE
|
||||||
|
ON QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP, TRIGGER_STATE);
|
||||||
|
CREATE INDEX IDX_QRTZ_T_N_G_STATE
|
||||||
|
ON QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_GROUP, TRIGGER_STATE);
|
||||||
|
CREATE INDEX IDX_QRTZ_T_NEXT_FIRE_TIME
|
||||||
|
ON QRTZ_TRIGGERS (SCHED_NAME, NEXT_FIRE_TIME);
|
||||||
|
CREATE INDEX IDX_QRTZ_T_NFT_ST
|
||||||
|
ON QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_STATE, NEXT_FIRE_TIME);
|
||||||
|
CREATE INDEX IDX_QRTZ_T_NFT_MISFIRE
|
||||||
|
ON QRTZ_TRIGGERS (SCHED_NAME, MISFIRE_INSTR, NEXT_FIRE_TIME);
|
||||||
|
CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE
|
||||||
|
ON QRTZ_TRIGGERS (SCHED_NAME, MISFIRE_INSTR, NEXT_FIRE_TIME, TRIGGER_STATE);
|
||||||
|
CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE_GRP
|
||||||
|
ON QRTZ_TRIGGERS (SCHED_NAME, MISFIRE_INSTR, NEXT_FIRE_TIME, TRIGGER_GROUP, TRIGGER_STATE);
|
||||||
|
|
||||||
-- ----------------------------
|
CREATE INDEX IDX_QRTZ_FT_TRIG_INST_NAME
|
||||||
-- qrtz_scheduler_state
|
ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, INSTANCE_NAME);
|
||||||
-- ----------------------------
|
CREATE INDEX IDX_QRTZ_FT_INST_JOB_REQ_RCVRY
|
||||||
CREATE TABLE qrtz_scheduler_state
|
ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, INSTANCE_NAME, REQUESTS_RECOVERY);
|
||||||
(
|
CREATE INDEX IDX_QRTZ_FT_J_G
|
||||||
sched_name varchar(120) NOT NULL,
|
ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, JOB_NAME, JOB_GROUP);
|
||||||
instance_name varchar(190) NOT NULL,
|
CREATE INDEX IDX_QRTZ_FT_JG
|
||||||
last_checkin_time int8 NOT NULL,
|
ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, JOB_GROUP);
|
||||||
checkin_interval int8 NOT NULL,
|
CREATE INDEX IDX_QRTZ_FT_T_G
|
||||||
PRIMARY KEY (sched_name, instance_name)
|
ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP);
|
||||||
);
|
CREATE INDEX IDX_QRTZ_FT_TG
|
||||||
|
ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, TRIGGER_GROUP);
|
||||||
-- @formatter:off
|
|
||||||
BEGIN;
|
|
||||||
COMMIT;
|
|
||||||
-- @formatter:on
|
|
||||||
|
|
||||||
-- ----------------------------
|
|
||||||
-- qrtz_simple_triggers
|
|
||||||
-- ----------------------------
|
|
||||||
CREATE TABLE qrtz_simple_triggers
|
|
||||||
(
|
|
||||||
sched_name varchar(120) NOT NULL,
|
|
||||||
trigger_name varchar(190) NOT NULL,
|
|
||||||
trigger_group varchar(190) NOT NULL,
|
|
||||||
repeat_count int8 NOT NULL,
|
|
||||||
repeat_interval int8 NOT NULL,
|
|
||||||
times_triggered int8 NOT NULL,
|
|
||||||
PRIMARY KEY (sched_name, trigger_name, trigger_group)
|
|
||||||
);
|
|
||||||
|
|
||||||
-- ----------------------------
|
|
||||||
-- qrtz_simprop_triggers
|
|
||||||
-- ----------------------------
|
|
||||||
CREATE TABLE qrtz_simprop_triggers
|
|
||||||
(
|
|
||||||
sched_name varchar(120) NOT NULL,
|
|
||||||
trigger_name varchar(190) NOT NULL,
|
|
||||||
trigger_group varchar(190) NOT NULL,
|
|
||||||
str_prop_1 varchar(512) NULL DEFAULT NULL,
|
|
||||||
str_prop_2 varchar(512) NULL DEFAULT NULL,
|
|
||||||
str_prop_3 varchar(512) NULL DEFAULT NULL,
|
|
||||||
int_prop_1 int4 NULL DEFAULT NULL,
|
|
||||||
int_prop_2 int4 NULL DEFAULT NULL,
|
|
||||||
long_prop_1 int8 NULL DEFAULT NULL,
|
|
||||||
long_prop_2 int8 NULL DEFAULT NULL,
|
|
||||||
dec_prop_1 numeric(13, 4) NULL DEFAULT NULL,
|
|
||||||
dec_prop_2 numeric(13, 4) NULL DEFAULT NULL,
|
|
||||||
bool_prop_1 varchar(1) NULL DEFAULT NULL,
|
|
||||||
bool_prop_2 varchar(1) NULL DEFAULT NULL,
|
|
||||||
PRIMARY KEY (sched_name, trigger_name, trigger_group)
|
|
||||||
);
|
|
||||||
|
|
||||||
-- ----------------------------
|
|
||||||
-- qrtz_triggers
|
|
||||||
-- ----------------------------
|
|
||||||
CREATE TABLE qrtz_triggers
|
|
||||||
(
|
|
||||||
sched_name varchar(120) NOT NULL,
|
|
||||||
trigger_name varchar(190) NOT NULL,
|
|
||||||
trigger_group varchar(190) NOT NULL,
|
|
||||||
job_name varchar(190) NOT NULL,
|
|
||||||
job_group varchar(190) NOT NULL,
|
|
||||||
description varchar(250) NULL DEFAULT NULL,
|
|
||||||
next_fire_time int8 NULL DEFAULT NULL,
|
|
||||||
prev_fire_time int8 NULL DEFAULT NULL,
|
|
||||||
priority int4 NULL DEFAULT NULL,
|
|
||||||
trigger_state varchar(16) NOT NULL,
|
|
||||||
trigger_type varchar(8) NOT NULL,
|
|
||||||
start_time int8 NOT NULL,
|
|
||||||
end_time int8 NULL DEFAULT NULL,
|
|
||||||
calendar_name varchar(190) NULL DEFAULT NULL,
|
|
||||||
misfire_instr int2 NULL DEFAULT NULL,
|
|
||||||
job_data bytea NULL,
|
|
||||||
PRIMARY KEY (sched_name, trigger_name, trigger_group)
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX idx_qrtz_t_j ON qrtz_triggers (sched_name, job_name, job_group);
|
|
||||||
CREATE INDEX idx_qrtz_t_jg ON qrtz_triggers (sched_name, job_group);
|
|
||||||
CREATE INDEX idx_qrtz_t_c ON qrtz_triggers (sched_name, calendar_name);
|
|
||||||
CREATE INDEX idx_qrtz_t_g ON qrtz_triggers (sched_name, trigger_group);
|
|
||||||
CREATE INDEX idx_qrtz_t_state ON qrtz_triggers (sched_name, trigger_state);
|
|
||||||
CREATE INDEX idx_qrtz_t_n_state ON qrtz_triggers (sched_name, trigger_name, trigger_group, trigger_state);
|
|
||||||
CREATE INDEX idx_qrtz_t_n_g_state ON qrtz_triggers (sched_name, trigger_group, trigger_state);
|
|
||||||
CREATE INDEX idx_qrtz_t_next_fire_time ON qrtz_triggers (sched_name, next_fire_time);
|
|
||||||
CREATE INDEX idx_qrtz_t_nft_st ON qrtz_triggers (sched_name, trigger_state, next_fire_time);
|
|
||||||
CREATE INDEX idx_qrtz_t_nft_misfire ON qrtz_triggers (sched_name, misfire_instr, next_fire_time);
|
|
||||||
CREATE INDEX idx_qrtz_t_nft_st_misfire ON qrtz_triggers (sched_name, misfire_instr, next_fire_time, trigger_state);
|
|
||||||
CREATE INDEX idx_qrtz_t_nft_st_misfire_grp ON qrtz_triggers (sched_name, misfire_instr, next_fire_time, trigger_group,
|
|
||||||
trigger_state);
|
|
||||||
|
|
||||||
-- @formatter:off
|
|
||||||
BEGIN;
|
|
||||||
COMMIT;
|
|
||||||
-- @formatter:on
|
|
||||||
|
|
||||||
|
|
||||||
-- ----------------------------
|
COMMIT;
|
||||||
-- FK: qrtz_blob_triggers
|
|
||||||
-- ----------------------------
|
|
||||||
ALTER TABLE qrtz_blob_triggers
|
|
||||||
ADD CONSTRAINT qrtz_blob_triggers_ibfk_1 FOREIGN KEY (sched_name, trigger_name, trigger_group) REFERENCES qrtz_triggers (sched_name,
|
|
||||||
trigger_name,
|
|
||||||
trigger_group);
|
|
||||||
|
|
||||||
-- ----------------------------
|
|
||||||
-- FK: qrtz_cron_triggers
|
|
||||||
-- ----------------------------
|
|
||||||
ALTER TABLE qrtz_cron_triggers
|
|
||||||
ADD CONSTRAINT qrtz_cron_triggers_ibfk_1 FOREIGN KEY (sched_name, trigger_name, trigger_group) REFERENCES qrtz_triggers (sched_name, trigger_name, trigger_group);
|
|
||||||
|
|
||||||
-- ----------------------------
|
|
||||||
-- FK: qrtz_simple_triggers
|
|
||||||
-- ----------------------------
|
|
||||||
ALTER TABLE qrtz_simple_triggers
|
|
||||||
ADD CONSTRAINT qrtz_simple_triggers_ibfk_1 FOREIGN KEY (sched_name, trigger_name, trigger_group) REFERENCES qrtz_triggers (sched_name,
|
|
||||||
trigger_name,
|
|
||||||
trigger_group);
|
|
||||||
|
|
||||||
-- ----------------------------
|
|
||||||
-- FK: qrtz_simprop_triggers
|
|
||||||
-- ----------------------------
|
|
||||||
ALTER TABLE qrtz_simprop_triggers
|
|
||||||
ADD CONSTRAINT qrtz_simprop_triggers_ibfk_1 FOREIGN KEY (sched_name, trigger_name, trigger_group) REFERENCES qrtz_triggers (sched_name, trigger_name, trigger_group);
|
|
||||||
|
|
||||||
-- ----------------------------
|
|
||||||
-- FK: qrtz_triggers
|
|
||||||
-- ----------------------------
|
|
||||||
ALTER TABLE qrtz_triggers
|
|
||||||
ADD CONSTRAINT qrtz_triggers_ibfk_1 FOREIGN KEY (sched_name, job_name, job_group) REFERENCES qrtz_job_details (sched_name, job_name, job_group);
|
|
@@ -18,7 +18,7 @@
|
|||||||
<flatten-maven-plugin.version>1.6.0</flatten-maven-plugin.version>
|
<flatten-maven-plugin.version>1.6.0</flatten-maven-plugin.version>
|
||||||
<!-- 统一依赖管理 -->
|
<!-- 统一依赖管理 -->
|
||||||
<spring.framework.version>5.3.39</spring.framework.version>
|
<spring.framework.version>5.3.39</spring.framework.version>
|
||||||
<spring.security.version>5.8.14</spring.security.version>
|
<spring.security.version>5.8.16</spring.security.version>
|
||||||
<spring.boot.version>2.7.18</spring.boot.version>
|
<spring.boot.version>2.7.18</spring.boot.version>
|
||||||
<!-- Web 相关 -->
|
<!-- Web 相关 -->
|
||||||
<springdoc.version>1.8.0</springdoc.version>
|
<springdoc.version>1.8.0</springdoc.version>
|
||||||
@@ -76,7 +76,8 @@
|
|||||||
<awssdk.version>2.30.14</awssdk.version>
|
<awssdk.version>2.30.14</awssdk.version>
|
||||||
<justauth.version>1.16.7</justauth.version>
|
<justauth.version>1.16.7</justauth.version>
|
||||||
<justauth-starter.version>1.4.0</justauth-starter.version>
|
<justauth-starter.version>1.4.0</justauth-starter.version>
|
||||||
<jimureport.version>1.9.4</jimureport.version>
|
<jimureport.version>2.1.0</jimureport.version>
|
||||||
|
<jimubi.version>1.9.5</jimubi.version>
|
||||||
<weixin-java.version>4.7.5.B</weixin-java.version>
|
<weixin-java.version>4.7.5.B</weixin-java.version>
|
||||||
<!-- 专属于 JDK8 安全漏洞升级 -->
|
<!-- 专属于 JDK8 安全漏洞升级 -->
|
||||||
<logback.version>1.2.13</logback.version> <!-- 无法使用 1.3.X 版本,启动会报错 -->
|
<logback.version>1.2.13</logback.version> <!-- 无法使用 1.3.X 版本,启动会报错 -->
|
||||||
@@ -626,7 +627,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jeecgframework.jimureport</groupId>
|
<groupId>org.jeecgframework.jimureport</groupId>
|
||||||
<artifactId>jimubi-spring-boot-starter</artifactId>
|
<artifactId>jimubi-spring-boot-starter</artifactId>
|
||||||
<version>${jimureport.version}</version>
|
<version>${jimubi.version}</version>
|
||||||
<exclusions>
|
<exclusions>
|
||||||
<exclusion>
|
<exclusion>
|
||||||
<groupId>com.github.jsqlparser</groupId>
|
<groupId>com.github.jsqlparser</groupId>
|
||||||
|
@@ -7,6 +7,7 @@ import org.springframework.boot.autoconfigure.AutoConfiguration;
|
|||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.scheduling.annotation.EnableAsync;
|
import org.springframework.scheduling.annotation.EnableAsync;
|
||||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||||
|
import org.springframework.core.task.SimpleAsyncTaskExecutor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 异步任务 Configuration
|
* 异步任务 Configuration
|
||||||
@@ -21,13 +22,20 @@ public class YudaoAsyncAutoConfiguration {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
|
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
|
||||||
if (!(bean instanceof ThreadPoolTaskExecutor)) {
|
// 处理 ThreadPoolTaskExecutor
|
||||||
return bean;
|
if (bean instanceof ThreadPoolTaskExecutor) {
|
||||||
|
ThreadPoolTaskExecutor executor = (ThreadPoolTaskExecutor) bean;
|
||||||
|
executor.setTaskDecorator(TtlRunnable::get);
|
||||||
|
return executor;
|
||||||
}
|
}
|
||||||
// 修改提交的任务,接入 TransmittableThreadLocal
|
// 处理 SimpleAsyncTaskExecutor
|
||||||
ThreadPoolTaskExecutor executor = (ThreadPoolTaskExecutor) bean;
|
// 参考 https://t.zsxq.com/CBoks 增加
|
||||||
executor.setTaskDecorator(TtlRunnable::get);
|
if (bean instanceof SimpleAsyncTaskExecutor) {
|
||||||
return executor;
|
SimpleAsyncTaskExecutor executor = (SimpleAsyncTaskExecutor) bean;
|
||||||
|
executor.setTaskDecorator(TtlRunnable::get);
|
||||||
|
return executor;
|
||||||
|
}
|
||||||
|
return bean;
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@@ -24,8 +24,8 @@
|
|||||||
<!-- Web 相关 -->
|
<!-- Web 相关 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>cn.iocoder.boot</groupId>
|
<groupId>cn.iocoder.boot</groupId>
|
||||||
<artifactId>yudao-spring-boot-starter-web</artifactId>
|
<artifactId>yudao-spring-boot-starter-security</artifactId>
|
||||||
<scope>provided</scope> <!-- 设置为 provided,只有 OncePerRequestFilter 使用到 -->
|
<scope>provided</scope> <!-- 设置为 provided,只有 DefaultDBFieldHandler 使用到 -->
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- DB 相关 -->
|
<!-- DB 相关 -->
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
package cn.iocoder.yudao.framework.mybatis.core.handler;
|
package cn.iocoder.yudao.framework.mybatis.core.handler;
|
||||||
|
|
||||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||||
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
|
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
|
||||||
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
|
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
|
||||||
import org.apache.ibatis.reflection.MetaObject;
|
import org.apache.ibatis.reflection.MetaObject;
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ public class DefaultDBFieldHandler implements MetaObjectHandler {
|
|||||||
baseDO.setUpdateTime(current);
|
baseDO.setUpdateTime(current);
|
||||||
}
|
}
|
||||||
|
|
||||||
Long userId = WebFrameworkUtils.getLoginUserId();
|
Long userId = SecurityFrameworkUtils.getLoginUserId();
|
||||||
// 当前登录用户不为空,创建人为空,则当前登录用户为创建人
|
// 当前登录用户不为空,创建人为空,则当前登录用户为创建人
|
||||||
if (Objects.nonNull(userId) && Objects.isNull(baseDO.getCreator())) {
|
if (Objects.nonNull(userId) && Objects.isNull(baseDO.getCreator())) {
|
||||||
baseDO.setCreator(userId.toString());
|
baseDO.setCreator(userId.toString());
|
||||||
@@ -54,7 +54,7 @@ public class DefaultDBFieldHandler implements MetaObjectHandler {
|
|||||||
|
|
||||||
// 当前登录用户不为空,更新人为空,则当前登录用户为更新人
|
// 当前登录用户不为空,更新人为空,则当前登录用户为更新人
|
||||||
Object modifier = getFieldValByName("updater", metaObject);
|
Object modifier = getFieldValByName("updater", metaObject);
|
||||||
Long userId = WebFrameworkUtils.getLoginUserId();
|
Long userId = SecurityFrameworkUtils.getLoginUserId();
|
||||||
if (Objects.nonNull(userId) && Objects.isNull(modifier)) {
|
if (Objects.nonNull(userId) && Objects.isNull(modifier)) {
|
||||||
setFieldValByName("updater", userId.toString(), metaObject);
|
setFieldValByName("updater", userId.toString(), metaObject);
|
||||||
}
|
}
|
||||||
|
@@ -42,6 +42,7 @@ public interface BaseMapperX<T> extends MPJBaseMapper<T> {
|
|||||||
default PageResult<T> selectPage(PageParam pageParam, Collection<SortingField> sortingFields, @Param("ew") Wrapper<T> queryWrapper) {
|
default PageResult<T> selectPage(PageParam pageParam, Collection<SortingField> sortingFields, @Param("ew") Wrapper<T> queryWrapper) {
|
||||||
// 特殊:不分页,直接查询全部
|
// 特殊:不分页,直接查询全部
|
||||||
if (PageParam.PAGE_SIZE_NONE.equals(pageParam.getPageSize())) {
|
if (PageParam.PAGE_SIZE_NONE.equals(pageParam.getPageSize())) {
|
||||||
|
MyBatisUtils.addOrder(queryWrapper, sortingFields);
|
||||||
List<T> list = selectList(queryWrapper);
|
List<T> list = selectList(queryWrapper);
|
||||||
return new PageResult<>(list, (long) list.size());
|
return new PageResult<>(list, (long) list.size());
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
package cn.iocoder.yudao.framework.mybatis.core.util;
|
package cn.iocoder.yudao.framework.mybatis.core.util;
|
||||||
|
|
||||||
import cn.hutool.core.collection.CollectionUtil;
|
import cn.hutool.core.collection.CollUtil;
|
||||||
import cn.hutool.core.lang.func.Func1;
|
import cn.hutool.core.lang.func.Func1;
|
||||||
import cn.hutool.core.lang.func.LambdaUtil;
|
import cn.hutool.core.lang.func.LambdaUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
@@ -8,6 +8,8 @@ import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
|||||||
import cn.iocoder.yudao.framework.common.pojo.SortingField;
|
import cn.iocoder.yudao.framework.common.pojo.SortingField;
|
||||||
import cn.iocoder.yudao.framework.mybatis.core.enums.DbTypeEnum;
|
import cn.iocoder.yudao.framework.mybatis.core.enums.DbTypeEnum;
|
||||||
import com.baomidou.mybatisplus.annotation.DbType;
|
import com.baomidou.mybatisplus.annotation.DbType;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.metadata.OrderItem;
|
import com.baomidou.mybatisplus.core.metadata.OrderItem;
|
||||||
import com.baomidou.mybatisplus.core.toolkit.StringPool;
|
import com.baomidou.mybatisplus.core.toolkit.StringPool;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||||
@@ -20,7 +22,6 @@ import net.sf.jsqlparser.schema.Table;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MyBatis 工具类
|
* MyBatis 工具类
|
||||||
@@ -37,15 +38,27 @@ public class MyBatisUtils {
|
|||||||
// 页码 + 数量
|
// 页码 + 数量
|
||||||
Page<T> page = new Page<>(pageParam.getPageNo(), pageParam.getPageSize());
|
Page<T> page = new Page<>(pageParam.getPageNo(), pageParam.getPageSize());
|
||||||
// 排序字段
|
// 排序字段
|
||||||
if (!CollectionUtil.isEmpty(sortingFields)) {
|
if (CollUtil.isNotEmpty(sortingFields)) {
|
||||||
page.addOrder(sortingFields.stream().map(sortingField -> SortingField.ORDER_ASC.equals(sortingField.getOrder())
|
for (SortingField sortingField : sortingFields) {
|
||||||
? OrderItem.asc(StrUtil.toUnderlineCase(sortingField.getField()))
|
page.addOrder(new OrderItem().setAsc(SortingField.ORDER_ASC.equals(sortingField.getOrder()))
|
||||||
: OrderItem.desc(StrUtil.toUnderlineCase(sortingField.getField())))
|
.setColumn(StrUtil.toUnderlineCase(sortingField.getField())));
|
||||||
.collect(Collectors.toList()));
|
}
|
||||||
}
|
}
|
||||||
return page;
|
return page;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static <T> void addOrder(Wrapper<T> wrapper, Collection<SortingField> sortingFields) {
|
||||||
|
if (CollUtil.isEmpty(sortingFields)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QueryWrapper<T> query = (QueryWrapper<T>) wrapper;
|
||||||
|
for (SortingField sortingField : sortingFields) {
|
||||||
|
query.orderBy(true,
|
||||||
|
SortingField.ORDER_ASC.equals(sortingField.getOrder()),
|
||||||
|
StrUtil.toUnderlineCase(sortingField.getField()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 将拦截器添加到链中
|
* 将拦截器添加到链中
|
||||||
* 由于 MybatisPlusInterceptor 不支持添加拦截器,所以只能全量设置
|
* 由于 MybatisPlusInterceptor 不支持添加拦截器,所以只能全量设置
|
||||||
|
@@ -126,8 +126,10 @@ public class SecurityFrameworkUtils {
|
|||||||
|
|
||||||
// 额外设置到 request 中,用于 ApiAccessLogFilter 可以获取到用户编号;
|
// 额外设置到 request 中,用于 ApiAccessLogFilter 可以获取到用户编号;
|
||||||
// 原因是,Spring Security 的 Filter 在 ApiAccessLogFilter 后面,在它记录访问日志时,线上上下文已经没有用户编号等信息
|
// 原因是,Spring Security 的 Filter 在 ApiAccessLogFilter 后面,在它记录访问日志时,线上上下文已经没有用户编号等信息
|
||||||
WebFrameworkUtils.setLoginUserId(request, loginUser.getId());
|
if (request != null) {
|
||||||
WebFrameworkUtils.setLoginUserType(request, loginUser.getUserType());
|
WebFrameworkUtils.setLoginUserId(request, loginUser.getId());
|
||||||
|
WebFrameworkUtils.setLoginUserType(request, loginUser.getUserType());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Authentication buildAuthentication(LoginUser loginUser, HttpServletRequest request) {
|
private static Authentication buildAuthentication(LoginUser loginUser, HttpServletRequest request) {
|
||||||
|
@@ -19,7 +19,8 @@
|
|||||||
国外:OpenAI、Ollama、Midjourney、StableDiffusion、Suno
|
国外:OpenAI、Ollama、Midjourney、StableDiffusion、Suno
|
||||||
</description>
|
</description>
|
||||||
<properties>
|
<properties>
|
||||||
<spring-ai.version>1.0.0-M6</spring-ai.version>
|
<spring-ai.version>1.0.0</spring-ai.version>
|
||||||
|
<alibaba-ai.version>1.0.0.2</alibaba-ai.version>
|
||||||
<tinyflow.version>1.0.2</tinyflow.version>
|
<tinyflow.version>1.0.2</tinyflow.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
@@ -75,65 +76,73 @@
|
|||||||
<!-- Spring AI Model 模型接入 -->
|
<!-- Spring AI Model 模型接入 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.ai</groupId>
|
<groupId>org.springframework.ai</groupId>
|
||||||
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
|
<artifactId>spring-ai-starter-model-openai</artifactId>
|
||||||
<version>${spring-ai.version}</version>
|
<version>${spring-ai.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.ai</groupId>
|
<groupId>org.springframework.ai</groupId>
|
||||||
<artifactId>spring-ai-azure-openai-spring-boot-starter</artifactId>
|
<artifactId>spring-ai-starter-model-azure-openai</artifactId>
|
||||||
<version>${spring-ai.version}</version>
|
<version>${spring-ai.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.ai</groupId>
|
<groupId>org.springframework.ai</groupId>
|
||||||
<artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
|
<artifactId>spring-ai-starter-model-deepseek</artifactId>
|
||||||
<version>${spring-ai.version}</version>
|
<version>${spring-ai.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.ai</groupId>
|
<groupId>org.springframework.ai</groupId>
|
||||||
<artifactId>spring-ai-stability-ai-spring-boot-starter</artifactId>
|
<artifactId>spring-ai-starter-model-ollama</artifactId>
|
||||||
<version>${spring-ai.version}</version>
|
<version>${spring-ai.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<!-- 通义千问 -->
|
|
||||||
<groupId>com.alibaba.cloud.ai</groupId>
|
|
||||||
<artifactId>spring-ai-alibaba-starter</artifactId>
|
|
||||||
<version>${spring-ai.version}.1</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<!-- 文心一言 -->
|
|
||||||
<groupId>org.springframework.ai</groupId>
|
<groupId>org.springframework.ai</groupId>
|
||||||
<artifactId>spring-ai-qianfan-spring-boot-starter</artifactId>
|
<artifactId>spring-ai-starter-model-stability-ai</artifactId>
|
||||||
<version>${spring-ai.version}</version>
|
<version>${spring-ai.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<!-- 智谱 GLM -->
|
<!-- 智谱 GLM -->
|
||||||
<groupId>org.springframework.ai</groupId>
|
<groupId>org.springframework.ai</groupId>
|
||||||
<artifactId>spring-ai-zhipuai-spring-boot-starter</artifactId>
|
<artifactId>spring-ai-starter-model-zhipuai</artifactId>
|
||||||
<version>${spring-ai.version}</version>
|
<version>${spring-ai.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.ai</groupId>
|
<groupId>org.springframework.ai</groupId>
|
||||||
<artifactId>spring-ai-minimax-spring-boot-starter</artifactId>
|
<artifactId>spring-ai-starter-model-minimax</artifactId>
|
||||||
<version>${spring-ai.version}</version>
|
<version>${spring-ai.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.ai</groupId>
|
<!-- 通义千问 -->
|
||||||
<artifactId>spring-ai-moonshot-spring-boot-starter</artifactId>
|
<groupId>com.alibaba.cloud.ai</groupId>
|
||||||
<version>${spring-ai.version}</version>
|
<artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
|
||||||
|
<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>
|
</dependency>
|
||||||
|
|
||||||
<!-- 向量存储:https://db-engines.com/en/ranking/vector+dbms -->
|
<!-- 向量存储:https://db-engines.com/en/ranking/vector+dbms -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<!-- Qdrant:https://qdrant.tech/ -->
|
<!-- Qdrant:https://qdrant.tech/ -->
|
||||||
<groupId>org.springframework.ai</groupId>
|
<groupId>org.springframework.ai</groupId>
|
||||||
<artifactId>spring-ai-qdrant-store</artifactId>
|
<artifactId>spring-ai-starter-vector-store-qdrant</artifactId>
|
||||||
<version>${spring-ai.version}</version>
|
<version>${spring-ai.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<!-- Redis:https://redis.io/docs/latest/develop/get-started/vector-database/ -->
|
<!-- Redis:https://redis.io/docs/latest/develop/get-started/vector-database/ -->
|
||||||
<groupId>org.springframework.ai</groupId>
|
<groupId>org.springframework.ai</groupId>
|
||||||
<artifactId>spring-ai-redis-store</artifactId>
|
<artifactId>spring-ai-starter-vector-store-redis</artifactId>
|
||||||
<version>${spring-ai.version}</version>
|
<version>${spring-ai.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
@@ -144,7 +153,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<!-- Milvus:https://milvus.io/ -->
|
<!-- Milvus:https://milvus.io/ -->
|
||||||
<groupId>org.springframework.ai</groupId>
|
<groupId>org.springframework.ai</groupId>
|
||||||
<artifactId>spring-ai-milvus-store</artifactId>
|
<artifactId>spring-ai-starter-vector-store-milvus</artifactId>
|
||||||
<version>${spring-ai.version}</version>
|
<version>${spring-ai.version}</version>
|
||||||
<exclusions>
|
<exclusions>
|
||||||
<!-- 解决和 logback 的日志冲突 -->
|
<!-- 解决和 logback 的日志冲突 -->
|
||||||
|
@@ -5,7 +5,6 @@ import cn.hutool.extra.spring.SpringUtil;
|
|||||||
import cn.iocoder.yudao.module.ai.framework.ai.core.AiModelFactory;
|
import cn.iocoder.yudao.module.ai.framework.ai.core.AiModelFactory;
|
||||||
import cn.iocoder.yudao.module.ai.framework.ai.core.AiModelFactoryImpl;
|
import cn.iocoder.yudao.module.ai.framework.ai.core.AiModelFactoryImpl;
|
||||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.baichuan.BaiChuanChatModel;
|
import cn.iocoder.yudao.module.ai.framework.ai.core.model.baichuan.BaiChuanChatModel;
|
||||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.deepseek.DeepSeekChatModel;
|
|
||||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.doubao.DouBaoChatModel;
|
import cn.iocoder.yudao.module.ai.framework.ai.core.model.doubao.DouBaoChatModel;
|
||||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.hunyuan.HunYuanChatModel;
|
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.midjourney.api.MidjourneyApi;
|
||||||
@@ -14,10 +13,6 @@ import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlo
|
|||||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.suno.api.SunoApi;
|
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.xinghuo.XingHuoChatModel;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.ai.autoconfigure.vectorstore.milvus.MilvusServiceClientProperties;
|
|
||||||
import org.springframework.ai.autoconfigure.vectorstore.milvus.MilvusVectorStoreProperties;
|
|
||||||
import org.springframework.ai.autoconfigure.vectorstore.qdrant.QdrantVectorStoreProperties;
|
|
||||||
import org.springframework.ai.autoconfigure.vectorstore.redis.RedisVectorStoreProperties;
|
|
||||||
import org.springframework.ai.embedding.BatchingStrategy;
|
import org.springframework.ai.embedding.BatchingStrategy;
|
||||||
import org.springframework.ai.embedding.TokenCountBatchingStrategy;
|
import org.springframework.ai.embedding.TokenCountBatchingStrategy;
|
||||||
import org.springframework.ai.model.tool.ToolCallingManager;
|
import org.springframework.ai.model.tool.ToolCallingManager;
|
||||||
@@ -26,6 +21,10 @@ import org.springframework.ai.openai.OpenAiChatOptions;
|
|||||||
import org.springframework.ai.openai.api.OpenAiApi;
|
import org.springframework.ai.openai.api.OpenAiApi;
|
||||||
import org.springframework.ai.tokenizer.JTokkitTokenCountEstimator;
|
import org.springframework.ai.tokenizer.JTokkitTokenCountEstimator;
|
||||||
import org.springframework.ai.tokenizer.TokenCountEstimator;
|
import org.springframework.ai.tokenizer.TokenCountEstimator;
|
||||||
|
import org.springframework.ai.vectorstore.milvus.autoconfigure.MilvusServiceClientProperties;
|
||||||
|
import org.springframework.ai.vectorstore.milvus.autoconfigure.MilvusVectorStoreProperties;
|
||||||
|
import org.springframework.ai.vectorstore.qdrant.autoconfigure.QdrantVectorStoreProperties;
|
||||||
|
import org.springframework.ai.vectorstore.redis.autoconfigure.RedisVectorStoreProperties;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
@@ -52,33 +51,6 @@ public class AiAutoConfiguration {
|
|||||||
|
|
||||||
// ========== 各种 AI Client 创建 ==========
|
// ========== 各种 AI Client 创建 ==========
|
||||||
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnProperty(value = "yudao.ai.deepseek.enable", havingValue = "true")
|
|
||||||
public DeepSeekChatModel deepSeekChatModel(YudaoAiProperties yudaoAiProperties) {
|
|
||||||
YudaoAiProperties.DeepSeekProperties properties = yudaoAiProperties.getDeepseek();
|
|
||||||
return buildDeepSeekChatModel(properties);
|
|
||||||
}
|
|
||||||
|
|
||||||
public DeepSeekChatModel buildDeepSeekChatModel(YudaoAiProperties.DeepSeekProperties properties) {
|
|
||||||
if (StrUtil.isEmpty(properties.getModel())) {
|
|
||||||
properties.setModel(DeepSeekChatModel.MODEL_DEFAULT);
|
|
||||||
}
|
|
||||||
OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
|
|
||||||
.openAiApi(OpenAiApi.builder()
|
|
||||||
.baseUrl(DeepSeekChatModel.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 DeepSeekChatModel(openAiChatModel);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnProperty(value = "yudao.ai.doubao.enable", havingValue = "true")
|
@ConditionalOnProperty(value = "yudao.ai.doubao.enable", havingValue = "true")
|
||||||
public DouBaoChatModel douBaoChatClient(YudaoAiProperties yudaoAiProperties) {
|
public DouBaoChatModel douBaoChatClient(YudaoAiProperties yudaoAiProperties) {
|
||||||
|
@@ -13,12 +13,6 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
|
|||||||
@Data
|
@Data
|
||||||
public class YudaoAiProperties {
|
public class YudaoAiProperties {
|
||||||
|
|
||||||
/**
|
|
||||||
* DeepSeek
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("SpellCheckingInspection")
|
|
||||||
private DeepSeekProperties deepseek;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 字节豆包
|
* 字节豆包
|
||||||
*/
|
*/
|
||||||
@@ -60,19 +54,6 @@ public class YudaoAiProperties {
|
|||||||
@SuppressWarnings("SpellCheckingInspection")
|
@SuppressWarnings("SpellCheckingInspection")
|
||||||
private SunoProperties suno;
|
private SunoProperties suno;
|
||||||
|
|
||||||
@Data
|
|
||||||
public static class DeepSeekProperties {
|
|
||||||
|
|
||||||
private String enable;
|
|
||||||
private String apiKey;
|
|
||||||
|
|
||||||
private String model;
|
|
||||||
private Double temperature;
|
|
||||||
private Integer maxTokens;
|
|
||||||
private Double topP;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public static class DouBaoProperties {
|
public static class DouBaoProperties {
|
||||||
|
|
||||||
|
@@ -8,11 +8,11 @@ import cn.hutool.core.util.ArrayUtil;
|
|||||||
import cn.hutool.core.util.RuntimeUtil;
|
import cn.hutool.core.util.RuntimeUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import cn.hutool.extra.spring.SpringUtil;
|
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.AiAutoConfiguration;
|
||||||
import cn.iocoder.yudao.module.ai.framework.ai.config.YudaoAiProperties;
|
import cn.iocoder.yudao.module.ai.framework.ai.config.YudaoAiProperties;
|
||||||
import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum;
|
|
||||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.baichuan.BaiChuanChatModel;
|
import cn.iocoder.yudao.module.ai.framework.ai.core.model.baichuan.BaiChuanChatModel;
|
||||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.deepseek.DeepSeekChatModel;
|
|
||||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.doubao.DouBaoChatModel;
|
import cn.iocoder.yudao.module.ai.framework.ai.core.model.doubao.DouBaoChatModel;
|
||||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.hunyuan.HunYuanChatModel;
|
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.midjourney.api.MidjourneyApi;
|
||||||
@@ -22,8 +22,9 @@ import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlo
|
|||||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowImageModel;
|
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.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.xinghuo.XingHuoChatModel;
|
||||||
import cn.iocoder.yudao.framework.common.util.spring.SpringUtils;
|
import com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeChatAutoConfiguration;
|
||||||
import com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeAutoConfiguration;
|
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.DashScopeApi;
|
||||||
import com.alibaba.cloud.ai.dashscope.api.DashScopeImageApi;
|
import com.alibaba.cloud.ai.dashscope.api.DashScopeImageApi;
|
||||||
import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel;
|
import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel;
|
||||||
@@ -32,47 +33,55 @@ import com.alibaba.cloud.ai.dashscope.embedding.DashScopeEmbeddingModel;
|
|||||||
import com.alibaba.cloud.ai.dashscope.embedding.DashScopeEmbeddingOptions;
|
import com.alibaba.cloud.ai.dashscope.embedding.DashScopeEmbeddingOptions;
|
||||||
import com.alibaba.cloud.ai.dashscope.image.DashScopeImageModel;
|
import com.alibaba.cloud.ai.dashscope.image.DashScopeImageModel;
|
||||||
import com.azure.ai.openai.OpenAIClientBuilder;
|
import com.azure.ai.openai.OpenAIClientBuilder;
|
||||||
|
import com.azure.core.credential.KeyCredential;
|
||||||
import io.micrometer.observation.ObservationRegistry;
|
import io.micrometer.observation.ObservationRegistry;
|
||||||
import io.milvus.client.MilvusServiceClient;
|
import io.milvus.client.MilvusServiceClient;
|
||||||
import io.qdrant.client.QdrantClient;
|
import io.qdrant.client.QdrantClient;
|
||||||
import io.qdrant.client.QdrantGrpcClient;
|
import io.qdrant.client.QdrantGrpcClient;
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
import org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiAutoConfiguration;
|
import org.springaicommunity.moonshot.MoonshotChatModel;
|
||||||
import org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiChatProperties;
|
import org.springaicommunity.moonshot.MoonshotChatOptions;
|
||||||
import org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiConnectionProperties;
|
import org.springaicommunity.moonshot.api.MoonshotApi;
|
||||||
import org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiEmbeddingProperties;
|
import org.springaicommunity.moonshot.autoconfigure.MoonshotChatAutoConfiguration;
|
||||||
import org.springframework.ai.autoconfigure.minimax.MiniMaxAutoConfiguration;
|
import org.springaicommunity.qianfan.QianFanChatModel;
|
||||||
import org.springframework.ai.autoconfigure.moonshot.MoonshotAutoConfiguration;
|
import org.springaicommunity.qianfan.QianFanEmbeddingModel;
|
||||||
import org.springframework.ai.autoconfigure.ollama.OllamaAutoConfiguration;
|
import org.springaicommunity.qianfan.QianFanEmbeddingOptions;
|
||||||
import org.springframework.ai.autoconfigure.openai.OpenAiAutoConfiguration;
|
import org.springaicommunity.qianfan.QianFanImageModel;
|
||||||
import org.springframework.ai.autoconfigure.qianfan.QianFanAutoConfiguration;
|
import org.springaicommunity.qianfan.api.QianFanApi;
|
||||||
import org.springframework.ai.autoconfigure.stabilityai.StabilityAiImageAutoConfiguration;
|
import org.springaicommunity.qianfan.api.QianFanImageApi;
|
||||||
import org.springframework.ai.autoconfigure.vectorstore.milvus.MilvusServiceClientConnectionDetails;
|
import org.springaicommunity.qianfan.autoconfigure.QianFanChatAutoConfiguration;
|
||||||
import org.springframework.ai.autoconfigure.vectorstore.milvus.MilvusServiceClientProperties;
|
import org.springaicommunity.qianfan.autoconfigure.QianFanEmbeddingAutoConfiguration;
|
||||||
import org.springframework.ai.autoconfigure.vectorstore.milvus.MilvusVectorStoreAutoConfiguration;
|
|
||||||
import org.springframework.ai.autoconfigure.vectorstore.milvus.MilvusVectorStoreProperties;
|
|
||||||
import org.springframework.ai.autoconfigure.vectorstore.qdrant.QdrantVectorStoreAutoConfiguration;
|
|
||||||
import org.springframework.ai.autoconfigure.vectorstore.qdrant.QdrantVectorStoreProperties;
|
|
||||||
import org.springframework.ai.autoconfigure.vectorstore.redis.RedisVectorStoreAutoConfiguration;
|
|
||||||
import org.springframework.ai.autoconfigure.vectorstore.redis.RedisVectorStoreProperties;
|
|
||||||
import org.springframework.ai.autoconfigure.zhipuai.ZhiPuAiAutoConfiguration;
|
|
||||||
import org.springframework.ai.azure.openai.AzureOpenAiChatModel;
|
import org.springframework.ai.azure.openai.AzureOpenAiChatModel;
|
||||||
import org.springframework.ai.azure.openai.AzureOpenAiEmbeddingModel;
|
import org.springframework.ai.azure.openai.AzureOpenAiEmbeddingModel;
|
||||||
import org.springframework.ai.chat.model.ChatModel;
|
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.document.MetadataMode;
|
||||||
import org.springframework.ai.embedding.BatchingStrategy;
|
import org.springframework.ai.embedding.BatchingStrategy;
|
||||||
import org.springframework.ai.embedding.EmbeddingModel;
|
import org.springframework.ai.embedding.EmbeddingModel;
|
||||||
|
import org.springframework.ai.embedding.observation.EmbeddingModelObservationConvention;
|
||||||
import org.springframework.ai.image.ImageModel;
|
import org.springframework.ai.image.ImageModel;
|
||||||
import org.springframework.ai.minimax.MiniMaxChatModel;
|
import org.springframework.ai.minimax.MiniMaxChatModel;
|
||||||
import org.springframework.ai.minimax.MiniMaxChatOptions;
|
import org.springframework.ai.minimax.MiniMaxChatOptions;
|
||||||
import org.springframework.ai.minimax.MiniMaxEmbeddingModel;
|
import org.springframework.ai.minimax.MiniMaxEmbeddingModel;
|
||||||
import org.springframework.ai.minimax.MiniMaxEmbeddingOptions;
|
import org.springframework.ai.minimax.MiniMaxEmbeddingOptions;
|
||||||
import org.springframework.ai.minimax.api.MiniMaxApi;
|
import org.springframework.ai.minimax.api.MiniMaxApi;
|
||||||
import org.springframework.ai.model.function.FunctionCallbackResolver;
|
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.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.tool.ToolCallingManager;
|
||||||
import org.springframework.ai.moonshot.MoonshotChatModel;
|
import org.springframework.ai.model.zhipuai.autoconfigure.ZhiPuAiChatAutoConfiguration;
|
||||||
import org.springframework.ai.moonshot.MoonshotChatOptions;
|
import org.springframework.ai.model.zhipuai.autoconfigure.ZhiPuAiEmbeddingAutoConfiguration;
|
||||||
import org.springframework.ai.moonshot.api.MoonshotApi;
|
import org.springframework.ai.model.zhipuai.autoconfigure.ZhiPuAiImageAutoConfiguration;
|
||||||
import org.springframework.ai.ollama.OllamaChatModel;
|
import org.springframework.ai.ollama.OllamaChatModel;
|
||||||
import org.springframework.ai.ollama.OllamaEmbeddingModel;
|
import org.springframework.ai.ollama.OllamaEmbeddingModel;
|
||||||
import org.springframework.ai.ollama.api.OllamaApi;
|
import org.springframework.ai.ollama.api.OllamaApi;
|
||||||
@@ -84,21 +93,23 @@ import org.springframework.ai.openai.OpenAiImageModel;
|
|||||||
import org.springframework.ai.openai.api.OpenAiApi;
|
import org.springframework.ai.openai.api.OpenAiApi;
|
||||||
import org.springframework.ai.openai.api.OpenAiImageApi;
|
import org.springframework.ai.openai.api.OpenAiImageApi;
|
||||||
import org.springframework.ai.openai.api.common.OpenAiApiConstants;
|
import org.springframework.ai.openai.api.common.OpenAiApiConstants;
|
||||||
import org.springframework.ai.qianfan.QianFanChatModel;
|
|
||||||
import org.springframework.ai.qianfan.QianFanEmbeddingModel;
|
|
||||||
import org.springframework.ai.qianfan.QianFanEmbeddingOptions;
|
|
||||||
import org.springframework.ai.qianfan.QianFanImageModel;
|
|
||||||
import org.springframework.ai.qianfan.api.QianFanApi;
|
|
||||||
import org.springframework.ai.qianfan.api.QianFanImageApi;
|
|
||||||
import org.springframework.ai.stabilityai.StabilityAiImageModel;
|
import org.springframework.ai.stabilityai.StabilityAiImageModel;
|
||||||
import org.springframework.ai.stabilityai.api.StabilityAiApi;
|
import org.springframework.ai.stabilityai.api.StabilityAiApi;
|
||||||
import org.springframework.ai.vectorstore.SimpleVectorStore;
|
import org.springframework.ai.vectorstore.SimpleVectorStore;
|
||||||
import org.springframework.ai.vectorstore.VectorStore;
|
import org.springframework.ai.vectorstore.VectorStore;
|
||||||
import org.springframework.ai.vectorstore.milvus.MilvusVectorStore;
|
import org.springframework.ai.vectorstore.milvus.MilvusVectorStore;
|
||||||
|
import org.springframework.ai.vectorstore.milvus.autoconfigure.MilvusServiceClientConnectionDetails;
|
||||||
|
import org.springframework.ai.vectorstore.milvus.autoconfigure.MilvusServiceClientProperties;
|
||||||
|
import org.springframework.ai.vectorstore.milvus.autoconfigure.MilvusVectorStoreAutoConfiguration;
|
||||||
|
import org.springframework.ai.vectorstore.milvus.autoconfigure.MilvusVectorStoreProperties;
|
||||||
import org.springframework.ai.vectorstore.observation.DefaultVectorStoreObservationConvention;
|
import org.springframework.ai.vectorstore.observation.DefaultVectorStoreObservationConvention;
|
||||||
import org.springframework.ai.vectorstore.observation.VectorStoreObservationConvention;
|
import org.springframework.ai.vectorstore.observation.VectorStoreObservationConvention;
|
||||||
import org.springframework.ai.vectorstore.qdrant.QdrantVectorStore;
|
import org.springframework.ai.vectorstore.qdrant.QdrantVectorStore;
|
||||||
|
import org.springframework.ai.vectorstore.qdrant.autoconfigure.QdrantVectorStoreAutoConfiguration;
|
||||||
|
import org.springframework.ai.vectorstore.qdrant.autoconfigure.QdrantVectorStoreProperties;
|
||||||
import org.springframework.ai.vectorstore.redis.RedisVectorStore;
|
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.*;
|
||||||
import org.springframework.ai.zhipuai.api.ZhiPuAiApi;
|
import org.springframework.ai.zhipuai.api.ZhiPuAiApi;
|
||||||
import org.springframework.ai.zhipuai.api.ZhiPuAiImageApi;
|
import org.springframework.ai.zhipuai.api.ZhiPuAiImageApi;
|
||||||
@@ -190,7 +201,7 @@ public class AiModelFactoryImpl implements AiModelFactory {
|
|||||||
case XING_HUO:
|
case XING_HUO:
|
||||||
return SpringUtil.getBean(XingHuoChatModel.class);
|
return SpringUtil.getBean(XingHuoChatModel.class);
|
||||||
case BAI_CHUAN:
|
case BAI_CHUAN:
|
||||||
return SpringUtil.getBean(AzureOpenAiChatModel.class);
|
return SpringUtil.getBean(BaiChuanChatModel.class);
|
||||||
case OPENAI:
|
case OPENAI:
|
||||||
return SpringUtil.getBean(OpenAiChatModel.class);
|
return SpringUtil.getBean(OpenAiChatModel.class);
|
||||||
case AZURE_OPENAI:
|
case AZURE_OPENAI:
|
||||||
@@ -319,27 +330,34 @@ public class AiModelFactoryImpl implements AiModelFactory {
|
|||||||
// ========== 各种创建 spring-ai 客户端的方法 ==========
|
// ========== 各种创建 spring-ai 客户端的方法 ==========
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 可参考 {@link DashScopeAutoConfiguration} 的 dashscopeChatModel 方法
|
* 可参考 {@link DashScopeChatAutoConfiguration} 的 dashscopeChatModel 方法
|
||||||
*/
|
*/
|
||||||
private static DashScopeChatModel buildTongYiChatModel(String key) {
|
private static DashScopeChatModel buildTongYiChatModel(String key) {
|
||||||
DashScopeApi dashScopeApi = new DashScopeApi(key);
|
DashScopeApi dashScopeApi = DashScopeApi.builder().apiKey(key).build();
|
||||||
DashScopeChatOptions options = DashScopeChatOptions.builder().withModel(DashScopeApi.DEFAULT_CHAT_MODEL)
|
DashScopeChatOptions options = DashScopeChatOptions.builder().withModel(DashScopeApi.DEFAULT_CHAT_MODEL)
|
||||||
.withTemperature(0.7).build();
|
.withTemperature(0.7).build();
|
||||||
return new DashScopeChatModel(dashScopeApi, options, getFunctionCallbackResolver(), DEFAULT_RETRY_TEMPLATE);
|
return DashScopeChatModel.builder()
|
||||||
|
.dashScopeApi(dashScopeApi)
|
||||||
|
.defaultOptions(options)
|
||||||
|
.toolCallingManager(getToolCallingManager())
|
||||||
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 可参考 {@link DashScopeAutoConfiguration} 的 dashScopeImageModel 方法
|
* 可参考 {@link DashScopeImageAutoConfiguration} 的 dashScopeImageModel 方法
|
||||||
*/
|
*/
|
||||||
private static DashScopeImageModel buildTongYiImagesModel(String key) {
|
private static DashScopeImageModel buildTongYiImagesModel(String key) {
|
||||||
DashScopeImageApi dashScopeImageApi = new DashScopeImageApi(key);
|
DashScopeImageApi dashScopeImageApi = new DashScopeImageApi(key);
|
||||||
return new DashScopeImageModel(dashScopeImageApi);
|
return DashScopeImageModel.builder()
|
||||||
|
.dashScopeApi(dashScopeImageApi)
|
||||||
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 可参考 {@link QianFanAutoConfiguration} 的 qianFanChatModel 方法
|
* 可参考 {@link QianFanChatAutoConfiguration} 的 qianFanChatModel 方法
|
||||||
*/
|
*/
|
||||||
private static QianFanChatModel buildYiYanChatModel(String key) {
|
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, '|');
|
List<String> keys = StrUtil.split(key, '|');
|
||||||
Assert.equals(keys.size(), 2, "YiYanChatClient 的密钥需要 (appKey|secretKey) 格式");
|
Assert.equals(keys.size(), 2, "YiYanChatClient 的密钥需要 (appKey|secretKey) 格式");
|
||||||
String appKey = keys.get(0);
|
String appKey = keys.get(0);
|
||||||
@@ -349,9 +367,10 @@ public class AiModelFactoryImpl implements AiModelFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 可参考 {@link QianFanAutoConfiguration} 的 qianFanImageModel 方法
|
* 可参考 {@link QianFanEmbeddingAutoConfiguration} 的 qianFanImageModel 方法
|
||||||
*/
|
*/
|
||||||
private QianFanImageModel buildQianFanImageModel(String key) {
|
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, '|');
|
List<String> keys = StrUtil.split(key, '|');
|
||||||
Assert.equals(keys.size(), 2, "YiYanChatClient 的密钥需要 (appKey|secretKey) 格式");
|
Assert.equals(keys.size(), 2, "YiYanChatClient 的密钥需要 (appKey|secretKey) 格式");
|
||||||
String appKey = keys.get(0);
|
String appKey = keys.get(0);
|
||||||
@@ -361,12 +380,17 @@ public class AiModelFactoryImpl implements AiModelFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 可参考 {@link AiAutoConfiguration#deepSeekChatModel(YudaoAiProperties)}
|
* 可参考 {@link DeepSeekChatAutoConfiguration} 的 deepSeekChatModel 方法
|
||||||
*/
|
*/
|
||||||
private static DeepSeekChatModel buildDeepSeekChatModel(String apiKey) {
|
private static DeepSeekChatModel buildDeepSeekChatModel(String apiKey) {
|
||||||
YudaoAiProperties.DeepSeekProperties properties = new YudaoAiProperties.DeepSeekProperties()
|
DeepSeekApi deepSeekApi = DeepSeekApi.builder().apiKey(apiKey).build();
|
||||||
.setApiKey(apiKey);
|
DeepSeekChatOptions options = DeepSeekChatOptions.builder().model(DeepSeekApi.DEFAULT_CHAT_MODEL)
|
||||||
return new AiAutoConfiguration().buildDeepSeekChatModel(properties);
|
.temperature(0.7).build();
|
||||||
|
return DeepSeekChatModel.builder()
|
||||||
|
.deepSeekApi(deepSeekApi)
|
||||||
|
.defaultOptions(options)
|
||||||
|
.toolCallingManager(getToolCallingManager())
|
||||||
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -397,17 +421,18 @@ public class AiModelFactoryImpl implements AiModelFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 可参考 {@link ZhiPuAiAutoConfiguration} 的 zhiPuAiChatModel 方法
|
* 可参考 {@link ZhiPuAiChatAutoConfiguration} 的 zhiPuAiChatModel 方法
|
||||||
*/
|
*/
|
||||||
private ZhiPuAiChatModel buildZhiPuChatModel(String apiKey, String url) {
|
private ZhiPuAiChatModel buildZhiPuChatModel(String apiKey, String url) {
|
||||||
ZhiPuAiApi zhiPuAiApi = StrUtil.isEmpty(url) ? new ZhiPuAiApi(apiKey)
|
ZhiPuAiApi zhiPuAiApi = StrUtil.isEmpty(url) ? new ZhiPuAiApi(apiKey)
|
||||||
: new ZhiPuAiApi(url, apiKey);
|
: new ZhiPuAiApi(url, apiKey);
|
||||||
ZhiPuAiChatOptions options = ZhiPuAiChatOptions.builder().model(ZhiPuAiApi.DEFAULT_CHAT_MODEL).temperature(0.7).build();
|
ZhiPuAiChatOptions options = ZhiPuAiChatOptions.builder().model(ZhiPuAiApi.DEFAULT_CHAT_MODEL).temperature(0.7).build();
|
||||||
return new ZhiPuAiChatModel(zhiPuAiApi, options, getFunctionCallbackResolver(), DEFAULT_RETRY_TEMPLATE);
|
return new ZhiPuAiChatModel(zhiPuAiApi, options, getToolCallingManager(), DEFAULT_RETRY_TEMPLATE,
|
||||||
|
getObservationRegistry().getIfAvailable());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 可参考 {@link ZhiPuAiAutoConfiguration} 的 zhiPuAiImageModel 方法
|
* 可参考 {@link ZhiPuAiImageAutoConfiguration} 的 zhiPuAiImageModel 方法
|
||||||
*/
|
*/
|
||||||
private ZhiPuAiImageModel buildZhiPuAiImageModel(String apiKey, String url) {
|
private ZhiPuAiImageModel buildZhiPuAiImageModel(String apiKey, String url) {
|
||||||
ZhiPuAiImageApi zhiPuAiApi = StrUtil.isEmpty(url) ? new ZhiPuAiImageApi(apiKey)
|
ZhiPuAiImageApi zhiPuAiApi = StrUtil.isEmpty(url) ? new ZhiPuAiImageApi(apiKey)
|
||||||
@@ -416,23 +441,30 @@ public class AiModelFactoryImpl implements AiModelFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 可参考 {@link MiniMaxAutoConfiguration} 的 miniMaxChatModel 方法
|
* 可参考 {@link MiniMaxChatAutoConfiguration} 的 miniMaxChatModel 方法
|
||||||
*/
|
*/
|
||||||
private MiniMaxChatModel buildMiniMaxChatModel(String apiKey, String url) {
|
private MiniMaxChatModel buildMiniMaxChatModel(String apiKey, String url) {
|
||||||
MiniMaxApi miniMaxApi = StrUtil.isEmpty(url) ? new MiniMaxApi(apiKey)
|
MiniMaxApi miniMaxApi = StrUtil.isEmpty(url) ? new MiniMaxApi(apiKey)
|
||||||
: new MiniMaxApi(url, apiKey);
|
: new MiniMaxApi(url, apiKey);
|
||||||
MiniMaxChatOptions options = MiniMaxChatOptions.builder().model(MiniMaxApi.DEFAULT_CHAT_MODEL).temperature(0.7).build();
|
MiniMaxChatOptions options = MiniMaxChatOptions.builder().model(MiniMaxApi.DEFAULT_CHAT_MODEL).temperature(0.7).build();
|
||||||
return new MiniMaxChatModel(miniMaxApi, options, getFunctionCallbackResolver(), DEFAULT_RETRY_TEMPLATE);
|
return new MiniMaxChatModel(miniMaxApi, options, getToolCallingManager(), DEFAULT_RETRY_TEMPLATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 可参考 {@link MoonshotAutoConfiguration} 的 moonshotChatModel 方法
|
* 可参考 {@link MoonshotChatAutoConfiguration} 的 moonshotChatModel 方法
|
||||||
*/
|
*/
|
||||||
private MoonshotChatModel buildMoonshotChatModel(String apiKey, String url) {
|
private MoonshotChatModel buildMoonshotChatModel(String apiKey, String url) {
|
||||||
MoonshotApi moonshotApi = StrUtil.isEmpty(url)? new MoonshotApi(apiKey)
|
MoonshotApi.Builder moonshotApiBuilder = MoonshotApi.builder()
|
||||||
: new MoonshotApi(url, apiKey);
|
.apiKey(apiKey);
|
||||||
|
if (StrUtil.isNotEmpty(url)) {
|
||||||
|
moonshotApiBuilder.baseUrl(url);
|
||||||
|
}
|
||||||
MoonshotChatOptions options = MoonshotChatOptions.builder().model(MoonshotApi.DEFAULT_CHAT_MODEL).build();
|
MoonshotChatOptions options = MoonshotChatOptions.builder().model(MoonshotApi.DEFAULT_CHAT_MODEL).build();
|
||||||
return new MoonshotChatModel(moonshotApi, options, getFunctionCallbackResolver(), DEFAULT_RETRY_TEMPLATE);
|
return MoonshotChatModel.builder()
|
||||||
|
.moonshotApi(moonshotApiBuilder.build())
|
||||||
|
.defaultOptions(options)
|
||||||
|
.toolCallingManager(getToolCallingManager())
|
||||||
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -456,33 +488,32 @@ public class AiModelFactoryImpl implements AiModelFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 可参考 {@link OpenAiAutoConfiguration} 的 openAiChatModel 方法
|
* 可参考 {@link OpenAiChatAutoConfiguration} 的 openAiChatModel 方法
|
||||||
*/
|
*/
|
||||||
private static OpenAiChatModel buildOpenAiChatModel(String openAiToken, String url) {
|
private static OpenAiChatModel buildOpenAiChatModel(String openAiToken, String url) {
|
||||||
url = StrUtil.blankToDefault(url, OpenAiApiConstants.DEFAULT_BASE_URL);
|
url = StrUtil.blankToDefault(url, OpenAiApiConstants.DEFAULT_BASE_URL);
|
||||||
OpenAiApi openAiApi = OpenAiApi.builder().baseUrl(url).apiKey(openAiToken).build();
|
OpenAiApi openAiApi = OpenAiApi.builder().baseUrl(url).apiKey(openAiToken).build();
|
||||||
return OpenAiChatModel.builder().openAiApi(openAiApi).toolCallingManager(getToolCallingManager()).build();
|
return OpenAiChatModel.builder()
|
||||||
|
.openAiApi(openAiApi)
|
||||||
|
.toolCallingManager(getToolCallingManager())
|
||||||
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO @芋艿:手头暂时没密钥,使用建议再测试下
|
|
||||||
/**
|
/**
|
||||||
* 可参考 {@link AzureOpenAiAutoConfiguration}
|
* 可参考 {@link AzureOpenAiChatAutoConfiguration}
|
||||||
*/
|
*/
|
||||||
private static AzureOpenAiChatModel buildAzureOpenAiChatModel(String apiKey, String url) {
|
private static AzureOpenAiChatModel buildAzureOpenAiChatModel(String apiKey, String url) {
|
||||||
AzureOpenAiAutoConfiguration azureOpenAiAutoConfiguration = new AzureOpenAiAutoConfiguration();
|
// TODO @芋艿:使用前,请测试,暂时没密钥!!!
|
||||||
// 创建 OpenAIClient 对象
|
OpenAIClientBuilder openAIClientBuilder = new OpenAIClientBuilder()
|
||||||
AzureOpenAiConnectionProperties connectionProperties = new AzureOpenAiConnectionProperties();
|
.endpoint(url).credential(new KeyCredential(apiKey));
|
||||||
connectionProperties.setApiKey(apiKey);
|
return AzureOpenAiChatModel.builder()
|
||||||
connectionProperties.setEndpoint(url);
|
.openAIClientBuilder(openAIClientBuilder)
|
||||||
OpenAIClientBuilder openAIClient = azureOpenAiAutoConfiguration.openAIClientBuilder(connectionProperties, null);
|
.toolCallingManager(getToolCallingManager())
|
||||||
// 获取 AzureOpenAiChatProperties 对象
|
.build();
|
||||||
AzureOpenAiChatProperties chatProperties = SpringUtil.getBean(AzureOpenAiChatProperties.class);
|
|
||||||
return azureOpenAiAutoConfiguration.azureOpenAiChatModel(openAIClient, chatProperties,
|
|
||||||
getToolCallingManager(), null, null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 可参考 {@link OpenAiAutoConfiguration} 的 openAiImageModel 方法
|
* 可参考 {@link OpenAiImageAutoConfiguration} 的 openAiImageModel 方法
|
||||||
*/
|
*/
|
||||||
private OpenAiImageModel buildOpenAiImageModel(String openAiToken, String url) {
|
private OpenAiImageModel buildOpenAiImageModel(String openAiToken, String url) {
|
||||||
url = StrUtil.blankToDefault(url, OpenAiApiConstants.DEFAULT_BASE_URL);
|
url = StrUtil.blankToDefault(url, OpenAiApiConstants.DEFAULT_BASE_URL);
|
||||||
@@ -500,11 +531,14 @@ public class AiModelFactoryImpl implements AiModelFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 可参考 {@link OllamaAutoConfiguration} 的 ollamaApi 方法
|
* 可参考 {@link OllamaChatAutoConfiguration} 的 ollamaChatModel 方法
|
||||||
*/
|
*/
|
||||||
private static OllamaChatModel buildOllamaChatModel(String url) {
|
private static OllamaChatModel buildOllamaChatModel(String url) {
|
||||||
OllamaApi ollamaApi = new OllamaApi(url);
|
OllamaApi ollamaApi = OllamaApi.builder().baseUrl(url).build();
|
||||||
return OllamaChatModel.builder().ollamaApi(ollamaApi).toolCallingManager(getToolCallingManager()).build();
|
return OllamaChatModel.builder()
|
||||||
|
.ollamaApi(ollamaApi)
|
||||||
|
.toolCallingManager(getToolCallingManager())
|
||||||
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -519,16 +553,16 @@ public class AiModelFactoryImpl implements AiModelFactory {
|
|||||||
// ========== 各种创建 EmbeddingModel 的方法 ==========
|
// ========== 各种创建 EmbeddingModel 的方法 ==========
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 可参考 {@link DashScopeAutoConfiguration} 的 dashscopeEmbeddingModel 方法
|
* 可参考 {@link DashScopeEmbeddingAutoConfiguration} 的 dashscopeEmbeddingModel 方法
|
||||||
*/
|
*/
|
||||||
private DashScopeEmbeddingModel buildTongYiEmbeddingModel(String apiKey, String model) {
|
private DashScopeEmbeddingModel buildTongYiEmbeddingModel(String apiKey, String model) {
|
||||||
DashScopeApi dashScopeApi = new DashScopeApi(apiKey);
|
DashScopeApi dashScopeApi = DashScopeApi.builder().apiKey(apiKey).build();
|
||||||
DashScopeEmbeddingOptions dashScopeEmbeddingOptions = DashScopeEmbeddingOptions.builder().withModel(model).build();
|
DashScopeEmbeddingOptions dashScopeEmbeddingOptions = DashScopeEmbeddingOptions.builder().withModel(model).build();
|
||||||
return new DashScopeEmbeddingModel(dashScopeApi, MetadataMode.EMBED, dashScopeEmbeddingOptions);
|
return new DashScopeEmbeddingModel(dashScopeApi, MetadataMode.EMBED, dashScopeEmbeddingOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 可参考 {@link ZhiPuAiAutoConfiguration} 的 zhiPuAiEmbeddingModel 方法
|
* 可参考 {@link ZhiPuAiEmbeddingAutoConfiguration} 的 zhiPuAiEmbeddingModel 方法
|
||||||
*/
|
*/
|
||||||
private ZhiPuAiEmbeddingModel buildZhiPuEmbeddingModel(String apiKey, String url, String model) {
|
private ZhiPuAiEmbeddingModel buildZhiPuEmbeddingModel(String apiKey, String url, String model) {
|
||||||
ZhiPuAiApi zhiPuAiApi = StrUtil.isEmpty(url) ? new ZhiPuAiApi(apiKey)
|
ZhiPuAiApi zhiPuAiApi = StrUtil.isEmpty(url) ? new ZhiPuAiApi(apiKey)
|
||||||
@@ -538,7 +572,7 @@ public class AiModelFactoryImpl implements AiModelFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 可参考 {@link MiniMaxAutoConfiguration} 的 miniMaxEmbeddingModel 方法
|
* 可参考 {@link MiniMaxEmbeddingAutoConfiguration} 的 miniMaxEmbeddingModel 方法
|
||||||
*/
|
*/
|
||||||
private EmbeddingModel buildMiniMaxEmbeddingModel(String apiKey, String url, String model) {
|
private EmbeddingModel buildMiniMaxEmbeddingModel(String apiKey, String url, String model) {
|
||||||
MiniMaxApi miniMaxApi = StrUtil.isEmpty(url)? new MiniMaxApi(apiKey)
|
MiniMaxApi miniMaxApi = StrUtil.isEmpty(url)? new MiniMaxApi(apiKey)
|
||||||
@@ -548,7 +582,7 @@ public class AiModelFactoryImpl implements AiModelFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 可参考 {@link QianFanAutoConfiguration} 的 qianFanEmbeddingModel 方法
|
* 可参考 {@link QianFanEmbeddingAutoConfiguration} 的 qianFanEmbeddingModel 方法
|
||||||
*/
|
*/
|
||||||
private QianFanEmbeddingModel buildYiYanEmbeddingModel(String key, String model) {
|
private QianFanEmbeddingModel buildYiYanEmbeddingModel(String key, String model) {
|
||||||
List<String> keys = StrUtil.split(key, '|');
|
List<String> keys = StrUtil.split(key, '|');
|
||||||
@@ -561,13 +595,16 @@ public class AiModelFactoryImpl implements AiModelFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private OllamaEmbeddingModel buildOllamaEmbeddingModel(String url, String model) {
|
private OllamaEmbeddingModel buildOllamaEmbeddingModel(String url, String model) {
|
||||||
OllamaApi ollamaApi = new OllamaApi(url);
|
OllamaApi ollamaApi = OllamaApi.builder().baseUrl(url).build();
|
||||||
OllamaOptions ollamaOptions = OllamaOptions.builder().model(model).build();
|
OllamaOptions ollamaOptions = OllamaOptions.builder().model(model).build();
|
||||||
return OllamaEmbeddingModel.builder().ollamaApi(ollamaApi).defaultOptions(ollamaOptions).build();
|
return OllamaEmbeddingModel.builder()
|
||||||
|
.ollamaApi(ollamaApi)
|
||||||
|
.defaultOptions(ollamaOptions)
|
||||||
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 可参考 {@link OpenAiAutoConfiguration} 的 openAiEmbeddingModel 方法
|
* 可参考 {@link OpenAiEmbeddingAutoConfiguration} 的 openAiEmbeddingModel 方法
|
||||||
*/
|
*/
|
||||||
private OpenAiEmbeddingModel buildOpenAiEmbeddingModel(String openAiToken, String url, String model) {
|
private OpenAiEmbeddingModel buildOpenAiEmbeddingModel(String openAiToken, String url, String model) {
|
||||||
url = StrUtil.blankToDefault(url, OpenAiApiConstants.DEFAULT_BASE_URL);
|
url = StrUtil.blankToDefault(url, OpenAiApiConstants.DEFAULT_BASE_URL);
|
||||||
@@ -576,21 +613,19 @@ public class AiModelFactoryImpl implements AiModelFactory {
|
|||||||
return new OpenAiEmbeddingModel(openAiApi, MetadataMode.EMBED, openAiEmbeddingProperties);
|
return new OpenAiEmbeddingModel(openAiApi, MetadataMode.EMBED, openAiEmbeddingProperties);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO @芋艿:手头暂时没密钥,使用建议再测试下
|
|
||||||
/**
|
/**
|
||||||
* 可参考 {@link AzureOpenAiAutoConfiguration} 的 azureOpenAiEmbeddingModel 方法
|
* 可参考 {@link AzureOpenAiEmbeddingAutoConfiguration} 的 azureOpenAiEmbeddingModel 方法
|
||||||
*/
|
*/
|
||||||
private AzureOpenAiEmbeddingModel buildAzureOpenAiEmbeddingModel(String apiKey, String url, String model) {
|
private AzureOpenAiEmbeddingModel buildAzureOpenAiEmbeddingModel(String apiKey, String url, String model) {
|
||||||
AzureOpenAiAutoConfiguration azureOpenAiAutoConfiguration = new AzureOpenAiAutoConfiguration();
|
// TODO @芋艿:手头暂时没密钥,使用建议再测试下
|
||||||
// 创建 OpenAIClient 对象
|
AzureOpenAiEmbeddingAutoConfiguration azureOpenAiAutoConfiguration = new AzureOpenAiEmbeddingAutoConfiguration();
|
||||||
AzureOpenAiConnectionProperties connectionProperties = new AzureOpenAiConnectionProperties();
|
// 创建 OpenAIClientBuilder 对象
|
||||||
connectionProperties.setApiKey(apiKey);
|
OpenAIClientBuilder openAIClientBuilder = new OpenAIClientBuilder()
|
||||||
connectionProperties.setEndpoint(url);
|
.endpoint(url).credential(new KeyCredential(apiKey));
|
||||||
OpenAIClientBuilder openAIClient = azureOpenAiAutoConfiguration.openAIClientBuilder(connectionProperties, null);
|
|
||||||
// 获取 AzureOpenAiChatProperties 对象
|
// 获取 AzureOpenAiChatProperties 对象
|
||||||
AzureOpenAiEmbeddingProperties embeddingProperties = SpringUtil.getBean(AzureOpenAiEmbeddingProperties.class);
|
AzureOpenAiEmbeddingProperties embeddingProperties = SpringUtil.getBean(AzureOpenAiEmbeddingProperties.class);
|
||||||
return azureOpenAiAutoConfiguration.azureOpenAiEmbeddingModel(openAIClient, embeddingProperties,
|
return azureOpenAiAutoConfiguration.azureOpenAiEmbeddingModel(openAIClientBuilder, embeddingProperties,
|
||||||
null, null);
|
getObservationRegistry(), getEmbeddingModelObservationConvention());
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== 各种创建 VectorStore 的方法 ==========
|
// ========== 各种创建 VectorStore 的方法 ==========
|
||||||
@@ -655,12 +690,12 @@ public class AiModelFactoryImpl implements AiModelFactory {
|
|||||||
Map<String, Class<?>> metadataFields) {
|
Map<String, Class<?>> metadataFields) {
|
||||||
// 创建 JedisPooled 对象
|
// 创建 JedisPooled 对象
|
||||||
RedisProperties redisProperties = SpringUtils.getBean(RedisProperties.class);
|
RedisProperties redisProperties = SpringUtils.getBean(RedisProperties.class);
|
||||||
JedisPooled jedisPooled = new JedisPooled(redisProperties.getHost(), redisProperties.getPort());
|
JedisPooled jedisPooled = new JedisPooled(redisProperties.getHost(), redisProperties.getPort(),
|
||||||
|
redisProperties.getUsername(), redisProperties.getPassword());
|
||||||
// 创建 RedisVectorStoreProperties 对象
|
// 创建 RedisVectorStoreProperties 对象
|
||||||
RedisVectorStoreAutoConfiguration configuration = new RedisVectorStoreAutoConfiguration();
|
|
||||||
RedisVectorStoreProperties properties = SpringUtil.getBean(RedisVectorStoreProperties.class);
|
RedisVectorStoreProperties properties = SpringUtil.getBean(RedisVectorStoreProperties.class);
|
||||||
RedisVectorStore redisVectorStore = RedisVectorStore.builder(jedisPooled, embeddingModel)
|
RedisVectorStore redisVectorStore = RedisVectorStore.builder(jedisPooled, embeddingModel)
|
||||||
.indexName(properties.getIndex()).prefix(properties.getPrefix())
|
.indexName(properties.getIndexName()).prefix(properties.getPrefix())
|
||||||
.initializeSchema(properties.isInitializeSchema())
|
.initializeSchema(properties.isInitializeSchema())
|
||||||
.metadataFields(convertList(metadataFields.entrySet(), entry -> {
|
.metadataFields(convertList(metadataFields.entrySet(), entry -> {
|
||||||
String fieldName = entry.getKey();
|
String fieldName = entry.getKey();
|
||||||
@@ -730,10 +765,12 @@ public class AiModelFactoryImpl implements AiModelFactory {
|
|||||||
|
|
||||||
private static ObjectProvider<VectorStoreObservationConvention> getCustomObservationConvention() {
|
private static ObjectProvider<VectorStoreObservationConvention> getCustomObservationConvention() {
|
||||||
return new ObjectProvider<>() {
|
return new ObjectProvider<>() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public VectorStoreObservationConvention getObject() throws BeansException {
|
public VectorStoreObservationConvention getObject() throws BeansException {
|
||||||
return new DefaultVectorStoreObservationConvention();
|
return new DefaultVectorStoreObservationConvention();
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -745,8 +782,15 @@ public class AiModelFactoryImpl implements AiModelFactory {
|
|||||||
return SpringUtil.getBean(ToolCallingManager.class);
|
return SpringUtil.getBean(ToolCallingManager.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static FunctionCallbackResolver getFunctionCallbackResolver() {
|
private static ObjectProvider<EmbeddingModelObservationConvention> getEmbeddingModelObservationConvention() {
|
||||||
return SpringUtil.getBean(FunctionCallbackResolver.class);
|
return new ObjectProvider<>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EmbeddingModelObservationConvention getObject() throws BeansException {
|
||||||
|
return SpringUtil.getBean(EmbeddingModelObservationConvention.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,45 +0,0 @@
|
|||||||
package cn.iocoder.yudao.module.ai.framework.ai.core.model.deepseek;
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* DeepSeek {@link ChatModel} 实现类
|
|
||||||
*
|
|
||||||
* @author fansili
|
|
||||||
*/
|
|
||||||
@Slf4j
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class DeepSeekChatModel implements ChatModel {
|
|
||||||
|
|
||||||
public static final String BASE_URL = "https://api.deepseek.com";
|
|
||||||
|
|
||||||
public static final String MODEL_DEFAULT = "deepseek-chat";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 兼容 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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -89,7 +89,7 @@ public class SiliconFlowImageModel implements ImageModel {
|
|||||||
var observationContext = ImageModelObservationContext.builder()
|
var observationContext = ImageModelObservationContext.builder()
|
||||||
.imagePrompt(imagePrompt)
|
.imagePrompt(imagePrompt)
|
||||||
.provider(SiliconFlowApiConstants.PROVIDER_NAME)
|
.provider(SiliconFlowApiConstants.PROVIDER_NAME)
|
||||||
.requestOptions(imagePrompt.getOptions())
|
.imagePrompt(imagePrompt)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
return ImageModelObservationDocumentation.IMAGE_MODEL_OPERATION
|
return ImageModelObservationDocumentation.IMAGE_MODEL_OPERATION
|
||||||
|
@@ -9,9 +9,6 @@ import cn.hutool.core.util.ObjUtil;
|
|||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import cn.hutool.extra.spring.SpringUtil;
|
import cn.hutool.extra.spring.SpringUtil;
|
||||||
import cn.hutool.http.HttpUtil;
|
import cn.hutool.http.HttpUtil;
|
||||||
import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum;
|
|
||||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.midjourney.api.MidjourneyApi;
|
|
||||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowImageOptions;
|
|
||||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||||
import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageDrawReqVO;
|
import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageDrawReqVO;
|
||||||
@@ -24,17 +21,20 @@ import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO;
|
|||||||
import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
|
import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
|
||||||
import cn.iocoder.yudao.module.ai.dal.mysql.image.AiImageMapper;
|
import cn.iocoder.yudao.module.ai.dal.mysql.image.AiImageMapper;
|
||||||
import cn.iocoder.yudao.module.ai.enums.image.AiImageStatusEnum;
|
import cn.iocoder.yudao.module.ai.enums.image.AiImageStatusEnum;
|
||||||
|
import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum;
|
||||||
|
import cn.iocoder.yudao.module.ai.framework.ai.core.model.midjourney.api.MidjourneyApi;
|
||||||
|
import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowImageOptions;
|
||||||
import cn.iocoder.yudao.module.ai.service.model.AiModelService;
|
import cn.iocoder.yudao.module.ai.service.model.AiModelService;
|
||||||
import cn.iocoder.yudao.module.infra.api.file.FileApi;
|
import cn.iocoder.yudao.module.infra.api.file.FileApi;
|
||||||
import com.alibaba.cloud.ai.dashscope.image.DashScopeImageOptions;
|
import com.alibaba.cloud.ai.dashscope.image.DashScopeImageOptions;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springaicommunity.qianfan.QianFanImageOptions;
|
||||||
import org.springframework.ai.image.ImageModel;
|
import org.springframework.ai.image.ImageModel;
|
||||||
import org.springframework.ai.image.ImageOptions;
|
import org.springframework.ai.image.ImageOptions;
|
||||||
import org.springframework.ai.image.ImagePrompt;
|
import org.springframework.ai.image.ImagePrompt;
|
||||||
import org.springframework.ai.image.ImageResponse;
|
import org.springframework.ai.image.ImageResponse;
|
||||||
import org.springframework.ai.openai.OpenAiImageOptions;
|
import org.springframework.ai.openai.OpenAiImageOptions;
|
||||||
import org.springframework.ai.qianfan.QianFanImageOptions;
|
|
||||||
import org.springframework.ai.stabilityai.api.StabilityAiImageOptions;
|
import org.springframework.ai.stabilityai.api.StabilityAiImageOptions;
|
||||||
import org.springframework.ai.zhipuai.ZhiPuAiImageOptions;
|
import org.springframework.ai.zhipuai.ZhiPuAiImageOptions;
|
||||||
import org.springframework.scheduling.annotation.Async;
|
import org.springframework.scheduling.annotation.Async;
|
||||||
@@ -140,10 +140,10 @@ public class AiImageServiceImpl implements AiImageService {
|
|||||||
private static ImageOptions buildImageOptions(AiImageDrawReqVO draw, AiModelDO model) {
|
private static ImageOptions buildImageOptions(AiImageDrawReqVO draw, AiModelDO model) {
|
||||||
if (ObjUtil.equal(model.getPlatform(), AiPlatformEnum.OPENAI.getPlatform())) {
|
if (ObjUtil.equal(model.getPlatform(), AiPlatformEnum.OPENAI.getPlatform())) {
|
||||||
// https://platform.openai.com/docs/api-reference/images/create
|
// https://platform.openai.com/docs/api-reference/images/create
|
||||||
return OpenAiImageOptions.builder().withModel(model.getModel())
|
return OpenAiImageOptions.builder().model(model.getModel())
|
||||||
.withHeight(draw.getHeight()).withWidth(draw.getWidth())
|
.height(draw.getHeight()).width(draw.getWidth())
|
||||||
.withStyle(MapUtil.getStr(draw.getOptions(), "style")) // 风格
|
.style(MapUtil.getStr(draw.getOptions(), "style")) // 风格
|
||||||
.withResponseFormat("b64_json")
|
.responseFormat("b64_json")
|
||||||
.build();
|
.build();
|
||||||
} else if (ObjUtil.equal(model.getPlatform(), AiPlatformEnum.SILICON_FLOW.getPlatform())) {
|
} else if (ObjUtil.equal(model.getPlatform(), AiPlatformEnum.SILICON_FLOW.getPlatform())) {
|
||||||
// https://docs.siliconflow.cn/cn/api-reference/images/images-generations
|
// https://docs.siliconflow.cn/cn/api-reference/images/images-generations
|
||||||
|
@@ -7,6 +7,8 @@ import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
|
|||||||
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
|
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
|
||||||
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
|
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
|
||||||
import com.fasterxml.jackson.annotation.JsonClassDescription;
|
import com.fasterxml.jackson.annotation.JsonClassDescription;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
@@ -17,7 +19,7 @@ import org.springframework.stereotype.Component;
|
|||||||
import java.util.function.BiFunction;
|
import java.util.function.BiFunction;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 工具:当前用户信息查询
|
* 工具:用户信息查询
|
||||||
*
|
*
|
||||||
* 同时,也是展示 ToolContext 上下文的使用
|
* 同时,也是展示 ToolContext 上下文的使用
|
||||||
*
|
*
|
||||||
@@ -31,8 +33,17 @@ public class UserProfileQueryToolFunction
|
|||||||
private AdminUserApi adminUserApi;
|
private AdminUserApi adminUserApi;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@JsonClassDescription("当前用户信息查询")
|
@JsonClassDescription("用户信息查询")
|
||||||
public static class Request { }
|
public static class Request {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户编号
|
||||||
|
*/
|
||||||
|
@JsonProperty(value = "id")
|
||||||
|
@JsonPropertyDescription("用户编号,例如说:1。如果查询自己,则 id 为空")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@@ -61,13 +72,19 @@ public class UserProfileQueryToolFunction
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Response apply(Request request, ToolContext toolContext) {
|
public Response apply(Request request, ToolContext toolContext) {
|
||||||
LoginUser loginUser = (LoginUser) toolContext.getContext().get(AiUtils.TOOL_CONTEXT_LOGIN_USER);
|
|
||||||
Long tenantId = (Long) toolContext.getContext().get(AiUtils.TOOL_CONTEXT_TENANT_ID);
|
Long tenantId = (Long) toolContext.getContext().get(AiUtils.TOOL_CONTEXT_TENANT_ID);
|
||||||
if (loginUser == null | tenantId == null) {
|
if (tenantId == null) {
|
||||||
return null;
|
return new Response();
|
||||||
|
}
|
||||||
|
if (request.getId() == null) {
|
||||||
|
LoginUser loginUser = (LoginUser) toolContext.getContext().get(AiUtils.TOOL_CONTEXT_LOGIN_USER);
|
||||||
|
if (loginUser == null) {
|
||||||
|
return new Response();
|
||||||
|
}
|
||||||
|
request.setId(loginUser.getId());
|
||||||
}
|
}
|
||||||
return TenantUtils.execute(tenantId, () -> {
|
return TenantUtils.execute(tenantId, () -> {
|
||||||
AdminUserRespDTO user = adminUserApi.getUser(loginUser.getId());
|
AdminUserRespDTO user = adminUserApi.getUser(request.getId());
|
||||||
return BeanUtils.toBean(user, Response.class);
|
return BeanUtils.toBean(user, Response.class);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -2,18 +2,18 @@ package cn.iocoder.yudao.module.ai.util;
|
|||||||
|
|
||||||
import cn.hutool.core.util.ObjUtil;
|
import cn.hutool.core.util.ObjUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum;
|
|
||||||
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
|
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
|
||||||
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
|
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 com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions;
|
||||||
|
import org.springaicommunity.moonshot.MoonshotChatOptions;
|
||||||
|
import org.springaicommunity.qianfan.QianFanChatOptions;
|
||||||
import org.springframework.ai.azure.openai.AzureOpenAiChatOptions;
|
import org.springframework.ai.azure.openai.AzureOpenAiChatOptions;
|
||||||
import org.springframework.ai.chat.messages.*;
|
import org.springframework.ai.chat.messages.*;
|
||||||
import org.springframework.ai.chat.prompt.ChatOptions;
|
import org.springframework.ai.chat.prompt.ChatOptions;
|
||||||
import org.springframework.ai.minimax.MiniMaxChatOptions;
|
import org.springframework.ai.minimax.MiniMaxChatOptions;
|
||||||
import org.springframework.ai.moonshot.MoonshotChatOptions;
|
|
||||||
import org.springframework.ai.ollama.api.OllamaOptions;
|
import org.springframework.ai.ollama.api.OllamaOptions;
|
||||||
import org.springframework.ai.openai.OpenAiChatOptions;
|
import org.springframework.ai.openai.OpenAiChatOptions;
|
||||||
import org.springframework.ai.qianfan.QianFanChatOptions;
|
|
||||||
import org.springframework.ai.zhipuai.ZhiPuAiChatOptions;
|
import org.springframework.ai.zhipuai.ZhiPuAiChatOptions;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -43,18 +43,18 @@ public class AiUtils {
|
|||||||
switch (platform) {
|
switch (platform) {
|
||||||
case TONG_YI:
|
case TONG_YI:
|
||||||
return DashScopeChatOptions.builder().withModel(model).withTemperature(temperature).withMaxToken(maxTokens)
|
return DashScopeChatOptions.builder().withModel(model).withTemperature(temperature).withMaxToken(maxTokens)
|
||||||
.withFunctions(toolNames).withToolContext(toolContext).build();
|
.withToolNames(toolNames).withToolContext(toolContext).build();
|
||||||
case YI_YAN:
|
case YI_YAN:
|
||||||
return QianFanChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens).build();
|
return QianFanChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens).build();
|
||||||
case ZHI_PU:
|
case ZHI_PU:
|
||||||
return ZhiPuAiChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens)
|
return ZhiPuAiChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens)
|
||||||
.functions(toolNames).toolContext(toolContext).build();
|
.toolNames(toolNames).toolContext(toolContext).build();
|
||||||
case MINI_MAX:
|
case MINI_MAX:
|
||||||
return MiniMaxChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens)
|
return MiniMaxChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens)
|
||||||
.functions(toolNames).toolContext(toolContext).build();
|
.toolNames(toolNames).toolContext(toolContext).build();
|
||||||
case MOONSHOT:
|
case MOONSHOT:
|
||||||
return MoonshotChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens)
|
return MoonshotChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens)
|
||||||
.functions(toolNames).toolContext(toolContext).build();
|
.toolNames(toolNames).toolContext(toolContext).build();
|
||||||
case OPENAI:
|
case OPENAI:
|
||||||
case DEEP_SEEK: // 复用 OpenAI 客户端
|
case DEEP_SEEK: // 复用 OpenAI 客户端
|
||||||
case DOU_BAO: // 复用 OpenAI 客户端
|
case DOU_BAO: // 复用 OpenAI 客户端
|
||||||
|
@@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
|
|||||||
|
|
||||||
import com.azure.ai.openai.OpenAIClientBuilder;
|
import com.azure.ai.openai.OpenAIClientBuilder;
|
||||||
import com.azure.core.credential.AzureKeyCredential;
|
import com.azure.core.credential.AzureKeyCredential;
|
||||||
import com.azure.core.util.ClientOptions;
|
|
||||||
import org.junit.jupiter.api.Disabled;
|
import org.junit.jupiter.api.Disabled;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.ai.azure.openai.AzureOpenAiChatModel;
|
import org.springframework.ai.azure.openai.AzureOpenAiChatModel;
|
||||||
@@ -17,7 +16,7 @@ import reactor.core.publisher.Flux;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiChatProperties.DEFAULT_DEPLOYMENT_NAME;
|
import static org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiChatProperties.DEFAULT_DEPLOYMENT_NAME;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link AzureOpenAiChatModel} 集成测试
|
* {@link AzureOpenAiChatModel} 集成测试
|
||||||
@@ -29,10 +28,13 @@ public class AzureOpenAIChatModelTests {
|
|||||||
// TODO @芋艿:晚点在调整
|
// TODO @芋艿:晚点在调整
|
||||||
private final OpenAIClientBuilder openAiApi = new OpenAIClientBuilder()
|
private final OpenAIClientBuilder openAiApi = new OpenAIClientBuilder()
|
||||||
.endpoint("https://eastusprejade.openai.azure.com")
|
.endpoint("https://eastusprejade.openai.azure.com")
|
||||||
.credential(new AzureKeyCredential("xxx"))
|
.credential(new AzureKeyCredential("xxx"));
|
||||||
.clientOptions((new ClientOptions()).setApplicationId("spring-ai"));
|
private final AzureOpenAiChatModel chatModel = AzureOpenAiChatModel.builder()
|
||||||
private final AzureOpenAiChatModel chatModel = new AzureOpenAiChatModel(openAiApi,
|
.openAIClientBuilder(openAiApi)
|
||||||
AzureOpenAiChatOptions.builder().deploymentName(DEFAULT_DEPLOYMENT_NAME).build());
|
.defaultOptions(AzureOpenAiChatOptions.builder()
|
||||||
|
.deploymentName(DEFAULT_DEPLOYMENT_NAME)
|
||||||
|
.build())
|
||||||
|
.build();
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Disabled
|
@Disabled
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
|
package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
|
||||||
|
|
||||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.baichuan.BaiChuanChatModel;
|
import cn.iocoder.yudao.module.ai.framework.ai.core.model.baichuan.BaiChuanChatModel;
|
||||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.deepseek.DeepSeekChatModel;
|
|
||||||
import org.junit.jupiter.api.Disabled;
|
import org.junit.jupiter.api.Disabled;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.ai.chat.messages.Message;
|
import org.springframework.ai.chat.messages.Message;
|
||||||
@@ -35,7 +34,7 @@ public class BaiChuanChatModelTests {
|
|||||||
.build())
|
.build())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
private final DeepSeekChatModel chatModel = new DeepSeekChatModel(openAiChatModel);
|
private final BaiChuanChatModel chatModel = new BaiChuanChatModel(openAiChatModel);
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Disabled
|
@Disabled
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
|
package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
|
||||||
|
|
||||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.deepseek.DeepSeekChatModel;
|
|
||||||
import org.junit.jupiter.api.Disabled;
|
import org.junit.jupiter.api.Disabled;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.ai.chat.messages.Message;
|
import org.springframework.ai.chat.messages.Message;
|
||||||
@@ -8,9 +7,9 @@ import org.springframework.ai.chat.messages.SystemMessage;
|
|||||||
import org.springframework.ai.chat.messages.UserMessage;
|
import org.springframework.ai.chat.messages.UserMessage;
|
||||||
import org.springframework.ai.chat.model.ChatResponse;
|
import org.springframework.ai.chat.model.ChatResponse;
|
||||||
import org.springframework.ai.chat.prompt.Prompt;
|
import org.springframework.ai.chat.prompt.Prompt;
|
||||||
import org.springframework.ai.openai.OpenAiChatModel;
|
import org.springframework.ai.deepseek.DeepSeekChatModel;
|
||||||
import org.springframework.ai.openai.OpenAiChatOptions;
|
import org.springframework.ai.deepseek.DeepSeekChatOptions;
|
||||||
import org.springframework.ai.openai.api.OpenAiApi;
|
import org.springframework.ai.deepseek.api.DeepSeekApi;
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -23,19 +22,16 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public class DeepSeekChatModelTests {
|
public class DeepSeekChatModelTests {
|
||||||
|
|
||||||
private final OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
|
private final DeepSeekChatModel chatModel = DeepSeekChatModel.builder()
|
||||||
.openAiApi(OpenAiApi.builder()
|
.deepSeekApi(DeepSeekApi.builder()
|
||||||
.baseUrl(DeepSeekChatModel.BASE_URL)
|
.apiKey("sk-eaf4172a057344dd9bc64b1f806b6axx") // apiKey
|
||||||
.apiKey("sk-e52047409b144d97b791a6a46a2d") // apiKey
|
|
||||||
.build())
|
.build())
|
||||||
.defaultOptions(OpenAiChatOptions.builder()
|
.defaultOptions(DeepSeekChatOptions.builder()
|
||||||
.model("deepseek-chat") // 模型
|
.model("deepseek-chat") // 模型
|
||||||
.temperature(0.7)
|
.temperature(0.7)
|
||||||
.build())
|
.build())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
private final DeepSeekChatModel chatModel = new DeepSeekChatModel(openAiChatModel);
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Disabled
|
@Disabled
|
||||||
public void testCall() {
|
public void testCall() {
|
||||||
|
@@ -1,20 +1,6 @@
|
|||||||
package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
|
package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Disabled;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
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.ollama.OllamaChatModel;
|
import org.springframework.ai.ollama.OllamaChatModel;
|
||||||
import org.springframework.ai.ollama.api.OllamaApi;
|
|
||||||
import org.springframework.ai.ollama.api.OllamaModel;
|
|
||||||
import org.springframework.ai.ollama.api.OllamaOptions;
|
|
||||||
import reactor.core.publisher.Flux;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link OllamaChatModel} 集成测试
|
* {@link OllamaChatModel} 集成测试
|
||||||
@@ -23,43 +9,43 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public class LlamaChatModelTests {
|
public class LlamaChatModelTests {
|
||||||
|
|
||||||
private final OllamaChatModel chatModel = OllamaChatModel.builder()
|
// private final OllamaChatModel chatModel = OllamaChatModel.builder()
|
||||||
.ollamaApi(new OllamaApi("http://127.0.0.1:11434")) // Ollama 服务地址
|
// .ollamaApi(new OllamaApi("http://127.0.0.1:11434")) // Ollama 服务地址
|
||||||
.defaultOptions(OllamaOptions.builder()
|
// .defaultOptions(OllamaOptions.builder()
|
||||||
.model(OllamaModel.LLAMA3.getName()) // 模型
|
// .model(OllamaModel.LLAMA3.getName()) // 模型
|
||||||
.build())
|
// .build())
|
||||||
.build();
|
// .build();
|
||||||
|
//
|
||||||
@Test
|
// @Test
|
||||||
@Disabled
|
// @Disabled
|
||||||
public void testCall() {
|
// public void testCall() {
|
||||||
// 准备参数
|
// // 准备参数
|
||||||
List<Message> messages = new ArrayList<>();
|
// List<Message> messages = new ArrayList<>();
|
||||||
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
|
// messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
|
||||||
messages.add(new UserMessage("1 + 1 = ?"));
|
// messages.add(new UserMessage("1 + 1 = ?"));
|
||||||
|
//
|
||||||
// 调用
|
// // 调用
|
||||||
ChatResponse response = chatModel.call(new Prompt(messages));
|
// ChatResponse response = chatModel.call(new Prompt(messages));
|
||||||
// 打印结果
|
// // 打印结果
|
||||||
System.out.println(response);
|
// System.out.println(response);
|
||||||
System.out.println(response.getResult().getOutput());
|
// System.out.println(response.getResult().getOutput());
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
@Test
|
// @Test
|
||||||
@Disabled
|
// @Disabled
|
||||||
public void testStream() {
|
// public void testStream() {
|
||||||
// 准备参数
|
// // 准备参数
|
||||||
List<Message> messages = new ArrayList<>();
|
// List<Message> messages = new ArrayList<>();
|
||||||
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
|
// messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
|
||||||
messages.add(new UserMessage("1 + 1 = ?"));
|
// messages.add(new UserMessage("1 + 1 = ?"));
|
||||||
|
//
|
||||||
// 调用
|
// // 调用
|
||||||
Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages));
|
// Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages));
|
||||||
// 打印结果
|
// // 打印结果
|
||||||
flux.doOnNext(response -> {
|
// flux.doOnNext(response -> {
|
||||||
// System.out.println(response);
|
//// System.out.println(response);
|
||||||
System.out.println(response.getResult().getOutput());
|
// System.out.println(response.getResult().getOutput());
|
||||||
}).then().block();
|
// }).then().block();
|
||||||
}
|
// }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -2,14 +2,14 @@ package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
|
|||||||
|
|
||||||
import org.junit.jupiter.api.Disabled;
|
import org.junit.jupiter.api.Disabled;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springaicommunity.moonshot.MoonshotChatModel;
|
||||||
|
import org.springaicommunity.moonshot.MoonshotChatOptions;
|
||||||
|
import org.springaicommunity.moonshot.api.MoonshotApi;
|
||||||
import org.springframework.ai.chat.messages.Message;
|
import org.springframework.ai.chat.messages.Message;
|
||||||
import org.springframework.ai.chat.messages.SystemMessage;
|
import org.springframework.ai.chat.messages.SystemMessage;
|
||||||
import org.springframework.ai.chat.messages.UserMessage;
|
import org.springframework.ai.chat.messages.UserMessage;
|
||||||
import org.springframework.ai.chat.model.ChatResponse;
|
import org.springframework.ai.chat.model.ChatResponse;
|
||||||
import org.springframework.ai.chat.prompt.Prompt;
|
import org.springframework.ai.chat.prompt.Prompt;
|
||||||
import org.springframework.ai.moonshot.MoonshotChatModel;
|
|
||||||
import org.springframework.ai.moonshot.MoonshotChatOptions;
|
|
||||||
import org.springframework.ai.moonshot.api.MoonshotApi;
|
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -22,11 +22,15 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public class MoonshotChatModelTests {
|
public class MoonshotChatModelTests {
|
||||||
|
|
||||||
private final MoonshotChatModel chatModel = new MoonshotChatModel(
|
private final MoonshotChatModel chatModel = MoonshotChatModel.builder()
|
||||||
new MoonshotApi("sk-aHYYV1SARscItye5QQRRNbXij4fy65Ee7pNZlC9gsSQnUKXA"), // 密钥
|
.moonshotApi(MoonshotApi.builder()
|
||||||
MoonshotChatOptions.builder()
|
.apiKey("sk-aHYYV1SARscItye5QQRRNbXij4fy65Ee7pNZlC9gsSQnUKXA") // 密钥
|
||||||
.model("moonshot-v1-8k") // 模型
|
.build())
|
||||||
.build());
|
.defaultOptions(MoonshotChatOptions.builder()
|
||||||
|
.model("kimi-k2-0711-preview") // 模型
|
||||||
|
.build())
|
||||||
|
.build();
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Disabled
|
@Disabled
|
||||||
public void testCall() {
|
public void testCall() {
|
||||||
|
@@ -23,7 +23,9 @@ import java.util.List;
|
|||||||
public class OllamaChatModelTests {
|
public class OllamaChatModelTests {
|
||||||
|
|
||||||
private final OllamaChatModel chatModel = OllamaChatModel.builder()
|
private final OllamaChatModel chatModel = OllamaChatModel.builder()
|
||||||
.ollamaApi(new OllamaApi("http://127.0.0.1:11434")) // Ollama 服务地址
|
.ollamaApi(OllamaApi.builder()
|
||||||
|
.baseUrl("http://127.0.0.1:11434") // Ollama 服务地址
|
||||||
|
.build())
|
||||||
.defaultOptions(OllamaOptions.builder()
|
.defaultOptions(OllamaOptions.builder()
|
||||||
// .model("qwen") // 模型(https://ollama.com/library/qwen)
|
// .model("qwen") // 模型(https://ollama.com/library/qwen)
|
||||||
.model("deepseek-r1") // 模型(https://ollama.com/library/deepseek-r1)
|
.model("deepseek-r1") // 模型(https://ollama.com/library/deepseek-r1)
|
||||||
|
@@ -25,10 +25,10 @@ public class OpenAIChatModelTests {
|
|||||||
private final OpenAiChatModel chatModel = OpenAiChatModel.builder()
|
private final OpenAiChatModel chatModel = OpenAiChatModel.builder()
|
||||||
.openAiApi(OpenAiApi.builder()
|
.openAiApi(OpenAiApi.builder()
|
||||||
.baseUrl("https://api.holdai.top")
|
.baseUrl("https://api.holdai.top")
|
||||||
.apiKey("sk-aN6nWn3fILjrgLFT0fC4Aa60B72e4253826c77B29dC94f17") // apiKey
|
.apiKey("sk-PytRecQlmjEteoa2RRN6cGnwslo72UUPLQVNEMS6K9yjbmpD") // apiKey
|
||||||
.build())
|
.build())
|
||||||
.defaultOptions(OpenAiChatOptions.builder()
|
.defaultOptions(OpenAiChatOptions.builder()
|
||||||
.model(OpenAiApi.ChatModel.GPT_4_O) // 模型
|
.model(OpenAiApi.ChatModel.GPT_4_1_NANO) // 模型
|
||||||
.temperature(0.7)
|
.temperature(0.7)
|
||||||
.build())
|
.build())
|
||||||
.build();
|
.build();
|
||||||
|
@@ -22,14 +22,17 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public class TongYiChatModelTests {
|
public class TongYiChatModelTests {
|
||||||
|
|
||||||
private final DashScopeChatModel chatModel = new DashScopeChatModel(
|
private final DashScopeChatModel chatModel = DashScopeChatModel.builder()
|
||||||
new DashScopeApi("sk-7d903764249848cfa912733146da12d1"),
|
.dashScopeApi(DashScopeApi.builder()
|
||||||
DashScopeChatOptions.builder()
|
.apiKey("sk-47aa124781be4bfb95244cc62f63f7d0")
|
||||||
|
.build())
|
||||||
|
.defaultOptions( DashScopeChatOptions.builder()
|
||||||
.withModel("qwen1.5-72b-chat") // 模型
|
.withModel("qwen1.5-72b-chat") // 模型
|
||||||
// .withModel("deepseek-r1") // 模型(deepseek-r1)
|
// .withModel("deepseek-r1") // 模型(deepseek-r1)
|
||||||
// .withModel("deepseek-v3") // 模型(deepseek-v3)
|
// .withModel("deepseek-v3") // 模型(deepseek-v3)
|
||||||
// .withModel("deepseek-r1-distill-qwen-1.5b") // 模型(deepseek-r1-distill-qwen-1.5b)
|
// .withModel("deepseek-r1-distill-qwen-1.5b") // 模型(deepseek-r1-distill-qwen-1.5b)
|
||||||
.build());
|
.build())
|
||||||
|
.build();
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Disabled
|
@Disabled
|
||||||
|
@@ -2,13 +2,13 @@ package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
|
|||||||
|
|
||||||
import org.junit.jupiter.api.Disabled;
|
import org.junit.jupiter.api.Disabled;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springaicommunity.qianfan.QianFanChatModel;
|
||||||
|
import org.springaicommunity.qianfan.QianFanChatOptions;
|
||||||
|
import org.springaicommunity.qianfan.api.QianFanApi;
|
||||||
import org.springframework.ai.chat.messages.Message;
|
import org.springframework.ai.chat.messages.Message;
|
||||||
import org.springframework.ai.chat.messages.UserMessage;
|
import org.springframework.ai.chat.messages.UserMessage;
|
||||||
import org.springframework.ai.chat.model.ChatResponse;
|
import org.springframework.ai.chat.model.ChatResponse;
|
||||||
import org.springframework.ai.chat.prompt.Prompt;
|
import org.springframework.ai.chat.prompt.Prompt;
|
||||||
import org.springframework.ai.qianfan.QianFanChatModel;
|
|
||||||
import org.springframework.ai.qianfan.QianFanChatOptions;
|
|
||||||
import org.springframework.ai.qianfan.api.QianFanApi;
|
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -23,9 +23,9 @@ import java.util.List;
|
|||||||
public class YiYanChatModelTests {
|
public class YiYanChatModelTests {
|
||||||
|
|
||||||
private final QianFanChatModel chatModel = new QianFanChatModel(
|
private final QianFanChatModel chatModel = new QianFanChatModel(
|
||||||
new QianFanApi("qS8k8dYr2nXunagK4SSU8Xjj", "pHGbx51ql2f0hOyabQvSZezahVC3hh3e"), // 密钥
|
new QianFanApi("DGnyzREuaY7av7c38bOM9Ji2", "9aR8myflEOPDrEeLhoXv0FdqANOAyIZW"), // 密钥
|
||||||
QianFanChatOptions.builder()
|
QianFanChatOptions.builder()
|
||||||
.model(QianFanApi.ChatModel.ERNIE_4_0_8K_Preview.getValue())
|
.model("ERNIE-4.5-8K-Preview")
|
||||||
.build()
|
.build()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@@ -18,7 +18,7 @@ public class OpenAiImageModelTests {
|
|||||||
|
|
||||||
private final OpenAiImageModel imageModel = new OpenAiImageModel(OpenAiImageApi.builder()
|
private final OpenAiImageModel imageModel = new OpenAiImageModel(OpenAiImageApi.builder()
|
||||||
.baseUrl("https://api.holdai.top") // apiKey
|
.baseUrl("https://api.holdai.top") // apiKey
|
||||||
.apiKey("sk-aN6nWn3fILjrgLFT0fC4Aa60B72e4253826c77B29dC94f17")
|
.apiKey("sk-PytRecQlmjEteoa2RRN6cGnwslo72UUPLQVNEMS6K9yjbmpD")
|
||||||
.build());
|
.build());
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -26,8 +26,8 @@ public class OpenAiImageModelTests {
|
|||||||
public void testCall() {
|
public void testCall() {
|
||||||
// 准备参数
|
// 准备参数
|
||||||
ImageOptions options = OpenAiImageOptions.builder()
|
ImageOptions options = OpenAiImageOptions.builder()
|
||||||
.withModel(OpenAiImageApi.ImageModel.DALL_E_2.getValue()) // 这个模型比较便宜
|
.model(OpenAiImageApi.ImageModel.DALL_E_2.getValue()) // 这个模型比较便宜
|
||||||
.withHeight(256).withWidth(256)
|
.height(256).width(256)
|
||||||
.build();
|
.build();
|
||||||
ImagePrompt prompt = new ImagePrompt("中国长城!", options);
|
ImagePrompt prompt = new ImagePrompt("中国长城!", options);
|
||||||
|
|
||||||
|
@@ -2,11 +2,11 @@ package cn.iocoder.yudao.module.ai.framework.ai.core.model.image;
|
|||||||
|
|
||||||
import org.junit.jupiter.api.Disabled;
|
import org.junit.jupiter.api.Disabled;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springaicommunity.qianfan.QianFanImageModel;
|
||||||
|
import org.springaicommunity.qianfan.QianFanImageOptions;
|
||||||
|
import org.springaicommunity.qianfan.api.QianFanImageApi;
|
||||||
import org.springframework.ai.image.ImagePrompt;
|
import org.springframework.ai.image.ImagePrompt;
|
||||||
import org.springframework.ai.image.ImageResponse;
|
import org.springframework.ai.image.ImageResponse;
|
||||||
import org.springframework.ai.qianfan.QianFanImageModel;
|
|
||||||
import org.springframework.ai.qianfan.QianFanImageOptions;
|
|
||||||
import org.springframework.ai.qianfan.api.QianFanImageApi;
|
|
||||||
|
|
||||||
import static cn.iocoder.yudao.module.ai.framework.ai.core.model.image.StabilityAiImageModelTests.viewImage;
|
import static cn.iocoder.yudao.module.ai.framework.ai.core.model.image.StabilityAiImageModelTests.viewImage;
|
||||||
|
|
||||||
|
@@ -31,8 +31,8 @@ public class StabilityAiImageModelTests {
|
|||||||
public void testCall() {
|
public void testCall() {
|
||||||
// 准备参数
|
// 准备参数
|
||||||
ImageOptions options = OpenAiImageOptions.builder()
|
ImageOptions options = OpenAiImageOptions.builder()
|
||||||
.withModel("stable-diffusion-v1-6")
|
.model("stable-diffusion-v1-6")
|
||||||
.withHeight(320).withWidth(320)
|
.height(320).width(320)
|
||||||
.build();
|
.build();
|
||||||
ImagePrompt prompt = new ImagePrompt("great wall", options);
|
ImagePrompt prompt = new ImagePrompt("great wall", options);
|
||||||
|
|
||||||
|
@@ -62,6 +62,7 @@ import org.springframework.transaction.support.TransactionSynchronizationManager
|
|||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import javax.validation.Valid;
|
import javax.validation.Valid;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||||
@@ -173,7 +174,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获得用户指定 taskId 任务编号的“待办”(未审批、且可审核)的任务
|
* 获得用户指定 taskId 任务编号的"待办"(未审批、且可审核)的任务
|
||||||
*
|
*
|
||||||
* @param userId 用户编号
|
* @param userId 用户编号
|
||||||
* @param taskId 任务编号
|
* @param taskId 任务编号
|
||||||
@@ -194,7 +195,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获得用户指定 processInstanceId 流程编号下的首个“待办”(未审批、且可审核)的任务
|
* 获得用户指定 processInstanceId 流程编号下的首个"待办"(未审批、且可审核)的任务
|
||||||
*
|
*
|
||||||
* @param userId 用户编号
|
* @param userId 用户编号
|
||||||
* @param processInstanceId 流程编号
|
* @param processInstanceId 流程编号
|
||||||
@@ -241,7 +242,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
|
|||||||
}
|
}
|
||||||
List<HistoricTaskInstance> tasks = taskQuery.listPage(PageUtils.getStart(pageVO), pageVO.getPageSize());
|
List<HistoricTaskInstance> tasks = taskQuery.listPage(PageUtils.getStart(pageVO), pageVO.getPageSize());
|
||||||
|
|
||||||
// 特殊:强制移除自动完成的“发起人”节点
|
// 特殊:强制移除自动完成的"发起人"节点
|
||||||
// 补充说明:由于 taskQuery 无法方面的过滤,所以暂时通过内存过滤
|
// 补充说明:由于 taskQuery 无法方面的过滤,所以暂时通过内存过滤
|
||||||
tasks.removeIf(task -> task.getTaskDefinitionKey().equals(START_USER_NODE_ID));
|
tasks.removeIf(task -> task.getTaskDefinitionKey().equals(START_USER_NODE_ID));
|
||||||
return new PageResult<>(tasks, count);
|
return new PageResult<>(tasks, count);
|
||||||
@@ -297,7 +298,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
|
|||||||
public Task validateTask(Long userId, String taskId) {
|
public Task validateTask(Long userId, String taskId) {
|
||||||
Task task = validateTaskExist(taskId);
|
Task task = validateTaskExist(taskId);
|
||||||
// 为什么判断 assignee 非空的情况下?
|
// 为什么判断 assignee 非空的情况下?
|
||||||
// 例如说:在审批人为空时,我们会有“自动审批通过”的策略,此时 userId 为 null,允许通过
|
// 例如说:在审批人为空时,我们会有"自动审批通过"的策略,此时 userId 为 null,允许通过
|
||||||
if (StrUtil.isNotBlank(task.getAssignee())
|
if (StrUtil.isNotBlank(task.getAssignee())
|
||||||
&& ObjectUtil.notEqual(userId, NumberUtils.parseLong(task.getAssignee()))) {
|
&& ObjectUtil.notEqual(userId, NumberUtils.parseLong(task.getAssignee()))) {
|
||||||
throw exception(TASK_OPERATE_FAIL_ASSIGN_NOT_SELF);
|
throw exception(TASK_OPERATE_FAIL_ASSIGN_NOT_SELF);
|
||||||
@@ -404,19 +405,28 @@ public class BpmTaskServiceImpl implements BpmTaskService {
|
|||||||
* @return 所有子任务列表
|
* @return 所有子任务列表
|
||||||
*/
|
*/
|
||||||
private List<Task> getAllChildTaskList(Task parentTask) {
|
private List<Task> getAllChildTaskList(Task parentTask) {
|
||||||
|
// 1. 获取流程实例的所有任务
|
||||||
|
List<Task> allTasks = taskService.createTaskQuery().processInstanceId(parentTask.getProcessInstanceId()).list();
|
||||||
|
if (CollUtil.isEmpty(allTasks)) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 构建父任务到子任务列表的映射关系,用于内存中快速查找
|
||||||
|
Map<String, List<Task>> childrenTaskMap = allTasks.stream()
|
||||||
|
.filter(task -> task.getParentTaskId() != null)
|
||||||
|
.collect(Collectors.groupingBy(Task::getParentTaskId));
|
||||||
|
if (CollUtil.isEmpty(childrenTaskMap)) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 使用栈进行遍历,广度或深度优先搜索所有子孙任务
|
||||||
List<Task> result = new ArrayList<>();
|
List<Task> result = new ArrayList<>();
|
||||||
// 1. 递归获取子级
|
|
||||||
Stack<Task> stack = new Stack<>();
|
Stack<Task> stack = new Stack<>();
|
||||||
stack.push(parentTask);
|
stack.push(parentTask);
|
||||||
// 2. 递归遍历
|
|
||||||
for (int i = 0; i < Short.MAX_VALUE; i++) {
|
while (!stack.isEmpty()) {
|
||||||
if (stack.isEmpty()) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// 2.1 获取子任务们
|
|
||||||
Task task = stack.pop();
|
Task task = stack.pop();
|
||||||
List<Task> childTaskList = getTaskListByParentTaskId(task.getId());
|
List<Task> childTaskList = childrenTaskMap.get(task.getId());
|
||||||
// 2.2 如果非空,则添加到 stack 进一步递归
|
|
||||||
if (CollUtil.isNotEmpty(childTaskList)) {
|
if (CollUtil.isNotEmpty(childTaskList)) {
|
||||||
stack.addAll(childTaskList);
|
stack.addAll(childTaskList);
|
||||||
result.addAll(childTaskList);
|
result.addAll(childTaskList);
|
||||||
@@ -659,7 +669,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 审批通过存在“后加签”的任务。
|
* 审批通过存在"后加签"的任务。
|
||||||
* <p>
|
* <p>
|
||||||
* 注意:该任务不能马上完成,需要一个中间状态(APPROVING),并激活剩余所有子任务(PROCESS)为可审批处理
|
* 注意:该任务不能马上完成,需要一个中间状态(APPROVING),并激活剩余所有子任务(PROCESS)为可审批处理
|
||||||
* 如果马上完成,则会触发下一个任务,甚至如果没有下一个任务则流程实例就直接结束了!
|
* 如果马上完成,则会触发下一个任务,甚至如果没有下一个任务则流程实例就直接结束了!
|
||||||
@@ -771,7 +781,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
|
|||||||
BpmCommentTypeEnum.REJECT.formatComment(reqVO.getReason()));
|
BpmCommentTypeEnum.REJECT.formatComment(reqVO.getReason()));
|
||||||
// 2.3 如果当前任务时被加签的,则加它的根任务也标记成未通过
|
// 2.3 如果当前任务时被加签的,则加它的根任务也标记成未通过
|
||||||
// 疑问:为什么要标记未通过呢?
|
// 疑问:为什么要标记未通过呢?
|
||||||
// 回答:例如说 A 任务被向前加签除 B 任务时,B 任务被审批不通过,此时 A 会被取消。而 yudao-ui-admin-vue3 不展示“已取消”的任务,导致展示不出审批不通过的细节。
|
// 回答:例如说 A 任务被向前加签除 B 任务时,B 任务被审批不通过,此时 A 会被取消。而 yudao-ui-admin-vue3 不展示"已取消"的任务,导致展示不出审批不通过的细节。
|
||||||
if (task.getParentTaskId() != null) {
|
if (task.getParentTaskId() != null) {
|
||||||
String rootParentId = getTaskRootParentId(task);
|
String rootParentId = getTaskRootParentId(task);
|
||||||
updateTaskStatusAndReason(rootParentId, BpmTaskStatusEnum.REJECT.getStatus(),
|
updateTaskStatusAndReason(rootParentId, BpmTaskStatusEnum.REJECT.getStatus(),
|
||||||
@@ -1044,8 +1054,8 @@ public class BpmTaskServiceImpl implements BpmTaskService {
|
|||||||
/**
|
/**
|
||||||
* 校验任务是否可以加签,主要校验加签类型是否一致:
|
* 校验任务是否可以加签,主要校验加签类型是否一致:
|
||||||
* <p>
|
* <p>
|
||||||
* 1. 如果存在“向前加签”的任务,则不能“向后加签”
|
* 1. 如果存在"向前加签"的任务,则不能"向后加签"
|
||||||
* 2. 如果存在“向后加签”的任务,则不能“向前加签”
|
* 2. 如果存在"向后加签"的任务,则不能"向前加签"
|
||||||
*
|
*
|
||||||
* @param userId 当前用户 ID
|
* @param userId 当前用户 ID
|
||||||
* @param reqVO 请求参数,包含任务 ID 和加签类型
|
* @param reqVO 请求参数,包含任务 ID 和加签类型
|
||||||
@@ -1366,8 +1376,8 @@ public class BpmTaskServiceImpl implements BpmTaskService {
|
|||||||
Boolean skipStartUserNodeFlag = Convert.toBool(runtimeService.getVariable(processInstance.getProcessInstanceId(),
|
Boolean skipStartUserNodeFlag = Convert.toBool(runtimeService.getVariable(processInstance.getProcessInstanceId(),
|
||||||
PROCESS_INSTANCE_VARIABLE_SKIP_START_USER_NODE, String.class));
|
PROCESS_INSTANCE_VARIABLE_SKIP_START_USER_NODE, String.class));
|
||||||
if (userTaskElement.getId().equals(START_USER_NODE_ID)
|
if (userTaskElement.getId().equals(START_USER_NODE_ID)
|
||||||
&& (skipStartUserNodeFlag == null // 目的:一般是“主流程”,发起人节点,自动通过审核
|
&& (skipStartUserNodeFlag == null // 目的:一般是"主流程",发起人节点,自动通过审核
|
||||||
|| Boolean.TRUE.equals(skipStartUserNodeFlag)) // 目的:一般是“子流程”,发起人节点,按配置自动通过审核
|
|| Boolean.TRUE.equals(skipStartUserNodeFlag)) // 目的:一般是"子流程",发起人节点,按配置自动通过审核
|
||||||
&& ObjUtil.notEqual(returnTaskFlag, Boolean.TRUE)) {
|
&& ObjUtil.notEqual(returnTaskFlag, Boolean.TRUE)) {
|
||||||
getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId())
|
getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId())
|
||||||
.setReason(BpmReasonEnum.ASSIGN_START_USER_APPROVE_WHEN_SKIP_START_USER_NODE.getReason()));
|
.setReason(BpmReasonEnum.ASSIGN_START_USER_APPROVE_WHEN_SKIP_START_USER_NODE.getReason()));
|
||||||
|
@@ -142,6 +142,7 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
|
|||||||
updateBusinessProduct(updateObj.getId(), businessProducts);
|
updateBusinessProduct(updateObj.getId(), businessProducts);
|
||||||
|
|
||||||
// 3. 记录操作日志上下文
|
// 3. 记录操作日志上下文
|
||||||
|
updateReqVO.setOwnerUserId(oldBusiness.getOwnerUserId()); // 避免操作日志出现“删除负责人”的情况
|
||||||
LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldBusiness, CrmBusinessSaveReqVO.class));
|
LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldBusiness, CrmBusinessSaveReqVO.class));
|
||||||
LogRecordContext.putVariable("businessName", oldBusiness.getName());
|
LogRecordContext.putVariable("businessName", oldBusiness.getName());
|
||||||
}
|
}
|
||||||
|
@@ -92,19 +92,20 @@ public class CrmClueServiceImpl implements CrmClueService {
|
|||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
@LogRecord(type = CRM_CLUE_TYPE, subType = CRM_CLUE_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}",
|
@LogRecord(type = CRM_CLUE_TYPE, subType = CRM_CLUE_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}",
|
||||||
success = CRM_CLUE_UPDATE_SUCCESS)
|
success = CRM_CLUE_UPDATE_SUCCESS)
|
||||||
@CrmPermission(bizType = CrmBizTypeEnum.CRM_CLUE, bizId = "#updateReq.id", level = CrmPermissionLevelEnum.OWNER)
|
@CrmPermission(bizType = CrmBizTypeEnum.CRM_CLUE, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.OWNER)
|
||||||
public void updateClue(CrmClueSaveReqVO updateReq) {
|
public void updateClue(CrmClueSaveReqVO updateReqVO) {
|
||||||
Assert.notNull(updateReq.getId(), "线索编号不能为空");
|
Assert.notNull(updateReqVO.getId(), "线索编号不能为空");
|
||||||
// 1.1 校验线索是否存在
|
// 1.1 校验线索是否存在
|
||||||
CrmClueDO oldClue = validateClueExists(updateReq.getId());
|
CrmClueDO oldClue = validateClueExists(updateReqVO.getId());
|
||||||
// 1.2 校验关联数据
|
// 1.2 校验关联数据
|
||||||
validateRelationDataExists(updateReq);
|
validateRelationDataExists(updateReqVO);
|
||||||
|
|
||||||
// 2. 更新线索
|
// 2. 更新线索
|
||||||
CrmClueDO updateObj = BeanUtils.toBean(updateReq, CrmClueDO.class);
|
CrmClueDO updateObj = BeanUtils.toBean(updateReqVO, CrmClueDO.class);
|
||||||
clueMapper.updateById(updateObj);
|
clueMapper.updateById(updateObj);
|
||||||
|
|
||||||
// 3. 记录操作日志上下文
|
// 3. 记录操作日志上下文
|
||||||
|
updateReqVO.setOwnerUserId(oldClue.getOwnerUserId()); // 避免操作日志出现“删除负责人”的情况
|
||||||
LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldClue, CrmCustomerSaveReqVO.class));
|
LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldClue, CrmCustomerSaveReqVO.class));
|
||||||
LogRecordContext.putVariable("clueName", oldClue.getName());
|
LogRecordContext.putVariable("clueName", oldClue.getName());
|
||||||
}
|
}
|
||||||
|
@@ -114,6 +114,7 @@ public class CrmContactServiceImpl implements CrmContactService {
|
|||||||
contactMapper.updateById(updateObj);
|
contactMapper.updateById(updateObj);
|
||||||
|
|
||||||
// 3. 记录操作日志
|
// 3. 记录操作日志
|
||||||
|
updateReqVO.setOwnerUserId(oldContact.getOwnerUserId()); // 避免操作日志出现“删除负责人”的情况
|
||||||
LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldContact, CrmContactSaveReqVO.class));
|
LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldContact, CrmContactSaveReqVO.class));
|
||||||
LogRecordContext.putVariable("contactName", oldContact.getName());
|
LogRecordContext.putVariable("contactName", oldContact.getName());
|
||||||
}
|
}
|
||||||
|
@@ -140,9 +140,9 @@ public class CrmContractServiceImpl implements CrmContractService {
|
|||||||
Assert.notNull(updateReqVO.getId(), "合同编号不能为空");
|
Assert.notNull(updateReqVO.getId(), "合同编号不能为空");
|
||||||
updateReqVO.setOwnerUserId(null); // 不允许更新的字段
|
updateReqVO.setOwnerUserId(null); // 不允许更新的字段
|
||||||
// 1.1 校验存在
|
// 1.1 校验存在
|
||||||
CrmContractDO contract = validateContractExists(updateReqVO.getId());
|
CrmContractDO oldContract = validateContractExists(updateReqVO.getId());
|
||||||
// 1.2 只有草稿、审批中,可以编辑;
|
// 1.2 只有草稿、审批中,可以编辑;
|
||||||
if (!ObjectUtils.equalsAny(contract.getAuditStatus(), CrmAuditStatusEnum.DRAFT.getStatus(),
|
if (!ObjectUtils.equalsAny(oldContract.getAuditStatus(), CrmAuditStatusEnum.DRAFT.getStatus(),
|
||||||
CrmAuditStatusEnum.PROCESS.getStatus())) {
|
CrmAuditStatusEnum.PROCESS.getStatus())) {
|
||||||
throw exception(CONTRACT_UPDATE_FAIL_NOT_DRAFT);
|
throw exception(CONTRACT_UPDATE_FAIL_NOT_DRAFT);
|
||||||
}
|
}
|
||||||
@@ -159,8 +159,9 @@ public class CrmContractServiceImpl implements CrmContractService {
|
|||||||
updateContractProduct(updateReqVO.getId(), contractProducts);
|
updateContractProduct(updateReqVO.getId(), contractProducts);
|
||||||
|
|
||||||
// 3. 记录操作日志上下文
|
// 3. 记录操作日志上下文
|
||||||
LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(contract, CrmContractSaveReqVO.class));
|
updateReqVO.setOwnerUserId(oldContract.getOwnerUserId()); // 避免操作日志出现“删除负责人”的情况
|
||||||
LogRecordContext.putVariable("contractName", contract.getName());
|
LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldContract, CrmContractSaveReqVO.class));
|
||||||
|
LogRecordContext.putVariable("contractName", oldContract.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateContractProduct(Long id, List<CrmContractProductDO> newList) {
|
private void updateContractProduct(Long id, List<CrmContractProductDO> newList) {
|
||||||
|
@@ -137,6 +137,7 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
|
|||||||
customerMapper.updateById(updateObj);
|
customerMapper.updateById(updateObj);
|
||||||
|
|
||||||
// 3. 记录操作日志上下文
|
// 3. 记录操作日志上下文
|
||||||
|
updateReqVO.setOwnerUserId(oldCustomer.getOwnerUserId()); // 避免操作日志出现“删除负责人”的情况
|
||||||
LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldCustomer, CrmCustomerSaveReqVO.class));
|
LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldCustomer, CrmCustomerSaveReqVO.class));
|
||||||
LogRecordContext.putVariable("customerName", oldCustomer.getName());
|
LogRecordContext.putVariable("customerName", oldCustomer.getName());
|
||||||
}
|
}
|
||||||
|
@@ -210,12 +210,12 @@ public class CrmPermissionServiceImpl implements CrmPermissionService {
|
|||||||
CrmPermissionDO oldPermission = permissionMapper.selectByBizTypeAndBizIdByUserId(
|
CrmPermissionDO oldPermission = permissionMapper.selectByBizTypeAndBizIdByUserId(
|
||||||
transferReqBO.getBizType(), transferReqBO.getBizId(), transferReqBO.getUserId());
|
transferReqBO.getBizType(), transferReqBO.getBizId(), transferReqBO.getUserId());
|
||||||
String bizTypeName = CrmBizTypeEnum.getNameByType(transferReqBO.getBizType());
|
String bizTypeName = CrmBizTypeEnum.getNameByType(transferReqBO.getBizType());
|
||||||
if (oldPermission == null // 不是拥有者,并且不是超管
|
if ((oldPermission == null || !isOwner(oldPermission.getLevel()))
|
||||||
|| (!isOwner(oldPermission.getLevel()) && !CrmPermissionUtils.isCrmAdmin())) {
|
&& !CrmPermissionUtils.isCrmAdmin()) { // 并且不是超管
|
||||||
throw exception(CRM_PERMISSION_DENIED, bizTypeName);
|
throw exception(CRM_PERMISSION_DENIED, bizTypeName);
|
||||||
}
|
}
|
||||||
// 1.1 校验转移对象是否已经是该负责人
|
// 1.1 校验转移对象是否已经是该负责人
|
||||||
if (ObjUtil.equal(transferReqBO.getNewOwnerUserId(), oldPermission.getUserId())) {
|
if (oldPermission != null && ObjUtil.equal(transferReqBO.getNewOwnerUserId(), oldPermission.getUserId())) {
|
||||||
throw exception(CRM_PERMISSION_MODEL_TRANSFER_FAIL_OWNER_USER_EXISTS, bizTypeName);
|
throw exception(CRM_PERMISSION_MODEL_TRANSFER_FAIL_OWNER_USER_EXISTS, bizTypeName);
|
||||||
}
|
}
|
||||||
// 1.2 校验新负责人是否存在
|
// 1.2 校验新负责人是否存在
|
||||||
|
@@ -104,6 +104,7 @@ public class CrmReceivablePlanServiceImpl implements CrmReceivablePlanService {
|
|||||||
receivablePlanMapper.updateById(updateObj);
|
receivablePlanMapper.updateById(updateObj);
|
||||||
|
|
||||||
// 3. 记录操作日志上下文
|
// 3. 记录操作日志上下文
|
||||||
|
updateReqVO.setOwnerUserId(oldReceivablePlan.getOwnerUserId()); // 避免操作日志出现“删除负责人”的情况
|
||||||
LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldReceivablePlan, CrmReceivablePlanSaveReqVO.class));
|
LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldReceivablePlan, CrmReceivablePlanSaveReqVO.class));
|
||||||
LogRecordContext.putVariable("receivablePlan", oldReceivablePlan);
|
LogRecordContext.putVariable("receivablePlan", oldReceivablePlan);
|
||||||
}
|
}
|
||||||
|
@@ -162,14 +162,14 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
|
|||||||
Assert.notNull(updateReqVO.getId(), "回款编号不能为空");
|
Assert.notNull(updateReqVO.getId(), "回款编号不能为空");
|
||||||
updateReqVO.setOwnerUserId(null).setCustomerId(null).setContractId(null).setPlanId(null); // 不允许修改的字段
|
updateReqVO.setOwnerUserId(null).setCustomerId(null).setContractId(null).setPlanId(null); // 不允许修改的字段
|
||||||
// 1.1 校验存在
|
// 1.1 校验存在
|
||||||
CrmReceivableDO receivable = validateReceivableExists(updateReqVO.getId());
|
CrmReceivableDO oldReceivable = validateReceivableExists(updateReqVO.getId());
|
||||||
updateReqVO.setOwnerUserId(receivable.getOwnerUserId()).setCustomerId(receivable.getCustomerId())
|
updateReqVO.setOwnerUserId(oldReceivable.getOwnerUserId()).setCustomerId(oldReceivable.getCustomerId())
|
||||||
.setContractId(receivable.getContractId()).setPlanId(receivable.getPlanId()); // 设置已存在的值
|
.setContractId(oldReceivable.getContractId()).setPlanId(oldReceivable.getPlanId()); // 设置已存在的值
|
||||||
// 1.2 校验可回款金额超过上限
|
// 1.2 校验可回款金额超过上限
|
||||||
validateReceivablePriceExceedsLimit(updateReqVO);
|
validateReceivablePriceExceedsLimit(updateReqVO);
|
||||||
|
|
||||||
// 1.3 只有草稿、审批中,可以编辑;
|
// 1.3 只有草稿、审批中,可以编辑;
|
||||||
if (!ObjectUtils.equalsAny(receivable.getAuditStatus(), CrmAuditStatusEnum.DRAFT.getStatus(),
|
if (!ObjectUtils.equalsAny(oldReceivable.getAuditStatus(), CrmAuditStatusEnum.DRAFT.getStatus(),
|
||||||
CrmAuditStatusEnum.PROCESS.getStatus())) {
|
CrmAuditStatusEnum.PROCESS.getStatus())) {
|
||||||
throw exception(RECEIVABLE_UPDATE_FAIL_EDITING_PROHIBITED);
|
throw exception(RECEIVABLE_UPDATE_FAIL_EDITING_PROHIBITED);
|
||||||
}
|
}
|
||||||
@@ -179,9 +179,10 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
|
|||||||
receivableMapper.updateById(updateObj);
|
receivableMapper.updateById(updateObj);
|
||||||
|
|
||||||
// 3. 记录操作日志上下文
|
// 3. 记录操作日志上下文
|
||||||
LogRecordContext.putVariable("receivable", receivable);
|
updateReqVO.setOwnerUserId(oldReceivable.getOwnerUserId()); // 避免操作日志出现“删除负责人”的情况
|
||||||
LogRecordContext.putVariable("period", getReceivablePeriod(receivable.getPlanId()));
|
LogRecordContext.putVariable("oldReceivable", oldReceivable);
|
||||||
LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(receivable, CrmReceivableSaveReqVO.class));
|
LogRecordContext.putVariable("period", getReceivablePeriod(oldReceivable.getPlanId()));
|
||||||
|
LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldReceivable, CrmReceivableSaveReqVO.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Integer getReceivablePeriod(Long planId) {
|
private Integer getReceivablePeriod(Long planId) {
|
||||||
|
@@ -10,7 +10,9 @@
|
|||||||
<if test="endTime != null">
|
<if test="endTime != null">
|
||||||
AND in_time < #{endTime}
|
AND in_time < #{endTime}
|
||||||
</if>
|
</if>
|
||||||
AND tenant_id = ${@cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder@getRequiredTenantId()}
|
<if test="@cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder@getTenantId() != null">
|
||||||
|
AND tenant_id = ${@cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder@getTenantId()}
|
||||||
|
</if>
|
||||||
AND deleted = 0) -
|
AND deleted = 0) -
|
||||||
(SELECT IFNULL(SUM(total_price), 0)
|
(SELECT IFNULL(SUM(total_price), 0)
|
||||||
FROM erp_purchase_return
|
FROM erp_purchase_return
|
||||||
@@ -18,7 +20,9 @@
|
|||||||
<if test="endTime != null">
|
<if test="endTime != null">
|
||||||
AND return_time < #{endTime}
|
AND return_time < #{endTime}
|
||||||
</if>
|
</if>
|
||||||
AND tenant_id = ${@cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder@getRequiredTenantId()}
|
<if test="@cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder@getTenantId() != null">
|
||||||
|
AND tenant_id = ${@cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder@getTenantId()}
|
||||||
|
</if>
|
||||||
AND deleted = 0)
|
AND deleted = 0)
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
@@ -10,7 +10,9 @@
|
|||||||
<if test="endTime != null">
|
<if test="endTime != null">
|
||||||
AND out_time < #{endTime}
|
AND out_time < #{endTime}
|
||||||
</if>
|
</if>
|
||||||
AND tenant_id = ${@cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder@getRequiredTenantId()}
|
<if test="@cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder@getTenantId() != null">
|
||||||
|
AND tenant_id = ${@cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder@getTenantId()}
|
||||||
|
</if>
|
||||||
AND deleted = 0) -
|
AND deleted = 0) -
|
||||||
(SELECT IFNULL(SUM(total_price), 0)
|
(SELECT IFNULL(SUM(total_price), 0)
|
||||||
FROM erp_sale_return
|
FROM erp_sale_return
|
||||||
@@ -18,7 +20,9 @@
|
|||||||
<if test="endTime != null">
|
<if test="endTime != null">
|
||||||
AND return_time < #{endTime}
|
AND return_time < #{endTime}
|
||||||
</if>
|
</if>
|
||||||
AND tenant_id = ${@cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder@getRequiredTenantId()}
|
<if test="@cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder@getTenantId() != null">
|
||||||
|
AND tenant_id = ${@cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder@getTenantId()}
|
||||||
|
</if>
|
||||||
AND deleted = 0)
|
AND deleted = 0)
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
@@ -1,9 +1,12 @@
|
|||||||
package cn.iocoder.yudao.module.infra.controller.admin.file.vo.file;
|
package cn.iocoder.yudao.module.infra.controller.admin.file.vo.file;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import javax.validation.constraints.AssertTrue;
|
||||||
import javax.validation.constraints.NotNull;
|
import javax.validation.constraints.NotNull;
|
||||||
|
|
||||||
@Schema(description = "管理后台 - 上传文件 Request VO")
|
@Schema(description = "管理后台 - 上传文件 Request VO")
|
||||||
@@ -17,4 +20,10 @@ public class FileUploadReqVO {
|
|||||||
@Schema(description = "文件目录", example = "XXX/YYY")
|
@Schema(description = "文件目录", example = "XXX/YYY")
|
||||||
private String directory;
|
private String directory;
|
||||||
|
|
||||||
|
@AssertTrue(message = "文件目录不正确")
|
||||||
|
@JsonIgnore
|
||||||
|
public boolean isDirectoryValid() {
|
||||||
|
return !StrUtil.containsAny(directory, "..", "/", "\\");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,9 +1,12 @@
|
|||||||
package cn.iocoder.yudao.module.infra.controller.app.file.vo;
|
package cn.iocoder.yudao.module.infra.controller.app.file.vo;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import javax.validation.constraints.AssertTrue;
|
||||||
import javax.validation.constraints.NotNull;
|
import javax.validation.constraints.NotNull;
|
||||||
|
|
||||||
@Schema(description = "用户 App - 上传文件 Request VO")
|
@Schema(description = "用户 App - 上传文件 Request VO")
|
||||||
@@ -17,4 +20,10 @@ public class AppFileUploadReqVO {
|
|||||||
@Schema(description = "文件目录", example = "XXX/YYY")
|
@Schema(description = "文件目录", example = "XXX/YYY")
|
||||||
private String directory;
|
private String directory;
|
||||||
|
|
||||||
|
@AssertTrue(message = "文件目录不正确")
|
||||||
|
@JsonIgnore
|
||||||
|
public boolean isDirectoryValid() {
|
||||||
|
return !StrUtil.containsAny(directory, "..", "/", "\\");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.infra.framework.file.core.utils;
|
|||||||
import cn.hutool.core.io.IoUtil;
|
import cn.hutool.core.io.IoUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import cn.iocoder.yudao.framework.common.util.http.HttpUtils;
|
import cn.iocoder.yudao.framework.common.util.http.HttpUtils;
|
||||||
import com.alibaba.ttl.TransmittableThreadLocal;
|
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.tika.Tika;
|
import org.apache.tika.Tika;
|
||||||
@@ -86,8 +85,8 @@ public class FileTypeUtils {
|
|||||||
response.setContentType(contentType);
|
response.setContentType(contentType);
|
||||||
// 针对 video 的特殊处理,解决视频地址在移动端播放的兼容性问题
|
// 针对 video 的特殊处理,解决视频地址在移动端播放的兼容性问题
|
||||||
if (StrUtil.containsIgnoreCase(contentType, "video")) {
|
if (StrUtil.containsIgnoreCase(contentType, "video")) {
|
||||||
response.setHeader("Content-Length", String.valueOf(content.length - 1));
|
response.setHeader("Content-Length", String.valueOf(content.length));
|
||||||
response.setHeader("Content-Range", String.valueOf(content.length - 1));
|
response.setHeader("Content-Range", "bytes 0-" + (content.length - 1) + "/" + content.length);
|
||||||
response.setHeader("Accept-Ranges", "bytes");
|
response.setHeader("Accept-Ranges", "bytes");
|
||||||
}
|
}
|
||||||
// 输出附件
|
// 输出附件
|
||||||
|
@@ -31,9 +31,13 @@ import java.util.List;
|
|||||||
public class CouponTemplateDO extends BaseDO {
|
public class CouponTemplateDO extends BaseDO {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 不限制领取数量
|
* 领取数量 - 不限制
|
||||||
*/
|
*/
|
||||||
public static final Integer TIME_LIMIT_COUNT_MAX = -1;
|
public static final Integer TAKE_LIMIT_COUNT_MAX = -1;
|
||||||
|
/**
|
||||||
|
* 发放数量 - 不限制
|
||||||
|
*/
|
||||||
|
public static final Integer TOTAL_COUNT_MAX = -1;
|
||||||
|
|
||||||
// ========== 基本信息 BEGIN ==========
|
// ========== 基本信息 BEGIN ==========
|
||||||
/**
|
/**
|
||||||
|
@@ -40,10 +40,16 @@ public interface CouponTemplateMapper extends BaseMapperX<CouponTemplateDO> {
|
|||||||
.orderByDesc(CouponTemplateDO::getId));
|
.orderByDesc(CouponTemplateDO::getId));
|
||||||
}
|
}
|
||||||
|
|
||||||
default void updateTakeCount(Long id, Integer incrCount) {
|
default int updateTakeCount(Long id, Integer incrCount) {
|
||||||
update(null, new LambdaUpdateWrapper<CouponTemplateDO>()
|
LambdaUpdateWrapper<CouponTemplateDO> updateWrapper = new LambdaUpdateWrapper<CouponTemplateDO>()
|
||||||
.eq(CouponTemplateDO::getId, id)
|
.eq(CouponTemplateDO::getId, id)
|
||||||
.setSql("take_count = take_count + " + incrCount));
|
.setSql("take_count = take_count + " + incrCount);
|
||||||
|
// 增加已领取的数量(incrCount 为正数),需要考虑发放数量 totalCount 的限制
|
||||||
|
if (incrCount > 0) {
|
||||||
|
updateWrapper.and(i -> i.apply("take_count < total_count")
|
||||||
|
.or().eq(CouponTemplateDO::getTotalCount, CouponTemplateDO.TOTAL_COUNT_MAX));
|
||||||
|
}
|
||||||
|
return update(updateWrapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
default List<CouponTemplateDO> selectListByTakeType(Integer takeType) {
|
default List<CouponTemplateDO> selectListByTakeType(Integer takeType) {
|
||||||
|
@@ -86,7 +86,7 @@ public class CombinationActivityServiceImpl implements CombinationActivityServic
|
|||||||
activityList.removeIf(item -> ObjectUtil.equal(item.getId(), activityId));
|
activityList.removeIf(item -> ObjectUtil.equal(item.getId(), activityId));
|
||||||
}
|
}
|
||||||
// 查找是否有其它活动,选择了该产品
|
// 查找是否有其它活动,选择了该产品
|
||||||
List<CombinationActivityDO> matchActivityList = filterList(activityList, activity -> ObjectUtil.equal(activity.getId(), spuId));
|
List<CombinationActivityDO> matchActivityList = filterList(activityList, activity -> ObjectUtil.equal(activity.getSpuId(), spuId));
|
||||||
if (CollUtil.isNotEmpty(matchActivityList)) {
|
if (CollUtil.isNotEmpty(matchActivityList)) {
|
||||||
throw exception(COMBINATION_ACTIVITY_SPU_CONFLICTS);
|
throw exception(COMBINATION_ACTIVITY_SPU_CONFLICTS);
|
||||||
}
|
}
|
||||||
|
@@ -137,7 +137,6 @@ public class CouponServiceImpl implements CouponService {
|
|||||||
|
|
||||||
// 4. 增加优惠劵模板的领取数量
|
// 4. 增加优惠劵模板的领取数量
|
||||||
couponTemplateService.updateCouponTemplateTakeCount(template.getId(), userIds.size());
|
couponTemplateService.updateCouponTemplateTakeCount(template.getId(), userIds.size());
|
||||||
|
|
||||||
return convertMultiMap(couponList, CouponDO::getUserId, CouponDO::getId);
|
return convertMultiMap(couponList, CouponDO::getUserId, CouponDO::getId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,7 +280,7 @@ public class CouponServiceImpl implements CouponService {
|
|||||||
}
|
}
|
||||||
// 校验发放数量不能过小(仅在 CouponTakeTypeEnum.USER 用户领取时)
|
// 校验发放数量不能过小(仅在 CouponTakeTypeEnum.USER 用户领取时)
|
||||||
if (CouponTakeTypeEnum.isUser(couponTemplate.getTakeType())
|
if (CouponTakeTypeEnum.isUser(couponTemplate.getTakeType())
|
||||||
&& ObjUtil.notEqual(couponTemplate.getTakeLimitCount(), CouponTemplateDO.TIME_LIMIT_COUNT_MAX) // 非不限制
|
&& ObjUtil.notEqual(couponTemplate.getTakeLimitCount(), CouponTemplateDO.TAKE_LIMIT_COUNT_MAX) // 非不限制
|
||||||
&& couponTemplate.getTakeCount() + userIds.size() > couponTemplate.getTotalCount()) {
|
&& couponTemplate.getTakeCount() + userIds.size() > couponTemplate.getTotalCount()) {
|
||||||
throw exception(COUPON_TEMPLATE_NOT_ENOUGH);
|
throw exception(COUPON_TEMPLATE_NOT_ENOUGH);
|
||||||
}
|
}
|
||||||
|
@@ -22,8 +22,7 @@ import java.util.List;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||||
import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.COUPON_TEMPLATE_NOT_EXISTS;
|
import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*;
|
||||||
import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.COUPON_TEMPLATE_TOTAL_COUNT_TOO_SMALL;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 优惠劵模板 Service 实现类
|
* 优惠劵模板 Service 实现类
|
||||||
@@ -60,7 +59,7 @@ public class CouponTemplateServiceImpl implements CouponTemplateService {
|
|||||||
CouponTemplateDO couponTemplate = validateCouponTemplateExists(updateReqVO.getId());
|
CouponTemplateDO couponTemplate = validateCouponTemplateExists(updateReqVO.getId());
|
||||||
// 校验发放数量不能过小(仅在 CouponTakeTypeEnum.USER 用户领取时)
|
// 校验发放数量不能过小(仅在 CouponTakeTypeEnum.USER 用户领取时)
|
||||||
if (CouponTakeTypeEnum.isUser(couponTemplate.getTakeType())
|
if (CouponTakeTypeEnum.isUser(couponTemplate.getTakeType())
|
||||||
&& ObjUtil.notEqual(couponTemplate.getTakeLimitCount(), CouponTemplateDO.TIME_LIMIT_COUNT_MAX) // 非不限制
|
&& ObjUtil.notEqual(couponTemplate.getTakeLimitCount(), CouponTemplateDO.TAKE_LIMIT_COUNT_MAX) // 非不限制
|
||||||
&& updateReqVO.getTotalCount() < couponTemplate.getTakeCount()) {
|
&& updateReqVO.getTotalCount() < couponTemplate.getTakeCount()) {
|
||||||
throw exception(COUPON_TEMPLATE_TOTAL_COUNT_TOO_SMALL, couponTemplate.getTakeCount());
|
throw exception(COUPON_TEMPLATE_TOTAL_COUNT_TOO_SMALL, couponTemplate.getTakeCount());
|
||||||
}
|
}
|
||||||
@@ -116,7 +115,10 @@ public class CouponTemplateServiceImpl implements CouponTemplateService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateCouponTemplateTakeCount(Long id, int incrCount) {
|
public void updateCouponTemplateTakeCount(Long id, int incrCount) {
|
||||||
couponTemplateMapper.updateTakeCount(id, incrCount);
|
int updateCount = couponTemplateMapper.updateTakeCount(id, incrCount);
|
||||||
|
if (updateCount == 0) {
|
||||||
|
throw exception(COUPON_TEMPLATE_NOT_ENOUGH);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@@ -76,8 +76,8 @@
|
|||||||
<select id="selectListByPayTimeBetweenAndGroupByMonth"
|
<select id="selectListByPayTimeBetweenAndGroupByMonth"
|
||||||
resultType="cn.iocoder.yudao.module.statistics.controller.admin.trade.vo.TradeOrderTrendRespVO">
|
resultType="cn.iocoder.yudao.module.statistics.controller.admin.trade.vo.TradeOrderTrendRespVO">
|
||||||
SELECT DATE_FORMAT(pay_time, '%Y-%m') AS date,
|
SELECT DATE_FORMAT(pay_time, '%Y-%m') AS date,
|
||||||
COUNT(1) AS orderPayCount,
|
COUNT(1) AS order_pay_count,
|
||||||
SUM(pay_price) AS orderPayPrice
|
SUM(pay_price) AS order_pay_price
|
||||||
FROM trade_order
|
FROM trade_order
|
||||||
WHERE pay_status = TRUE
|
WHERE pay_status = TRUE
|
||||||
AND create_time BETWEEN #{beginTime} AND #{endTime}
|
AND create_time BETWEEN #{beginTime} AND #{endTime}
|
||||||
@@ -95,8 +95,8 @@
|
|||||||
|
|
||||||
<select id="selectPaySummaryByPayStatusAndPayTimeBetween"
|
<select id="selectPaySummaryByPayStatusAndPayTimeBetween"
|
||||||
resultType="cn.iocoder.yudao.module.statistics.controller.admin.trade.vo.TradeOrderSummaryRespVO">
|
resultType="cn.iocoder.yudao.module.statistics.controller.admin.trade.vo.TradeOrderSummaryRespVO">
|
||||||
SELECT IFNULL(SUM(pay_price), 0) AS orderPayPrice,
|
SELECT IFNULL(SUM(pay_price), 0) AS order_pay_price,
|
||||||
COUNT(1) AS orderPayCount
|
COUNT(1) AS order_pay_count
|
||||||
FROM trade_order
|
FROM trade_order
|
||||||
WHERE pay_status = #{payStatus}
|
WHERE pay_status = #{payStatus}
|
||||||
AND pay_time BETWEEN #{beginTime} AND #{endTime}
|
AND pay_time BETWEEN #{beginTime} AND #{endTime}
|
||||||
|
@@ -39,7 +39,8 @@ public interface ErrorCodeConstants {
|
|||||||
ErrorCode ORDER_UPDATE_PAID_ORDER_REFUNDED_FAIL_REFUND_NOT_FOUND = new ErrorCode(1_011_000_034, "交易订单更新支付订单退款状态失败,原因:退款单不存在");
|
ErrorCode ORDER_UPDATE_PAID_ORDER_REFUNDED_FAIL_REFUND_NOT_FOUND = new ErrorCode(1_011_000_034, "交易订单更新支付订单退款状态失败,原因:退款单不存在");
|
||||||
ErrorCode ORDER_UPDATE_PAID_ORDER_REFUNDED_FAIL_REFUND_STATUS_NOT_SUCCESS = new ErrorCode(1_011_000_035, "交易订单更新支付订单退款状态失败,原因:退款单状态不是【退款成功】");
|
ErrorCode ORDER_UPDATE_PAID_ORDER_REFUNDED_FAIL_REFUND_STATUS_NOT_SUCCESS = new ErrorCode(1_011_000_035, "交易订单更新支付订单退款状态失败,原因:退款单状态不是【退款成功】");
|
||||||
ErrorCode ORDER_PICK_UP_FAIL_NOT_VERIFY_USER = new ErrorCode(1_011_000_036, "交易订单自提失败,原因:你没有核销该门店订单的权限");
|
ErrorCode ORDER_PICK_UP_FAIL_NOT_VERIFY_USER = new ErrorCode(1_011_000_036, "交易订单自提失败,原因:你没有核销该门店订单的权限");
|
||||||
ErrorCode ORDER_CREATE_FAIL_INSUFFICIENT_USER_POINTS = new ErrorCode(1_011_000_037, "交易订单创建失败,原因:用户积分不足");
|
ErrorCode ORDER_PICK_UP_FAIL_COMBINATION_NOT_SUCCESS = new ErrorCode(1_011_000_037, "交易订单自提失败,原因:商品拼团记录不是【成功】状态");
|
||||||
|
ErrorCode ORDER_CREATE_FAIL_INSUFFICIENT_USER_POINTS = new ErrorCode(1_011_000_038, "交易订单创建失败,原因:用户积分不足");
|
||||||
|
|
||||||
// ========== After Sale 模块 1-011-000-100 ==========
|
// ========== After Sale 模块 1-011-000-100 ==========
|
||||||
ErrorCode AFTER_SALE_NOT_FOUND = new ErrorCode(1_011_000_100, "售后单不存在");
|
ErrorCode AFTER_SALE_NOT_FOUND = new ErrorCode(1_011_000_100, "售后单不存在");
|
||||||
|
@@ -141,9 +141,8 @@ public class AfterSaleController {
|
|||||||
public CommonResult<Boolean> updateAfterSaleRefunded(@RequestBody PayRefundNotifyReqDTO notifyReqDTO) {
|
public CommonResult<Boolean> updateAfterSaleRefunded(@RequestBody PayRefundNotifyReqDTO notifyReqDTO) {
|
||||||
log.info("[updateAfterRefund][notifyReqDTO({})]", notifyReqDTO);
|
log.info("[updateAfterRefund][notifyReqDTO({})]", notifyReqDTO);
|
||||||
if (StrUtil.startWithAny(notifyReqDTO.getMerchantRefundId(), "order-")) {
|
if (StrUtil.startWithAny(notifyReqDTO.getMerchantRefundId(), "order-")) {
|
||||||
tradeOrderUpdateService.updatePaidOrderRefunded(
|
Long orderId = Long.parseLong(StrUtil.subAfter(notifyReqDTO.getMerchantRefundId(), "order-", true));
|
||||||
Long.parseLong(notifyReqDTO.getMerchantRefundId()),
|
tradeOrderUpdateService.updatePaidOrderRefunded(orderId, notifyReqDTO.getPayRefundId());
|
||||||
notifyReqDTO.getPayRefundId());
|
|
||||||
} else {
|
} else {
|
||||||
afterSaleService.updateAfterSaleRefunded(
|
afterSaleService.updateAfterSaleRefunded(
|
||||||
Long.parseLong(notifyReqDTO.getMerchantRefundId()),
|
Long.parseLong(notifyReqDTO.getMerchantRefundId()),
|
||||||
|
@@ -25,6 +25,9 @@ import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
|
|||||||
import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum;
|
import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum;
|
||||||
import cn.iocoder.yudao.module.product.api.comment.ProductCommentApi;
|
import cn.iocoder.yudao.module.product.api.comment.ProductCommentApi;
|
||||||
import cn.iocoder.yudao.module.product.api.comment.dto.ProductCommentCreateReqDTO;
|
import cn.iocoder.yudao.module.product.api.comment.dto.ProductCommentCreateReqDTO;
|
||||||
|
import cn.iocoder.yudao.module.promotion.api.combination.CombinationRecordApi;
|
||||||
|
import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordRespDTO;
|
||||||
|
import cn.iocoder.yudao.module.promotion.enums.combination.CombinationRecordStatusEnum;
|
||||||
import cn.iocoder.yudao.module.system.api.social.SocialClientApi;
|
import cn.iocoder.yudao.module.system.api.social.SocialClientApi;
|
||||||
import cn.iocoder.yudao.module.system.api.social.dto.SocialWxaSubscribeMessageSendReqDTO;
|
import cn.iocoder.yudao.module.system.api.social.dto.SocialWxaSubscribeMessageSendReqDTO;
|
||||||
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderDeliveryReqVO;
|
import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderDeliveryReqVO;
|
||||||
@@ -121,6 +124,8 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
|
|||||||
public SocialClientApi socialClientApi;
|
public SocialClientApi socialClientApi;
|
||||||
@Resource
|
@Resource
|
||||||
public PayRefundApi payRefundApi;
|
public PayRefundApi payRefundApi;
|
||||||
|
@Resource
|
||||||
|
private CombinationRecordApi combinationRecordApi;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private TradeOrderProperties tradeOrderProperties;
|
private TradeOrderProperties tradeOrderProperties;
|
||||||
@@ -775,6 +780,14 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
|
|||||||
if (ObjUtil.notEqual(DeliveryTypeEnum.PICK_UP.getType(), order.getDeliveryType())) {
|
if (ObjUtil.notEqual(DeliveryTypeEnum.PICK_UP.getType(), order.getDeliveryType())) {
|
||||||
throw exception(ORDER_RECEIVE_FAIL_DELIVERY_TYPE_NOT_PICK_UP);
|
throw exception(ORDER_RECEIVE_FAIL_DELIVERY_TYPE_NOT_PICK_UP);
|
||||||
}
|
}
|
||||||
|
// 情况一:如果是拼团订单,则校验拼团是否成功
|
||||||
|
if (TradeOrderTypeEnum.isCombination(order.getType())) {
|
||||||
|
CombinationRecordRespDTO combinationRecord = combinationRecordApi.getCombinationRecordByOrderId(
|
||||||
|
order.getUserId(), order.getId());
|
||||||
|
if (!CombinationRecordStatusEnum.isSuccess(combinationRecord.getStatus())) {
|
||||||
|
throw exception(ORDER_PICK_UP_FAIL_COMBINATION_NOT_SUCCESS);
|
||||||
|
}
|
||||||
|
}
|
||||||
DeliveryPickUpStoreDO deliveryPickUpStore = pickUpStoreService.getDeliveryPickUpStore(order.getPickUpStoreId());
|
DeliveryPickUpStoreDO deliveryPickUpStore = pickUpStoreService.getDeliveryPickUpStore(order.getPickUpStoreId());
|
||||||
if (deliveryPickUpStore == null
|
if (deliveryPickUpStore == null
|
||||||
|| !CollUtil.contains(deliveryPickUpStore.getVerifyUserIds(), userId)) {
|
|| !CollUtil.contains(deliveryPickUpStore.getVerifyUserIds(), userId)) {
|
||||||
|
@@ -1,10 +1,12 @@
|
|||||||
package cn.iocoder.yudao.module.mp.service.handler.user;
|
package cn.iocoder.yudao.module.mp.service.handler.user;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.ObjUtil;
|
||||||
import cn.iocoder.yudao.module.mp.framework.mp.core.context.MpContextHolder;
|
import cn.iocoder.yudao.module.mp.framework.mp.core.context.MpContextHolder;
|
||||||
import cn.iocoder.yudao.module.mp.service.message.MpAutoReplyService;
|
import cn.iocoder.yudao.module.mp.service.message.MpAutoReplyService;
|
||||||
import cn.iocoder.yudao.module.mp.service.user.MpUserService;
|
import cn.iocoder.yudao.module.mp.service.user.MpUserService;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import me.chanjar.weixin.common.error.WxErrorException;
|
import me.chanjar.weixin.common.error.WxErrorException;
|
||||||
|
import me.chanjar.weixin.common.error.WxMpErrorMsgEnum;
|
||||||
import me.chanjar.weixin.common.session.WxSessionManager;
|
import me.chanjar.weixin.common.session.WxSessionManager;
|
||||||
import me.chanjar.weixin.mp.api.WxMpMessageHandler;
|
import me.chanjar.weixin.mp.api.WxMpMessageHandler;
|
||||||
import me.chanjar.weixin.mp.api.WxMpService;
|
import me.chanjar.weixin.mp.api.WxMpService;
|
||||||
@@ -40,6 +42,13 @@ public class SubscribeHandler implements WxMpMessageHandler {
|
|||||||
wxMpUser = weixinService.getUserService().userInfo(wxMessage.getFromUser());
|
wxMpUser = weixinService.getUserService().userInfo(wxMessage.getFromUser());
|
||||||
} catch (WxErrorException e) {
|
} catch (WxErrorException e) {
|
||||||
log.error("[handle][粉丝({})] 获取粉丝信息失败!", wxMessage.getFromUser(), e);
|
log.error("[handle][粉丝({})] 获取粉丝信息失败!", wxMessage.getFromUser(), e);
|
||||||
|
// 特殊情况(个人账号,无接口权限):https://t.zsxq.com/cLFq5
|
||||||
|
if (ObjUtil.equal(e.getError().getErrorCode(), WxMpErrorMsgEnum.CODE_48001)) {
|
||||||
|
wxMpUser = new WxMpUser();
|
||||||
|
wxMpUser.setOpenId(wxMessage.getFromUser());
|
||||||
|
wxMpUser.setSubscribe(true);
|
||||||
|
wxMpUser.setSubscribeTime(System.currentTimeMillis() / 1000L);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 第二步,保存粉丝信息
|
// 第二步,保存粉丝信息
|
||||||
|
@@ -127,6 +127,8 @@ public class AuthController {
|
|||||||
@PostMapping("/sms-login")
|
@PostMapping("/sms-login")
|
||||||
@PermitAll
|
@PermitAll
|
||||||
@Operation(summary = "使用短信验证码登录")
|
@Operation(summary = "使用短信验证码登录")
|
||||||
|
// 可按需开启限流:https://github.com/YunaiV/ruoyi-vue-pro/issues/851
|
||||||
|
// @RateLimiter(time = 60, count = 6, keyResolver = ExpressionRateLimiterKeyResolver.class, keyArg = "#reqVO.mobile")
|
||||||
public CommonResult<AuthLoginRespVO> smsLogin(@RequestBody @Valid AuthSmsLoginReqVO reqVO) {
|
public CommonResult<AuthLoginRespVO> smsLogin(@RequestBody @Valid AuthSmsLoginReqVO reqVO) {
|
||||||
return success(authService.smsLogin(reqVO));
|
return success(authService.smsLogin(reqVO));
|
||||||
}
|
}
|
||||||
|
@@ -38,7 +38,8 @@ public class SocialUserController {
|
|||||||
@PostMapping("/bind")
|
@PostMapping("/bind")
|
||||||
@Operation(summary = "社交绑定,使用 code 授权码")
|
@Operation(summary = "社交绑定,使用 code 授权码")
|
||||||
public CommonResult<Boolean> socialBind(@RequestBody @Valid SocialUserBindReqVO reqVO) {
|
public CommonResult<Boolean> socialBind(@RequestBody @Valid SocialUserBindReqVO reqVO) {
|
||||||
socialUserService.bindSocialUser(BeanUtils.toBean(reqVO, SocialUserBindReqDTO.class)
|
socialUserService.bindSocialUser(new SocialUserBindReqDTO().setSocialType(reqVO.getType())
|
||||||
|
.setCode(reqVO.getCode()).setState(reqVO.getState())
|
||||||
.setUserId(getLoginUserId()).setUserType(UserTypeEnum.ADMIN.getValue()));
|
.setUserId(getLoginUserId()).setUserType(UserTypeEnum.ADMIN.getValue()));
|
||||||
return CommonResult.success(true);
|
return CommonResult.success(true);
|
||||||
}
|
}
|
||||||
|
@@ -6,8 +6,8 @@ server:
|
|||||||
spring:
|
spring:
|
||||||
autoconfigure:
|
autoconfigure:
|
||||||
exclude:
|
exclude:
|
||||||
- org.springframework.ai.autoconfigure.vectorstore.qdrant.QdrantVectorStoreAutoConfiguration # 禁用 AI 模块的 Qdrant,手动创建
|
- org.springframework.ai.vectorstore.qdrant.autoconfigure.QdrantVectorStoreAutoConfiguration # 禁用 AI 模块的 Qdrant,手动创建
|
||||||
- org.springframework.ai.autoconfigure.vectorstore.milvus.MilvusVectorStoreAutoConfiguration # 禁用 AI 模块的 Milvus,手动创建
|
- org.springframework.ai.vectorstore.milvus.autoconfigure.MilvusVectorStoreAutoConfiguration # 禁用 AI 模块的 Milvus,手动创建
|
||||||
# 数据源配置项
|
# 数据源配置项
|
||||||
autoconfigure:
|
autoconfigure:
|
||||||
exclude:
|
exclude:
|
||||||
|
@@ -11,8 +11,8 @@ spring:
|
|||||||
- de.codecentric.boot.admin.server.config.AdminServerAutoConfiguration # 禁用 Spring Boot Admin 的 Server 的自动配置
|
- de.codecentric.boot.admin.server.config.AdminServerAutoConfiguration # 禁用 Spring Boot Admin 的 Server 的自动配置
|
||||||
- de.codecentric.boot.admin.server.ui.config.AdminServerUiAutoConfiguration # 禁用 Spring Boot Admin 的 Server UI 的自动配置
|
- de.codecentric.boot.admin.server.ui.config.AdminServerUiAutoConfiguration # 禁用 Spring Boot Admin 的 Server UI 的自动配置
|
||||||
- de.codecentric.boot.admin.client.config.SpringBootAdminClientAutoConfiguration # 禁用 Spring Boot Admin 的 Client 的自动配置
|
- de.codecentric.boot.admin.client.config.SpringBootAdminClientAutoConfiguration # 禁用 Spring Boot Admin 的 Client 的自动配置
|
||||||
- org.springframework.ai.autoconfigure.vectorstore.qdrant.QdrantVectorStoreAutoConfiguration # 禁用 AI 模块的 Qdrant,手动创建
|
- org.springframework.ai.vectorstore.qdrant.autoconfigure.QdrantVectorStoreAutoConfiguration # 禁用 AI 模块的 Qdrant,手动创建
|
||||||
- org.springframework.ai.autoconfigure.vectorstore.milvus.MilvusVectorStoreAutoConfiguration # 禁用 AI 模块的 Milvus,手动创建
|
- org.springframework.ai.vectorstore.milvus.autoconfigure.MilvusVectorStoreAutoConfiguration # 禁用 AI 模块的 Milvus,手动创建
|
||||||
# 数据源配置项
|
# 数据源配置项
|
||||||
datasource:
|
datasource:
|
||||||
druid: # Druid 【监控】相关的全局配置
|
druid: # Druid 【监控】相关的全局配置
|
||||||
|
@@ -150,7 +150,7 @@ spring:
|
|||||||
vectorstore: # 向量存储
|
vectorstore: # 向量存储
|
||||||
redis:
|
redis:
|
||||||
initialize-schema: true
|
initialize-schema: true
|
||||||
index: knowledge_index # Redis 中向量索引的名称:用于存储和检索向量数据的索引标识符,所有相关的向量搜索操作都会基于这个索引进行
|
index-name: knowledge_index # Redis 中向量索引的名称:用于存储和检索向量数据的索引标识符,所有相关的向量搜索操作都会基于这个索引进行
|
||||||
prefix: "knowledge_segment:" # Redis 中存储向量数据的键名前缀:这个前缀会添加到每个存储在 Redis 中的向量数据键名前,每个 document 都是一个 hash 结构
|
prefix: "knowledge_segment:" # Redis 中存储向量数据的键名前缀:这个前缀会添加到每个存储在 Redis 中的向量数据键名前,每个 document 都是一个 hash 结构
|
||||||
qdrant:
|
qdrant:
|
||||||
initialize-schema: true
|
initialize-schema: true
|
||||||
@@ -188,13 +188,14 @@ spring:
|
|||||||
api-key: xxxx
|
api-key: xxxx
|
||||||
moonshot: # 月之暗灭(KIMI)
|
moonshot: # 月之暗灭(KIMI)
|
||||||
api-key: sk-abc
|
api-key: sk-abc
|
||||||
|
deepseek: # DeepSeek
|
||||||
|
api-key: sk-e94db327cc7d457d99a8de8810fc6b12
|
||||||
|
chat:
|
||||||
|
options:
|
||||||
|
model: deepseek-chat
|
||||||
|
|
||||||
yudao:
|
yudao:
|
||||||
ai:
|
ai:
|
||||||
deep-seek: # DeepSeek
|
|
||||||
enable: true
|
|
||||||
api-key: sk-e94db327cc7d457d99a8de8810fc6b12
|
|
||||||
model: deepseek-chat
|
|
||||||
doubao: # 字节豆包
|
doubao: # 字节豆包
|
||||||
enable: true
|
enable: true
|
||||||
api-key: 5c1b5747-26d2-4ebd-a4e0-dd0e8d8b4272
|
api-key: 5c1b5747-26d2-4ebd-a4e0-dd0e8d8b4272
|
||||||
|
Reference in New Issue
Block a user