diff --git a/pom.xml b/pom.xml
index e85946ce91..fdb62e5464 100644
--- a/pom.xml
+++ b/pom.xml
@@ -32,7 +32,7 @@
https://github.com/YunaiV/ruoyi-vue-pro
- 2.6.0-SNAPSHOT
+ 2025.08-SNAPSHOT
17
${java.version}
@@ -87,6 +87,13 @@
lombok
${lombok.version}
+
+
+ org.projectlombok
+ lombok-mapstruct-binding
+ 0.2.0
+
org.mapstruct
mapstruct-processor
diff --git a/sql/postgresql/quartz.sql b/sql/postgresql/quartz.sql
index 4ec390c527..46bb938431 100644
--- a/sql/postgresql/quartz.sql
+++ b/sql/postgresql/quartz.sql
@@ -1,253 +1,208 @@
--- ----------------------------
--- qrtz_blob_triggers
--- ----------------------------
-CREATE TABLE qrtz_blob_triggers
+-- https://github.com/quartz-scheduler/quartz/blob/main/quartz/src/main/resources/org/quartz/impl/jdbcjobstore/tables_postgres.sql
+-- Thanks to Patrick Lightbody for submitting this...
+--
+-- 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,
- trigger_name varchar(190) NOT NULL,
- trigger_group varchar(190) NOT NULL,
- blob_data bytea NULL,
- PRIMARY KEY (sched_name, trigger_name, trigger_group)
+ SCHED_NAME VARCHAR(120) NOT NULL,
+ JOB_NAME VARCHAR(200) NOT NULL,
+ JOB_GROUP VARCHAR(200) NOT NULL,
+ DESCRIPTION VARCHAR(250) NULL,
+ 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);
-
--- ----------------------------
--- qrtz_calendars
--- ----------------------------
-CREATE TABLE qrtz_calendars
+CREATE TABLE QRTZ_TRIGGERS
(
- sched_name varchar(120) NOT NULL,
- calendar_name varchar(190) NOT NULL,
- calendar bytea NOT NULL,
- PRIMARY KEY (sched_name, calendar_name)
+ SCHED_NAME VARCHAR(120) NOT NULL,
+ TRIGGER_NAME VARCHAR(200) NOT NULL,
+ TRIGGER_GROUP VARCHAR(200) NOT NULL,
+ 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)
);
--- ----------------------------
--- qrtz_cron_triggers
--- ----------------------------
-CREATE TABLE qrtz_cron_triggers
+CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS
(
- sched_name varchar(120) NOT NULL,
- trigger_name varchar(190) NOT NULL,
- trigger_group varchar(190) NOT NULL,
- cron_expression varchar(120) NOT NULL,
- time_zone_id varchar(80) NULL DEFAULT NULL,
- PRIMARY KEY (sched_name, trigger_name, trigger_group)
+ SCHED_NAME VARCHAR(120) NOT NULL,
+ TRIGGER_GROUP VARCHAR(200) NOT NULL,
+ PRIMARY KEY (SCHED_NAME, TRIGGER_GROUP)
);
--- @formatter:off
-BEGIN;
-COMMIT;
--- @formatter:on
-
--- ----------------------------
--- qrtz_fired_triggers
--- ----------------------------
-CREATE TABLE qrtz_fired_triggers
+CREATE TABLE QRTZ_FIRED_TRIGGERS
(
- sched_name varchar(120) NOT NULL,
- entry_id varchar(95) NOT NULL,
- trigger_name varchar(190) NOT NULL,
- trigger_group varchar(190) NOT NULL,
- instance_name varchar(190) NOT NULL,
- fired_time int8 NOT NULL,
- sched_time int8 NOT NULL,
- priority int4 NOT NULL,
- state varchar(16) NOT NULL,
- job_name varchar(190) NULL DEFAULT NULL,
- job_group varchar(190) NULL DEFAULT NULL,
- is_nonconcurrent varchar(1) NULL DEFAULT NULL,
- requests_recovery varchar(1) NULL DEFAULT NULL,
- PRIMARY KEY (sched_name, entry_id)
+ SCHED_NAME VARCHAR(120) NOT NULL,
+ ENTRY_ID VARCHAR(95) NOT NULL,
+ TRIGGER_NAME VARCHAR(200) NOT NULL,
+ TRIGGER_GROUP VARCHAR(200) NOT NULL,
+ INSTANCE_NAME VARCHAR(200) NOT NULL,
+ FIRED_TIME BIGINT NOT NULL,
+ SCHED_TIME BIGINT NOT NULL,
+ PRIORITY INTEGER NOT NULL,
+ STATE VARCHAR(16) NOT NULL,
+ JOB_NAME VARCHAR(200) NULL,
+ JOB_GROUP VARCHAR(200) NULL,
+ IS_NONCONCURRENT BOOL NULL,
+ REQUESTS_RECOVERY BOOL NULL,
+ PRIMARY KEY (SCHED_NAME, ENTRY_ID)
);
-CREATE INDEX idx_qrtz_ft_trig_inst_name ON qrtz_fired_triggers (sched_name, instance_name);
-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
+CREATE TABLE QRTZ_SCHEDULER_STATE
(
- sched_name varchar(120) NOT NULL,
- job_name varchar(190) NOT NULL,
- job_group varchar(190) NOT NULL,
- description varchar(250) NULL DEFAULT NULL,
- job_class_name varchar(250) NOT NULL,
- 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)
+ SCHED_NAME VARCHAR(120) NOT NULL,
+ INSTANCE_NAME VARCHAR(200) NOT NULL,
+ LAST_CHECKIN_TIME BIGINT NOT NULL,
+ CHECKIN_INTERVAL BIGINT NOT NULL,
+ PRIMARY KEY (SCHED_NAME, INSTANCE_NAME)
);
-CREATE INDEX idx_qrtz_j_req_recovery ON qrtz_job_details (sched_name, requests_recovery);
-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
+CREATE TABLE QRTZ_LOCKS
(
- sched_name varchar(120) NOT NULL,
- lock_name varchar(40) NOT NULL,
- PRIMARY KEY (sched_name, lock_name)
+ SCHED_NAME VARCHAR(120) NOT NULL,
+ LOCK_NAME VARCHAR(40) NOT NULL,
+ PRIMARY KEY (SCHED_NAME, LOCK_NAME)
);
--- @formatter:off
-BEGIN;
-COMMIT;
--- @formatter:on
+CREATE INDEX IDX_QRTZ_J_REQ_RECOVERY
+ ON QRTZ_JOB_DETAILS (SCHED_NAME, REQUESTS_RECOVERY);
+CREATE INDEX IDX_QRTZ_J_GRP
+ ON QRTZ_JOB_DETAILS (SCHED_NAME, JOB_GROUP);
--- ----------------------------
--- qrtz_paused_trigger_grps
--- ----------------------------
-CREATE TABLE qrtz_paused_trigger_grps
-(
- sched_name varchar(120) NOT NULL,
- trigger_group varchar(190) NOT NULL,
- PRIMARY KEY (sched_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);
--- ----------------------------
--- qrtz_scheduler_state
--- ----------------------------
-CREATE TABLE qrtz_scheduler_state
-(
- sched_name varchar(120) NOT NULL,
- instance_name varchar(190) NOT NULL,
- last_checkin_time int8 NOT NULL,
- checkin_interval int8 NOT NULL,
- PRIMARY KEY (sched_name, instance_name)
-);
-
--- @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
+CREATE INDEX IDX_QRTZ_FT_TRIG_INST_NAME
+ ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, INSTANCE_NAME);
+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);
--- ----------------------------
--- 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);
+COMMIT;
\ No newline at end of file
diff --git a/sql/tools/convertor.py b/sql/tools/convertor.py
index 7ab8ad1ef3..d286d3b238 100644
--- a/sql/tools/convertor.py
+++ b/sql/tools/convertor.py
@@ -17,6 +17,7 @@ uv run --with simple-ddl-parser convertor.py dm8 > ../dm/ruoyi-vue-pro-dm8.sql
import argparse
import pathlib
import re
+import sys
import time
from abc import ABC, abstractmethod
from typing import Dict, Generator, Optional, Tuple, Union
@@ -293,8 +294,10 @@ class Convertor(ABC):
# 将parse失败的脚本打印出来
if error_scripts:
+ print("!!! 以下内容无法正常解析", file=sys.stderr)
for script in error_scripts:
- print(script)
+ # print to stderr
+ print(script, file=sys.stderr)
class PostgreSQLConvertor(Convertor):
diff --git a/yudao-dependencies/pom.xml b/yudao-dependencies/pom.xml
index 94c3e31aa8..668249863a 100644
--- a/yudao-dependencies/pom.xml
+++ b/yudao-dependencies/pom.xml
@@ -14,7 +14,7 @@
https://github.com/YunaiV/ruoyi-vue-pro
- 2.6.0-SNAPSHOT
+ 2025.08-SNAPSHOT
1.6.0
3.4.5
@@ -24,9 +24,9 @@
1.2.24
3.5.19
- 3.5.10.1
+ 3.5.12
+ 1.5.4
4.3.1
- 1.4.13
3.0.6
3.41.0
8.1.3.140
@@ -54,7 +54,7 @@
1.6.3
5.8.35
6.0.0-M19
- 4.0.3
+ 1.2.0
2.4.1
1.2.83
33.4.8-jre
@@ -69,12 +69,11 @@
0.9.0
4.5.13
- 2.17.0
- 1.27.1
2.30.14
1.16.7
1.4.0
- 1.9.4
+ 2.0.0
+ 1.9.5
4.7.5.B
@@ -479,20 +478,11 @@
- com.alibaba
- easyexcel
- ${easyexcel.version}
-
-
- commons-io
- commons-io
- ${commons-io.version}
-
-
- org.apache.commons
- commons-compress
- ${commons-compress.version}
+ cn.idev.excel
+ fastexcel
+ ${fastexcel.version}
+
org.apache.tika
tika-core
@@ -603,7 +593,7 @@
org.jeecgframework.jimureport
jimubi-spring-boot3-starter
- ${jimureport.version}
+ ${jimubi.version}
com.github.jsqlparser
diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/cache/CacheUtils.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/cache/CacheUtils.java
index 12a6e17246..4d9168ebdb 100644
--- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/cache/CacheUtils.java
+++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/cache/CacheUtils.java
@@ -14,6 +14,13 @@ import java.util.concurrent.Executors;
*/
public class CacheUtils {
+ /**
+ * 异步刷新的 LoadingCache 最大缓存数量
+ *
+ * @see 本地缓存 CacheUtils 工具类建议
+ */
+ private static final Integer CACHE_MAX_SIZE = 10000;
+
/**
* 构建异步刷新的 LoadingCache 对象
*
@@ -29,6 +36,7 @@ public class CacheUtils {
*/
public static LoadingCache buildAsyncReloadingCache(Duration duration, CacheLoader loader) {
return CacheBuilder.newBuilder()
+ .maximumSize(CACHE_MAX_SIZE)
// 只阻塞当前数据加载线程,其他线程返回旧值
.refreshAfterWrite(duration)
// 通过 asyncReloading 实现全异步加载,包括 refreshAfterWrite 被阻塞的加载线程
@@ -43,7 +51,11 @@ public class CacheUtils {
* @return LoadingCache 对象
*/
public static LoadingCache buildCache(Duration duration, CacheLoader loader) {
- return CacheBuilder.newBuilder().refreshAfterWrite(duration).build(loader);
+ return CacheBuilder.newBuilder()
+ .maximumSize(CACHE_MAX_SIZE)
+ // 只阻塞当前数据加载线程,其他线程返回旧值
+ .refreshAfterWrite(duration)
+ .build(loader);
}
}
diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/DateUtils.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/DateUtils.java
index b51a838c69..d6051e85fe 100644
--- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/DateUtils.java
+++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/DateUtils.java
@@ -74,30 +74,30 @@ public class DateUtils {
* 创建指定时间
*
* @param year 年
- * @param mouth 月
+ * @param month 月
* @param day 日
* @return 指定时间
*/
- public static Date buildTime(int year, int mouth, int day) {
- return buildTime(year, mouth, day, 0, 0, 0);
+ public static Date buildTime(int year, int month, int day) {
+ return buildTime(year, month, day, 0, 0, 0);
}
/**
* 创建指定时间
*
* @param year 年
- * @param mouth 月
+ * @param month 月
* @param day 日
* @param hour 小时
* @param minute 分钟
* @param second 秒
* @return 指定时间
*/
- public static Date buildTime(int year, int mouth, int day,
+ public static Date buildTime(int year, int month, int day,
int hour, int minute, int second) {
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.YEAR, year);
- calendar.set(Calendar.MONTH, mouth - 1);
+ calendar.set(Calendar.MONTH, month - 1);
calendar.set(Calendar.DAY_OF_MONTH, day);
calendar.set(Calendar.HOUR_OF_DAY, hour);
calendar.set(Calendar.MINUTE, minute);
diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/LocalDateTimeUtils.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/LocalDateTimeUtils.java
index a2c5241e1d..4cbd4b6183 100644
--- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/LocalDateTimeUtils.java
+++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/LocalDateTimeUtils.java
@@ -69,17 +69,17 @@ public class LocalDateTimeUtils {
* 创建指定时间
*
* @param year 年
- * @param mouth 月
+ * @param month 月
* @param day 日
* @return 指定时间
*/
- public static LocalDateTime buildTime(int year, int mouth, int day) {
- return LocalDateTime.of(year, mouth, day, 0, 0, 0);
+ public static LocalDateTime buildTime(int year, int month, int day) {
+ return LocalDateTime.of(year, month, day, 0, 0, 0);
}
- public static LocalDateTime[] buildBetweenTime(int year1, int mouth1, int day1,
- int year2, int mouth2, int day2) {
- return new LocalDateTime[]{buildTime(year1, mouth1, day1), buildTime(year2, mouth2, day2)};
+ public static LocalDateTime[] buildBetweenTime(int year1, int month1, int day1,
+ int year2, int month2, int day2) {
+ return new LocalDateTime[]{buildTime(year1, month1, day1), buildTime(year2, month2, day2)};
}
/**
diff --git a/yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/config/YudaoDeptDataPermissionAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/config/YudaoDeptDataPermissionAutoConfiguration.java
index ab5b3bc7ec..46b6ca7c59 100644
--- a/yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/config/YudaoDeptDataPermissionAutoConfiguration.java
+++ b/yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/config/YudaoDeptDataPermissionAutoConfiguration.java
@@ -18,7 +18,7 @@ import java.util.List;
*/
@AutoConfiguration
@ConditionalOnClass(LoginUser.class)
-@ConditionalOnBean(value = {PermissionCommonApi.class, DeptDataPermissionRuleCustomizer.class})
+@ConditionalOnBean(value = {DeptDataPermissionRuleCustomizer.class})
public class YudaoDeptDataPermissionAutoConfiguration {
@Bean
diff --git a/yudao-framework/yudao-spring-boot-starter-excel/pom.xml b/yudao-framework/yudao-spring-boot-starter-excel/pom.xml
index 0413986a64..b1218183cb 100644
--- a/yudao-framework/yudao-spring-boot-starter-excel/pom.xml
+++ b/yudao-framework/yudao-spring-boot-starter-excel/pom.xml
@@ -42,8 +42,14 @@
- com.alibaba
- easyexcel
+ cn.idev.excel
+ fastexcel
+
+
+
+ jakarta.validation
+ jakarta.validation-api
+ provided
@@ -51,11 +57,6 @@
guava
-
- org.apache.commons
- commons-compress
-
-
cn.iocoder.boot
yudao-spring-boot-starter-biz-ip
diff --git a/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/dict/core/DictFrameworkUtils.java b/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/dict/core/DictFrameworkUtils.java
index 9fc67bfe7f..2a2350f02b 100644
--- a/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/dict/core/DictFrameworkUtils.java
+++ b/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/dict/core/DictFrameworkUtils.java
@@ -76,4 +76,9 @@ public class DictFrameworkUtils {
return dictData!= null ? dictData.getValue(): null;
}
+ @SneakyThrows
+ public static List getDictDataValueList(String dictType) {
+ List dictDatas = GET_DICT_DATA_CACHE.get(dictType);
+ return convertList(dictDatas, DictDataRespDTO::getValue);
+ }
}
diff --git a/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/dict/validation/InDict.java b/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/dict/validation/InDict.java
new file mode 100644
index 0000000000..de7498775c
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/dict/validation/InDict.java
@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.framework.dict.validation;
+
+import jakarta.validation.Constraint;
+import jakarta.validation.Payload;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({
+ ElementType.METHOD,
+ ElementType.FIELD,
+ ElementType.ANNOTATION_TYPE,
+ ElementType.CONSTRUCTOR,
+ ElementType.PARAMETER,
+ ElementType.TYPE_USE
+})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Constraint(
+ validatedBy = {InDictValidator.class, InDictCollectionValidator.class}
+)
+public @interface InDict {
+
+ /**
+ * 数据字典 type
+ */
+ String type();
+
+ String message() default "必须在指定范围 {value}";
+
+ Class>[] groups() default {};
+
+ Class extends Payload>[] payload() default {};
+
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/dict/validation/InDictCollectionValidator.java b/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/dict/validation/InDictCollectionValidator.java
new file mode 100644
index 0000000000..a7184b066a
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/dict/validation/InDictCollectionValidator.java
@@ -0,0 +1,43 @@
+package cn.iocoder.yudao.framework.dict.validation;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.dict.core.DictFrameworkUtils;
+import jakarta.validation.ConstraintValidator;
+import jakarta.validation.ConstraintValidatorContext;
+
+import java.util.Collection;
+import java.util.List;
+
+public class InDictCollectionValidator implements ConstraintValidator> {
+
+ private String dictType;
+
+ @Override
+ public void initialize(InDict annotation) {
+ this.dictType = annotation.type();
+ }
+
+ @Override
+ public boolean isValid(Collection> list, ConstraintValidatorContext context) {
+ // 为空时,默认不校验,即认为通过
+ if (CollUtil.isEmpty(list)) {
+ return true;
+ }
+ // 校验全部通过
+ List dbValues = DictFrameworkUtils.getDictDataValueList(dictType);
+ boolean match = list.stream().allMatch(v -> dbValues.stream()
+ .anyMatch(dbValue -> dbValue.equalsIgnoreCase(v.toString())));
+ if (match) {
+ return true;
+ }
+
+ // 校验不通过,自定义提示语句
+ context.disableDefaultConstraintViolation(); // 禁用默认的 message 的值
+ context.buildConstraintViolationWithTemplate(
+ context.getDefaultConstraintMessageTemplate().replaceAll("\\{value}", dbValues.toString())
+ ).addConstraintViolation(); // 重新添加错误提示语句
+ return false;
+ }
+
+}
+
diff --git a/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/dict/validation/InDictValidator.java b/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/dict/validation/InDictValidator.java
new file mode 100644
index 0000000000..b67f017750
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/dict/validation/InDictValidator.java
@@ -0,0 +1,41 @@
+package cn.iocoder.yudao.framework.dict.validation;
+
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.dict.core.DictFrameworkUtils;
+import jakarta.validation.ConstraintValidator;
+import jakarta.validation.ConstraintValidatorContext;
+
+import java.util.List;
+
+public class InDictValidator implements ConstraintValidator {
+
+ private String dictType;
+
+ @Override
+ public void initialize(InDict annotation) {
+ this.dictType = annotation.type();
+ }
+
+ @Override
+ public boolean isValid(Object value, ConstraintValidatorContext context) {
+ // 为空时,默认不校验,即认为通过
+ if (value == null) {
+ return true;
+ }
+ // 校验通过
+ final List values = DictFrameworkUtils.getDictDataValueList(dictType);
+ boolean match = values.stream().anyMatch(v -> StrUtil.equalsIgnoreCase(v, value.toString()));
+ if (match) {
+ return true;
+ }
+
+ // 校验不通过,自定义提示语句
+ context.disableDefaultConstraintViolation(); // 禁用默认的 message 的值
+ context.buildConstraintViolationWithTemplate(
+ context.getDefaultConstraintMessageTemplate().replaceAll("\\{value}", values.toString())
+ ).addConstraintViolation(); // 重新添加错误提示语句
+ return false;
+ }
+
+}
+
diff --git a/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/convert/AreaConvert.java b/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/convert/AreaConvert.java
index 9778b17aea..b5ca863173 100644
--- a/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/convert/AreaConvert.java
+++ b/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/convert/AreaConvert.java
@@ -3,11 +3,11 @@ package cn.iocoder.yudao.framework.excel.core.convert;
import cn.hutool.core.convert.Convert;
import cn.iocoder.yudao.framework.ip.core.Area;
import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils;
-import com.alibaba.excel.converters.Converter;
-import com.alibaba.excel.enums.CellDataTypeEnum;
-import com.alibaba.excel.metadata.GlobalConfiguration;
-import com.alibaba.excel.metadata.data.ReadCellData;
-import com.alibaba.excel.metadata.property.ExcelContentProperty;
+import cn.idev.excel.converters.Converter;
+import cn.idev.excel.enums.CellDataTypeEnum;
+import cn.idev.excel.metadata.GlobalConfiguration;
+import cn.idev.excel.metadata.data.ReadCellData;
+import cn.idev.excel.metadata.property.ExcelContentProperty;
import lombok.extern.slf4j.Slf4j;
/**
diff --git a/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/convert/DictConvert.java b/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/convert/DictConvert.java
index e393195ed1..b9e0dcb735 100644
--- a/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/convert/DictConvert.java
+++ b/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/convert/DictConvert.java
@@ -3,12 +3,12 @@ package cn.iocoder.yudao.framework.excel.core.convert;
import cn.hutool.core.convert.Convert;
import cn.iocoder.yudao.framework.dict.core.DictFrameworkUtils;
import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
-import com.alibaba.excel.converters.Converter;
-import com.alibaba.excel.enums.CellDataTypeEnum;
-import com.alibaba.excel.metadata.GlobalConfiguration;
-import com.alibaba.excel.metadata.data.ReadCellData;
-import com.alibaba.excel.metadata.data.WriteCellData;
-import com.alibaba.excel.metadata.property.ExcelContentProperty;
+import cn.idev.excel.converters.Converter;
+import cn.idev.excel.enums.CellDataTypeEnum;
+import cn.idev.excel.metadata.GlobalConfiguration;
+import cn.idev.excel.metadata.data.ReadCellData;
+import cn.idev.excel.metadata.data.WriteCellData;
+import cn.idev.excel.metadata.property.ExcelContentProperty;
import lombok.extern.slf4j.Slf4j;
/**
diff --git a/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/convert/JsonConvert.java b/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/convert/JsonConvert.java
index 0d4794e5fa..6958c32e0c 100644
--- a/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/convert/JsonConvert.java
+++ b/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/convert/JsonConvert.java
@@ -1,11 +1,11 @@
package cn.iocoder.yudao.framework.excel.core.convert;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
-import com.alibaba.excel.converters.Converter;
-import com.alibaba.excel.enums.CellDataTypeEnum;
-import com.alibaba.excel.metadata.GlobalConfiguration;
-import com.alibaba.excel.metadata.data.WriteCellData;
-import com.alibaba.excel.metadata.property.ExcelContentProperty;
+import cn.idev.excel.converters.Converter;
+import cn.idev.excel.enums.CellDataTypeEnum;
+import cn.idev.excel.metadata.GlobalConfiguration;
+import cn.idev.excel.metadata.data.WriteCellData;
+import cn.idev.excel.metadata.property.ExcelContentProperty;
/**
* Excel Json 转换器
diff --git a/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/convert/MoneyConvert.java b/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/convert/MoneyConvert.java
index ee66fe7dec..9ed0bd581f 100644
--- a/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/convert/MoneyConvert.java
+++ b/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/convert/MoneyConvert.java
@@ -1,10 +1,10 @@
package cn.iocoder.yudao.framework.excel.core.convert;
-import com.alibaba.excel.converters.Converter;
-import com.alibaba.excel.enums.CellDataTypeEnum;
-import com.alibaba.excel.metadata.GlobalConfiguration;
-import com.alibaba.excel.metadata.data.WriteCellData;
-import com.alibaba.excel.metadata.property.ExcelContentProperty;
+import cn.idev.excel.converters.Converter;
+import cn.idev.excel.enums.CellDataTypeEnum;
+import cn.idev.excel.metadata.GlobalConfiguration;
+import cn.idev.excel.metadata.data.WriteCellData;
+import cn.idev.excel.metadata.property.ExcelContentProperty;
import java.math.BigDecimal;
import java.math.RoundingMode;
diff --git a/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/handler/ColumnWidthMatchStyleStrategy.java b/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/handler/ColumnWidthMatchStyleStrategy.java
new file mode 100644
index 0000000000..49a5b31572
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/handler/ColumnWidthMatchStyleStrategy.java
@@ -0,0 +1,78 @@
+package cn.iocoder.yudao.framework.excel.core.handler;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.idev.excel.enums.CellDataTypeEnum;
+import cn.idev.excel.metadata.Head;
+import cn.idev.excel.metadata.data.WriteCellData;
+import cn.idev.excel.util.MapUtils;
+import cn.idev.excel.write.metadata.holder.WriteSheetHolder;
+import cn.idev.excel.write.style.column.AbstractColumnWidthStyleStrategy;
+import cn.idev.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
+import org.apache.poi.ss.usermodel.Cell;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Excel 自适应列宽处理器
+ *
+ * 相比 {@link LongestMatchColumnWidthStyleStrategy} 来说,额外处理了 DATE 类型!
+ *
+ * @see 添加自适应列宽处理器,并替换默认列宽策略
+ * @author hmb
+ */
+public class ColumnWidthMatchStyleStrategy extends AbstractColumnWidthStyleStrategy {
+
+ private static final int MAX_COLUMN_WIDTH = 255;
+
+ private final Map> cache = MapUtils.newHashMapWithExpectedSize(8);
+
+ @Override
+ protected void setColumnWidth(WriteSheetHolder writeSheetHolder, List> cellDataList, Cell cell,
+ Head head, Integer relativeRowIndex, Boolean isHead) {
+ boolean needSetWidth = isHead || CollUtil.isNotEmpty(cellDataList);
+ if (!needSetWidth) {
+ return;
+ }
+ Map maxColumnWidthMap = cache.computeIfAbsent(writeSheetHolder.getSheetNo(),
+ key -> new HashMap<>(16));
+ Integer columnWidth = dataLength(cellDataList, cell, isHead);
+ if (columnWidth < 0) {
+ return;
+ }
+ if (columnWidth > MAX_COLUMN_WIDTH) {
+ columnWidth = MAX_COLUMN_WIDTH;
+ }
+ Integer maxColumnWidth = maxColumnWidthMap.get(cell.getColumnIndex());
+ if (maxColumnWidth == null || columnWidth > maxColumnWidth) {
+ maxColumnWidthMap.put(cell.getColumnIndex(), columnWidth);
+ writeSheetHolder.getSheet().setColumnWidth(cell.getColumnIndex(), columnWidth * 256);
+ }
+ }
+
+ @SuppressWarnings("EnhancedSwitchMigration")
+ private Integer dataLength(List> cellDataList, Cell cell, Boolean isHead) {
+ if (isHead) {
+ return cell.getStringCellValue().getBytes().length;
+ }
+ WriteCellData> cellData = cellDataList.get(0);
+ CellDataTypeEnum type = cellData.getType();
+ if (type == null) {
+ return -1;
+ }
+ switch (type) {
+ case STRING:
+ return cellData.getStringValue().getBytes().length;
+ case BOOLEAN:
+ return cellData.getBooleanValue().toString().getBytes().length;
+ case NUMBER:
+ return cellData.getNumberValue().toString().getBytes().length;
+ case DATE:
+ return cellData.getDateValue().toString().getBytes().length;
+ default:
+ return -1;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/handler/SelectSheetWriteHandler.java b/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/handler/SelectSheetWriteHandler.java
index 8e3e28eb44..c55e1210a2 100644
--- a/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/handler/SelectSheetWriteHandler.java
+++ b/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/handler/SelectSheetWriteHandler.java
@@ -3,7 +3,6 @@ package cn.iocoder.yudao.framework.excel.core.handler;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
-import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.poi.excel.ExcelUtil;
@@ -11,12 +10,12 @@ import cn.iocoder.yudao.framework.common.core.KeyValue;
import cn.iocoder.yudao.framework.dict.core.DictFrameworkUtils;
import cn.iocoder.yudao.framework.excel.core.annotations.ExcelColumnSelect;
import cn.iocoder.yudao.framework.excel.core.function.ExcelColumnSelectFunction;
-import com.alibaba.excel.annotation.ExcelIgnore;
-import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
-import com.alibaba.excel.annotation.ExcelProperty;
-import com.alibaba.excel.write.handler.SheetWriteHandler;
-import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
-import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
+import cn.idev.excel.annotation.ExcelIgnore;
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import cn.idev.excel.write.handler.SheetWriteHandler;
+import cn.idev.excel.write.metadata.holder.WriteSheetHolder;
+import cn.idev.excel.write.metadata.holder.WriteWorkbookHolder;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.hssf.usermodel.HSSFDataValidation;
import org.apache.poi.ss.usermodel.*;
@@ -87,7 +86,7 @@ public class SelectSheetWriteHandler implements SheetWriteHandler {
/**
* 判断字段是否是静态的、最终的、 transient 的
- * 原因:EasyExcel 默认是忽略 static final 或 transient 的字段,所以需要判断
+ * 原因:FastExcel 默认是忽略 static final 或 transient 的字段,所以需要判断
*
* @param field 字段
* @return 是否是静态的、最终的、transient 的
@@ -185,4 +184,4 @@ public class SelectSheetWriteHandler implements SheetWriteHandler {
writeSheetHolder.getSheet().addValidationData(validation);
}
-}
\ No newline at end of file
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/util/ExcelUtils.java b/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/util/ExcelUtils.java
index eb037d9e17..f05d3e51e5 100644
--- a/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/util/ExcelUtils.java
+++ b/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/util/ExcelUtils.java
@@ -1,10 +1,10 @@
package cn.iocoder.yudao.framework.excel.core.util;
+import cn.idev.excel.FastExcelFactory;
+import cn.idev.excel.converters.longconverter.LongStringConverter;
import cn.iocoder.yudao.framework.common.util.http.HttpUtils;
+import cn.iocoder.yudao.framework.excel.core.handler.ColumnWidthMatchStyleStrategy;
import cn.iocoder.yudao.framework.excel.core.handler.SelectSheetWriteHandler;
-import com.alibaba.excel.EasyExcel;
-import com.alibaba.excel.converters.longconverter.LongStringConverter;
-import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.multipart.MultipartFile;
@@ -32,9 +32,9 @@ public class ExcelUtils {
public static void write(HttpServletResponse response, String filename, String sheetName,
Class head, List data) throws IOException {
// 输出 Excel
- EasyExcel.write(response.getOutputStream(), head)
+ FastExcelFactory.write(response.getOutputStream(), head)
.autoCloseStream(false) // 不要自动关闭,交给 Servlet 自己处理
- .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) // 基于 column 长度,自动适配。最大 255 宽度
+ .registerWriteHandler(new ColumnWidthMatchStyleStrategy()) // 基于 column 长度,自动适配。最大 255 宽度
.registerWriteHandler(new SelectSheetWriteHandler(head)) // 基于固定 sheet 实现下拉框
.registerConverter(new LongStringConverter()) // 避免 Long 类型丢失精度
.sheet(sheetName).doWrite(data);
@@ -44,7 +44,7 @@ public class ExcelUtils {
}
public static List read(MultipartFile file, Class head) throws IOException {
- return EasyExcel.read(file.getInputStream(), head, null)
+ return FastExcelFactory.read(file.getInputStream(), head, null)
.autoCloseStream(false) // 不要自动关闭,交给 Servlet 自己处理
.doReadAllSync();
}
diff --git a/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/package-info.java b/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/package-info.java
index 53bc5c01bf..72c3ac42e1 100644
--- a/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/package-info.java
+++ b/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/package-info.java
@@ -1,4 +1,4 @@
/**
- * 基于 EasyExcel 实现 Excel 相关的操作
+ * 基于 FastExcel 实现 Excel 相关的操作
*/
package cn.iocoder.yudao.framework.excel;
diff --git a/yudao-framework/yudao-spring-boot-starter-job/src/main/java/cn/iocoder/yudao/framework/quartz/config/YudaoAsyncAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-job/src/main/java/cn/iocoder/yudao/framework/quartz/config/YudaoAsyncAutoConfiguration.java
index 6d517e5e40..4b08210971 100644
--- a/yudao-framework/yudao-spring-boot-starter-job/src/main/java/cn/iocoder/yudao/framework/quartz/config/YudaoAsyncAutoConfiguration.java
+++ b/yudao-framework/yudao-spring-boot-starter-job/src/main/java/cn/iocoder/yudao/framework/quartz/config/YudaoAsyncAutoConfiguration.java
@@ -7,6 +7,7 @@ import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+import org.springframework.core.task.SimpleAsyncTaskExecutor;
/**
* 异步任务 Configuration
@@ -21,13 +22,20 @@ public class YudaoAsyncAutoConfiguration {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
- if (!(bean instanceof ThreadPoolTaskExecutor)) {
- return bean;
+ // 处理 ThreadPoolTaskExecutor
+ if (bean instanceof ThreadPoolTaskExecutor) {
+ ThreadPoolTaskExecutor executor = (ThreadPoolTaskExecutor) bean;
+ executor.setTaskDecorator(TtlRunnable::get);
+ return executor;
}
- // 修改提交的任务,接入 TransmittableThreadLocal
- ThreadPoolTaskExecutor executor = (ThreadPoolTaskExecutor) bean;
- executor.setTaskDecorator(TtlRunnable::get);
- return executor;
+ // 处理 SimpleAsyncTaskExecutor
+ // 参考 https://t.zsxq.com/CBoks 增加
+ if (bean instanceof SimpleAsyncTaskExecutor) {
+ SimpleAsyncTaskExecutor executor = (SimpleAsyncTaskExecutor) bean;
+ executor.setTaskDecorator(TtlRunnable::get);
+ return executor;
+ }
+ return bean;
}
};
diff --git a/yudao-framework/yudao-spring-boot-starter-monitor/pom.xml b/yudao-framework/yudao-spring-boot-starter-monitor/pom.xml
index ebd1210c03..6b6970fbef 100644
--- a/yudao-framework/yudao-spring-boot-starter-monitor/pom.xml
+++ b/yudao-framework/yudao-spring-boot-starter-monitor/pom.xml
@@ -44,29 +44,35 @@
io.opentracing
opentracing-util
+ true
org.apache.skywalking
apm-toolkit-trace
+ true
org.apache.skywalking
apm-toolkit-logback-1.x
+ true
org.apache.skywalking
apm-toolkit-opentracing
+ true
io.micrometer
micrometer-registry-prometheus
+ true
de.codecentric
- spring-boot-admin-starter-client
+ spring-boot-admin-starter-client
+ true
diff --git a/yudao-framework/yudao-spring-boot-starter-monitor/src/main/java/cn/iocoder/yudao/framework/tracer/config/YudaoTracerAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-monitor/src/main/java/cn/iocoder/yudao/framework/tracer/config/YudaoTracerAutoConfiguration.java
index c7d9e2c0db..7876742620 100644
--- a/yudao-framework/yudao-spring-boot-starter-monitor/src/main/java/cn/iocoder/yudao/framework/tracer/config/YudaoTracerAutoConfiguration.java
+++ b/yudao-framework/yudao-spring-boot-starter-monitor/src/main/java/cn/iocoder/yudao/framework/tracer/config/YudaoTracerAutoConfiguration.java
@@ -3,6 +3,9 @@ package cn.iocoder.yudao.framework.tracer.config;
import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum;
import cn.iocoder.yudao.framework.tracer.core.aop.BizTraceAspect;
import cn.iocoder.yudao.framework.tracer.core.filter.TraceFilter;
+import io.opentracing.Tracer;
+import io.opentracing.util.GlobalTracer;
+import org.apache.skywalking.apm.toolkit.opentracing.SkywalkingTracer;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@@ -16,30 +19,32 @@ import org.springframework.context.annotation.Bean;
* @author mashu
*/
@AutoConfiguration
-@ConditionalOnClass({BizTraceAspect.class})
+@ConditionalOnClass(name = {
+ "org.apache.skywalking.apm.toolkit.opentracing.SkywalkingTracer",
+ "io.opentracing.Tracer"
+})
@EnableConfigurationProperties(TracerProperties.class)
@ConditionalOnProperty(prefix = "yudao.tracer", value = "enable", matchIfMissing = true)
public class YudaoTracerAutoConfiguration {
- // TODO @芋艿:重要。目前 opentracing 版本存在冲突,要么保证 skywalking,要么保证阿里云短信 sdk
-// @Bean
-// public TracerProperties bizTracerProperties() {
-// return new TracerProperties();
-// }
-//
-// @Bean
-// public BizTraceAspect bizTracingAop() {
-// return new BizTraceAspect(tracer());
-// }
-//
-// @Bean
-// public Tracer tracer() {
-// // 创建 SkywalkingTracer 对象
-// SkywalkingTracer tracer = new SkywalkingTracer();
-// // 设置为 GlobalTracer 的追踪器
-// GlobalTracer.register(tracer);
-// return tracer;
-// }
+ @Bean
+ public TracerProperties bizTracerProperties() {
+ return new TracerProperties();
+ }
+
+ @Bean
+ public BizTraceAspect bizTracingAop() {
+ return new BizTraceAspect(tracer());
+ }
+
+ @Bean
+ public Tracer tracer() {
+ // 创建 SkywalkingTracer 对象
+ SkywalkingTracer tracer = new SkywalkingTracer();
+ // 设置为 GlobalTracer 的追踪器
+ GlobalTracer.registerIfAbsent(tracer);
+ return tracer;
+ }
/**
* 创建 TraceFilter 过滤器,响应 header 设置 traceId
diff --git a/yudao-framework/yudao-spring-boot-starter-mybatis/pom.xml b/yudao-framework/yudao-spring-boot-starter-mybatis/pom.xml
index 5a619c5118..3272059d85 100644
--- a/yudao-framework/yudao-spring-boot-starter-mybatis/pom.xml
+++ b/yudao-framework/yudao-spring-boot-starter-mybatis/pom.xml
@@ -24,8 +24,8 @@
cn.iocoder.boot
- yudao-spring-boot-starter-web
- provided
+ yudao-spring-boot-starter-security
+ provided
diff --git a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/handler/DefaultDBFieldHandler.java b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/handler/DefaultDBFieldHandler.java
index 94dada129b..3c35999109 100644
--- a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/handler/DefaultDBFieldHandler.java
+++ b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/handler/DefaultDBFieldHandler.java
@@ -1,7 +1,7 @@
package cn.iocoder.yudao.framework.mybatis.core.handler;
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 org.apache.ibatis.reflection.MetaObject;
@@ -32,7 +32,7 @@ public class DefaultDBFieldHandler implements MetaObjectHandler {
baseDO.setUpdateTime(current);
}
- Long userId = WebFrameworkUtils.getLoginUserId();
+ Long userId = SecurityFrameworkUtils.getLoginUserId();
// 当前登录用户不为空,创建人为空,则当前登录用户为创建人
if (Objects.nonNull(userId) && Objects.isNull(baseDO.getCreator())) {
baseDO.setCreator(userId.toString());
@@ -54,7 +54,7 @@ public class DefaultDBFieldHandler implements MetaObjectHandler {
// 当前登录用户不为空,更新人为空,则当前登录用户为更新人
Object modifier = getFieldValByName("updater", metaObject);
- Long userId = WebFrameworkUtils.getLoginUserId();
+ Long userId = SecurityFrameworkUtils.getLoginUserId();
if (Objects.nonNull(userId) && Objects.isNull(modifier)) {
setFieldValByName("updater", userId.toString(), metaObject);
}
diff --git a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java
index d7ad5fad8f..52ca947cc5 100644
--- a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java
+++ b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java
@@ -42,6 +42,7 @@ public interface BaseMapperX extends MPJBaseMapper {
default PageResult selectPage(PageParam pageParam, Collection sortingFields, @Param("ew") Wrapper queryWrapper) {
// 特殊:不分页,直接查询全部
if (PageParam.PAGE_SIZE_NONE.equals(pageParam.getPageSize())) {
+ MyBatisUtils.addOrder(queryWrapper, sortingFields);
List list = selectList(queryWrapper);
return new PageResult<>(list, (long) list.size());
}
diff --git a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/query/MPJLambdaWrapperX.java b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/query/MPJLambdaWrapperX.java
index 48e901d624..8b5a0fcfc8 100644
--- a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/query/MPJLambdaWrapperX.java
+++ b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/query/MPJLambdaWrapperX.java
@@ -118,7 +118,6 @@ public class MPJLambdaWrapperX extends MPJLambdaWrapper {
@Override
public MPJLambdaWrapperX orderByDesc(SFunction column) {
- //noinspection unchecked
super.orderByDesc(true, column);
return this;
}
@@ -208,7 +207,7 @@ public class MPJLambdaWrapperX extends MPJLambdaWrapper {
}
@Override
- public MPJLambdaWrapperX selectCount(SFunction column, String alias) {
+ public MPJLambdaWrapperX selectCount(SFunction column, String alias) {
super.selectCount(column, alias);
return this;
}
@@ -226,7 +225,7 @@ public class MPJLambdaWrapperX extends MPJLambdaWrapper {
}
@Override
- public MPJLambdaWrapperX selectSum(SFunction column, String alias) {
+ public MPJLambdaWrapperX selectSum(SFunction column, String alias) {
super.selectSum(column, alias);
return this;
}
@@ -244,7 +243,7 @@ public class MPJLambdaWrapperX extends MPJLambdaWrapper {
}
@Override
- public MPJLambdaWrapperX selectMax(SFunction column, String alias) {
+ public MPJLambdaWrapperX selectMax(SFunction column, String alias) {
super.selectMax(column, alias);
return this;
}
@@ -262,7 +261,7 @@ public class MPJLambdaWrapperX extends MPJLambdaWrapper {
}
@Override
- public MPJLambdaWrapperX selectMin(SFunction column, String alias) {
+ public MPJLambdaWrapperX selectMin(SFunction column, String alias) {
super.selectMin(column, alias);
return this;
}
@@ -280,7 +279,7 @@ public class MPJLambdaWrapperX extends MPJLambdaWrapper {
}
@Override
- public MPJLambdaWrapperX selectAvg(SFunction column, String alias) {
+ public MPJLambdaWrapperX selectAvg(SFunction column, String alias) {
super.selectAvg(column, alias);
return this;
}
@@ -298,7 +297,7 @@ public class MPJLambdaWrapperX extends MPJLambdaWrapper {
}
@Override
- public MPJLambdaWrapperX selectLen(SFunction column, String alias) {
+ public MPJLambdaWrapperX selectLen(SFunction column, String alias) {
super.selectLen(column, alias);
return this;
}
diff --git a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/MyBatisUtils.java b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/MyBatisUtils.java
index 784f699e44..ac33ba8eff 100644
--- a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/MyBatisUtils.java
+++ b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/MyBatisUtils.java
@@ -1,6 +1,6 @@
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.LambdaUtil;
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.mybatis.core.enums.DbTypeEnum;
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.toolkit.StringPool;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
@@ -20,7 +22,6 @@ import net.sf.jsqlparser.schema.Table;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
-import java.util.stream.Collectors;
/**
* MyBatis 工具类
@@ -37,15 +38,27 @@ public class MyBatisUtils {
// 页码 + 数量
Page page = new Page<>(pageParam.getPageNo(), pageParam.getPageSize());
// 排序字段
- if (!CollectionUtil.isEmpty(sortingFields)) {
- page.addOrder(sortingFields.stream().map(sortingField -> SortingField.ORDER_ASC.equals(sortingField.getOrder())
- ? OrderItem.asc(StrUtil.toUnderlineCase(sortingField.getField()))
- : OrderItem.desc(StrUtil.toUnderlineCase(sortingField.getField())))
- .collect(Collectors.toList()));
+ if (CollUtil.isNotEmpty(sortingFields)) {
+ for (SortingField sortingField : sortingFields) {
+ page.addOrder(new OrderItem().setAsc(SortingField.ORDER_ASC.equals(sortingField.getOrder()))
+ .setColumn(StrUtil.toUnderlineCase(sortingField.getField())));
+ }
}
return page;
}
+ public static void addOrder(Wrapper wrapper, Collection sortingFields) {
+ if (CollUtil.isEmpty(sortingFields)) {
+ return;
+ }
+ QueryWrapper query = (QueryWrapper) wrapper;
+ for (SortingField sortingField : sortingFields) {
+ query.orderBy(true,
+ SortingField.ORDER_ASC.equals(sortingField.getOrder()),
+ StrUtil.toUnderlineCase(sortingField.getField()));
+ }
+ }
+
/**
* 将拦截器添加到链中
* 由于 MybatisPlusInterceptor 不支持添加拦截器,所以只能全量设置
diff --git a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/util/SecurityFrameworkUtils.java b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/util/SecurityFrameworkUtils.java
index 98dc7afb8d..4f8024474b 100644
--- a/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/util/SecurityFrameworkUtils.java
+++ b/yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/security/core/util/SecurityFrameworkUtils.java
@@ -126,8 +126,10 @@ public class SecurityFrameworkUtils {
// 额外设置到 request 中,用于 ApiAccessLogFilter 可以获取到用户编号;
// 原因是,Spring Security 的 Filter 在 ApiAccessLogFilter 后面,在它记录访问日志时,线上上下文已经没有用户编号等信息
- WebFrameworkUtils.setLoginUserId(request, loginUser.getId());
- WebFrameworkUtils.setLoginUserType(request, loginUser.getUserType());
+ if (request != null) {
+ WebFrameworkUtils.setLoginUserId(request, loginUser.getId());
+ WebFrameworkUtils.setLoginUserType(request, loginUser.getUserType());
+ }
}
private static Authentication buildAuthentication(LoginUser loginUser, HttpServletRequest request) {
diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/filter/CacheRequestBodyFilter.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/filter/CacheRequestBodyFilter.java
index 9071998f91..49958fc096 100644
--- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/filter/CacheRequestBodyFilter.java
+++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/filter/CacheRequestBodyFilter.java
@@ -1,5 +1,6 @@
package cn.iocoder.yudao.framework.web.core.filter;
+import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
import org.springframework.web.filter.OncePerRequestFilter;
@@ -16,6 +17,14 @@ import java.io.IOException;
*/
public class CacheRequestBodyFilter extends OncePerRequestFilter {
+ /**
+ * 需要排除的 URI
+ *
+ * 1. 排除 Spring Boot Admin 相关请求,避免客户端连接中断导致的异常。
+ * 例如说:795 ISSUE
+ */
+ private static final String[] IGNORE_URIS = {"/admin/", "/actuator/"};
+
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws IOException, ServletException {
@@ -24,7 +33,13 @@ public class CacheRequestBodyFilter extends OncePerRequestFilter {
@Override
protected boolean shouldNotFilter(HttpServletRequest request) {
- // 只处理 json 请求内容
+ // 1. 校验是否为排除的 URL
+ String requestURI = request.getRequestURI();
+ if (StrUtil.startWithAny(requestURI, IGNORE_URIS)) {
+ return true;
+ }
+
+ // 2. 只处理 json 请求内容
return !ServletUtils.isJsonRequest(request);
}
diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.java
index 11ef9a8ef6..627a5ea784 100644
--- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.java
+++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.java
@@ -6,6 +6,8 @@ import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.servlet.JakartaServletUtil;
+import cn.iocoder.yudao.framework.common.biz.infra.logger.ApiErrorLogCommonApi;
+import cn.iocoder.yudao.framework.common.biz.infra.logger.dto.ApiErrorLogCreateReqDTO;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
@@ -14,8 +16,6 @@ import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
-import cn.iocoder.yudao.framework.common.biz.infra.logger.ApiErrorLogCommonApi;
-import cn.iocoder.yudao.framework.common.biz.infra.logger.dto.ApiErrorLogCreateReqDTO;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.ConstraintViolation;
@@ -29,6 +29,7 @@ import org.springframework.util.Assert;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
+import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
@@ -101,6 +102,9 @@ public class GlobalExceptionHandler {
if (ex instanceof HttpRequestMethodNotSupportedException) {
return httpRequestMethodNotSupportedExceptionHandler((HttpRequestMethodNotSupportedException) ex);
}
+ if (ex instanceof HttpMediaTypeNotSupportedException) {
+ return httpMediaTypeNotSupportedExceptionHandler((HttpMediaTypeNotSupportedException) ex);
+ }
if (ex instanceof ServiceException) {
return serviceExceptionHandler((ServiceException) ex);
}
@@ -171,17 +175,19 @@ public class GlobalExceptionHandler {
/**
* 处理 SpringMVC 请求参数类型错误
*
- * 例如说,接口上设置了 @RequestBody实体中 xx 属性类型为 Integer,结果传递 xx 参数类型为 String
+ * 例如说,接口上设置了 @RequestBody 实体中 xx 属性类型为 Integer,结果传递 xx 参数类型为 String
*/
@ExceptionHandler(HttpMessageNotReadableException.class)
public CommonResult> methodArgumentTypeInvalidFormatExceptionHandler(HttpMessageNotReadableException ex) {
log.warn("[methodArgumentTypeInvalidFormatExceptionHandler]", ex);
- if(ex.getCause() instanceof InvalidFormatException) {
+ if (ex.getCause() instanceof InvalidFormatException) {
InvalidFormatException invalidFormatException = (InvalidFormatException) ex.getCause();
return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数类型错误:%s", invalidFormatException.getValue()));
- } else {
- return defaultExceptionHandler(ServletUtils.getRequest(), ex);
}
+ if (StrUtil.startWith(ex.getMessage(), "Required request body is missing")) {
+ return CommonResult.error(BAD_REQUEST.getCode(), "请求参数类型错误: request body 缺失");
+ }
+ return defaultExceptionHandler(ServletUtils.getRequest(), ex);
}
/**
@@ -237,6 +243,17 @@ public class GlobalExceptionHandler {
return CommonResult.error(METHOD_NOT_ALLOWED.getCode(), String.format("请求方法不正确:%s", ex.getMessage()));
}
+ /**
+ * 处理 SpringMVC 请求的 Content-Type 不正确
+ *
+ * 例如说,A 接口的 Content-Type 为 application/json,结果请求的 Content-Type 为 application/octet-stream,导致不匹配
+ */
+ @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
+ public CommonResult> httpMediaTypeNotSupportedExceptionHandler(HttpMediaTypeNotSupportedException ex) {
+ log.warn("[httpMediaTypeNotSupportedExceptionHandler]", ex);
+ return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求类型不正确:%s", ex.getMessage()));
+ }
+
/**
* 处理 Spring Security 权限不足的异常
*
diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/xss/config/YudaoXssAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/xss/config/YudaoXssAutoConfiguration.java
index 99b6a448f3..16f87dae1f 100644
--- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/xss/config/YudaoXssAutoConfiguration.java
+++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/xss/config/YudaoXssAutoConfiguration.java
@@ -5,7 +5,6 @@ import cn.iocoder.yudao.framework.xss.core.clean.JsoupXssCleaner;
import cn.iocoder.yudao.framework.xss.core.clean.XssCleaner;
import cn.iocoder.yudao.framework.xss.core.filter.XssFilter;
import cn.iocoder.yudao.framework.xss.core.json.XssStringJsonDeserializer;
-import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -42,13 +41,13 @@ public class YudaoXssAutoConfiguration implements WebMvcConfigurer {
*/
@Bean
@ConditionalOnMissingBean(name = "xssJacksonCustomizer")
- @ConditionalOnBean(ObjectMapper.class)
@ConditionalOnProperty(value = "yudao.xss.enable", havingValue = "true")
public Jackson2ObjectMapperBuilderCustomizer xssJacksonCustomizer(XssProperties properties,
PathMatcher pathMatcher,
XssCleaner xssCleaner) {
// 在反序列化时进行 xss 过滤,可以替换使用 XssStringJsonSerializer,在序列化时进行处理
- return builder -> builder.deserializerByType(String.class, new XssStringJsonDeserializer(properties, pathMatcher, xssCleaner));
+ return builder ->
+ builder.deserializerByType(String.class, new XssStringJsonDeserializer(properties, pathMatcher, xssCleaner));
}
/**
diff --git a/yudao-module-ai/pom.xml b/yudao-module-ai/pom.xml
index 4b02a7e00f..4a2323bcfa 100644
--- a/yudao-module-ai/pom.xml
+++ b/yudao-module-ai/pom.xml
@@ -19,7 +19,8 @@
国外:OpenAI、Ollama、Midjourney、StableDiffusion、Suno
- 1.0.0-M6
+ 1.0.0
+ 1.0.0.2
1.0.2
@@ -75,65 +76,73 @@
org.springframework.ai
- spring-ai-openai-spring-boot-starter
+ spring-ai-starter-model-openai
${spring-ai.version}
org.springframework.ai
- spring-ai-azure-openai-spring-boot-starter
+ spring-ai-starter-model-azure-openai
${spring-ai.version}
org.springframework.ai
- spring-ai-ollama-spring-boot-starter
+ spring-ai-starter-model-deepseek
${spring-ai.version}
org.springframework.ai
- spring-ai-stability-ai-spring-boot-starter
+ spring-ai-starter-model-ollama
${spring-ai.version}
-
- com.alibaba.cloud.ai
- spring-ai-alibaba-starter
- ${spring-ai.version}.1
-
-
-
org.springframework.ai
- spring-ai-qianfan-spring-boot-starter
+ spring-ai-starter-model-stability-ai
${spring-ai.version}
org.springframework.ai
- spring-ai-zhipuai-spring-boot-starter
+ spring-ai-starter-model-zhipuai
${spring-ai.version}
org.springframework.ai
- spring-ai-minimax-spring-boot-starter
+ spring-ai-starter-model-minimax
${spring-ai.version}
+
- org.springframework.ai
- spring-ai-moonshot-spring-boot-starter
- ${spring-ai.version}
+
+ com.alibaba.cloud.ai
+ spring-ai-alibaba-starter-dashscope
+ ${alibaba-ai.version}
+
+
+
+
+ org.springaicommunity
+ qianfan-spring-boot-starter
+ 1.0.0
+
+
+
+ org.springaicommunity
+ moonshot-spring-boot-starter
+ 1.0.0
org.springframework.ai
- spring-ai-qdrant-store
+ spring-ai-starter-vector-store-qdrant
${spring-ai.version}
org.springframework.ai
- spring-ai-redis-store
+ spring-ai-starter-vector-store-redis
${spring-ai.version}
@@ -144,7 +153,7 @@
org.springframework.ai
- spring-ai-milvus-store
+ spring-ai-starter-vector-store-milvus
${spring-ai.version}
diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/AiChatConversationController.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/AiChatConversationController.java
index 5142cde443..ddd426d283 100644
--- a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/AiChatConversationController.java
+++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/AiChatConversationController.java
@@ -12,6 +12,7 @@ import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatCo
import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO;
import cn.iocoder.yudao.module.ai.service.chat.AiChatConversationService;
import cn.iocoder.yudao.module.ai.service.chat.AiChatMessageService;
+import com.fhs.core.trans.anno.TransMethodResult;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -54,6 +55,7 @@ public class AiChatConversationController {
@GetMapping("/my-list")
@Operation(summary = "获得【我的】聊天对话列表")
+ @TransMethodResult
public CommonResult> getChatConversationMyList() {
List list = chatConversationService.getChatConversationListByUserId(getLoginUserId());
return success(BeanUtils.toBean(list, AiChatConversationRespVO.class));
@@ -62,6 +64,7 @@ public class AiChatConversationController {
@GetMapping("/get-my")
@Operation(summary = "获得【我的】聊天对话")
@Parameter(name = "id", required = true, description = "对话编号", example = "1024")
+ @TransMethodResult
public CommonResult getChatConversationMy(@RequestParam("id") Long id) {
AiChatConversationDO conversation = chatConversationService.getChatConversation(id);
if (conversation != null && ObjUtil.notEqual(conversation.getUserId(), getLoginUserId())) {
@@ -90,6 +93,7 @@ public class AiChatConversationController {
@GetMapping("/page")
@Operation(summary = "获得对话分页", description = "用于【对话管理】菜单")
@PreAuthorize("@ss.hasPermission('ai:chat-conversation:query')")
+ @TransMethodResult
public CommonResult> getChatConversationPage(AiChatConversationPageReqVO pageReqVO) {
PageResult pageResult = chatConversationService.getChatConversationPage(pageReqVO);
if (CollUtil.isEmpty(pageResult.getList())) {
diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/AiChatRoleController.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/AiChatRoleController.java
index 5714c5fedd..804e211527 100644
--- a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/AiChatRoleController.java
+++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/model/AiChatRoleController.java
@@ -10,6 +10,7 @@ import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatRole.AiChatRoleS
import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatRole.AiChatRoleSaveReqVO;
import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO;
import cn.iocoder.yudao.module.ai.service.model.AiChatRoleService;
+import com.fhs.core.trans.anno.TransMethodResult;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -35,6 +36,7 @@ public class AiChatRoleController {
@GetMapping("/my-page")
@Operation(summary = "获得【我的】聊天角色分页")
+ @TransMethodResult
public CommonResult> getChatRoleMyPage(@Valid AiChatRolePageReqVO pageReqVO) {
PageResult pageResult = chatRoleService.getChatRoleMyPage(pageReqVO, getLoginUserId());
return success(BeanUtils.toBean(pageResult, AiChatRoleRespVO.class));
@@ -43,6 +45,7 @@ public class AiChatRoleController {
@GetMapping("/get-my")
@Operation(summary = "获得【我的】聊天角色")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
+ @TransMethodResult
public CommonResult getChatRoleMy(@RequestParam("id") Long id) {
AiChatRoleDO chatRole = chatRoleService.getChatRole(id);
if (ObjUtil.notEqual(chatRole.getUserId(), getLoginUserId())) {
@@ -108,6 +111,7 @@ public class AiChatRoleController {
@Operation(summary = "获得聊天角色")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('ai:chat-role:query')")
+ @TransMethodResult
public CommonResult getChatRole(@RequestParam("id") Long id) {
AiChatRoleDO chatRole = chatRoleService.getChatRole(id);
return success(BeanUtils.toBean(chatRole, AiChatRoleRespVO.class));
diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/config/AiAutoConfiguration.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/config/AiAutoConfiguration.java
index a28d726b90..4ff7c9e4dc 100644
--- a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/config/AiAutoConfiguration.java
+++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/config/AiAutoConfiguration.java
@@ -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.AiModelFactoryImpl;
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.hunyuan.HunYuanChatModel;
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.xinghuo.XingHuoChatModel;
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.TokenCountBatchingStrategy;
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.tokenizer.JTokkitTokenCountEstimator;
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.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
@@ -52,33 +51,6 @@ public class AiAutoConfiguration {
// ========== 各种 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
@ConditionalOnProperty(value = "yudao.ai.doubao.enable", havingValue = "true")
public DouBaoChatModel douBaoChatClient(YudaoAiProperties yudaoAiProperties) {
diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/config/YudaoAiProperties.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/config/YudaoAiProperties.java
index 7f8046768a..7c26aa89ca 100644
--- a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/config/YudaoAiProperties.java
+++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/config/YudaoAiProperties.java
@@ -13,12 +13,6 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
@Data
public class YudaoAiProperties {
- /**
- * DeepSeek
- */
- @SuppressWarnings("SpellCheckingInspection")
- private DeepSeekProperties deepseek;
-
/**
* 字节豆包
*/
@@ -60,19 +54,6 @@ public class YudaoAiProperties {
@SuppressWarnings("SpellCheckingInspection")
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
public static class DouBaoProperties {
diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/AiModelFactoryImpl.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/AiModelFactoryImpl.java
index f258ffaf1b..f7b42e30ae 100644
--- a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/AiModelFactoryImpl.java
+++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/AiModelFactoryImpl.java
@@ -8,11 +8,11 @@ import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.RuntimeUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
+import cn.iocoder.yudao.framework.common.util.spring.SpringUtils;
+import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum;
import cn.iocoder.yudao.module.ai.framework.ai.config.AiAutoConfiguration;
import cn.iocoder.yudao.module.ai.framework.ai.config.YudaoAiProperties;
-import cn.iocoder.yudao.module.ai.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.deepseek.DeepSeekChatModel;
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.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.suno.api.SunoApi;
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.DashScopeAutoConfiguration;
+import com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeChatAutoConfiguration;
+import com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeEmbeddingAutoConfiguration;
+import com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeImageAutoConfiguration;
import com.alibaba.cloud.ai.dashscope.api.DashScopeApi;
import com.alibaba.cloud.ai.dashscope.api.DashScopeImageApi;
import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel;
@@ -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.image.DashScopeImageModel;
import com.azure.ai.openai.OpenAIClientBuilder;
+import com.azure.core.credential.KeyCredential;
import io.micrometer.observation.ObservationRegistry;
import io.milvus.client.MilvusServiceClient;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.QdrantGrpcClient;
import lombok.SneakyThrows;
-import org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiAutoConfiguration;
-import org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiChatProperties;
-import org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiConnectionProperties;
-import org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiEmbeddingProperties;
-import org.springframework.ai.autoconfigure.minimax.MiniMaxAutoConfiguration;
-import org.springframework.ai.autoconfigure.moonshot.MoonshotAutoConfiguration;
-import org.springframework.ai.autoconfigure.ollama.OllamaAutoConfiguration;
-import org.springframework.ai.autoconfigure.openai.OpenAiAutoConfiguration;
-import org.springframework.ai.autoconfigure.qianfan.QianFanAutoConfiguration;
-import org.springframework.ai.autoconfigure.stabilityai.StabilityAiImageAutoConfiguration;
-import org.springframework.ai.autoconfigure.vectorstore.milvus.MilvusServiceClientConnectionDetails;
-import org.springframework.ai.autoconfigure.vectorstore.milvus.MilvusServiceClientProperties;
-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.springaicommunity.moonshot.MoonshotChatModel;
+import org.springaicommunity.moonshot.MoonshotChatOptions;
+import org.springaicommunity.moonshot.api.MoonshotApi;
+import org.springaicommunity.moonshot.autoconfigure.MoonshotChatAutoConfiguration;
+import org.springaicommunity.qianfan.QianFanChatModel;
+import org.springaicommunity.qianfan.QianFanEmbeddingModel;
+import org.springaicommunity.qianfan.QianFanEmbeddingOptions;
+import org.springaicommunity.qianfan.QianFanImageModel;
+import org.springaicommunity.qianfan.api.QianFanApi;
+import org.springaicommunity.qianfan.api.QianFanImageApi;
+import org.springaicommunity.qianfan.autoconfigure.QianFanChatAutoConfiguration;
+import org.springaicommunity.qianfan.autoconfigure.QianFanEmbeddingAutoConfiguration;
import org.springframework.ai.azure.openai.AzureOpenAiChatModel;
import org.springframework.ai.azure.openai.AzureOpenAiEmbeddingModel;
import org.springframework.ai.chat.model.ChatModel;
+import org.springframework.ai.deepseek.DeepSeekChatModel;
+import org.springframework.ai.deepseek.DeepSeekChatOptions;
+import org.springframework.ai.deepseek.api.DeepSeekApi;
import org.springframework.ai.document.MetadataMode;
import org.springframework.ai.embedding.BatchingStrategy;
import org.springframework.ai.embedding.EmbeddingModel;
+import org.springframework.ai.embedding.observation.EmbeddingModelObservationConvention;
import org.springframework.ai.image.ImageModel;
import org.springframework.ai.minimax.MiniMaxChatModel;
import org.springframework.ai.minimax.MiniMaxChatOptions;
import org.springframework.ai.minimax.MiniMaxEmbeddingModel;
import org.springframework.ai.minimax.MiniMaxEmbeddingOptions;
import org.springframework.ai.minimax.api.MiniMaxApi;
-import org.springframework.ai.model.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.moonshot.MoonshotChatModel;
-import org.springframework.ai.moonshot.MoonshotChatOptions;
-import org.springframework.ai.moonshot.api.MoonshotApi;
+import org.springframework.ai.model.zhipuai.autoconfigure.ZhiPuAiChatAutoConfiguration;
+import org.springframework.ai.model.zhipuai.autoconfigure.ZhiPuAiEmbeddingAutoConfiguration;
+import org.springframework.ai.model.zhipuai.autoconfigure.ZhiPuAiImageAutoConfiguration;
import org.springframework.ai.ollama.OllamaChatModel;
import org.springframework.ai.ollama.OllamaEmbeddingModel;
import org.springframework.ai.ollama.api.OllamaApi;
@@ -84,21 +93,23 @@ import org.springframework.ai.openai.OpenAiImageModel;
import org.springframework.ai.openai.api.OpenAiApi;
import org.springframework.ai.openai.api.OpenAiImageApi;
import org.springframework.ai.openai.api.common.OpenAiApiConstants;
-import org.springframework.ai.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.api.StabilityAiApi;
import org.springframework.ai.vectorstore.SimpleVectorStore;
import org.springframework.ai.vectorstore.VectorStore;
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.VectorStoreObservationConvention;
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.autoconfigure.RedisVectorStoreAutoConfiguration;
+import org.springframework.ai.vectorstore.redis.autoconfigure.RedisVectorStoreProperties;
import org.springframework.ai.zhipuai.*;
import org.springframework.ai.zhipuai.api.ZhiPuAiApi;
import org.springframework.ai.zhipuai.api.ZhiPuAiImageApi;
@@ -190,7 +201,7 @@ public class AiModelFactoryImpl implements AiModelFactory {
case XING_HUO:
return SpringUtil.getBean(XingHuoChatModel.class);
case BAI_CHUAN:
- return SpringUtil.getBean(AzureOpenAiChatModel.class);
+ return SpringUtil.getBean(BaiChuanChatModel.class);
case OPENAI:
return SpringUtil.getBean(OpenAiChatModel.class);
case AZURE_OPENAI:
@@ -319,27 +330,34 @@ public class AiModelFactoryImpl implements AiModelFactory {
// ========== 各种创建 spring-ai 客户端的方法 ==========
/**
- * 可参考 {@link DashScopeAutoConfiguration} 的 dashscopeChatModel 方法
+ * 可参考 {@link DashScopeChatAutoConfiguration} 的 dashscopeChatModel 方法
*/
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)
.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) {
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) {
+ // TODO spring ai qianfan 有 bug,无法使用 https://github.com/spring-ai-community/qianfan/issues/6
List keys = StrUtil.split(key, '|');
Assert.equals(keys.size(), 2, "YiYanChatClient 的密钥需要 (appKey|secretKey) 格式");
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) {
+ // TODO spring ai qianfan 有 bug,无法使用 https://github.com/spring-ai-community/qianfan/issues/6
List keys = StrUtil.split(key, '|');
Assert.equals(keys.size(), 2, "YiYanChatClient 的密钥需要 (appKey|secretKey) 格式");
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) {
- YudaoAiProperties.DeepSeekProperties properties = new YudaoAiProperties.DeepSeekProperties()
- .setApiKey(apiKey);
- return new AiAutoConfiguration().buildDeepSeekChatModel(properties);
+ DeepSeekApi deepSeekApi = DeepSeekApi.builder().apiKey(apiKey).build();
+ DeepSeekChatOptions options = DeepSeekChatOptions.builder().model(DeepSeekApi.DEFAULT_CHAT_MODEL)
+ .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) {
ZhiPuAiApi zhiPuAiApi = StrUtil.isEmpty(url) ? new ZhiPuAiApi(apiKey)
: new ZhiPuAiApi(url, apiKey);
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) {
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) {
MiniMaxApi miniMaxApi = StrUtil.isEmpty(url) ? new MiniMaxApi(apiKey)
: new MiniMaxApi(url, apiKey);
MiniMaxChatOptions options = MiniMaxChatOptions.builder().model(MiniMaxApi.DEFAULT_CHAT_MODEL).temperature(0.7).build();
- return new MiniMaxChatModel(miniMaxApi, options, 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) {
- MoonshotApi moonshotApi = StrUtil.isEmpty(url)? new MoonshotApi(apiKey)
- : new MoonshotApi(url, apiKey);
+ MoonshotApi.Builder moonshotApiBuilder = MoonshotApi.builder()
+ .apiKey(apiKey);
+ if (StrUtil.isNotEmpty(url)) {
+ moonshotApiBuilder.baseUrl(url);
+ }
MoonshotChatOptions options = MoonshotChatOptions.builder().model(MoonshotApi.DEFAULT_CHAT_MODEL).build();
- return 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) {
url = StrUtil.blankToDefault(url, OpenAiApiConstants.DEFAULT_BASE_URL);
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) {
- AzureOpenAiAutoConfiguration azureOpenAiAutoConfiguration = new AzureOpenAiAutoConfiguration();
- // 创建 OpenAIClient 对象
- AzureOpenAiConnectionProperties connectionProperties = new AzureOpenAiConnectionProperties();
- connectionProperties.setApiKey(apiKey);
- connectionProperties.setEndpoint(url);
- OpenAIClientBuilder openAIClient = azureOpenAiAutoConfiguration.openAIClientBuilder(connectionProperties, null);
- // 获取 AzureOpenAiChatProperties 对象
- AzureOpenAiChatProperties chatProperties = SpringUtil.getBean(AzureOpenAiChatProperties.class);
- return azureOpenAiAutoConfiguration.azureOpenAiChatModel(openAIClient, chatProperties,
- getToolCallingManager(), null, null);
+ // TODO @芋艿:使用前,请测试,暂时没密钥!!!
+ OpenAIClientBuilder openAIClientBuilder = new OpenAIClientBuilder()
+ .endpoint(url).credential(new KeyCredential(apiKey));
+ return AzureOpenAiChatModel.builder()
+ .openAIClientBuilder(openAIClientBuilder)
+ .toolCallingManager(getToolCallingManager())
+ .build();
}
/**
- * 可参考 {@link OpenAiAutoConfiguration} 的 openAiImageModel 方法
+ * 可参考 {@link OpenAiImageAutoConfiguration} 的 openAiImageModel 方法
*/
private OpenAiImageModel buildOpenAiImageModel(String openAiToken, String 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) {
- OllamaApi ollamaApi = new OllamaApi(url);
- return OllamaChatModel.builder().ollamaApi(ollamaApi).toolCallingManager(getToolCallingManager()).build();
+ OllamaApi ollamaApi = OllamaApi.builder().baseUrl(url).build();
+ return OllamaChatModel.builder()
+ .ollamaApi(ollamaApi)
+ .toolCallingManager(getToolCallingManager())
+ .build();
}
/**
@@ -519,16 +553,16 @@ public class AiModelFactoryImpl implements AiModelFactory {
// ========== 各种创建 EmbeddingModel 的方法 ==========
/**
- * 可参考 {@link DashScopeAutoConfiguration} 的 dashscopeEmbeddingModel 方法
+ * 可参考 {@link DashScopeEmbeddingAutoConfiguration} 的 dashscopeEmbeddingModel 方法
*/
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();
return new DashScopeEmbeddingModel(dashScopeApi, MetadataMode.EMBED, dashScopeEmbeddingOptions);
}
/**
- * 可参考 {@link ZhiPuAiAutoConfiguration} 的 zhiPuAiEmbeddingModel 方法
+ * 可参考 {@link ZhiPuAiEmbeddingAutoConfiguration} 的 zhiPuAiEmbeddingModel 方法
*/
private ZhiPuAiEmbeddingModel buildZhiPuEmbeddingModel(String apiKey, String url, String model) {
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) {
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) {
List keys = StrUtil.split(key, '|');
@@ -561,13 +595,16 @@ public class AiModelFactoryImpl implements AiModelFactory {
}
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();
- 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) {
url = StrUtil.blankToDefault(url, OpenAiApiConstants.DEFAULT_BASE_URL);
@@ -576,21 +613,19 @@ public class AiModelFactoryImpl implements AiModelFactory {
return new OpenAiEmbeddingModel(openAiApi, MetadataMode.EMBED, openAiEmbeddingProperties);
}
- // TODO @芋艿:手头暂时没密钥,使用建议再测试下
/**
- * 可参考 {@link AzureOpenAiAutoConfiguration} 的 azureOpenAiEmbeddingModel 方法
+ * 可参考 {@link AzureOpenAiEmbeddingAutoConfiguration} 的 azureOpenAiEmbeddingModel 方法
*/
private AzureOpenAiEmbeddingModel buildAzureOpenAiEmbeddingModel(String apiKey, String url, String model) {
- AzureOpenAiAutoConfiguration azureOpenAiAutoConfiguration = new AzureOpenAiAutoConfiguration();
- // 创建 OpenAIClient 对象
- AzureOpenAiConnectionProperties connectionProperties = new AzureOpenAiConnectionProperties();
- connectionProperties.setApiKey(apiKey);
- connectionProperties.setEndpoint(url);
- OpenAIClientBuilder openAIClient = azureOpenAiAutoConfiguration.openAIClientBuilder(connectionProperties, null);
+ // TODO @芋艿:手头暂时没密钥,使用建议再测试下
+ AzureOpenAiEmbeddingAutoConfiguration azureOpenAiAutoConfiguration = new AzureOpenAiEmbeddingAutoConfiguration();
+ // 创建 OpenAIClientBuilder 对象
+ OpenAIClientBuilder openAIClientBuilder = new OpenAIClientBuilder()
+ .endpoint(url).credential(new KeyCredential(apiKey));
// 获取 AzureOpenAiChatProperties 对象
AzureOpenAiEmbeddingProperties embeddingProperties = SpringUtil.getBean(AzureOpenAiEmbeddingProperties.class);
- return azureOpenAiAutoConfiguration.azureOpenAiEmbeddingModel(openAIClient, embeddingProperties,
- null, null);
+ return azureOpenAiAutoConfiguration.azureOpenAiEmbeddingModel(openAIClientBuilder, embeddingProperties,
+ getObservationRegistry(), getEmbeddingModelObservationConvention());
}
// ========== 各种创建 VectorStore 的方法 ==========
@@ -655,12 +690,12 @@ public class AiModelFactoryImpl implements AiModelFactory {
Map> metadataFields) {
// 创建 JedisPooled 对象
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 对象
- RedisVectorStoreAutoConfiguration configuration = new RedisVectorStoreAutoConfiguration();
RedisVectorStoreProperties properties = SpringUtil.getBean(RedisVectorStoreProperties.class);
RedisVectorStore redisVectorStore = RedisVectorStore.builder(jedisPooled, embeddingModel)
- .indexName(properties.getIndex()).prefix(properties.getPrefix())
+ .indexName(properties.getIndexName()).prefix(properties.getPrefix())
.initializeSchema(properties.isInitializeSchema())
.metadataFields(convertList(metadataFields.entrySet(), entry -> {
String fieldName = entry.getKey();
@@ -730,10 +765,12 @@ public class AiModelFactoryImpl implements AiModelFactory {
private static ObjectProvider getCustomObservationConvention() {
return new ObjectProvider<>() {
+
@Override
public VectorStoreObservationConvention getObject() throws BeansException {
return new DefaultVectorStoreObservationConvention();
}
+
};
}
@@ -745,8 +782,15 @@ public class AiModelFactoryImpl implements AiModelFactory {
return SpringUtil.getBean(ToolCallingManager.class);
}
- private static FunctionCallbackResolver getFunctionCallbackResolver() {
- return SpringUtil.getBean(FunctionCallbackResolver.class);
+ private static ObjectProvider getEmbeddingModelObservationConvention() {
+ return new ObjectProvider<>() {
+
+ @Override
+ public EmbeddingModelObservationConvention getObject() throws BeansException {
+ return SpringUtil.getBean(EmbeddingModelObservationConvention.class);
+ }
+
+ };
}
}
diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/deepseek/DeepSeekChatModel.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/deepseek/DeepSeekChatModel.java
deleted file mode 100644
index d603abf6b0..0000000000
--- a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/deepseek/DeepSeekChatModel.java
+++ /dev/null
@@ -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 stream(Prompt prompt) {
- return openAiChatModel.stream(prompt);
- }
-
- @Override
- public ChatOptions getDefaultOptions() {
- return openAiChatModel.getDefaultOptions();
- }
-
-}
diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/siliconflow/SiliconFlowImageModel.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/siliconflow/SiliconFlowImageModel.java
index 43f8ad2168..44a652309e 100644
--- a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/siliconflow/SiliconFlowImageModel.java
+++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/siliconflow/SiliconFlowImageModel.java
@@ -89,7 +89,7 @@ public class SiliconFlowImageModel implements ImageModel {
var observationContext = ImageModelObservationContext.builder()
.imagePrompt(imagePrompt)
.provider(SiliconFlowApiConstants.PROVIDER_NAME)
- .requestOptions(imagePrompt.getOptions())
+ .imagePrompt(imagePrompt)
.build();
return ImageModelObservationDocumentation.IMAGE_MODEL_OPERATION
diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageServiceImpl.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageServiceImpl.java
index 671098a704..79214a0325 100644
--- a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageServiceImpl.java
+++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/image/AiImageServiceImpl.java
@@ -9,9 +9,6 @@ import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
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.util.object.BeanUtils;
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.mysql.image.AiImageMapper;
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.infra.api.file.FileApi;
import com.alibaba.cloud.ai.dashscope.image.DashScopeImageOptions;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
+import org.springaicommunity.qianfan.QianFanImageOptions;
import org.springframework.ai.image.ImageModel;
import org.springframework.ai.image.ImageOptions;
import org.springframework.ai.image.ImagePrompt;
import org.springframework.ai.image.ImageResponse;
import org.springframework.ai.openai.OpenAiImageOptions;
-import org.springframework.ai.qianfan.QianFanImageOptions;
import org.springframework.ai.stabilityai.api.StabilityAiImageOptions;
import org.springframework.ai.zhipuai.ZhiPuAiImageOptions;
import org.springframework.scheduling.annotation.Async;
@@ -140,10 +140,10 @@ public class AiImageServiceImpl implements AiImageService {
private static ImageOptions buildImageOptions(AiImageDrawReqVO draw, AiModelDO model) {
if (ObjUtil.equal(model.getPlatform(), AiPlatformEnum.OPENAI.getPlatform())) {
// https://platform.openai.com/docs/api-reference/images/create
- return OpenAiImageOptions.builder().withModel(model.getModel())
- .withHeight(draw.getHeight()).withWidth(draw.getWidth())
- .withStyle(MapUtil.getStr(draw.getOptions(), "style")) // 风格
- .withResponseFormat("b64_json")
+ return OpenAiImageOptions.builder().model(model.getModel())
+ .height(draw.getHeight()).width(draw.getWidth())
+ .style(MapUtil.getStr(draw.getOptions(), "style")) // 风格
+ .responseFormat("b64_json")
.build();
} else if (ObjUtil.equal(model.getPlatform(), AiPlatformEnum.SILICON_FLOW.getPlatform())) {
// https://docs.siliconflow.cn/cn/api-reference/images/images-generations
diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/model/tool/UserProfileQueryToolFunction.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/model/tool/UserProfileQueryToolFunction.java
index 6e0ba51c9e..5656d39292 100644
--- a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/model/tool/UserProfileQueryToolFunction.java
+++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/model/tool/UserProfileQueryToolFunction.java
@@ -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.dto.AdminUserRespDTO;
import com.fasterxml.jackson.annotation.JsonClassDescription;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonPropertyDescription;
import jakarta.annotation.Resource;
import lombok.AllArgsConstructor;
import lombok.Data;
@@ -17,7 +19,7 @@ import org.springframework.stereotype.Component;
import java.util.function.BiFunction;
/**
- * 工具:当前用户信息查询
+ * 工具:用户信息查询
*
* 同时,也是展示 ToolContext 上下文的使用
*
@@ -31,8 +33,17 @@ public class UserProfileQueryToolFunction
private AdminUserApi adminUserApi;
@Data
- @JsonClassDescription("当前用户信息查询")
- public static class Request { }
+ @JsonClassDescription("用户信息查询")
+ public static class Request {
+
+ /**
+ * 用户编号
+ */
+ @JsonProperty(value = "id")
+ @JsonPropertyDescription("用户编号,例如说:1。如果查询自己,则 id 为空")
+ private Long id;
+
+ }
@Data
@AllArgsConstructor
@@ -61,13 +72,19 @@ public class UserProfileQueryToolFunction
@Override
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);
- if (loginUser == null | tenantId == null) {
- return null;
+ if (tenantId == 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, () -> {
- AdminUserRespDTO user = adminUserApi.getUser(loginUser.getId());
+ AdminUserRespDTO user = adminUserApi.getUser(request.getId());
return BeanUtils.toBean(user, Response.class);
});
}
diff --git a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/util/AiUtils.java b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/util/AiUtils.java
index ac3ff39a49..0744ff6307 100644
--- a/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/util/AiUtils.java
+++ b/yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/util/AiUtils.java
@@ -2,18 +2,18 @@ package cn.iocoder.yudao.module.ai.util;
import cn.hutool.core.util.ObjUtil;
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.tenant.core.context.TenantContextHolder;
+import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum;
import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions;
+import org.springaicommunity.moonshot.MoonshotChatOptions;
+import org.springaicommunity.qianfan.QianFanChatOptions;
import org.springframework.ai.azure.openai.AzureOpenAiChatOptions;
import org.springframework.ai.chat.messages.*;
import org.springframework.ai.chat.prompt.ChatOptions;
import org.springframework.ai.minimax.MiniMaxChatOptions;
-import org.springframework.ai.moonshot.MoonshotChatOptions;
import org.springframework.ai.ollama.api.OllamaOptions;
import org.springframework.ai.openai.OpenAiChatOptions;
-import org.springframework.ai.qianfan.QianFanChatOptions;
import org.springframework.ai.zhipuai.ZhiPuAiChatOptions;
import java.util.Collections;
@@ -43,18 +43,18 @@ public class AiUtils {
switch (platform) {
case TONG_YI:
return DashScopeChatOptions.builder().withModel(model).withTemperature(temperature).withMaxToken(maxTokens)
- .withFunctions(toolNames).withToolContext(toolContext).build();
+ .withToolNames(toolNames).withToolContext(toolContext).build();
case YI_YAN:
return QianFanChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens).build();
case ZHI_PU:
return ZhiPuAiChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens)
- .functions(toolNames).toolContext(toolContext).build();
+ .toolNames(toolNames).toolContext(toolContext).build();
case MINI_MAX:
return MiniMaxChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens)
- .functions(toolNames).toolContext(toolContext).build();
+ .toolNames(toolNames).toolContext(toolContext).build();
case MOONSHOT:
return MoonshotChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens)
- .functions(toolNames).toolContext(toolContext).build();
+ .toolNames(toolNames).toolContext(toolContext).build();
case OPENAI:
case DEEP_SEEK: // 复用 OpenAI 客户端
case DOU_BAO: // 复用 OpenAI 客户端
diff --git a/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/AzureOpenAIChatModelTests.java b/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/AzureOpenAIChatModelTests.java
index 5c924a5823..69776d8e68 100644
--- a/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/AzureOpenAIChatModelTests.java
+++ b/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/AzureOpenAIChatModelTests.java
@@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
import com.azure.ai.openai.OpenAIClientBuilder;
import com.azure.core.credential.AzureKeyCredential;
-import com.azure.core.util.ClientOptions;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.ai.azure.openai.AzureOpenAiChatModel;
@@ -17,7 +16,7 @@ import reactor.core.publisher.Flux;
import java.util.ArrayList;
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} 集成测试
@@ -29,10 +28,13 @@ public class AzureOpenAIChatModelTests {
// TODO @芋艿:晚点在调整
private final OpenAIClientBuilder openAiApi = new OpenAIClientBuilder()
.endpoint("https://eastusprejade.openai.azure.com")
- .credential(new AzureKeyCredential("xxx"))
- .clientOptions((new ClientOptions()).setApplicationId("spring-ai"));
- private final AzureOpenAiChatModel chatModel = new AzureOpenAiChatModel(openAiApi,
- AzureOpenAiChatOptions.builder().deploymentName(DEFAULT_DEPLOYMENT_NAME).build());
+ .credential(new AzureKeyCredential("xxx"));
+ private final AzureOpenAiChatModel chatModel = AzureOpenAiChatModel.builder()
+ .openAIClientBuilder(openAiApi)
+ .defaultOptions(AzureOpenAiChatOptions.builder()
+ .deploymentName(DEFAULT_DEPLOYMENT_NAME)
+ .build())
+ .build();
@Test
@Disabled
diff --git a/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/BaiChuanChatModelTests.java b/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/BaiChuanChatModelTests.java
index d1cc381fb9..06b0b25653 100644
--- a/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/BaiChuanChatModelTests.java
+++ b/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/BaiChuanChatModelTests.java
@@ -1,7 +1,6 @@
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.deepseek.DeepSeekChatModel;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.ai.chat.messages.Message;
@@ -35,7 +34,7 @@ public class BaiChuanChatModelTests {
.build())
.build();
- private final DeepSeekChatModel chatModel = new DeepSeekChatModel(openAiChatModel);
+ private final BaiChuanChatModel chatModel = new BaiChuanChatModel(openAiChatModel);
@Test
@Disabled
diff --git a/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/DeepSeekChatModelTests.java b/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/DeepSeekChatModelTests.java
index d20a1761f6..7b51df1662 100644
--- a/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/DeepSeekChatModelTests.java
+++ b/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/DeepSeekChatModelTests.java
@@ -1,6 +1,5 @@
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.Test;
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.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
-import org.springframework.ai.openai.OpenAiChatModel;
-import org.springframework.ai.openai.OpenAiChatOptions;
-import org.springframework.ai.openai.api.OpenAiApi;
+import org.springframework.ai.deepseek.DeepSeekChatModel;
+import org.springframework.ai.deepseek.DeepSeekChatOptions;
+import org.springframework.ai.deepseek.api.DeepSeekApi;
import reactor.core.publisher.Flux;
import java.util.ArrayList;
@@ -23,19 +22,16 @@ import java.util.List;
*/
public class DeepSeekChatModelTests {
- private final OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
- .openAiApi(OpenAiApi.builder()
- .baseUrl(DeepSeekChatModel.BASE_URL)
- .apiKey("sk-e52047409b144d97b791a6a46a2d") // apiKey
+ private final DeepSeekChatModel chatModel = DeepSeekChatModel.builder()
+ .deepSeekApi(DeepSeekApi.builder()
+ .apiKey("sk-eaf4172a057344dd9bc64b1f806b6axx") // apiKey
.build())
- .defaultOptions(OpenAiChatOptions.builder()
+ .defaultOptions(DeepSeekChatOptions.builder()
.model("deepseek-chat") // 模型
.temperature(0.7)
.build())
.build();
- private final DeepSeekChatModel chatModel = new DeepSeekChatModel(openAiChatModel);
-
@Test
@Disabled
public void testCall() {
diff --git a/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/LlamaChatModelTests.java b/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/LlamaChatModelTests.java
index 153342d44c..69e2c1daaa 100644
--- a/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/LlamaChatModelTests.java
+++ b/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/LlamaChatModelTests.java
@@ -1,20 +1,6 @@
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.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} 集成测试
@@ -23,43 +9,43 @@ import java.util.List;
*/
public class LlamaChatModelTests {
- private final OllamaChatModel chatModel = OllamaChatModel.builder()
- .ollamaApi(new OllamaApi("http://127.0.0.1:11434")) // Ollama 服务地址
- .defaultOptions(OllamaOptions.builder()
- .model(OllamaModel.LLAMA3.getName()) // 模型
- .build())
- .build();
-
- @Test
- @Disabled
- public void testCall() {
- // 准备参数
- List messages = new ArrayList<>();
- messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
- messages.add(new UserMessage("1 + 1 = ?"));
-
- // 调用
- ChatResponse response = chatModel.call(new Prompt(messages));
- // 打印结果
- System.out.println(response);
- System.out.println(response.getResult().getOutput());
- }
-
- @Test
- @Disabled
- public void testStream() {
- // 准备参数
- List messages = new ArrayList<>();
- messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
- messages.add(new UserMessage("1 + 1 = ?"));
-
- // 调用
- Flux flux = chatModel.stream(new Prompt(messages));
- // 打印结果
- flux.doOnNext(response -> {
-// System.out.println(response);
- System.out.println(response.getResult().getOutput());
- }).then().block();
- }
+// private final OllamaChatModel chatModel = OllamaChatModel.builder()
+// .ollamaApi(new OllamaApi("http://127.0.0.1:11434")) // Ollama 服务地址
+// .defaultOptions(OllamaOptions.builder()
+// .model(OllamaModel.LLAMA3.getName()) // 模型
+// .build())
+// .build();
+//
+// @Test
+// @Disabled
+// public void testCall() {
+// // 准备参数
+// List messages = new ArrayList<>();
+// messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
+// messages.add(new UserMessage("1 + 1 = ?"));
+//
+// // 调用
+// ChatResponse response = chatModel.call(new Prompt(messages));
+// // 打印结果
+// System.out.println(response);
+// System.out.println(response.getResult().getOutput());
+// }
+//
+// @Test
+// @Disabled
+// public void testStream() {
+// // 准备参数
+// List messages = new ArrayList<>();
+// messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
+// messages.add(new UserMessage("1 + 1 = ?"));
+//
+// // 调用
+// Flux flux = chatModel.stream(new Prompt(messages));
+// // 打印结果
+// flux.doOnNext(response -> {
+//// System.out.println(response);
+// System.out.println(response.getResult().getOutput());
+// }).then().block();
+// }
}
diff --git a/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/MoonshotChatModelTests.java b/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/MoonshotChatModelTests.java
index 7de7fd709c..992334b4d9 100644
--- a/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/MoonshotChatModelTests.java
+++ b/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/MoonshotChatModelTests.java
@@ -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.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.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.moonshot.MoonshotChatModel;
-import org.springframework.ai.moonshot.MoonshotChatOptions;
-import org.springframework.ai.moonshot.api.MoonshotApi;
import reactor.core.publisher.Flux;
import java.util.ArrayList;
@@ -22,11 +22,15 @@ import java.util.List;
*/
public class MoonshotChatModelTests {
- private final MoonshotChatModel chatModel = new MoonshotChatModel(
- new MoonshotApi("sk-aHYYV1SARscItye5QQRRNbXij4fy65Ee7pNZlC9gsSQnUKXA"), // 密钥
- MoonshotChatOptions.builder()
- .model("moonshot-v1-8k") // 模型
- .build());
+ private final MoonshotChatModel chatModel = MoonshotChatModel.builder()
+ .moonshotApi(MoonshotApi.builder()
+ .apiKey("sk-aHYYV1SARscItye5QQRRNbXij4fy65Ee7pNZlC9gsSQnUKXA") // 密钥
+ .build())
+ .defaultOptions(MoonshotChatOptions.builder()
+ .model("kimi-k2-0711-preview") // 模型
+ .build())
+ .build();
+
@Test
@Disabled
public void testCall() {
diff --git a/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/OllamaChatModelTests.java b/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/OllamaChatModelTests.java
index f86e67a667..d2bf68812b 100644
--- a/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/OllamaChatModelTests.java
+++ b/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/OllamaChatModelTests.java
@@ -23,7 +23,9 @@ import java.util.List;
public class OllamaChatModelTests {
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()
// .model("qwen") // 模型(https://ollama.com/library/qwen)
.model("deepseek-r1") // 模型(https://ollama.com/library/deepseek-r1)
diff --git a/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/OpenAIChatModelTests.java b/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/OpenAIChatModelTests.java
index ff866fe40b..c650fd0420 100644
--- a/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/OpenAIChatModelTests.java
+++ b/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/OpenAIChatModelTests.java
@@ -25,10 +25,10 @@ public class OpenAIChatModelTests {
private final OpenAiChatModel chatModel = OpenAiChatModel.builder()
.openAiApi(OpenAiApi.builder()
.baseUrl("https://api.holdai.top")
- .apiKey("sk-aN6nWn3fILjrgLFT0fC4Aa60B72e4253826c77B29dC94f17") // apiKey
+ .apiKey("sk-PytRecQlmjEteoa2RRN6cGnwslo72UUPLQVNEMS6K9yjbmpD") // apiKey
.build())
.defaultOptions(OpenAiChatOptions.builder()
- .model(OpenAiApi.ChatModel.GPT_4_O) // 模型
+ .model(OpenAiApi.ChatModel.GPT_4_1_NANO) // 模型
.temperature(0.7)
.build())
.build();
diff --git a/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/TongYiChatModelTests.java b/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/TongYiChatModelTests.java
index 4f0efdb20c..4f2e27edd2 100644
--- a/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/TongYiChatModelTests.java
+++ b/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/TongYiChatModelTests.java
@@ -22,14 +22,17 @@ import java.util.List;
*/
public class TongYiChatModelTests {
- private final DashScopeChatModel chatModel = new DashScopeChatModel(
- new DashScopeApi("sk-7d903764249848cfa912733146da12d1"),
- DashScopeChatOptions.builder()
+ private final DashScopeChatModel chatModel = DashScopeChatModel.builder()
+ .dashScopeApi(DashScopeApi.builder()
+ .apiKey("sk-47aa124781be4bfb95244cc62f63f7d0")
+ .build())
+ .defaultOptions( DashScopeChatOptions.builder()
.withModel("qwen1.5-72b-chat") // 模型
// .withModel("deepseek-r1") // 模型(deepseek-r1)
// .withModel("deepseek-v3") // 模型(deepseek-v3)
// .withModel("deepseek-r1-distill-qwen-1.5b") // 模型(deepseek-r1-distill-qwen-1.5b)
- .build());
+ .build())
+ .build();
@Test
@Disabled
diff --git a/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/YiYanChatModelTests.java b/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/YiYanChatModelTests.java
index ab6f642437..cb7be2a296 100644
--- a/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/YiYanChatModelTests.java
+++ b/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/YiYanChatModelTests.java
@@ -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.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.UserMessage;
import org.springframework.ai.chat.model.ChatResponse;
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 java.util.ArrayList;
@@ -23,9 +23,9 @@ import java.util.List;
public class YiYanChatModelTests {
private final QianFanChatModel chatModel = new QianFanChatModel(
- new QianFanApi("qS8k8dYr2nXunagK4SSU8Xjj", "pHGbx51ql2f0hOyabQvSZezahVC3hh3e"), // 密钥
+ new QianFanApi("DGnyzREuaY7av7c38bOM9Ji2", "9aR8myflEOPDrEeLhoXv0FdqANOAyIZW"), // 密钥
QianFanChatOptions.builder()
- .model(QianFanApi.ChatModel.ERNIE_4_0_8K_Preview.getValue())
+ .model("ERNIE-4.5-8K-Preview")
.build()
);
diff --git a/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/image/OpenAiImageModelTests.java b/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/image/OpenAiImageModelTests.java
index 49015b9b9e..1b124529de 100644
--- a/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/image/OpenAiImageModelTests.java
+++ b/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/image/OpenAiImageModelTests.java
@@ -18,7 +18,7 @@ public class OpenAiImageModelTests {
private final OpenAiImageModel imageModel = new OpenAiImageModel(OpenAiImageApi.builder()
.baseUrl("https://api.holdai.top") // apiKey
- .apiKey("sk-aN6nWn3fILjrgLFT0fC4Aa60B72e4253826c77B29dC94f17")
+ .apiKey("sk-PytRecQlmjEteoa2RRN6cGnwslo72UUPLQVNEMS6K9yjbmpD")
.build());
@Test
@@ -26,8 +26,8 @@ public class OpenAiImageModelTests {
public void testCall() {
// 准备参数
ImageOptions options = OpenAiImageOptions.builder()
- .withModel(OpenAiImageApi.ImageModel.DALL_E_2.getValue()) // 这个模型比较便宜
- .withHeight(256).withWidth(256)
+ .model(OpenAiImageApi.ImageModel.DALL_E_2.getValue()) // 这个模型比较便宜
+ .height(256).width(256)
.build();
ImagePrompt prompt = new ImagePrompt("中国长城!", options);
diff --git a/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/image/QianFanImageTests.java b/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/image/QianFanImageTests.java
index 8f44ab9ad1..156360f255 100644
--- a/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/image/QianFanImageTests.java
+++ b/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/image/QianFanImageTests.java
@@ -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.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.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;
diff --git a/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/image/StabilityAiImageModelTests.java b/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/image/StabilityAiImageModelTests.java
index b58e6df00e..8cf556d9f8 100644
--- a/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/image/StabilityAiImageModelTests.java
+++ b/yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/image/StabilityAiImageModelTests.java
@@ -31,8 +31,8 @@ public class StabilityAiImageModelTests {
public void testCall() {
// 准备参数
ImageOptions options = OpenAiImageOptions.builder()
- .withModel("stable-diffusion-v1-6")
- .withHeight(320).withWidth(320)
+ .model("stable-diffusion-v1-6")
+ .height(320).width(320)
.build();
ImagePrompt prompt = new ImagePrompt("great wall", options);
diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/expression/BpmProcessExpressionRespVO.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/expression/BpmProcessExpressionRespVO.java
index d877f60a88..7ce45b3350 100644
--- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/expression/BpmProcessExpressionRespVO.java
+++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/expression/BpmProcessExpressionRespVO.java
@@ -1,6 +1,6 @@
package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.expression;
-import com.alibaba.excel.annotation.ExcelProperty;
+import cn.idev.excel.annotation.ExcelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -27,4 +27,4 @@ public class BpmProcessExpressionRespVO {
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
-}
\ No newline at end of file
+}
diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java
index 1cbebe06e4..4d34df8308 100644
--- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java
+++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/model/simple/BpmSimpleModelNodeVO.java
@@ -71,6 +71,9 @@ public class BpmSimpleModelNodeVO {
@Schema(description = "是否填写审批意见", example = "false")
private Boolean reasonRequire;
+ @Schema(description = "跳过表达式", example = "{true}")
+ private String skipExpression; // 用于审批节点
+
/**
* 审批节点拒绝处理
*/
diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailRespVO.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailRespVO.java
index 38c2bc1013..0a785b10f4 100644
--- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailRespVO.java
+++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailRespVO.java
@@ -73,7 +73,7 @@ public class BpmApprovalDetailRespVO {
private List candidateUsers; // 只包含未生成 ApprovalTaskInfo 的用户列表
@Schema(description = "流程编号", example = "8761d8e0-0922-11f0-bd37-00ff1db677bf")
- private String processInstanceId; // 当且仅当,该节点是子流程节点时,才会有值(CallActivity 的 processInstanceId 字段)
+ private String processInstanceId; // 当且仅当,该节点是子流程节点时,才会有值(CallActivity 的 calledProcessInstanceId 字段)
}
diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java
index d5d6fa77c4..0e3b1b9208 100644
--- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java
+++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java
@@ -40,7 +40,7 @@ public interface ErrorCodeConstants {
ErrorCode PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS = new ErrorCode(1_009_004_004, "任务({})的候选人({})不存在");
ErrorCode PROCESS_INSTANCE_START_USER_CAN_START = new ErrorCode(1_009_004_005, "发起流程失败,你没有权限发起该流程");
ErrorCode PROCESS_INSTANCE_CANCEL_FAIL_NOT_ALLOW = new ErrorCode(1_009_004_005, "流程取消失败,该流程不允许取消");
- ErrorCode PROCESS_INSTANCE_HTTP_TRIGGER_CALL_ERROR = new ErrorCode(1_009_004_006, "流程 Http 触发器请求调用失败");
+ ErrorCode PROCESS_INSTANCE_HTTP_CALL_ERROR = new ErrorCode(1_009_004_006, "流程 Http 请求调用失败");
ErrorCode PROCESS_INSTANCE_APPROVE_USER_SELECT_ASSIGNEES_NOT_CONFIG = new ErrorCode(1_009_004_007, "下一个任务({})的审批人未配置");
ErrorCode PROCESS_INSTANCE_CANCEL_CHILD_FAIL_NOT_ALLOW = new ErrorCode(1_009_004_008, "子流程取消失败,子流程不允许取消");
@@ -58,7 +58,6 @@ public interface ErrorCodeConstants {
ErrorCode TASK_SIGN_DELETE_NO_PARENT = new ErrorCode(1_009_005_012, "任务减签失败,被减签的任务必须是通过加签生成的任务");
ErrorCode TASK_TRANSFER_FAIL_USER_REPEAT = new ErrorCode(1_009_005_013, "任务转办失败,转办人和当前审批人为同一人");
ErrorCode TASK_TRANSFER_FAIL_USER_NOT_EXISTS = new ErrorCode(1_009_005_014, "任务转办失败,转办人不存在");
- ErrorCode TASK_CREATE_FAIL_NO_CANDIDATE_USER = new ErrorCode(1_009_006_003, "操作失败,原因:找不到任务的审批人!");
ErrorCode TASK_SIGNATURE_NOT_EXISTS = new ErrorCode(1_009_005_015, "签名不能为空!");
ErrorCode TASK_REASON_REQUIRE = new ErrorCode(1_009_005_016, "审批意见不能为空!");
@@ -74,6 +73,7 @@ public interface ErrorCodeConstants {
ErrorCode CATEGORY_NOT_EXISTS = new ErrorCode(1_009_012_000, "流程分类不存在");
ErrorCode CATEGORY_NAME_DUPLICATE = new ErrorCode(1_009_012_001, "流程分类名字【{}】重复");
ErrorCode CATEGORY_CODE_DUPLICATE = new ErrorCode(1_009_012_002, "流程分类编码【{}】重复");
+ ErrorCode CATEGORY_DELETE_FAIL_MODEL_USED = new ErrorCode(1_009_012_003, "删除失败,流程分类【{}】已被流程模型使用,请先删除对应的流程模型");
// ========== BPM 流程监听器 1-009-013-000 ==========
ErrorCode PROCESS_LISTENER_NOT_EXISTS = new ErrorCode(1_009_013_000, "流程监听器不存在");
diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeTypeEnum.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeTypeEnum.java
index e5ffa12025..d97c145c3f 100644
--- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeTypeEnum.java
+++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeTypeEnum.java
@@ -35,7 +35,7 @@ public enum BpmSimpleModelNodeTypeEnum implements ArrayValuable {
// 50 ~ 条件分支
CONDITION_NODE(50, "条件", "sequenceFlow"), // 用于构建流转条件的表达式
CONDITION_BRANCH_NODE(51, "条件分支", "exclusiveGateway"),
- PARALLEL_BRANCH_NODE(52, "并行分支", "parallelGateway"),
+ PARALLEL_BRANCH_NODE(52, "并行分支", "inclusiveGateway"), // 并行分支使用包容网关实现,条件表达式结果设置为 true
INCLUSIVE_BRANCH_NODE(53, "包容分支", "inclusiveGateway"),
ROUTER_BRANCH_NODE(54, "路由分支", "exclusiveGateway")
;
diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmTaskStatusEnum.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmTaskStatusEnum.java
index a19f122bd8..9ba3b5cb3a 100644
--- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmTaskStatusEnum.java
+++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmTaskStatusEnum.java
@@ -14,6 +14,7 @@ import lombok.Getter;
@AllArgsConstructor
public enum BpmTaskStatusEnum {
+ SKIP(-2, "跳过"),
NOT_START(-1, "未开始"),
RUNNING(1, "审批中"),
APPROVE(2, "审批通过"),
diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java
index 7f66b29d3b..df8b0d5fd5 100644
--- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java
+++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/BpmTaskCandidateInvoker.java
@@ -18,10 +18,7 @@ import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import com.google.common.annotations.VisibleForTesting;
import lombok.extern.slf4j.Slf4j;
-import org.flowable.bpmn.model.BpmnModel;
-import org.flowable.bpmn.model.CallActivity;
-import org.flowable.bpmn.model.FlowElement;
-import org.flowable.bpmn.model.UserTask;
+import org.flowable.bpmn.model.*;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.runtime.ProcessInstance;
@@ -132,7 +129,7 @@ public class BpmTaskCandidateInvoker {
Long startUserId, String processDefinitionId, Map processVariables) {
// 如果是 CallActivity 子流程,不进行计算候选人
FlowElement flowElement = BpmnModelUtils.getFlowElementById(bpmnModel, activityId);
- if (flowElement instanceof CallActivity) {
+ if (flowElement instanceof CallActivity || flowElement instanceof SubProcess) {
return new HashSet<>();
}
// 审批类型非人工审核时,不进行计算候选人。原因是:后续会自动通过、不通过
diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/expression/BpmTaskAssignLeaderExpression.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/expression/BpmTaskAssignLeaderExpression.java
index 7c1950f8ce..e9180c8695 100644
--- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/expression/BpmTaskAssignLeaderExpression.java
+++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/expression/BpmTaskAssignLeaderExpression.java
@@ -24,6 +24,7 @@ import static java.util.Collections.emptySet;
* @author 芋道源码
*/
@Component
+@Deprecated // 仅仅是表达式的示例,建议使用 BpmTaskCandidateStartUserDeptLeaderStrategy 替代
public class BpmTaskAssignLeaderExpression {
@Resource
diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/expression/BpmTaskAssignStartUserExpression.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/expression/BpmTaskAssignStartUserExpression.java
index ac243c0f43..f22dc508cb 100644
--- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/expression/BpmTaskAssignStartUserExpression.java
+++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/expression/BpmTaskAssignStartUserExpression.java
@@ -16,6 +16,7 @@ import java.util.Set;
* @author 芋道源码
*/
@Component
+@Deprecated // 仅仅是表达式的示例,建议使用 BpmTaskCandidateStartUserStrategy 替代
public class BpmTaskAssignStartUserExpression {
@Resource
diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/other/BpmTaskCandidateExpressionStrategy.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/other/BpmTaskCandidateExpressionStrategy.java
index 64ca9e8538..86c137a4b7 100644
--- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/other/BpmTaskCandidateExpressionStrategy.java
+++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/other/BpmTaskCandidateExpressionStrategy.java
@@ -8,6 +8,7 @@ import com.google.common.collect.Sets;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.common.engine.api.FlowableException;
+import org.flowable.common.engine.impl.javax.el.PropertyNotFoundException;
import org.flowable.engine.delegate.DelegateExecution;
import org.springframework.stereotype.Component;
@@ -48,10 +49,12 @@ public class BpmTaskCandidateExpressionStrategy implements BpmTaskCandidateStrat
Object result = FlowableUtils.getExpressionValue(variables, param);
return CollectionUtils.toLinkedHashSet(Long.class, result);
} catch (FlowableException ex) {
- // 预测未运行的节点时候,表达式如果包含 execution 或者不存在的流程变量会抛异常,
- log.warn("[calculateUsersByActivity][表达式({}) 变量({}) 解析报错", param, variables, ex);
- // 不能预测候选人,返回空列表, 避免流程无法进行
- return Sets.newHashSet();
+ // 预测未运行的节点时候,表达式如果包含 execution 或者不存在的流程变量会抛异常,此时忽略该异常!相当于说,不做流程预测!!!
+ if (ex.getCause() != null && ex.getCause() instanceof PropertyNotFoundException) {
+ return Sets.newHashSet();
+ }
+ log.error("[calculateUsersByActivity][表达式({}) 变量({}) 解析报错", param, variables, ex);
+ throw ex;
}
}
diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmProcessInstanceEventListener.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmProcessInstanceEventListener.java
index ba2aaa6bcb..3deb545fa8 100644
--- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmProcessInstanceEventListener.java
+++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmProcessInstanceEventListener.java
@@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.listener;
+import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
import com.google.common.collect.ImmutableSet;
import jakarta.annotation.Resource;
@@ -37,18 +38,26 @@ public class BpmProcessInstanceEventListener extends AbstractFlowableEngineEvent
@Override
protected void processCreated(FlowableEngineEntityEvent event) {
- processInstanceService.processProcessInstanceCreated((ProcessInstance)event.getEntity());
+ ProcessInstance processInstance = (ProcessInstance) event.getEntity();
+ FlowableUtils.execute(processInstance.getTenantId(),
+ () -> processInstanceService.processProcessInstanceCreated(processInstance));
}
@Override
protected void processCompleted(FlowableEngineEntityEvent event) {
- processInstanceService.processProcessInstanceCompleted((ProcessInstance)event.getEntity());
+ ProcessInstance processInstance = (ProcessInstance) event.getEntity();
+ FlowableUtils.execute(processInstance.getTenantId(),
+ () -> processInstanceService.processProcessInstanceCompleted(processInstance));
}
- @Override // 特殊情况:当跳转到 EndEvent 流程实例未结束, 会执行 deleteProcessInstance 方法
+ @Override
protected void processCancelled(FlowableCancelledEvent event) {
+ // 特殊情况:当跳转到 EndEvent 流程实例未结束, 会执行 deleteProcessInstance 方法
ProcessInstance processInstance = processInstanceService.getProcessInstance(event.getProcessInstanceId());
- processInstanceService.processProcessInstanceCompleted(processInstance);
+ if (processInstance != null) {
+ FlowableUtils.execute(processInstance.getTenantId(),
+ () -> processInstanceService.processProcessInstanceCompleted(processInstance));
+ }
}
}
diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java
index e50df0bcf0..6e2e48cbec 100644
--- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java
+++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmTaskEventListener.java
@@ -3,10 +3,12 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.listener;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONObject;
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventTypeEnum;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
+import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService;
import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService;
import com.google.common.collect.ImmutableSet;
@@ -58,17 +60,20 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener {
@Override
protected void taskCreated(FlowableEngineEntityEvent event) {
- taskService.processTaskCreated((Task) event.getEntity());
+ Task entity = (Task) event.getEntity();
+ FlowableUtils.execute(entity.getTenantId(), () -> taskService.processTaskCreated(entity));
}
@Override
protected void taskAssigned(FlowableEngineEntityEvent event) {
- taskService.processTaskAssigned((Task) event.getEntity());
+ Task entity = (Task) event.getEntity();
+ FlowableUtils.execute(entity.getTenantId(), () -> taskService.processTaskAssigned(entity));
}
@Override
protected void taskCompleted(FlowableEngineEntityEvent event) {
- taskService.processTaskCompleted((Task) event.getEntity());
+ Task entity = (Task) event.getEntity();
+ FlowableUtils.execute(entity.getTenantId(), () -> taskService.processTaskCompleted(entity));
}
@Override
@@ -94,6 +99,23 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener {
String processDefinitionId = event.getProcessDefinitionId();
BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processDefinitionId);
Job entity = (Job) event.getEntity();
+ // 特殊 from https://t.zsxq.com/h6oWr :当 elementId 为空时,尝试从 JobHandlerConfiguration 中解析 JSON 获取
+ String elementId = entity.getElementId();
+ if (elementId == null && entity.getJobHandlerConfiguration() != null) {
+ try {
+ String handlerConfig = entity.getJobHandlerConfiguration();
+ if (handlerConfig.startsWith("{") && handlerConfig.contains("activityId")) {
+ elementId = new JSONObject(handlerConfig).getStr("activityId");
+ }
+ } catch (Exception e) {
+ log.error("[timerFired][解析 entity({}) 失败]", entity, e);
+ return;
+ }
+ }
+ if (elementId == null) {
+ log.error("[timerFired][解析 entity({}) elementId 为空,跳过处理]", entity);
+ return;
+ }
FlowElement element = BpmnModelUtils.getFlowElementById(bpmnModel, entity.getElementId());
if (!(element instanceof BoundaryEvent)) {
return;
diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmHttpRequestUtils.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmHttpRequestUtils.java
index 2503c0fff9..358ee9f4dc 100644
--- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmHttpRequestUtils.java
+++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmHttpRequestUtils.java
@@ -6,15 +6,15 @@ import cn.iocoder.yudao.framework.common.core.KeyValue;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.spring.SpringUtils;
+import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
+import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEvent;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmHttpRequestParamTypeEnum;
import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
import com.fasterxml.jackson.core.type.TypeReference;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.runtime.ProcessInstance;
-import org.springframework.http.HttpEntity;
-import org.springframework.http.HttpMethod;
-import org.springframework.http.ResponseEntity;
+import org.springframework.http.*;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestClientException;
@@ -26,7 +26,7 @@ import java.util.Map;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.HEADER_TENANT_ID;
-import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.PROCESS_INSTANCE_HTTP_TRIGGER_CALL_ERROR;
+import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.PROCESS_INSTANCE_HTTP_CALL_ERROR;
/**
* 工作流发起 HTTP 请求工具类
@@ -42,7 +42,6 @@ public class BpmHttpRequestUtils {
List bodyParams,
Boolean handleResponse,
List> response) {
- RestTemplate restTemplate = SpringUtils.getBean(RestTemplate.class);
BpmProcessInstanceService processInstanceService = SpringUtils.getBean(BpmProcessInstanceService.class);
// 1.1 设置请求头
@@ -51,6 +50,7 @@ public class BpmHttpRequestUtils {
MultiValueMap body = buildHttpBody(processInstance, bodyParams);
// 2. 发起请求
+ RestTemplate restTemplate = SpringUtils.getBean(RestTemplate.class);
ResponseEntity responseEntity = sendHttpRequest(url, headers, body, restTemplate);
// 3. 处理返回
@@ -78,27 +78,55 @@ public class BpmHttpRequestUtils {
}
}
+ public static void executeBpmHttpRequest(BpmProcessInstanceStatusEvent event,
+ String url) {
+ // 1.1 设置请求头
+ HttpHeaders headers = new HttpHeaders();
+ headers.setContentType(MediaType.APPLICATION_JSON);
+ if (TenantContextHolder.getTenantId() != null) {
+ headers.add(HEADER_TENANT_ID, String.valueOf(TenantContextHolder.getTenantId()));
+ } else {
+ BpmProcessInstanceService processInstanceService = SpringUtils.getBean(BpmProcessInstanceService.class);
+ ProcessInstance processInstance = processInstanceService.getProcessInstance(event.getId());
+ if (processInstance != null) {
+ headers.add(HEADER_TENANT_ID, String.valueOf(TenantContextHolder.getTenantId()));
+ }
+ }
+ // 1.2 设置请求体
+// MultiValueMap body = new LinkedMultiValueMap<>();
+// body.add("id", event.getId());
+// body.add("processDefinitionKey", event.getProcessDefinitionKey());
+// body.add("status", event.getStatus().toString());
+// if (StrUtil.isNotEmpty(event.getBusinessKey())) {
+// body.add("businessKey", event.getBusinessKey());
+// }
+
+ // 2. 发起请求
+ RestTemplate restTemplate = SpringUtils.getBean(RestTemplate.class);
+ sendHttpRequest(url, headers, event, restTemplate);
+ }
+
public static ResponseEntity sendHttpRequest(String url,
MultiValueMap headers,
- MultiValueMap body,
+ Object body,
RestTemplate restTemplate) {
- HttpEntity> requestEntity = new HttpEntity<>(body, headers);
+ HttpEntity