Commit 53d5c394b6979dac4cfe74eb816a1ca9e98c55eb

Authored by YunaiV
2 parents 0f9cf918 3a07bcc3

Merge branch 'master-jdk17' of https://gitee.com/zhijiantianya/ruoyi-vue-pro into feature/iot

# Conflicts:
#	yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.java
#	yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceMqttConnectionParamsRespVO.java
#	yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceRespVO.java
#	yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/IotOtaFirmwareController.java
#	yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/IotOtaUpgradeRecordController.java
#	yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/vo/IotThingModelRespVO.java
Showing 227 changed files with 1854 additions and 1227 deletions

Too many changes.

To preserve performance only 100 of 227 files are displayed.

@@ -32,7 +32,7 @@ @@ -32,7 +32,7 @@
32 <url>https://github.com/YunaiV/ruoyi-vue-pro</url> 32 <url>https://github.com/YunaiV/ruoyi-vue-pro</url>
33 33
34 <properties> 34 <properties>
35 - <revision>2.6.0-SNAPSHOT</revision> 35 + <revision>2025.08-SNAPSHOT</revision>
36 <!-- Maven 相关 --> 36 <!-- Maven 相关 -->
37 <java.version>17</java.version> 37 <java.version>17</java.version>
38 <maven.compiler.source>${java.version}</maven.compiler.source> 38 <maven.compiler.source>${java.version}</maven.compiler.source>
@@ -88,6 +88,13 @@ @@ -88,6 +88,13 @@
88 <version>${lombok.version}</version> 88 <version>${lombok.version}</version>
89 </path> 89 </path>
90 <path> 90 <path>
  91 + <!-- 确保 Lombok 生成的 getter/setter 方法能被 MapStruct 正确识别,
  92 + 避免出现 No property named “xxx" exists 的编译错误 -->
  93 + <groupId>org.projectlombok</groupId>
  94 + <artifactId>lombok-mapstruct-binding</artifactId>
  95 + <version>0.2.0</version>
  96 + </path>
  97 + <path>
91 <groupId>org.mapstruct</groupId> 98 <groupId>org.mapstruct</groupId>
92 <artifactId>mapstruct-processor</artifactId> 99 <artifactId>mapstruct-processor</artifactId>
93 <version>${mapstruct.version}</version> 100 <version>${mapstruct.version}</version>
sql/postgresql/quartz.sql
1 --- ----------------------------  
2 --- qrtz_blob_triggers  
3 --- ----------------------------  
4 -CREATE TABLE qrtz_blob_triggers 1 +-- https://github.com/quartz-scheduler/quartz/blob/main/quartz/src/main/resources/org/quartz/impl/jdbcjobstore/tables_postgres.sql
  2 +-- Thanks to Patrick Lightbody for submitting this...
  3 +--
  4 +-- In your Quartz properties file, you'll need to set
  5 +-- org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
  6 +
  7 +DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS;
  8 +DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS;
  9 +DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE;
  10 +DROP TABLE IF EXISTS QRTZ_LOCKS;
  11 +DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS;
  12 +DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS;
  13 +DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS;
  14 +DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS;
  15 +DROP TABLE IF EXISTS QRTZ_TRIGGERS;
  16 +DROP TABLE IF EXISTS QRTZ_JOB_DETAILS;
  17 +DROP TABLE IF EXISTS QRTZ_CALENDARS;
  18 +
  19 +CREATE TABLE QRTZ_JOB_DETAILS
5 ( 20 (
6 - sched_name varchar(120) NOT NULL,  
7 - trigger_name varchar(190) NOT NULL,  
8 - trigger_group varchar(190) NOT NULL,  
9 - blob_data bytea NULL,  
10 - PRIMARY KEY (sched_name, trigger_name, trigger_group) 21 + SCHED_NAME VARCHAR(120) NOT NULL,
  22 + JOB_NAME VARCHAR(200) NOT NULL,
  23 + JOB_GROUP VARCHAR(200) NOT NULL,
  24 + DESCRIPTION VARCHAR(250) NULL,
  25 + JOB_CLASS_NAME VARCHAR(250) NOT NULL,
  26 + IS_DURABLE BOOL NOT NULL,
  27 + IS_NONCONCURRENT BOOL NOT NULL,
  28 + IS_UPDATE_DATA BOOL NOT NULL,
  29 + REQUESTS_RECOVERY BOOL NOT NULL,
  30 + JOB_DATA BYTEA NULL,
  31 + PRIMARY KEY (SCHED_NAME, JOB_NAME, JOB_GROUP)
11 ); 32 );
12 33
13 -CREATE INDEX idx_qrtz_blob_triggers_sched_name ON qrtz_blob_triggers (sched_name, trigger_name, trigger_group);  
14 -  
15 --- ----------------------------  
16 --- qrtz_calendars  
17 --- ----------------------------  
18 -CREATE TABLE qrtz_calendars 34 +CREATE TABLE QRTZ_TRIGGERS
19 ( 35 (
20 - sched_name varchar(120) NOT NULL,  
21 - calendar_name varchar(190) NOT NULL,  
22 - calendar bytea NOT NULL,  
23 - PRIMARY KEY (sched_name, calendar_name) 36 + SCHED_NAME VARCHAR(120) NOT NULL,
  37 + TRIGGER_NAME VARCHAR(200) NOT NULL,
  38 + TRIGGER_GROUP VARCHAR(200) NOT NULL,
  39 + JOB_NAME VARCHAR(200) NOT NULL,
  40 + JOB_GROUP VARCHAR(200) NOT NULL,
  41 + DESCRIPTION VARCHAR(250) NULL,
  42 + NEXT_FIRE_TIME BIGINT NULL,
  43 + PREV_FIRE_TIME BIGINT NULL,
  44 + PRIORITY INTEGER NULL,
  45 + TRIGGER_STATE VARCHAR(16) NOT NULL,
  46 + TRIGGER_TYPE VARCHAR(8) NOT NULL,
  47 + START_TIME BIGINT NOT NULL,
  48 + END_TIME BIGINT NULL,
  49 + CALENDAR_NAME VARCHAR(200) NULL,
  50 + MISFIRE_INSTR SMALLINT NULL,
  51 + JOB_DATA BYTEA NULL,
  52 + PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP),
  53 + FOREIGN KEY (SCHED_NAME, JOB_NAME, JOB_GROUP)
  54 + REFERENCES QRTZ_JOB_DETAILS (SCHED_NAME, JOB_NAME, JOB_GROUP)
24 ); 55 );
25 56
26 -  
27 --- ----------------------------  
28 --- qrtz_cron_triggers  
29 --- ----------------------------  
30 -CREATE TABLE qrtz_cron_triggers 57 +CREATE TABLE QRTZ_SIMPLE_TRIGGERS
31 ( 58 (
32 - sched_name varchar(120) NOT NULL,  
33 - trigger_name varchar(190) NOT NULL,  
34 - trigger_group varchar(190) NOT NULL,  
35 - cron_expression varchar(120) NOT NULL,  
36 - time_zone_id varchar(80) NULL DEFAULT NULL,  
37 - PRIMARY KEY (sched_name, trigger_name, trigger_group) 59 + SCHED_NAME VARCHAR(120) NOT NULL,
  60 + TRIGGER_NAME VARCHAR(200) NOT NULL,
  61 + TRIGGER_GROUP VARCHAR(200) NOT NULL,
  62 + REPEAT_COUNT BIGINT NOT NULL,
  63 + REPEAT_INTERVAL BIGINT NOT NULL,
  64 + TIMES_TRIGGERED BIGINT NOT NULL,
  65 + PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP),
  66 + FOREIGN KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP)
  67 + REFERENCES QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP)
38 ); 68 );
39 69
40 --- @formatter:off  
41 -BEGIN;  
42 -COMMIT;  
43 --- @formatter:on  
44 -  
45 --- ----------------------------  
46 --- qrtz_fired_triggers  
47 --- ----------------------------  
48 -CREATE TABLE qrtz_fired_triggers 70 +CREATE TABLE QRTZ_CRON_TRIGGERS
49 ( 71 (
50 - sched_name varchar(120) NOT NULL,  
51 - entry_id varchar(95) NOT NULL,  
52 - trigger_name varchar(190) NOT NULL,  
53 - trigger_group varchar(190) NOT NULL,  
54 - instance_name varchar(190) NOT NULL,  
55 - fired_time int8 NOT NULL,  
56 - sched_time int8 NOT NULL,  
57 - priority int4 NOT NULL,  
58 - state varchar(16) NOT NULL,  
59 - job_name varchar(190) NULL DEFAULT NULL,  
60 - job_group varchar(190) NULL DEFAULT NULL,  
61 - is_nonconcurrent varchar(1) NULL DEFAULT NULL,  
62 - requests_recovery varchar(1) NULL DEFAULT NULL,  
63 - PRIMARY KEY (sched_name, entry_id) 72 + SCHED_NAME VARCHAR(120) NOT NULL,
  73 + TRIGGER_NAME VARCHAR(200) NOT NULL,
  74 + TRIGGER_GROUP VARCHAR(200) NOT NULL,
  75 + CRON_EXPRESSION VARCHAR(120) NOT NULL,
  76 + TIME_ZONE_ID VARCHAR(80),
  77 + PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP),
  78 + FOREIGN KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP)
  79 + REFERENCES QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP)
64 ); 80 );
65 81
66 -CREATE INDEX idx_qrtz_ft_trig_inst_name ON qrtz_fired_triggers (sched_name, instance_name);  
67 -CREATE INDEX idx_qrtz_ft_inst_job_req_rcvry ON qrtz_fired_triggers (sched_name, instance_name, requests_recovery);  
68 -CREATE INDEX idx_qrtz_ft_j_g ON qrtz_fired_triggers (sched_name, job_name, job_group);  
69 -CREATE INDEX idx_qrtz_ft_jg ON qrtz_fired_triggers (sched_name, job_group);  
70 -CREATE INDEX idx_qrtz_ft_t_g ON qrtz_fired_triggers (sched_name, trigger_name, trigger_group);  
71 -CREATE INDEX idx_qrtz_ft_tg ON qrtz_fired_triggers (sched_name, trigger_group);  
72 -  
73 --- ----------------------------  
74 --- qrtz_job_details  
75 --- ----------------------------  
76 -CREATE TABLE qrtz_job_details 82 +CREATE TABLE QRTZ_SIMPROP_TRIGGERS
77 ( 83 (
78 - sched_name varchar(120) NOT NULL,  
79 - job_name varchar(190) NOT NULL,  
80 - job_group varchar(190) NOT NULL,  
81 - description varchar(250) NULL DEFAULT NULL,  
82 - job_class_name varchar(250) NOT NULL,  
83 - is_durable varchar(1) NOT NULL,  
84 - is_nonconcurrent varchar(1) NOT NULL,  
85 - is_update_data varchar(1) NOT NULL,  
86 - requests_recovery varchar(1) NOT NULL,  
87 - job_data bytea NULL,  
88 - PRIMARY KEY (sched_name, job_name, job_group) 84 + SCHED_NAME VARCHAR(120) NOT NULL,
  85 + TRIGGER_NAME VARCHAR(200) NOT NULL,
  86 + TRIGGER_GROUP VARCHAR(200) NOT NULL,
  87 + STR_PROP_1 VARCHAR(512) NULL,
  88 + STR_PROP_2 VARCHAR(512) NULL,
  89 + STR_PROP_3 VARCHAR(512) NULL,
  90 + INT_PROP_1 INT NULL,
  91 + INT_PROP_2 INT NULL,
  92 + LONG_PROP_1 BIGINT NULL,
  93 + LONG_PROP_2 BIGINT NULL,
  94 + DEC_PROP_1 NUMERIC(13, 4) NULL,
  95 + DEC_PROP_2 NUMERIC(13, 4) NULL,
  96 + BOOL_PROP_1 BOOL NULL,
  97 + BOOL_PROP_2 BOOL NULL,
  98 + PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP),
  99 + FOREIGN KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP)
  100 + REFERENCES QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP)
89 ); 101 );
90 102
91 -CREATE INDEX idx_qrtz_j_req_recovery ON qrtz_job_details (sched_name, requests_recovery);  
92 -CREATE INDEX idx_qrtz_j_grp ON qrtz_job_details (sched_name, job_group);  
93 -  
94 --- @formatter:off  
95 -BEGIN;  
96 -COMMIT;  
97 --- @formatter:on  
98 -  
99 --- ----------------------------  
100 --- qrtz_locks  
101 --- ----------------------------  
102 -CREATE TABLE qrtz_locks 103 +CREATE TABLE QRTZ_BLOB_TRIGGERS
103 ( 104 (
104 - sched_name varchar(120) NOT NULL,  
105 - lock_name varchar(40) NOT NULL,  
106 - PRIMARY KEY (sched_name, lock_name) 105 + SCHED_NAME VARCHAR(120) NOT NULL,
  106 + TRIGGER_NAME VARCHAR(200) NOT NULL,
  107 + TRIGGER_GROUP VARCHAR(200) NOT NULL,
  108 + BLOB_DATA BYTEA NULL,
  109 + PRIMARY KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP),
  110 + FOREIGN KEY (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP)
  111 + REFERENCES QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP)
107 ); 112 );
108 113
109 --- @formatter:off  
110 -BEGIN;  
111 -COMMIT;  
112 --- @formatter:on  
113 -  
114 --- ----------------------------  
115 --- qrtz_paused_trigger_grps  
116 --- ----------------------------  
117 -CREATE TABLE qrtz_paused_trigger_grps 114 +CREATE TABLE QRTZ_CALENDARS
118 ( 115 (
119 - sched_name varchar(120) NOT NULL,  
120 - trigger_group varchar(190) NOT NULL,  
121 - PRIMARY KEY (sched_name, trigger_group) 116 + SCHED_NAME VARCHAR(120) NOT NULL,
  117 + CALENDAR_NAME VARCHAR(200) NOT NULL,
  118 + CALENDAR BYTEA NOT NULL,
  119 + PRIMARY KEY (SCHED_NAME, CALENDAR_NAME)
122 ); 120 );
123 121
124 --- ----------------------------  
125 --- qrtz_scheduler_state  
126 --- ----------------------------  
127 -CREATE TABLE qrtz_scheduler_state 122 +
  123 +CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS
128 ( 124 (
129 - sched_name varchar(120) NOT NULL,  
130 - instance_name varchar(190) NOT NULL,  
131 - last_checkin_time int8 NOT NULL,  
132 - checkin_interval int8 NOT NULL,  
133 - PRIMARY KEY (sched_name, instance_name) 125 + SCHED_NAME VARCHAR(120) NOT NULL,
  126 + TRIGGER_GROUP VARCHAR(200) NOT NULL,
  127 + PRIMARY KEY (SCHED_NAME, TRIGGER_GROUP)
134 ); 128 );
135 129
136 --- @formatter:off  
137 -BEGIN;  
138 -COMMIT;  
139 --- @formatter:on  
140 -  
141 --- ----------------------------  
142 --- qrtz_simple_triggers  
143 --- ----------------------------  
144 -CREATE TABLE qrtz_simple_triggers 130 +CREATE TABLE QRTZ_FIRED_TRIGGERS
145 ( 131 (
146 - sched_name varchar(120) NOT NULL,  
147 - trigger_name varchar(190) NOT NULL,  
148 - trigger_group varchar(190) NOT NULL,  
149 - repeat_count int8 NOT NULL,  
150 - repeat_interval int8 NOT NULL,  
151 - times_triggered int8 NOT NULL,  
152 - PRIMARY KEY (sched_name, trigger_name, trigger_group) 132 + SCHED_NAME VARCHAR(120) NOT NULL,
  133 + ENTRY_ID VARCHAR(95) NOT NULL,
  134 + TRIGGER_NAME VARCHAR(200) NOT NULL,
  135 + TRIGGER_GROUP VARCHAR(200) NOT NULL,
  136 + INSTANCE_NAME VARCHAR(200) NOT NULL,
  137 + FIRED_TIME BIGINT NOT NULL,
  138 + SCHED_TIME BIGINT NOT NULL,
  139 + PRIORITY INTEGER NOT NULL,
  140 + STATE VARCHAR(16) NOT NULL,
  141 + JOB_NAME VARCHAR(200) NULL,
  142 + JOB_GROUP VARCHAR(200) NULL,
  143 + IS_NONCONCURRENT BOOL NULL,
  144 + REQUESTS_RECOVERY BOOL NULL,
  145 + PRIMARY KEY (SCHED_NAME, ENTRY_ID)
153 ); 146 );
154 147
155 --- ----------------------------  
156 --- qrtz_simprop_triggers  
157 --- ----------------------------  
158 -CREATE TABLE qrtz_simprop_triggers 148 +CREATE TABLE QRTZ_SCHEDULER_STATE
159 ( 149 (
160 - sched_name varchar(120) NOT NULL,  
161 - trigger_name varchar(190) NOT NULL,  
162 - trigger_group varchar(190) NOT NULL,  
163 - str_prop_1 varchar(512) NULL DEFAULT NULL,  
164 - str_prop_2 varchar(512) NULL DEFAULT NULL,  
165 - str_prop_3 varchar(512) NULL DEFAULT NULL,  
166 - int_prop_1 int4 NULL DEFAULT NULL,  
167 - int_prop_2 int4 NULL DEFAULT NULL,  
168 - long_prop_1 int8 NULL DEFAULT NULL,  
169 - long_prop_2 int8 NULL DEFAULT NULL,  
170 - dec_prop_1 numeric(13, 4) NULL DEFAULT NULL,  
171 - dec_prop_2 numeric(13, 4) NULL DEFAULT NULL,  
172 - bool_prop_1 varchar(1) NULL DEFAULT NULL,  
173 - bool_prop_2 varchar(1) NULL DEFAULT NULL,  
174 - PRIMARY KEY (sched_name, trigger_name, trigger_group) 150 + SCHED_NAME VARCHAR(120) NOT NULL,
  151 + INSTANCE_NAME VARCHAR(200) NOT NULL,
  152 + LAST_CHECKIN_TIME BIGINT NOT NULL,
  153 + CHECKIN_INTERVAL BIGINT NOT NULL,
  154 + PRIMARY KEY (SCHED_NAME, INSTANCE_NAME)
175 ); 155 );
176 156
177 --- ----------------------------  
178 --- qrtz_triggers  
179 --- ----------------------------  
180 -CREATE TABLE qrtz_triggers 157 +CREATE TABLE QRTZ_LOCKS
181 ( 158 (
182 - sched_name varchar(120) NOT NULL,  
183 - trigger_name varchar(190) NOT NULL,  
184 - trigger_group varchar(190) NOT NULL,  
185 - job_name varchar(190) NOT NULL,  
186 - job_group varchar(190) NOT NULL,  
187 - description varchar(250) NULL DEFAULT NULL,  
188 - next_fire_time int8 NULL DEFAULT NULL,  
189 - prev_fire_time int8 NULL DEFAULT NULL,  
190 - priority int4 NULL DEFAULT NULL,  
191 - trigger_state varchar(16) NOT NULL,  
192 - trigger_type varchar(8) NOT NULL,  
193 - start_time int8 NOT NULL,  
194 - end_time int8 NULL DEFAULT NULL,  
195 - calendar_name varchar(190) NULL DEFAULT NULL,  
196 - misfire_instr int2 NULL DEFAULT NULL,  
197 - job_data bytea NULL,  
198 - PRIMARY KEY (sched_name, trigger_name, trigger_group) 159 + SCHED_NAME VARCHAR(120) NOT NULL,
  160 + LOCK_NAME VARCHAR(40) NOT NULL,
  161 + PRIMARY KEY (SCHED_NAME, LOCK_NAME)
199 ); 162 );
200 163
201 -CREATE INDEX idx_qrtz_t_j ON qrtz_triggers (sched_name, job_name, job_group);  
202 -CREATE INDEX idx_qrtz_t_jg ON qrtz_triggers (sched_name, job_group);  
203 -CREATE INDEX idx_qrtz_t_c ON qrtz_triggers (sched_name, calendar_name);  
204 -CREATE INDEX idx_qrtz_t_g ON qrtz_triggers (sched_name, trigger_group);  
205 -CREATE INDEX idx_qrtz_t_state ON qrtz_triggers (sched_name, trigger_state);  
206 -CREATE INDEX idx_qrtz_t_n_state ON qrtz_triggers (sched_name, trigger_name, trigger_group, trigger_state);  
207 -CREATE INDEX idx_qrtz_t_n_g_state ON qrtz_triggers (sched_name, trigger_group, trigger_state);  
208 -CREATE INDEX idx_qrtz_t_next_fire_time ON qrtz_triggers (sched_name, next_fire_time);  
209 -CREATE INDEX idx_qrtz_t_nft_st ON qrtz_triggers (sched_name, trigger_state, next_fire_time);  
210 -CREATE INDEX idx_qrtz_t_nft_misfire ON qrtz_triggers (sched_name, misfire_instr, next_fire_time);  
211 -CREATE INDEX idx_qrtz_t_nft_st_misfire ON qrtz_triggers (sched_name, misfire_instr, next_fire_time, trigger_state);  
212 -CREATE INDEX idx_qrtz_t_nft_st_misfire_grp ON qrtz_triggers (sched_name, misfire_instr, next_fire_time, trigger_group,  
213 - trigger_state);  
214 -  
215 --- @formatter:off  
216 -BEGIN;  
217 -COMMIT;  
218 --- @formatter:on  
219 -  
220 -  
221 --- ----------------------------  
222 --- FK: qrtz_blob_triggers  
223 --- ----------------------------  
224 -ALTER TABLE qrtz_blob_triggers  
225 - ADD CONSTRAINT qrtz_blob_triggers_ibfk_1 FOREIGN KEY (sched_name, trigger_name, trigger_group) REFERENCES qrtz_triggers (sched_name,  
226 - trigger_name,  
227 - trigger_group);  
228 -  
229 --- ----------------------------  
230 --- FK: qrtz_cron_triggers  
231 --- ----------------------------  
232 -ALTER TABLE qrtz_cron_triggers  
233 - ADD CONSTRAINT qrtz_cron_triggers_ibfk_1 FOREIGN KEY (sched_name, trigger_name, trigger_group) REFERENCES qrtz_triggers (sched_name, trigger_name, trigger_group);  
234 -  
235 --- ----------------------------  
236 --- FK: qrtz_simple_triggers  
237 --- ----------------------------  
238 -ALTER TABLE qrtz_simple_triggers  
239 - ADD CONSTRAINT qrtz_simple_triggers_ibfk_1 FOREIGN KEY (sched_name, trigger_name, trigger_group) REFERENCES qrtz_triggers (sched_name,  
240 - trigger_name,  
241 - trigger_group);  
242 -  
243 --- ----------------------------  
244 --- FK: qrtz_simprop_triggers  
245 --- ----------------------------  
246 -ALTER TABLE qrtz_simprop_triggers  
247 - ADD CONSTRAINT qrtz_simprop_triggers_ibfk_1 FOREIGN KEY (sched_name, trigger_name, trigger_group) REFERENCES qrtz_triggers (sched_name, trigger_name, trigger_group);  
248 -  
249 --- ----------------------------  
250 --- FK: qrtz_triggers  
251 --- ----------------------------  
252 -ALTER TABLE qrtz_triggers  
253 - ADD CONSTRAINT qrtz_triggers_ibfk_1 FOREIGN KEY (sched_name, job_name, job_group) REFERENCES qrtz_job_details (sched_name, job_name, job_group); 164 +CREATE INDEX IDX_QRTZ_J_REQ_RECOVERY
  165 + ON QRTZ_JOB_DETAILS (SCHED_NAME, REQUESTS_RECOVERY);
  166 +CREATE INDEX IDX_QRTZ_J_GRP
  167 + ON QRTZ_JOB_DETAILS (SCHED_NAME, JOB_GROUP);
  168 +
  169 +CREATE INDEX IDX_QRTZ_T_J
  170 + ON QRTZ_TRIGGERS (SCHED_NAME, JOB_NAME, JOB_GROUP);
  171 +CREATE INDEX IDX_QRTZ_T_JG
  172 + ON QRTZ_TRIGGERS (SCHED_NAME, JOB_GROUP);
  173 +CREATE INDEX IDX_QRTZ_T_C
  174 + ON QRTZ_TRIGGERS (SCHED_NAME, CALENDAR_NAME);
  175 +CREATE INDEX IDX_QRTZ_T_G
  176 + ON QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_GROUP);
  177 +CREATE INDEX IDX_QRTZ_T_STATE
  178 + ON QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_STATE);
  179 +CREATE INDEX IDX_QRTZ_T_N_STATE
  180 + ON QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP, TRIGGER_STATE);
  181 +CREATE INDEX IDX_QRTZ_T_N_G_STATE
  182 + ON QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_GROUP, TRIGGER_STATE);
  183 +CREATE INDEX IDX_QRTZ_T_NEXT_FIRE_TIME
  184 + ON QRTZ_TRIGGERS (SCHED_NAME, NEXT_FIRE_TIME);
  185 +CREATE INDEX IDX_QRTZ_T_NFT_ST
  186 + ON QRTZ_TRIGGERS (SCHED_NAME, TRIGGER_STATE, NEXT_FIRE_TIME);
  187 +CREATE INDEX IDX_QRTZ_T_NFT_MISFIRE
  188 + ON QRTZ_TRIGGERS (SCHED_NAME, MISFIRE_INSTR, NEXT_FIRE_TIME);
  189 +CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE
  190 + ON QRTZ_TRIGGERS (SCHED_NAME, MISFIRE_INSTR, NEXT_FIRE_TIME, TRIGGER_STATE);
  191 +CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE_GRP
  192 + ON QRTZ_TRIGGERS (SCHED_NAME, MISFIRE_INSTR, NEXT_FIRE_TIME, TRIGGER_GROUP, TRIGGER_STATE);
  193 +
  194 +CREATE INDEX IDX_QRTZ_FT_TRIG_INST_NAME
  195 + ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, INSTANCE_NAME);
  196 +CREATE INDEX IDX_QRTZ_FT_INST_JOB_REQ_RCVRY
  197 + ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, INSTANCE_NAME, REQUESTS_RECOVERY);
  198 +CREATE INDEX IDX_QRTZ_FT_J_G
  199 + ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, JOB_NAME, JOB_GROUP);
  200 +CREATE INDEX IDX_QRTZ_FT_JG
  201 + ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, JOB_GROUP);
  202 +CREATE INDEX IDX_QRTZ_FT_T_G
  203 + ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP);
  204 +CREATE INDEX IDX_QRTZ_FT_TG
  205 + ON QRTZ_FIRED_TRIGGERS (SCHED_NAME, TRIGGER_GROUP);
  206 +
  207 +
  208 +COMMIT;
254 \ No newline at end of file 209 \ No newline at end of file
sql/tools/convertor.py
@@ -17,6 +17,7 @@ uv run --with simple-ddl-parser convertor.py dm8 &gt; ../dm/ruoyi-vue-pro-dm8.sql @@ -17,6 +17,7 @@ uv run --with simple-ddl-parser convertor.py dm8 &gt; ../dm/ruoyi-vue-pro-dm8.sql
17 import argparse 17 import argparse
18 import pathlib 18 import pathlib
19 import re 19 import re
  20 +import sys
20 import time 21 import time
21 from abc import ABC, abstractmethod 22 from abc import ABC, abstractmethod
22 from typing import Dict, Generator, Optional, Tuple, Union 23 from typing import Dict, Generator, Optional, Tuple, Union
@@ -293,8 +294,10 @@ class Convertor(ABC): @@ -293,8 +294,10 @@ class Convertor(ABC):
293 294
294 # 将parse失败的脚本打印出来 295 # 将parse失败的脚本打印出来
295 if error_scripts: 296 if error_scripts:
  297 + print("!!! 以下内容无法正常解析", file=sys.stderr)
296 for script in error_scripts: 298 for script in error_scripts:
297 - print(script) 299 + # print to stderr
  300 + print(script, file=sys.stderr)
298 301
299 302
300 class PostgreSQLConvertor(Convertor): 303 class PostgreSQLConvertor(Convertor):
yudao-dependencies/pom.xml
@@ -14,7 +14,7 @@ @@ -14,7 +14,7 @@
14 <url>https://github.com/YunaiV/ruoyi-vue-pro</url> 14 <url>https://github.com/YunaiV/ruoyi-vue-pro</url>
15 15
16 <properties> 16 <properties>
17 - <revision>2.6.0-SNAPSHOT</revision> 17 + <revision>2025.08-SNAPSHOT</revision>
18 <flatten-maven-plugin.version>1.6.0</flatten-maven-plugin.version> 18 <flatten-maven-plugin.version>1.6.0</flatten-maven-plugin.version>
19 <!-- 统一依赖管理 --> 19 <!-- 统一依赖管理 -->
20 <spring.boot.version>3.4.5</spring.boot.version> 20 <spring.boot.version>3.4.5</spring.boot.version>
@@ -24,9 +24,9 @@ @@ -24,9 +24,9 @@
24 <!-- DB 相关 --> 24 <!-- DB 相关 -->
25 <druid.version>1.2.24</druid.version> 25 <druid.version>1.2.24</druid.version>
26 <mybatis.version>3.5.19</mybatis.version> 26 <mybatis.version>3.5.19</mybatis.version>
27 - <mybatis-plus.version>3.5.10.1</mybatis-plus.version> 27 + <mybatis-plus.version>3.5.12</mybatis-plus.version>
  28 + <mybatis-plus-join.version>1.5.4</mybatis-plus-join.version>
28 <dynamic-datasource.version>4.3.1</dynamic-datasource.version> 29 <dynamic-datasource.version>4.3.1</dynamic-datasource.version>
29 - <mybatis-plus-join.version>1.4.13</mybatis-plus-join.version>  
30 <easy-trans.version>3.0.6</easy-trans.version> 30 <easy-trans.version>3.0.6</easy-trans.version>
31 <redisson.version>3.41.0</redisson.version> 31 <redisson.version>3.41.0</redisson.version>
32 <dm8.jdbc.version>8.1.3.140</dm8.jdbc.version> 32 <dm8.jdbc.version>8.1.3.140</dm8.jdbc.version>
@@ -54,7 +54,7 @@ @@ -54,7 +54,7 @@
54 <mapstruct.version>1.6.3</mapstruct.version> 54 <mapstruct.version>1.6.3</mapstruct.version>
55 <hutool-5.version>5.8.35</hutool-5.version> 55 <hutool-5.version>5.8.35</hutool-5.version>
56 <hutool-6.version>6.0.0-M19</hutool-6.version> 56 <hutool-6.version>6.0.0-M19</hutool-6.version>
57 - <easyexcel.version>4.0.3</easyexcel.version> 57 + <fastexcel.version>1.2.0</fastexcel.version>
58 <velocity.version>2.4.1</velocity.version> 58 <velocity.version>2.4.1</velocity.version>
59 <fastjson.version>1.2.83</fastjson.version> 59 <fastjson.version>1.2.83</fastjson.version>
60 <guava.version>33.4.8-jre</guava.version> 60 <guava.version>33.4.8-jre</guava.version>
@@ -69,12 +69,11 @@ @@ -69,12 +69,11 @@
69 <pf4j-spring.version>0.9.0</pf4j-spring.version> 69 <pf4j-spring.version>0.9.0</pf4j-spring.version>
70 <vertx.version>4.5.13</vertx.version> 70 <vertx.version>4.5.13</vertx.version>
71 <!-- 三方云服务相关 --> 71 <!-- 三方云服务相关 -->
72 - <commons-io.version>2.17.0</commons-io.version>  
73 - <commons-compress.version>1.27.1</commons-compress.version>  
74 <awssdk.version>2.30.14</awssdk.version> 72 <awssdk.version>2.30.14</awssdk.version>
75 <justauth.version>1.16.7</justauth.version> 73 <justauth.version>1.16.7</justauth.version>
76 <justauth-starter.version>1.4.0</justauth-starter.version> 74 <justauth-starter.version>1.4.0</justauth-starter.version>
77 - <jimureport.version>1.9.4</jimureport.version> 75 + <jimureport.version>2.0.0</jimureport.version>
  76 + <jimubi.version>1.9.5</jimubi.version>
78 <weixin-java.version>4.7.5.B</weixin-java.version> 77 <weixin-java.version>4.7.5.B</weixin-java.version>
79 </properties> 78 </properties>
80 79
@@ -479,20 +478,11 @@ @@ -479,20 +478,11 @@
479 </dependency> 478 </dependency>
480 479
481 <dependency> 480 <dependency>
482 - <groupId>com.alibaba</groupId>  
483 - <artifactId>easyexcel</artifactId>  
484 - <version>${easyexcel.version}</version>  
485 - </dependency>  
486 - <dependency>  
487 - <groupId>commons-io</groupId>  
488 - <artifactId>commons-io</artifactId>  
489 - <version>${commons-io.version}</version>  
490 - </dependency>  
491 - <dependency>  
492 - <groupId>org.apache.commons</groupId>  
493 - <artifactId>commons-compress</artifactId>  
494 - <version>${commons-compress.version}</version> 481 + <groupId>cn.idev.excel</groupId>
  482 + <artifactId>fastexcel</artifactId>
  483 + <version>${fastexcel.version}</version>
495 </dependency> 484 </dependency>
  485 +
496 <dependency> 486 <dependency>
497 <groupId>org.apache.tika</groupId> 487 <groupId>org.apache.tika</groupId>
498 <artifactId>tika-core</artifactId> <!-- 文件类型的识别 --> 488 <artifactId>tika-core</artifactId> <!-- 文件类型的识别 -->
@@ -603,7 +593,7 @@ @@ -603,7 +593,7 @@
603 <dependency> 593 <dependency>
604 <groupId>org.jeecgframework.jimureport</groupId> 594 <groupId>org.jeecgframework.jimureport</groupId>
605 <artifactId>jimubi-spring-boot3-starter</artifactId> 595 <artifactId>jimubi-spring-boot3-starter</artifactId>
606 - <version>${jimureport.version}</version> 596 + <version>${jimubi.version}</version>
607 <exclusions> 597 <exclusions>
608 <exclusion> 598 <exclusion>
609 <groupId>com.github.jsqlparser</groupId> 599 <groupId>com.github.jsqlparser</groupId>
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/cache/CacheUtils.java
@@ -15,6 +15,13 @@ import java.util.concurrent.Executors; @@ -15,6 +15,13 @@ import java.util.concurrent.Executors;
15 public class CacheUtils { 15 public class CacheUtils {
16 16
17 /** 17 /**
  18 + * 异步刷新的 LoadingCache 最大缓存数量
  19 + *
  20 + * @see <a href="">本地缓存 CacheUtils 工具类建议</a>
  21 + */
  22 + private static final Integer CACHE_MAX_SIZE = 10000;
  23 +
  24 + /**
18 * 构建异步刷新的 LoadingCache 对象 25 * 构建异步刷新的 LoadingCache 对象
19 * 26 *
20 * 注意:如果你的缓存和 ThreadLocal 有关系,要么自己处理 ThreadLocal 的传递,要么使用 {@link #buildCache(Duration, CacheLoader)} 方法 27 * 注意:如果你的缓存和 ThreadLocal 有关系,要么自己处理 ThreadLocal 的传递,要么使用 {@link #buildCache(Duration, CacheLoader)} 方法
@@ -29,6 +36,7 @@ public class CacheUtils { @@ -29,6 +36,7 @@ public class CacheUtils {
29 */ 36 */
30 public static <K, V> LoadingCache<K, V> buildAsyncReloadingCache(Duration duration, CacheLoader<K, V> loader) { 37 public static <K, V> LoadingCache<K, V> buildAsyncReloadingCache(Duration duration, CacheLoader<K, V> loader) {
31 return CacheBuilder.newBuilder() 38 return CacheBuilder.newBuilder()
  39 + .maximumSize(CACHE_MAX_SIZE)
32 // 只阻塞当前数据加载线程,其他线程返回旧值 40 // 只阻塞当前数据加载线程,其他线程返回旧值
33 .refreshAfterWrite(duration) 41 .refreshAfterWrite(duration)
34 // 通过 asyncReloading 实现全异步加载,包括 refreshAfterWrite 被阻塞的加载线程 42 // 通过 asyncReloading 实现全异步加载,包括 refreshAfterWrite 被阻塞的加载线程
@@ -43,7 +51,11 @@ public class CacheUtils { @@ -43,7 +51,11 @@ public class CacheUtils {
43 * @return LoadingCache 对象 51 * @return LoadingCache 对象
44 */ 52 */
45 public static <K, V> LoadingCache<K, V> buildCache(Duration duration, CacheLoader<K, V> loader) { 53 public static <K, V> LoadingCache<K, V> buildCache(Duration duration, CacheLoader<K, V> loader) {
46 - return CacheBuilder.newBuilder().refreshAfterWrite(duration).build(loader); 54 + return CacheBuilder.newBuilder()
  55 + .maximumSize(CACHE_MAX_SIZE)
  56 + // 只阻塞当前数据加载线程,其他线程返回旧值
  57 + .refreshAfterWrite(duration)
  58 + .build(loader);
47 } 59 }
48 60
49 } 61 }
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/DateUtils.java
@@ -74,30 +74,30 @@ public class DateUtils { @@ -74,30 +74,30 @@ public class DateUtils {
74 * 创建指定时间 74 * 创建指定时间
75 * 75 *
76 * @param year 年 76 * @param year 年
77 - * @param mouth 月 77 + * @param month 月
78 * @param day 日 78 * @param day 日
79 * @return 指定时间 79 * @return 指定时间
80 */ 80 */
81 - public static Date buildTime(int year, int mouth, int day) {  
82 - return buildTime(year, mouth, day, 0, 0, 0); 81 + public static Date buildTime(int year, int month, int day) {
  82 + return buildTime(year, month, day, 0, 0, 0);
83 } 83 }
84 84
85 /** 85 /**
86 * 创建指定时间 86 * 创建指定时间
87 * 87 *
88 * @param year 年 88 * @param year 年
89 - * @param mouth 月 89 + * @param month 月
90 * @param day 日 90 * @param day 日
91 * @param hour 小时 91 * @param hour 小时
92 * @param minute 分钟 92 * @param minute 分钟
93 * @param second 秒 93 * @param second 秒
94 * @return 指定时间 94 * @return 指定时间
95 */ 95 */
96 - public static Date buildTime(int year, int mouth, int day, 96 + public static Date buildTime(int year, int month, int day,
97 int hour, int minute, int second) { 97 int hour, int minute, int second) {
98 Calendar calendar = Calendar.getInstance(); 98 Calendar calendar = Calendar.getInstance();
99 calendar.set(Calendar.YEAR, year); 99 calendar.set(Calendar.YEAR, year);
100 - calendar.set(Calendar.MONTH, mouth - 1); 100 + calendar.set(Calendar.MONTH, month - 1);
101 calendar.set(Calendar.DAY_OF_MONTH, day); 101 calendar.set(Calendar.DAY_OF_MONTH, day);
102 calendar.set(Calendar.HOUR_OF_DAY, hour); 102 calendar.set(Calendar.HOUR_OF_DAY, hour);
103 calendar.set(Calendar.MINUTE, minute); 103 calendar.set(Calendar.MINUTE, minute);
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/LocalDateTimeUtils.java
@@ -69,17 +69,17 @@ public class LocalDateTimeUtils { @@ -69,17 +69,17 @@ public class LocalDateTimeUtils {
69 * 创建指定时间 69 * 创建指定时间
70 * 70 *
71 * @param year 年 71 * @param year 年
72 - * @param mouth 月 72 + * @param month 月
73 * @param day 日 73 * @param day 日
74 * @return 指定时间 74 * @return 指定时间
75 */ 75 */
76 - public static LocalDateTime buildTime(int year, int mouth, int day) {  
77 - return LocalDateTime.of(year, mouth, day, 0, 0, 0); 76 + public static LocalDateTime buildTime(int year, int month, int day) {
  77 + return LocalDateTime.of(year, month, day, 0, 0, 0);
78 } 78 }
79 79
80 - public static LocalDateTime[] buildBetweenTime(int year1, int mouth1, int day1,  
81 - int year2, int mouth2, int day2) {  
82 - return new LocalDateTime[]{buildTime(year1, mouth1, day1), buildTime(year2, mouth2, day2)}; 80 + public static LocalDateTime[] buildBetweenTime(int year1, int month1, int day1,
  81 + int year2, int month2, int day2) {
  82 + return new LocalDateTime[]{buildTime(year1, month1, day1), buildTime(year2, month2, day2)};
83 } 83 }
84 84
85 /** 85 /**
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; @@ -18,7 +18,7 @@ import java.util.List;
18 */ 18 */
19 @AutoConfiguration 19 @AutoConfiguration
20 @ConditionalOnClass(LoginUser.class) 20 @ConditionalOnClass(LoginUser.class)
21 -@ConditionalOnBean(value = {PermissionCommonApi.class, DeptDataPermissionRuleCustomizer.class}) 21 +@ConditionalOnBean(value = {DeptDataPermissionRuleCustomizer.class})
22 public class YudaoDeptDataPermissionAutoConfiguration { 22 public class YudaoDeptDataPermissionAutoConfiguration {
23 23
24 @Bean 24 @Bean
yudao-framework/yudao-spring-boot-starter-excel/pom.xml
@@ -42,18 +42,19 @@ @@ -42,18 +42,19 @@
42 42
43 <!-- 工具类相关 --> 43 <!-- 工具类相关 -->
44 <dependency> 44 <dependency>
45 - <groupId>com.alibaba</groupId>  
46 - <artifactId>easyexcel</artifactId> 45 + <groupId>cn.idev.excel</groupId>
  46 + <artifactId>fastexcel</artifactId>
47 </dependency> 47 </dependency>
48 48
49 <dependency> 49 <dependency>
50 - <groupId>com.google.guava</groupId>  
51 - <artifactId>guava</artifactId> 50 + <groupId>jakarta.validation</groupId>
  51 + <artifactId>jakarta.validation-api</artifactId>
  52 + <scope>provided</scope>
52 </dependency> 53 </dependency>
53 54
54 <dependency> 55 <dependency>
55 - <groupId>org.apache.commons</groupId>  
56 - <artifactId>commons-compress</artifactId> <!-- 解决 https://github.com/alibaba/easyexcel/issues/3954 问题 --> 56 + <groupId>com.google.guava</groupId>
  57 + <artifactId>guava</artifactId>
57 </dependency> 58 </dependency>
58 59
59 <dependency> 60 <dependency>
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 { @@ -76,4 +76,9 @@ public class DictFrameworkUtils {
76 return dictData!= null ? dictData.getValue(): null; 76 return dictData!= null ? dictData.getValue(): null;
77 } 77 }
78 78
  79 + @SneakyThrows
  80 + public static List<String> getDictDataValueList(String dictType) {
  81 + List<DictDataRespDTO> dictDatas = GET_DICT_DATA_CACHE.get(dictType);
  82 + return convertList(dictDatas, DictDataRespDTO::getValue);
  83 + }
79 } 84 }
yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/dict/validation/InDict.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.dict.validation;
  2 +
  3 +import jakarta.validation.Constraint;
  4 +import jakarta.validation.Payload;
  5 +
  6 +import java.lang.annotation.Documented;
  7 +import java.lang.annotation.ElementType;
  8 +import java.lang.annotation.Retention;
  9 +import java.lang.annotation.RetentionPolicy;
  10 +import java.lang.annotation.Target;
  11 +
  12 +@Target({
  13 + ElementType.METHOD,
  14 + ElementType.FIELD,
  15 + ElementType.ANNOTATION_TYPE,
  16 + ElementType.CONSTRUCTOR,
  17 + ElementType.PARAMETER,
  18 + ElementType.TYPE_USE
  19 +})
  20 +@Retention(RetentionPolicy.RUNTIME)
  21 +@Documented
  22 +@Constraint(
  23 + validatedBy = {InDictValidator.class, InDictCollectionValidator.class}
  24 +)
  25 +public @interface InDict {
  26 +
  27 + /**
  28 + * 数据字典 type
  29 + */
  30 + String type();
  31 +
  32 + String message() default "必须在指定范围 {value}";
  33 +
  34 + Class<?>[] groups() default {};
  35 +
  36 + Class<? extends Payload>[] payload() default {};
  37 +
  38 +}
yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/dict/validation/InDictCollectionValidator.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.dict.validation;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.iocoder.yudao.framework.dict.core.DictFrameworkUtils;
  5 +import jakarta.validation.ConstraintValidator;
  6 +import jakarta.validation.ConstraintValidatorContext;
  7 +
  8 +import java.util.Collection;
  9 +import java.util.List;
  10 +
  11 +public class InDictCollectionValidator implements ConstraintValidator<InDict, Collection<?>> {
  12 +
  13 + private String dictType;
  14 +
  15 + @Override
  16 + public void initialize(InDict annotation) {
  17 + this.dictType = annotation.type();
  18 + }
  19 +
  20 + @Override
  21 + public boolean isValid(Collection<?> list, ConstraintValidatorContext context) {
  22 + // 为空时,默认不校验,即认为通过
  23 + if (CollUtil.isEmpty(list)) {
  24 + return true;
  25 + }
  26 + // 校验全部通过
  27 + List<String> dbValues = DictFrameworkUtils.getDictDataValueList(dictType);
  28 + boolean match = list.stream().allMatch(v -> dbValues.stream()
  29 + .anyMatch(dbValue -> dbValue.equalsIgnoreCase(v.toString())));
  30 + if (match) {
  31 + return true;
  32 + }
  33 +
  34 + // 校验不通过,自定义提示语句
  35 + context.disableDefaultConstraintViolation(); // 禁用默认的 message 的值
  36 + context.buildConstraintViolationWithTemplate(
  37 + context.getDefaultConstraintMessageTemplate().replaceAll("\\{value}", dbValues.toString())
  38 + ).addConstraintViolation(); // 重新添加错误提示语句
  39 + return false;
  40 + }
  41 +
  42 +}
  43 +
yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/dict/validation/InDictValidator.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.dict.validation;
  2 +
  3 +import cn.hutool.core.util.StrUtil;
  4 +import cn.iocoder.yudao.framework.dict.core.DictFrameworkUtils;
  5 +import jakarta.validation.ConstraintValidator;
  6 +import jakarta.validation.ConstraintValidatorContext;
  7 +
  8 +import java.util.List;
  9 +
  10 +public class InDictValidator implements ConstraintValidator<InDict, Object> {
  11 +
  12 + private String dictType;
  13 +
  14 + @Override
  15 + public void initialize(InDict annotation) {
  16 + this.dictType = annotation.type();
  17 + }
  18 +
  19 + @Override
  20 + public boolean isValid(Object value, ConstraintValidatorContext context) {
  21 + // 为空时,默认不校验,即认为通过
  22 + if (value == null) {
  23 + return true;
  24 + }
  25 + // 校验通过
  26 + final List<String> values = DictFrameworkUtils.getDictDataValueList(dictType);
  27 + boolean match = values.stream().anyMatch(v -> StrUtil.equalsIgnoreCase(v, value.toString()));
  28 + if (match) {
  29 + return true;
  30 + }
  31 +
  32 + // 校验不通过,自定义提示语句
  33 + context.disableDefaultConstraintViolation(); // 禁用默认的 message 的值
  34 + context.buildConstraintViolationWithTemplate(
  35 + context.getDefaultConstraintMessageTemplate().replaceAll("\\{value}", values.toString())
  36 + ).addConstraintViolation(); // 重新添加错误提示语句
  37 + return false;
  38 + }
  39 +
  40 +}
  41 +
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; @@ -3,11 +3,11 @@ package cn.iocoder.yudao.framework.excel.core.convert;
3 import cn.hutool.core.convert.Convert; 3 import cn.hutool.core.convert.Convert;
4 import cn.iocoder.yudao.framework.ip.core.Area; 4 import cn.iocoder.yudao.framework.ip.core.Area;
5 import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils; 5 import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils;
6 -import com.alibaba.excel.converters.Converter;  
7 -import com.alibaba.excel.enums.CellDataTypeEnum;  
8 -import com.alibaba.excel.metadata.GlobalConfiguration;  
9 -import com.alibaba.excel.metadata.data.ReadCellData;  
10 -import com.alibaba.excel.metadata.property.ExcelContentProperty; 6 +import cn.idev.excel.converters.Converter;
  7 +import cn.idev.excel.enums.CellDataTypeEnum;
  8 +import cn.idev.excel.metadata.GlobalConfiguration;
  9 +import cn.idev.excel.metadata.data.ReadCellData;
  10 +import cn.idev.excel.metadata.property.ExcelContentProperty;
11 import lombok.extern.slf4j.Slf4j; 11 import lombok.extern.slf4j.Slf4j;
12 12
13 /** 13 /**
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; @@ -3,12 +3,12 @@ package cn.iocoder.yudao.framework.excel.core.convert;
3 import cn.hutool.core.convert.Convert; 3 import cn.hutool.core.convert.Convert;
4 import cn.iocoder.yudao.framework.dict.core.DictFrameworkUtils; 4 import cn.iocoder.yudao.framework.dict.core.DictFrameworkUtils;
5 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 5 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
6 -import com.alibaba.excel.converters.Converter;  
7 -import com.alibaba.excel.enums.CellDataTypeEnum;  
8 -import com.alibaba.excel.metadata.GlobalConfiguration;  
9 -import com.alibaba.excel.metadata.data.ReadCellData;  
10 -import com.alibaba.excel.metadata.data.WriteCellData;  
11 -import com.alibaba.excel.metadata.property.ExcelContentProperty; 6 +import cn.idev.excel.converters.Converter;
  7 +import cn.idev.excel.enums.CellDataTypeEnum;
  8 +import cn.idev.excel.metadata.GlobalConfiguration;
  9 +import cn.idev.excel.metadata.data.ReadCellData;
  10 +import cn.idev.excel.metadata.data.WriteCellData;
  11 +import cn.idev.excel.metadata.property.ExcelContentProperty;
12 import lombok.extern.slf4j.Slf4j; 12 import lombok.extern.slf4j.Slf4j;
13 13
14 /** 14 /**
yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/convert/JsonConvert.java
1 package cn.iocoder.yudao.framework.excel.core.convert; 1 package cn.iocoder.yudao.framework.excel.core.convert;
2 2
3 import cn.iocoder.yudao.framework.common.util.json.JsonUtils; 3 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
4 -import com.alibaba.excel.converters.Converter;  
5 -import com.alibaba.excel.enums.CellDataTypeEnum;  
6 -import com.alibaba.excel.metadata.GlobalConfiguration;  
7 -import com.alibaba.excel.metadata.data.WriteCellData;  
8 -import com.alibaba.excel.metadata.property.ExcelContentProperty; 4 +import cn.idev.excel.converters.Converter;
  5 +import cn.idev.excel.enums.CellDataTypeEnum;
  6 +import cn.idev.excel.metadata.GlobalConfiguration;
  7 +import cn.idev.excel.metadata.data.WriteCellData;
  8 +import cn.idev.excel.metadata.property.ExcelContentProperty;
9 9
10 /** 10 /**
11 * Excel Json 转换器 11 * Excel Json 转换器
yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/convert/MoneyConvert.java
1 package cn.iocoder.yudao.framework.excel.core.convert; 1 package cn.iocoder.yudao.framework.excel.core.convert;
2 2
3 -import com.alibaba.excel.converters.Converter;  
4 -import com.alibaba.excel.enums.CellDataTypeEnum;  
5 -import com.alibaba.excel.metadata.GlobalConfiguration;  
6 -import com.alibaba.excel.metadata.data.WriteCellData;  
7 -import com.alibaba.excel.metadata.property.ExcelContentProperty; 3 +import cn.idev.excel.converters.Converter;
  4 +import cn.idev.excel.enums.CellDataTypeEnum;
  5 +import cn.idev.excel.metadata.GlobalConfiguration;
  6 +import cn.idev.excel.metadata.data.WriteCellData;
  7 +import cn.idev.excel.metadata.property.ExcelContentProperty;
8 8
9 import java.math.BigDecimal; 9 import java.math.BigDecimal;
10 import java.math.RoundingMode; 10 import java.math.RoundingMode;
yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/handler/ColumnWidthMatchStyleStrategy.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.excel.core.handler;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.idev.excel.enums.CellDataTypeEnum;
  5 +import cn.idev.excel.metadata.Head;
  6 +import cn.idev.excel.metadata.data.WriteCellData;
  7 +import cn.idev.excel.util.MapUtils;
  8 +import cn.idev.excel.write.metadata.holder.WriteSheetHolder;
  9 +import cn.idev.excel.write.style.column.AbstractColumnWidthStyleStrategy;
  10 +import cn.idev.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
  11 +import org.apache.poi.ss.usermodel.Cell;
  12 +
  13 +import java.util.HashMap;
  14 +import java.util.List;
  15 +import java.util.Map;
  16 +
  17 +/**
  18 + * Excel 自适应列宽处理器
  19 + *
  20 + * 相比 {@link LongestMatchColumnWidthStyleStrategy} 来说,额外处理了 DATE 类型!
  21 + *
  22 + * @see <a href="https://github.com/YunaiV/yudao-cloud/pull/196/">添加自适应列宽处理器,并替换默认列宽策略</a>
  23 + * @author hmb
  24 + */
  25 +public class ColumnWidthMatchStyleStrategy extends AbstractColumnWidthStyleStrategy {
  26 +
  27 + private static final int MAX_COLUMN_WIDTH = 255;
  28 +
  29 + private final Map<Integer, Map<Integer, Integer>> cache = MapUtils.newHashMapWithExpectedSize(8);
  30 +
  31 + @Override
  32 + protected void setColumnWidth(WriteSheetHolder writeSheetHolder, List<WriteCellData<?>> cellDataList, Cell cell,
  33 + Head head, Integer relativeRowIndex, Boolean isHead) {
  34 + boolean needSetWidth = isHead || CollUtil.isNotEmpty(cellDataList);
  35 + if (!needSetWidth) {
  36 + return;
  37 + }
  38 + Map<Integer, Integer> maxColumnWidthMap = cache.computeIfAbsent(writeSheetHolder.getSheetNo(),
  39 + key -> new HashMap<>(16));
  40 + Integer columnWidth = dataLength(cellDataList, cell, isHead);
  41 + if (columnWidth < 0) {
  42 + return;
  43 + }
  44 + if (columnWidth > MAX_COLUMN_WIDTH) {
  45 + columnWidth = MAX_COLUMN_WIDTH;
  46 + }
  47 + Integer maxColumnWidth = maxColumnWidthMap.get(cell.getColumnIndex());
  48 + if (maxColumnWidth == null || columnWidth > maxColumnWidth) {
  49 + maxColumnWidthMap.put(cell.getColumnIndex(), columnWidth);
  50 + writeSheetHolder.getSheet().setColumnWidth(cell.getColumnIndex(), columnWidth * 256);
  51 + }
  52 + }
  53 +
  54 + @SuppressWarnings("EnhancedSwitchMigration")
  55 + private Integer dataLength(List<WriteCellData<?>> cellDataList, Cell cell, Boolean isHead) {
  56 + if (isHead) {
  57 + return cell.getStringCellValue().getBytes().length;
  58 + }
  59 + WriteCellData<?> cellData = cellDataList.get(0);
  60 + CellDataTypeEnum type = cellData.getType();
  61 + if (type == null) {
  62 + return -1;
  63 + }
  64 + switch (type) {
  65 + case STRING:
  66 + return cellData.getStringValue().getBytes().length;
  67 + case BOOLEAN:
  68 + return cellData.getBooleanValue().toString().getBytes().length;
  69 + case NUMBER:
  70 + return cellData.getNumberValue().toString().getBytes().length;
  71 + case DATE:
  72 + return cellData.getDateValue().toString().getBytes().length;
  73 + default:
  74 + return -1;
  75 + }
  76 + }
  77 +
  78 +}
0 \ No newline at end of file 79 \ No newline at end of file
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; @@ -3,7 +3,6 @@ package cn.iocoder.yudao.framework.excel.core.handler;
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.lang.Assert; 4 import cn.hutool.core.lang.Assert;
5 import cn.hutool.core.util.ObjectUtil; 5 import cn.hutool.core.util.ObjectUtil;
6 -import cn.hutool.core.util.ReflectUtil;  
7 import cn.hutool.core.util.StrUtil; 6 import cn.hutool.core.util.StrUtil;
8 import cn.hutool.extra.spring.SpringUtil; 7 import cn.hutool.extra.spring.SpringUtil;
9 import cn.hutool.poi.excel.ExcelUtil; 8 import cn.hutool.poi.excel.ExcelUtil;
@@ -11,12 +10,12 @@ import cn.iocoder.yudao.framework.common.core.KeyValue; @@ -11,12 +10,12 @@ import cn.iocoder.yudao.framework.common.core.KeyValue;
11 import cn.iocoder.yudao.framework.dict.core.DictFrameworkUtils; 10 import cn.iocoder.yudao.framework.dict.core.DictFrameworkUtils;
12 import cn.iocoder.yudao.framework.excel.core.annotations.ExcelColumnSelect; 11 import cn.iocoder.yudao.framework.excel.core.annotations.ExcelColumnSelect;
13 import cn.iocoder.yudao.framework.excel.core.function.ExcelColumnSelectFunction; 12 import cn.iocoder.yudao.framework.excel.core.function.ExcelColumnSelectFunction;
14 -import com.alibaba.excel.annotation.ExcelIgnore;  
15 -import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;  
16 -import com.alibaba.excel.annotation.ExcelProperty;  
17 -import com.alibaba.excel.write.handler.SheetWriteHandler;  
18 -import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;  
19 -import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder; 13 +import cn.idev.excel.annotation.ExcelIgnore;
  14 +import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
  15 +import cn.idev.excel.annotation.ExcelProperty;
  16 +import cn.idev.excel.write.handler.SheetWriteHandler;
  17 +import cn.idev.excel.write.metadata.holder.WriteSheetHolder;
  18 +import cn.idev.excel.write.metadata.holder.WriteWorkbookHolder;
20 import lombok.extern.slf4j.Slf4j; 19 import lombok.extern.slf4j.Slf4j;
21 import org.apache.poi.hssf.usermodel.HSSFDataValidation; 20 import org.apache.poi.hssf.usermodel.HSSFDataValidation;
22 import org.apache.poi.ss.usermodel.*; 21 import org.apache.poi.ss.usermodel.*;
@@ -87,7 +86,7 @@ public class SelectSheetWriteHandler implements SheetWriteHandler { @@ -87,7 +86,7 @@ public class SelectSheetWriteHandler implements SheetWriteHandler {
87 86
88 /** 87 /**
89 * 判断字段是否是静态的、最终的、 transient 的 88 * 判断字段是否是静态的、最终的、 transient 的
90 - * 原因:EasyExcel 默认是忽略 static final 或 transient 的字段,所以需要判断 89 + * 原因:FastExcel 默认是忽略 static final 或 transient 的字段,所以需要判断
91 * 90 *
92 * @param field 字段 91 * @param field 字段
93 * @return 是否是静态的、最终的、transient 的 92 * @return 是否是静态的、最终的、transient 的
@@ -185,4 +184,4 @@ public class SelectSheetWriteHandler implements SheetWriteHandler { @@ -185,4 +184,4 @@ public class SelectSheetWriteHandler implements SheetWriteHandler {
185 writeSheetHolder.getSheet().addValidationData(validation); 184 writeSheetHolder.getSheet().addValidationData(validation);
186 } 185 }
187 186
188 -}  
189 \ No newline at end of file 187 \ No newline at end of file
  188 +}
yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/util/ExcelUtils.java
1 package cn.iocoder.yudao.framework.excel.core.util; 1 package cn.iocoder.yudao.framework.excel.core.util;
2 2
  3 +import cn.idev.excel.FastExcelFactory;
  4 +import cn.idev.excel.converters.longconverter.LongStringConverter;
3 import cn.iocoder.yudao.framework.common.util.http.HttpUtils; 5 import cn.iocoder.yudao.framework.common.util.http.HttpUtils;
  6 +import cn.iocoder.yudao.framework.excel.core.handler.ColumnWidthMatchStyleStrategy;
4 import cn.iocoder.yudao.framework.excel.core.handler.SelectSheetWriteHandler; 7 import cn.iocoder.yudao.framework.excel.core.handler.SelectSheetWriteHandler;
5 -import com.alibaba.excel.EasyExcel;  
6 -import com.alibaba.excel.converters.longconverter.LongStringConverter;  
7 -import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;  
8 import jakarta.servlet.http.HttpServletResponse; 8 import jakarta.servlet.http.HttpServletResponse;
9 import org.springframework.web.multipart.MultipartFile; 9 import org.springframework.web.multipart.MultipartFile;
10 10
@@ -32,9 +32,9 @@ public class ExcelUtils { @@ -32,9 +32,9 @@ public class ExcelUtils {
32 public static <T> void write(HttpServletResponse response, String filename, String sheetName, 32 public static <T> void write(HttpServletResponse response, String filename, String sheetName,
33 Class<T> head, List<T> data) throws IOException { 33 Class<T> head, List<T> data) throws IOException {
34 // 输出 Excel 34 // 输出 Excel
35 - EasyExcel.write(response.getOutputStream(), head) 35 + FastExcelFactory.write(response.getOutputStream(), head)
36 .autoCloseStream(false) // 不要自动关闭,交给 Servlet 自己处理 36 .autoCloseStream(false) // 不要自动关闭,交给 Servlet 自己处理
37 - .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) // 基于 column 长度,自动适配。最大 255 宽度 37 + .registerWriteHandler(new ColumnWidthMatchStyleStrategy()) // 基于 column 长度,自动适配。最大 255 宽度
38 .registerWriteHandler(new SelectSheetWriteHandler(head)) // 基于固定 sheet 实现下拉框 38 .registerWriteHandler(new SelectSheetWriteHandler(head)) // 基于固定 sheet 实现下拉框
39 .registerConverter(new LongStringConverter()) // 避免 Long 类型丢失精度 39 .registerConverter(new LongStringConverter()) // 避免 Long 类型丢失精度
40 .sheet(sheetName).doWrite(data); 40 .sheet(sheetName).doWrite(data);
@@ -44,7 +44,7 @@ public class ExcelUtils { @@ -44,7 +44,7 @@ public class ExcelUtils {
44 } 44 }
45 45
46 public static <T> List<T> read(MultipartFile file, Class<T> head) throws IOException { 46 public static <T> List<T> read(MultipartFile file, Class<T> head) throws IOException {
47 - return EasyExcel.read(file.getInputStream(), head, null) 47 + return FastExcelFactory.read(file.getInputStream(), head, null)
48 .autoCloseStream(false) // 不要自动关闭,交给 Servlet 自己处理 48 .autoCloseStream(false) // 不要自动关闭,交给 Servlet 自己处理
49 .doReadAllSync(); 49 .doReadAllSync();
50 } 50 }
yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/package-info.java
1 /** 1 /**
2 - * 基于 EasyExcel 实现 Excel 相关的操作 2 + * 基于 FastExcel 实现 Excel 相关的操作
3 */ 3 */
4 package cn.iocoder.yudao.framework.excel; 4 package cn.iocoder.yudao.framework.excel;
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; @@ -7,6 +7,7 @@ import org.springframework.boot.autoconfigure.AutoConfiguration;
7 import org.springframework.context.annotation.Bean; 7 import org.springframework.context.annotation.Bean;
8 import org.springframework.scheduling.annotation.EnableAsync; 8 import org.springframework.scheduling.annotation.EnableAsync;
9 import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; 9 import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
  10 +import org.springframework.core.task.SimpleAsyncTaskExecutor;
10 11
11 /** 12 /**
12 * 异步任务 Configuration 13 * 异步任务 Configuration
@@ -21,13 +22,20 @@ public class YudaoAsyncAutoConfiguration { @@ -21,13 +22,20 @@ public class YudaoAsyncAutoConfiguration {
21 22
22 @Override 23 @Override
23 public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { 24 public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
24 - if (!(bean instanceof ThreadPoolTaskExecutor)) {  
25 - return bean; 25 + // 处理 ThreadPoolTaskExecutor
  26 + if (bean instanceof ThreadPoolTaskExecutor) {
  27 + ThreadPoolTaskExecutor executor = (ThreadPoolTaskExecutor) bean;
  28 + executor.setTaskDecorator(TtlRunnable::get);
  29 + return executor;
26 } 30 }
27 - // 修改提交的任务,接入 TransmittableThreadLocal  
28 - ThreadPoolTaskExecutor executor = (ThreadPoolTaskExecutor) bean;  
29 - executor.setTaskDecorator(TtlRunnable::get);  
30 - return executor; 31 + // 处理 SimpleAsyncTaskExecutor
  32 + // 参考 https://t.zsxq.com/CBoks 增加
  33 + if (bean instanceof SimpleAsyncTaskExecutor) {
  34 + SimpleAsyncTaskExecutor executor = (SimpleAsyncTaskExecutor) bean;
  35 + executor.setTaskDecorator(TtlRunnable::get);
  36 + return executor;
  37 + }
  38 + return bean;
31 } 39 }
32 40
33 }; 41 };
yudao-framework/yudao-spring-boot-starter-monitor/pom.xml
@@ -44,29 +44,35 @@ @@ -44,29 +44,35 @@
44 <dependency> 44 <dependency>
45 <groupId>io.opentracing</groupId> 45 <groupId>io.opentracing</groupId>
46 <artifactId>opentracing-util</artifactId> 46 <artifactId>opentracing-util</artifactId>
  47 + <optional>true</optional>
47 </dependency> 48 </dependency>
48 <dependency> 49 <dependency>
49 <groupId>org.apache.skywalking</groupId> 50 <groupId>org.apache.skywalking</groupId>
50 <artifactId>apm-toolkit-trace</artifactId> 51 <artifactId>apm-toolkit-trace</artifactId>
  52 + <optional>true</optional>
51 </dependency> 53 </dependency>
52 <dependency> 54 <dependency>
53 <groupId>org.apache.skywalking</groupId> 55 <groupId>org.apache.skywalking</groupId>
54 <artifactId>apm-toolkit-logback-1.x</artifactId> 56 <artifactId>apm-toolkit-logback-1.x</artifactId>
  57 + <optional>true</optional>
55 </dependency> 58 </dependency>
56 <dependency> 59 <dependency>
57 <groupId>org.apache.skywalking</groupId> 60 <groupId>org.apache.skywalking</groupId>
58 <artifactId>apm-toolkit-opentracing</artifactId> 61 <artifactId>apm-toolkit-opentracing</artifactId>
  62 + <optional>true</optional>
59 </dependency> 63 </dependency>
60 64
61 <!-- Micrometer 对 Prometheus 的支持 --> 65 <!-- Micrometer 对 Prometheus 的支持 -->
62 <dependency> 66 <dependency>
63 <groupId>io.micrometer</groupId> 67 <groupId>io.micrometer</groupId>
64 <artifactId>micrometer-registry-prometheus</artifactId> 68 <artifactId>micrometer-registry-prometheus</artifactId>
  69 + <optional>true</optional>
65 </dependency> 70 </dependency>
66 71
67 <dependency> 72 <dependency>
68 <groupId>de.codecentric</groupId> 73 <groupId>de.codecentric</groupId>
69 - <artifactId>spring-boot-admin-starter-client</artifactId> <!-- 实现 Spring Boot Admin Server 服务端 --> 74 + <artifactId>spring-boot-admin-starter-client</artifactId> <!-- 实现 Spring Boot Admin Client 客户端 -->
  75 + <optional>true</optional>
70 </dependency> 76 </dependency>
71 </dependencies> 77 </dependencies>
72 78
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; @@ -3,6 +3,9 @@ package cn.iocoder.yudao.framework.tracer.config;
3 import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum; 3 import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum;
4 import cn.iocoder.yudao.framework.tracer.core.aop.BizTraceAspect; 4 import cn.iocoder.yudao.framework.tracer.core.aop.BizTraceAspect;
5 import cn.iocoder.yudao.framework.tracer.core.filter.TraceFilter; 5 import cn.iocoder.yudao.framework.tracer.core.filter.TraceFilter;
  6 +import io.opentracing.Tracer;
  7 +import io.opentracing.util.GlobalTracer;
  8 +import org.apache.skywalking.apm.toolkit.opentracing.SkywalkingTracer;
6 import org.springframework.boot.autoconfigure.AutoConfiguration; 9 import org.springframework.boot.autoconfigure.AutoConfiguration;
7 import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 10 import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
8 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 11 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@@ -16,30 +19,32 @@ import org.springframework.context.annotation.Bean; @@ -16,30 +19,32 @@ import org.springframework.context.annotation.Bean;
16 * @author mashu 19 * @author mashu
17 */ 20 */
18 @AutoConfiguration 21 @AutoConfiguration
19 -@ConditionalOnClass({BizTraceAspect.class}) 22 +@ConditionalOnClass(name = {
  23 + "org.apache.skywalking.apm.toolkit.opentracing.SkywalkingTracer",
  24 + "io.opentracing.Tracer"
  25 +})
20 @EnableConfigurationProperties(TracerProperties.class) 26 @EnableConfigurationProperties(TracerProperties.class)
21 @ConditionalOnProperty(prefix = "yudao.tracer", value = "enable", matchIfMissing = true) 27 @ConditionalOnProperty(prefix = "yudao.tracer", value = "enable", matchIfMissing = true)
22 public class YudaoTracerAutoConfiguration { 28 public class YudaoTracerAutoConfiguration {
23 29
24 - // TODO @芋艿:重要。目前 opentracing 版本存在冲突,要么保证 skywalking,要么保证阿里云短信 sdk  
25 -// @Bean  
26 -// public TracerProperties bizTracerProperties() {  
27 -// return new TracerProperties();  
28 -// }  
29 -//  
30 -// @Bean  
31 -// public BizTraceAspect bizTracingAop() {  
32 -// return new BizTraceAspect(tracer());  
33 -// }  
34 -//  
35 -// @Bean  
36 -// public Tracer tracer() {  
37 -// // 创建 SkywalkingTracer 对象  
38 -// SkywalkingTracer tracer = new SkywalkingTracer();  
39 -// // 设置为 GlobalTracer 的追踪器  
40 -// GlobalTracer.register(tracer);  
41 -// return tracer;  
42 -// } 30 + @Bean
  31 + public TracerProperties bizTracerProperties() {
  32 + return new TracerProperties();
  33 + }
  34 +
  35 + @Bean
  36 + public BizTraceAspect bizTracingAop() {
  37 + return new BizTraceAspect(tracer());
  38 + }
  39 +
  40 + @Bean
  41 + public Tracer tracer() {
  42 + // 创建 SkywalkingTracer 对象
  43 + SkywalkingTracer tracer = new SkywalkingTracer();
  44 + // 设置为 GlobalTracer 的追踪器
  45 + GlobalTracer.registerIfAbsent(tracer);
  46 + return tracer;
  47 + }
43 48
44 /** 49 /**
45 * 创建 TraceFilter 过滤器,响应 header 设置 traceId 50 * 创建 TraceFilter 过滤器,响应 header 设置 traceId
yudao-framework/yudao-spring-boot-starter-mybatis/pom.xml
@@ -24,8 +24,8 @@ @@ -24,8 +24,8 @@
24 <!-- Web 相关 --> 24 <!-- Web 相关 -->
25 <dependency> 25 <dependency>
26 <groupId>cn.iocoder.boot</groupId> 26 <groupId>cn.iocoder.boot</groupId>
27 - <artifactId>yudao-spring-boot-starter-web</artifactId>  
28 - <scope>provided</scope> <!-- 设置为 provided,只有 OncePerRequestFilter 使用到 --> 27 + <artifactId>yudao-spring-boot-starter-security</artifactId>
  28 + <scope>provided</scope> <!-- 设置为 provided,只有 DefaultDBFieldHandler 使用到 -->
29 </dependency> 29 </dependency>
30 30
31 <!-- DB 相关 --> 31 <!-- DB 相关 -->
yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/handler/DefaultDBFieldHandler.java
1 package cn.iocoder.yudao.framework.mybatis.core.handler; 1 package cn.iocoder.yudao.framework.mybatis.core.handler;
2 2
3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; 3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
4 -import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils; 4 +import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
5 import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; 5 import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
6 import org.apache.ibatis.reflection.MetaObject; 6 import org.apache.ibatis.reflection.MetaObject;
7 7
@@ -32,7 +32,7 @@ public class DefaultDBFieldHandler implements MetaObjectHandler { @@ -32,7 +32,7 @@ public class DefaultDBFieldHandler implements MetaObjectHandler {
32 baseDO.setUpdateTime(current); 32 baseDO.setUpdateTime(current);
33 } 33 }
34 34
35 - Long userId = WebFrameworkUtils.getLoginUserId(); 35 + Long userId = SecurityFrameworkUtils.getLoginUserId();
36 // 当前登录用户不为空,创建人为空,则当前登录用户为创建人 36 // 当前登录用户不为空,创建人为空,则当前登录用户为创建人
37 if (Objects.nonNull(userId) && Objects.isNull(baseDO.getCreator())) { 37 if (Objects.nonNull(userId) && Objects.isNull(baseDO.getCreator())) {
38 baseDO.setCreator(userId.toString()); 38 baseDO.setCreator(userId.toString());
@@ -54,7 +54,7 @@ public class DefaultDBFieldHandler implements MetaObjectHandler { @@ -54,7 +54,7 @@ public class DefaultDBFieldHandler implements MetaObjectHandler {
54 54
55 // 当前登录用户不为空,更新人为空,则当前登录用户为更新人 55 // 当前登录用户不为空,更新人为空,则当前登录用户为更新人
56 Object modifier = getFieldValByName("updater", metaObject); 56 Object modifier = getFieldValByName("updater", metaObject);
57 - Long userId = WebFrameworkUtils.getLoginUserId(); 57 + Long userId = SecurityFrameworkUtils.getLoginUserId();
58 if (Objects.nonNull(userId) && Objects.isNull(modifier)) { 58 if (Objects.nonNull(userId) && Objects.isNull(modifier)) {
59 setFieldValByName("updater", userId.toString(), metaObject); 59 setFieldValByName("updater", userId.toString(), metaObject);
60 } 60 }
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&lt;T&gt; extends MPJBaseMapper&lt;T&gt; { @@ -42,6 +42,7 @@ public interface BaseMapperX&lt;T&gt; extends MPJBaseMapper&lt;T&gt; {
42 default PageResult<T> selectPage(PageParam pageParam, Collection<SortingField> sortingFields, @Param("ew") Wrapper<T> queryWrapper) { 42 default PageResult<T> selectPage(PageParam pageParam, Collection<SortingField> sortingFields, @Param("ew") Wrapper<T> queryWrapper) {
43 // 特殊:不分页,直接查询全部 43 // 特殊:不分页,直接查询全部
44 if (PageParam.PAGE_SIZE_NONE.equals(pageParam.getPageSize())) { 44 if (PageParam.PAGE_SIZE_NONE.equals(pageParam.getPageSize())) {
  45 + MyBatisUtils.addOrder(queryWrapper, sortingFields);
45 List<T> list = selectList(queryWrapper); 46 List<T> list = selectList(queryWrapper);
46 return new PageResult<>(list, (long) list.size()); 47 return new PageResult<>(list, (long) list.size());
47 } 48 }
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&lt;T&gt; extends MPJLambdaWrapper&lt;T&gt; { @@ -118,7 +118,6 @@ public class MPJLambdaWrapperX&lt;T&gt; extends MPJLambdaWrapper&lt;T&gt; {
118 118
119 @Override 119 @Override
120 public <X> MPJLambdaWrapperX<T> orderByDesc(SFunction<X, ?> column) { 120 public <X> MPJLambdaWrapperX<T> orderByDesc(SFunction<X, ?> column) {
121 - //noinspection unchecked  
122 super.orderByDesc(true, column); 121 super.orderByDesc(true, column);
123 return this; 122 return this;
124 } 123 }
@@ -208,7 +207,7 @@ public class MPJLambdaWrapperX&lt;T&gt; extends MPJLambdaWrapper&lt;T&gt; { @@ -208,7 +207,7 @@ public class MPJLambdaWrapperX&lt;T&gt; extends MPJLambdaWrapper&lt;T&gt; {
208 } 207 }
209 208
210 @Override 209 @Override
211 - public <S, X> MPJLambdaWrapperX<T> selectCount(SFunction<S, ?> column, String alias) { 210 + public <S> MPJLambdaWrapperX<T> selectCount(SFunction<S, ?> column, String alias) {
212 super.selectCount(column, alias); 211 super.selectCount(column, alias);
213 return this; 212 return this;
214 } 213 }
@@ -226,7 +225,7 @@ public class MPJLambdaWrapperX&lt;T&gt; extends MPJLambdaWrapper&lt;T&gt; { @@ -226,7 +225,7 @@ public class MPJLambdaWrapperX&lt;T&gt; extends MPJLambdaWrapper&lt;T&gt; {
226 } 225 }
227 226
228 @Override 227 @Override
229 - public <S, X> MPJLambdaWrapperX<T> selectSum(SFunction<S, ?> column, String alias) { 228 + public <S> MPJLambdaWrapperX<T> selectSum(SFunction<S, ?> column, String alias) {
230 super.selectSum(column, alias); 229 super.selectSum(column, alias);
231 return this; 230 return this;
232 } 231 }
@@ -244,7 +243,7 @@ public class MPJLambdaWrapperX&lt;T&gt; extends MPJLambdaWrapper&lt;T&gt; { @@ -244,7 +243,7 @@ public class MPJLambdaWrapperX&lt;T&gt; extends MPJLambdaWrapper&lt;T&gt; {
244 } 243 }
245 244
246 @Override 245 @Override
247 - public <S, X> MPJLambdaWrapperX<T> selectMax(SFunction<S, ?> column, String alias) { 246 + public <S> MPJLambdaWrapperX<T> selectMax(SFunction<S, ?> column, String alias) {
248 super.selectMax(column, alias); 247 super.selectMax(column, alias);
249 return this; 248 return this;
250 } 249 }
@@ -262,7 +261,7 @@ public class MPJLambdaWrapperX&lt;T&gt; extends MPJLambdaWrapper&lt;T&gt; { @@ -262,7 +261,7 @@ public class MPJLambdaWrapperX&lt;T&gt; extends MPJLambdaWrapper&lt;T&gt; {
262 } 261 }
263 262
264 @Override 263 @Override
265 - public <S, X> MPJLambdaWrapperX<T> selectMin(SFunction<S, ?> column, String alias) { 264 + public <S> MPJLambdaWrapperX<T> selectMin(SFunction<S, ?> column, String alias) {
266 super.selectMin(column, alias); 265 super.selectMin(column, alias);
267 return this; 266 return this;
268 } 267 }
@@ -280,7 +279,7 @@ public class MPJLambdaWrapperX&lt;T&gt; extends MPJLambdaWrapper&lt;T&gt; { @@ -280,7 +279,7 @@ public class MPJLambdaWrapperX&lt;T&gt; extends MPJLambdaWrapper&lt;T&gt; {
280 } 279 }
281 280
282 @Override 281 @Override
283 - public <S, X> MPJLambdaWrapperX<T> selectAvg(SFunction<S, ?> column, String alias) { 282 + public <S> MPJLambdaWrapperX<T> selectAvg(SFunction<S, ?> column, String alias) {
284 super.selectAvg(column, alias); 283 super.selectAvg(column, alias);
285 return this; 284 return this;
286 } 285 }
@@ -298,7 +297,7 @@ public class MPJLambdaWrapperX&lt;T&gt; extends MPJLambdaWrapper&lt;T&gt; { @@ -298,7 +297,7 @@ public class MPJLambdaWrapperX&lt;T&gt; extends MPJLambdaWrapper&lt;T&gt; {
298 } 297 }
299 298
300 @Override 299 @Override
301 - public <S, X> MPJLambdaWrapperX<T> selectLen(SFunction<S, ?> column, String alias) { 300 + public <S> MPJLambdaWrapperX<T> selectLen(SFunction<S, ?> column, String alias) {
302 super.selectLen(column, alias); 301 super.selectLen(column, alias);
303 return this; 302 return this;
304 } 303 }
yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/MyBatisUtils.java
1 package cn.iocoder.yudao.framework.mybatis.core.util; 1 package cn.iocoder.yudao.framework.mybatis.core.util;
2 2
3 -import cn.hutool.core.collection.CollectionUtil; 3 +import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.lang.func.Func1; 4 import cn.hutool.core.lang.func.Func1;
5 import cn.hutool.core.lang.func.LambdaUtil; 5 import cn.hutool.core.lang.func.LambdaUtil;
6 import cn.hutool.core.util.StrUtil; 6 import cn.hutool.core.util.StrUtil;
@@ -8,6 +8,8 @@ import cn.iocoder.yudao.framework.common.pojo.PageParam; @@ -8,6 +8,8 @@ import cn.iocoder.yudao.framework.common.pojo.PageParam;
8 import cn.iocoder.yudao.framework.common.pojo.SortingField; 8 import cn.iocoder.yudao.framework.common.pojo.SortingField;
9 import cn.iocoder.yudao.framework.mybatis.core.enums.DbTypeEnum; 9 import cn.iocoder.yudao.framework.mybatis.core.enums.DbTypeEnum;
10 import com.baomidou.mybatisplus.annotation.DbType; 10 import com.baomidou.mybatisplus.annotation.DbType;
  11 +import com.baomidou.mybatisplus.core.conditions.Wrapper;
  12 +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
11 import com.baomidou.mybatisplus.core.metadata.OrderItem; 13 import com.baomidou.mybatisplus.core.metadata.OrderItem;
12 import com.baomidou.mybatisplus.core.toolkit.StringPool; 14 import com.baomidou.mybatisplus.core.toolkit.StringPool;
13 import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; 15 import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
@@ -20,7 +22,6 @@ import net.sf.jsqlparser.schema.Table; @@ -20,7 +22,6 @@ import net.sf.jsqlparser.schema.Table;
20 import java.util.ArrayList; 22 import java.util.ArrayList;
21 import java.util.Collection; 23 import java.util.Collection;
22 import java.util.List; 24 import java.util.List;
23 -import java.util.stream.Collectors;  
24 25
25 /** 26 /**
26 * MyBatis 工具类 27 * MyBatis 工具类
@@ -37,15 +38,27 @@ public class MyBatisUtils { @@ -37,15 +38,27 @@ public class MyBatisUtils {
37 // 页码 + 数量 38 // 页码 + 数量
38 Page<T> page = new Page<>(pageParam.getPageNo(), pageParam.getPageSize()); 39 Page<T> page = new Page<>(pageParam.getPageNo(), pageParam.getPageSize());
39 // 排序字段 40 // 排序字段
40 - if (!CollectionUtil.isEmpty(sortingFields)) {  
41 - page.addOrder(sortingFields.stream().map(sortingField -> SortingField.ORDER_ASC.equals(sortingField.getOrder())  
42 - ? OrderItem.asc(StrUtil.toUnderlineCase(sortingField.getField()))  
43 - : OrderItem.desc(StrUtil.toUnderlineCase(sortingField.getField())))  
44 - .collect(Collectors.toList())); 41 + if (CollUtil.isNotEmpty(sortingFields)) {
  42 + for (SortingField sortingField : sortingFields) {
  43 + page.addOrder(new OrderItem().setAsc(SortingField.ORDER_ASC.equals(sortingField.getOrder()))
  44 + .setColumn(StrUtil.toUnderlineCase(sortingField.getField())));
  45 + }
45 } 46 }
46 return page; 47 return page;
47 } 48 }
48 49
  50 + public static <T> void addOrder(Wrapper<T> wrapper, Collection<SortingField> sortingFields) {
  51 + if (CollUtil.isEmpty(sortingFields)) {
  52 + return;
  53 + }
  54 + QueryWrapper<T> query = (QueryWrapper<T>) wrapper;
  55 + for (SortingField sortingField : sortingFields) {
  56 + query.orderBy(true,
  57 + SortingField.ORDER_ASC.equals(sortingField.getOrder()),
  58 + StrUtil.toUnderlineCase(sortingField.getField()));
  59 + }
  60 + }
  61 +
49 /** 62 /**
50 * 将拦截器添加到链中 63 * 将拦截器添加到链中
51 * 由于 MybatisPlusInterceptor 不支持添加拦截器,所以只能全量设置 64 * 由于 MybatisPlusInterceptor 不支持添加拦截器,所以只能全量设置
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 { @@ -126,8 +126,10 @@ public class SecurityFrameworkUtils {
126 126
127 // 额外设置到 request 中,用于 ApiAccessLogFilter 可以获取到用户编号; 127 // 额外设置到 request 中,用于 ApiAccessLogFilter 可以获取到用户编号;
128 // 原因是,Spring Security 的 Filter 在 ApiAccessLogFilter 后面,在它记录访问日志时,线上上下文已经没有用户编号等信息 128 // 原因是,Spring Security 的 Filter 在 ApiAccessLogFilter 后面,在它记录访问日志时,线上上下文已经没有用户编号等信息
129 - WebFrameworkUtils.setLoginUserId(request, loginUser.getId());  
130 - WebFrameworkUtils.setLoginUserType(request, loginUser.getUserType()); 129 + if (request != null) {
  130 + WebFrameworkUtils.setLoginUserId(request, loginUser.getId());
  131 + WebFrameworkUtils.setLoginUserType(request, loginUser.getUserType());
  132 + }
131 } 133 }
132 134
133 private static Authentication buildAuthentication(LoginUser loginUser, HttpServletRequest request) { 135 private static Authentication buildAuthentication(LoginUser loginUser, HttpServletRequest request) {
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/filter/CacheRequestBodyFilter.java
1 package cn.iocoder.yudao.framework.web.core.filter; 1 package cn.iocoder.yudao.framework.web.core.filter;
2 2
  3 +import cn.hutool.core.util.StrUtil;
3 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; 4 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
4 import org.springframework.web.filter.OncePerRequestFilter; 5 import org.springframework.web.filter.OncePerRequestFilter;
5 6
@@ -16,6 +17,14 @@ import java.io.IOException; @@ -16,6 +17,14 @@ import java.io.IOException;
16 */ 17 */
17 public class CacheRequestBodyFilter extends OncePerRequestFilter { 18 public class CacheRequestBodyFilter extends OncePerRequestFilter {
18 19
  20 + /**
  21 + * 需要排除的 URI
  22 + *
  23 + * 1. 排除 Spring Boot Admin 相关请求,避免客户端连接中断导致的异常。
  24 + * 例如说:<a href="https://github.com/YunaiV/ruoyi-vue-pro/issues/795">795 ISSUE</a>
  25 + */
  26 + private static final String[] IGNORE_URIS = {"/admin/", "/actuator/"};
  27 +
19 @Override 28 @Override
20 protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) 29 protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
21 throws IOException, ServletException { 30 throws IOException, ServletException {
@@ -24,7 +33,13 @@ public class CacheRequestBodyFilter extends OncePerRequestFilter { @@ -24,7 +33,13 @@ public class CacheRequestBodyFilter extends OncePerRequestFilter {
24 33
25 @Override 34 @Override
26 protected boolean shouldNotFilter(HttpServletRequest request) { 35 protected boolean shouldNotFilter(HttpServletRequest request) {
27 - // 只处理 json 请求内容 36 + // 1. 校验是否为排除的 URL
  37 + String requestURI = request.getRequestURI();
  38 + if (StrUtil.startWithAny(requestURI, IGNORE_URIS)) {
  39 + return true;
  40 + }
  41 +
  42 + // 2. 只处理 json 请求内容
28 return !ServletUtils.isJsonRequest(request); 43 return !ServletUtils.isJsonRequest(request);
29 } 44 }
30 45
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; @@ -6,6 +6,8 @@ import cn.hutool.core.map.MapUtil;
6 import cn.hutool.core.util.ObjUtil; 6 import cn.hutool.core.util.ObjUtil;
7 import cn.hutool.core.util.StrUtil; 7 import cn.hutool.core.util.StrUtil;
8 import cn.hutool.extra.servlet.JakartaServletUtil; 8 import cn.hutool.extra.servlet.JakartaServletUtil;
  9 +import cn.iocoder.yudao.framework.common.biz.infra.logger.ApiErrorLogCommonApi;
  10 +import cn.iocoder.yudao.framework.common.biz.infra.logger.dto.ApiErrorLogCreateReqDTO;
9 import cn.iocoder.yudao.framework.common.exception.ServiceException; 11 import cn.iocoder.yudao.framework.common.exception.ServiceException;
10 import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil; 12 import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
11 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 13 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
@@ -14,8 +16,6 @@ import cn.iocoder.yudao.framework.common.util.json.JsonUtils; @@ -14,8 +16,6 @@ import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
14 import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils; 16 import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
15 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; 17 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
16 import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils; 18 import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
17 -import cn.iocoder.yudao.framework.common.biz.infra.logger.ApiErrorLogCommonApi;  
18 -import cn.iocoder.yudao.framework.common.biz.infra.logger.dto.ApiErrorLogCreateReqDTO;  
19 import com.fasterxml.jackson.databind.exc.InvalidFormatException; 19 import com.fasterxml.jackson.databind.exc.InvalidFormatException;
20 import jakarta.servlet.http.HttpServletRequest; 20 import jakarta.servlet.http.HttpServletRequest;
21 import jakarta.validation.ConstraintViolation; 21 import jakarta.validation.ConstraintViolation;
@@ -29,6 +29,7 @@ import org.springframework.util.Assert; @@ -29,6 +29,7 @@ import org.springframework.util.Assert;
29 import org.springframework.validation.BindException; 29 import org.springframework.validation.BindException;
30 import org.springframework.validation.FieldError; 30 import org.springframework.validation.FieldError;
31 import org.springframework.validation.ObjectError; 31 import org.springframework.validation.ObjectError;
  32 +import org.springframework.web.HttpMediaTypeNotSupportedException;
32 import org.springframework.web.HttpRequestMethodNotSupportedException; 33 import org.springframework.web.HttpRequestMethodNotSupportedException;
33 import org.springframework.web.bind.MethodArgumentNotValidException; 34 import org.springframework.web.bind.MethodArgumentNotValidException;
34 import org.springframework.web.bind.MissingServletRequestParameterException; 35 import org.springframework.web.bind.MissingServletRequestParameterException;
@@ -101,6 +102,9 @@ public class GlobalExceptionHandler { @@ -101,6 +102,9 @@ public class GlobalExceptionHandler {
101 if (ex instanceof HttpRequestMethodNotSupportedException) { 102 if (ex instanceof HttpRequestMethodNotSupportedException) {
102 return httpRequestMethodNotSupportedExceptionHandler((HttpRequestMethodNotSupportedException) ex); 103 return httpRequestMethodNotSupportedExceptionHandler((HttpRequestMethodNotSupportedException) ex);
103 } 104 }
  105 + if (ex instanceof HttpMediaTypeNotSupportedException) {
  106 + return httpMediaTypeNotSupportedExceptionHandler((HttpMediaTypeNotSupportedException) ex);
  107 + }
104 if (ex instanceof ServiceException) { 108 if (ex instanceof ServiceException) {
105 return serviceExceptionHandler((ServiceException) ex); 109 return serviceExceptionHandler((ServiceException) ex);
106 } 110 }
@@ -171,17 +175,19 @@ public class GlobalExceptionHandler { @@ -171,17 +175,19 @@ public class GlobalExceptionHandler {
171 /** 175 /**
172 * 处理 SpringMVC 请求参数类型错误 176 * 处理 SpringMVC 请求参数类型错误
173 * 177 *
174 - * 例如说,接口上设置了 @RequestBody实体中 xx 属性类型为 Integer,结果传递 xx 参数类型为 String 178 + * 例如说,接口上设置了 @RequestBody 实体中 xx 属性类型为 Integer,结果传递 xx 参数类型为 String
175 */ 179 */
176 @ExceptionHandler(HttpMessageNotReadableException.class) 180 @ExceptionHandler(HttpMessageNotReadableException.class)
177 public CommonResult<?> methodArgumentTypeInvalidFormatExceptionHandler(HttpMessageNotReadableException ex) { 181 public CommonResult<?> methodArgumentTypeInvalidFormatExceptionHandler(HttpMessageNotReadableException ex) {
178 log.warn("[methodArgumentTypeInvalidFormatExceptionHandler]", ex); 182 log.warn("[methodArgumentTypeInvalidFormatExceptionHandler]", ex);
179 - if(ex.getCause() instanceof InvalidFormatException) { 183 + if (ex.getCause() instanceof InvalidFormatException) {
180 InvalidFormatException invalidFormatException = (InvalidFormatException) ex.getCause(); 184 InvalidFormatException invalidFormatException = (InvalidFormatException) ex.getCause();
181 return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数类型错误:%s", invalidFormatException.getValue())); 185 return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数类型错误:%s", invalidFormatException.getValue()));
182 - } else {  
183 - return defaultExceptionHandler(ServletUtils.getRequest(), ex);  
184 } 186 }
  187 + if (StrUtil.startWith(ex.getMessage(), "Required request body is missing")) {
  188 + return CommonResult.error(BAD_REQUEST.getCode(), "请求参数类型错误: request body 缺失");
  189 + }
  190 + return defaultExceptionHandler(ServletUtils.getRequest(), ex);
185 } 191 }
186 192
187 /** 193 /**
@@ -238,6 +244,17 @@ public class GlobalExceptionHandler { @@ -238,6 +244,17 @@ public class GlobalExceptionHandler {
238 } 244 }
239 245
240 /** 246 /**
  247 + * 处理 SpringMVC 请求的 Content-Type 不正确
  248 + *
  249 + * 例如说,A 接口的 Content-Type 为 application/json,结果请求的 Content-Type 为 application/octet-stream,导致不匹配
  250 + */
  251 + @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
  252 + public CommonResult<?> httpMediaTypeNotSupportedExceptionHandler(HttpMediaTypeNotSupportedException ex) {
  253 + log.warn("[httpMediaTypeNotSupportedExceptionHandler]", ex);
  254 + return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求类型不正确:%s", ex.getMessage()));
  255 + }
  256 +
  257 + /**
241 * 处理 Spring Security 权限不足的异常 258 * 处理 Spring Security 权限不足的异常
242 * 259 *
243 * 来源是,使用 @PreAuthorize 注解,AOP 进行权限拦截 260 * 来源是,使用 @PreAuthorize 注解,AOP 进行权限拦截
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; @@ -5,7 +5,6 @@ import cn.iocoder.yudao.framework.xss.core.clean.JsoupXssCleaner;
5 import cn.iocoder.yudao.framework.xss.core.clean.XssCleaner; 5 import cn.iocoder.yudao.framework.xss.core.clean.XssCleaner;
6 import cn.iocoder.yudao.framework.xss.core.filter.XssFilter; 6 import cn.iocoder.yudao.framework.xss.core.filter.XssFilter;
7 import cn.iocoder.yudao.framework.xss.core.json.XssStringJsonDeserializer; 7 import cn.iocoder.yudao.framework.xss.core.json.XssStringJsonDeserializer;
8 -import com.fasterxml.jackson.databind.ObjectMapper;  
9 import org.springframework.boot.autoconfigure.AutoConfiguration; 8 import org.springframework.boot.autoconfigure.AutoConfiguration;
10 import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; 9 import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
11 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 10 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -42,13 +41,13 @@ public class YudaoXssAutoConfiguration implements WebMvcConfigurer { @@ -42,13 +41,13 @@ public class YudaoXssAutoConfiguration implements WebMvcConfigurer {
42 */ 41 */
43 @Bean 42 @Bean
44 @ConditionalOnMissingBean(name = "xssJacksonCustomizer") 43 @ConditionalOnMissingBean(name = "xssJacksonCustomizer")
45 - @ConditionalOnBean(ObjectMapper.class)  
46 @ConditionalOnProperty(value = "yudao.xss.enable", havingValue = "true") 44 @ConditionalOnProperty(value = "yudao.xss.enable", havingValue = "true")
47 public Jackson2ObjectMapperBuilderCustomizer xssJacksonCustomizer(XssProperties properties, 45 public Jackson2ObjectMapperBuilderCustomizer xssJacksonCustomizer(XssProperties properties,
48 PathMatcher pathMatcher, 46 PathMatcher pathMatcher,
49 XssCleaner xssCleaner) { 47 XssCleaner xssCleaner) {
50 // 在反序列化时进行 xss 过滤,可以替换使用 XssStringJsonSerializer,在序列化时进行处理 48 // 在反序列化时进行 xss 过滤,可以替换使用 XssStringJsonSerializer,在序列化时进行处理
51 - return builder -> builder.deserializerByType(String.class, new XssStringJsonDeserializer(properties, pathMatcher, xssCleaner)); 49 + return builder ->
  50 + builder.deserializerByType(String.class, new XssStringJsonDeserializer(properties, pathMatcher, xssCleaner));
52 } 51 }
53 52
54 /** 53 /**
yudao-module-ai/pom.xml
@@ -19,7 +19,8 @@ @@ -19,7 +19,8 @@
19 国外:OpenAI、Ollama、Midjourney、StableDiffusion、Suno 19 国外:OpenAI、Ollama、Midjourney、StableDiffusion、Suno
20 </description> 20 </description>
21 <properties> 21 <properties>
22 - <spring-ai.version>1.0.0-M6</spring-ai.version> 22 + <spring-ai.version>1.0.0</spring-ai.version>
  23 + <alibaba-ai.version>1.0.0.2</alibaba-ai.version>
23 <tinyflow.version>1.0.2</tinyflow.version> 24 <tinyflow.version>1.0.2</tinyflow.version>
24 </properties> 25 </properties>
25 26
@@ -75,65 +76,73 @@ @@ -75,65 +76,73 @@
75 <!-- Spring AI Model 模型接入 --> 76 <!-- Spring AI Model 模型接入 -->
76 <dependency> 77 <dependency>
77 <groupId>org.springframework.ai</groupId> 78 <groupId>org.springframework.ai</groupId>
78 - <artifactId>spring-ai-openai-spring-boot-starter</artifactId> 79 + <artifactId>spring-ai-starter-model-openai</artifactId>
79 <version>${spring-ai.version}</version> 80 <version>${spring-ai.version}</version>
80 </dependency> 81 </dependency>
81 <dependency> 82 <dependency>
82 <groupId>org.springframework.ai</groupId> 83 <groupId>org.springframework.ai</groupId>
83 - <artifactId>spring-ai-azure-openai-spring-boot-starter</artifactId> 84 + <artifactId>spring-ai-starter-model-azure-openai</artifactId>
84 <version>${spring-ai.version}</version> 85 <version>${spring-ai.version}</version>
85 </dependency> 86 </dependency>
86 <dependency> 87 <dependency>
87 <groupId>org.springframework.ai</groupId> 88 <groupId>org.springframework.ai</groupId>
88 - <artifactId>spring-ai-ollama-spring-boot-starter</artifactId> 89 + <artifactId>spring-ai-starter-model-deepseek</artifactId>
89 <version>${spring-ai.version}</version> 90 <version>${spring-ai.version}</version>
90 </dependency> 91 </dependency>
91 <dependency> 92 <dependency>
92 <groupId>org.springframework.ai</groupId> 93 <groupId>org.springframework.ai</groupId>
93 - <artifactId>spring-ai-stability-ai-spring-boot-starter</artifactId> 94 + <artifactId>spring-ai-starter-model-ollama</artifactId>
94 <version>${spring-ai.version}</version> 95 <version>${spring-ai.version}</version>
95 </dependency> 96 </dependency>
96 <dependency> 97 <dependency>
97 - <!-- 通义千问 -->  
98 - <groupId>com.alibaba.cloud.ai</groupId>  
99 - <artifactId>spring-ai-alibaba-starter</artifactId>  
100 - <version>${spring-ai.version}.1</version>  
101 - </dependency>  
102 - <dependency>  
103 - <!-- 文心一言 -->  
104 <groupId>org.springframework.ai</groupId> 98 <groupId>org.springframework.ai</groupId>
105 - <artifactId>spring-ai-qianfan-spring-boot-starter</artifactId> 99 + <artifactId>spring-ai-starter-model-stability-ai</artifactId>
106 <version>${spring-ai.version}</version> 100 <version>${spring-ai.version}</version>
107 </dependency> 101 </dependency>
108 <dependency> 102 <dependency>
109 <!-- 智谱 GLM --> 103 <!-- 智谱 GLM -->
110 <groupId>org.springframework.ai</groupId> 104 <groupId>org.springframework.ai</groupId>
111 - <artifactId>spring-ai-zhipuai-spring-boot-starter</artifactId> 105 + <artifactId>spring-ai-starter-model-zhipuai</artifactId>
112 <version>${spring-ai.version}</version> 106 <version>${spring-ai.version}</version>
113 </dependency> 107 </dependency>
114 <dependency> 108 <dependency>
115 <groupId>org.springframework.ai</groupId> 109 <groupId>org.springframework.ai</groupId>
116 - <artifactId>spring-ai-minimax-spring-boot-starter</artifactId> 110 + <artifactId>spring-ai-starter-model-minimax</artifactId>
117 <version>${spring-ai.version}</version> 111 <version>${spring-ai.version}</version>
118 </dependency> 112 </dependency>
  113 +
119 <dependency> 114 <dependency>
120 - <groupId>org.springframework.ai</groupId>  
121 - <artifactId>spring-ai-moonshot-spring-boot-starter</artifactId>  
122 - <version>${spring-ai.version}</version> 115 + <!-- 通义千问 -->
  116 + <groupId>com.alibaba.cloud.ai</groupId>
  117 + <artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
  118 + <version>${alibaba-ai.version}</version>
  119 + </dependency>
  120 +
  121 + <dependency>
  122 + <!-- 文心一言 -->
  123 + <groupId>org.springaicommunity</groupId>
  124 + <artifactId>qianfan-spring-boot-starter</artifactId>
  125 + <version>1.0.0</version>
  126 + </dependency>
  127 + <dependency>
  128 + <!-- 月之暗灭 -->
  129 + <groupId>org.springaicommunity</groupId>
  130 + <artifactId>moonshot-spring-boot-starter</artifactId>
  131 + <version>1.0.0</version>
123 </dependency> 132 </dependency>
124 133
125 <!-- 向量存储:https://db-engines.com/en/ranking/vector+dbms --> 134 <!-- 向量存储:https://db-engines.com/en/ranking/vector+dbms -->
126 <dependency> 135 <dependency>
127 <!-- Qdrant:https://qdrant.tech/ --> 136 <!-- Qdrant:https://qdrant.tech/ -->
128 <groupId>org.springframework.ai</groupId> 137 <groupId>org.springframework.ai</groupId>
129 - <artifactId>spring-ai-qdrant-store</artifactId> 138 + <artifactId>spring-ai-starter-vector-store-qdrant</artifactId>
130 <version>${spring-ai.version}</version> 139 <version>${spring-ai.version}</version>
131 </dependency> 140 </dependency>
132 141
133 <dependency> 142 <dependency>
134 <!-- Redis:https://redis.io/docs/latest/develop/get-started/vector-database/ --> 143 <!-- Redis:https://redis.io/docs/latest/develop/get-started/vector-database/ -->
135 <groupId>org.springframework.ai</groupId> 144 <groupId>org.springframework.ai</groupId>
136 - <artifactId>spring-ai-redis-store</artifactId> 145 + <artifactId>spring-ai-starter-vector-store-redis</artifactId>
137 <version>${spring-ai.version}</version> 146 <version>${spring-ai.version}</version>
138 </dependency> 147 </dependency>
139 <dependency> 148 <dependency>
@@ -144,7 +153,7 @@ @@ -144,7 +153,7 @@
144 <dependency> 153 <dependency>
145 <!-- Milvus:https://milvus.io/ --> 154 <!-- Milvus:https://milvus.io/ -->
146 <groupId>org.springframework.ai</groupId> 155 <groupId>org.springframework.ai</groupId>
147 - <artifactId>spring-ai-milvus-store</artifactId> 156 + <artifactId>spring-ai-starter-vector-store-milvus</artifactId>
148 <version>${spring-ai.version}</version> 157 <version>${spring-ai.version}</version>
149 <exclusions> 158 <exclusions>
150 <!-- 解决和 logback 的日志冲突 --> 159 <!-- 解决和 logback 的日志冲突 -->
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 @@ -12,6 +12,7 @@ import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.conversation.AiChatCo
12 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO; 12 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO;
13 import cn.iocoder.yudao.module.ai.service.chat.AiChatConversationService; 13 import cn.iocoder.yudao.module.ai.service.chat.AiChatConversationService;
14 import cn.iocoder.yudao.module.ai.service.chat.AiChatMessageService; 14 import cn.iocoder.yudao.module.ai.service.chat.AiChatMessageService;
  15 +import com.fhs.core.trans.anno.TransMethodResult;
15 import io.swagger.v3.oas.annotations.Operation; 16 import io.swagger.v3.oas.annotations.Operation;
16 import io.swagger.v3.oas.annotations.Parameter; 17 import io.swagger.v3.oas.annotations.Parameter;
17 import io.swagger.v3.oas.annotations.tags.Tag; 18 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -54,6 +55,7 @@ public class AiChatConversationController { @@ -54,6 +55,7 @@ public class AiChatConversationController {
54 55
55 @GetMapping("/my-list") 56 @GetMapping("/my-list")
56 @Operation(summary = "获得【我的】聊天对话列表") 57 @Operation(summary = "获得【我的】聊天对话列表")
  58 + @TransMethodResult
57 public CommonResult<List<AiChatConversationRespVO>> getChatConversationMyList() { 59 public CommonResult<List<AiChatConversationRespVO>> getChatConversationMyList() {
58 List<AiChatConversationDO> list = chatConversationService.getChatConversationListByUserId(getLoginUserId()); 60 List<AiChatConversationDO> list = chatConversationService.getChatConversationListByUserId(getLoginUserId());
59 return success(BeanUtils.toBean(list, AiChatConversationRespVO.class)); 61 return success(BeanUtils.toBean(list, AiChatConversationRespVO.class));
@@ -62,6 +64,7 @@ public class AiChatConversationController { @@ -62,6 +64,7 @@ public class AiChatConversationController {
62 @GetMapping("/get-my") 64 @GetMapping("/get-my")
63 @Operation(summary = "获得【我的】聊天对话") 65 @Operation(summary = "获得【我的】聊天对话")
64 @Parameter(name = "id", required = true, description = "对话编号", example = "1024") 66 @Parameter(name = "id", required = true, description = "对话编号", example = "1024")
  67 + @TransMethodResult
65 public CommonResult<AiChatConversationRespVO> getChatConversationMy(@RequestParam("id") Long id) { 68 public CommonResult<AiChatConversationRespVO> getChatConversationMy(@RequestParam("id") Long id) {
66 AiChatConversationDO conversation = chatConversationService.getChatConversation(id); 69 AiChatConversationDO conversation = chatConversationService.getChatConversation(id);
67 if (conversation != null && ObjUtil.notEqual(conversation.getUserId(), getLoginUserId())) { 70 if (conversation != null && ObjUtil.notEqual(conversation.getUserId(), getLoginUserId())) {
@@ -90,6 +93,7 @@ public class AiChatConversationController { @@ -90,6 +93,7 @@ public class AiChatConversationController {
90 @GetMapping("/page") 93 @GetMapping("/page")
91 @Operation(summary = "获得对话分页", description = "用于【对话管理】菜单") 94 @Operation(summary = "获得对话分页", description = "用于【对话管理】菜单")
92 @PreAuthorize("@ss.hasPermission('ai:chat-conversation:query')") 95 @PreAuthorize("@ss.hasPermission('ai:chat-conversation:query')")
  96 + @TransMethodResult
93 public CommonResult<PageResult<AiChatConversationRespVO>> getChatConversationPage(AiChatConversationPageReqVO pageReqVO) { 97 public CommonResult<PageResult<AiChatConversationRespVO>> getChatConversationPage(AiChatConversationPageReqVO pageReqVO) {
94 PageResult<AiChatConversationDO> pageResult = chatConversationService.getChatConversationPage(pageReqVO); 98 PageResult<AiChatConversationDO> pageResult = chatConversationService.getChatConversationPage(pageReqVO);
95 if (CollUtil.isEmpty(pageResult.getList())) { 99 if (CollUtil.isEmpty(pageResult.getList())) {
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 @@ -10,6 +10,7 @@ import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatRole.AiChatRoleS
10 import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatRole.AiChatRoleSaveReqVO; 10 import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatRole.AiChatRoleSaveReqVO;
11 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO; 11 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO;
12 import cn.iocoder.yudao.module.ai.service.model.AiChatRoleService; 12 import cn.iocoder.yudao.module.ai.service.model.AiChatRoleService;
  13 +import com.fhs.core.trans.anno.TransMethodResult;
13 import io.swagger.v3.oas.annotations.Operation; 14 import io.swagger.v3.oas.annotations.Operation;
14 import io.swagger.v3.oas.annotations.Parameter; 15 import io.swagger.v3.oas.annotations.Parameter;
15 import io.swagger.v3.oas.annotations.tags.Tag; 16 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -35,6 +36,7 @@ public class AiChatRoleController { @@ -35,6 +36,7 @@ public class AiChatRoleController {
35 36
36 @GetMapping("/my-page") 37 @GetMapping("/my-page")
37 @Operation(summary = "获得【我的】聊天角色分页") 38 @Operation(summary = "获得【我的】聊天角色分页")
  39 + @TransMethodResult
38 public CommonResult<PageResult<AiChatRoleRespVO>> getChatRoleMyPage(@Valid AiChatRolePageReqVO pageReqVO) { 40 public CommonResult<PageResult<AiChatRoleRespVO>> getChatRoleMyPage(@Valid AiChatRolePageReqVO pageReqVO) {
39 PageResult<AiChatRoleDO> pageResult = chatRoleService.getChatRoleMyPage(pageReqVO, getLoginUserId()); 41 PageResult<AiChatRoleDO> pageResult = chatRoleService.getChatRoleMyPage(pageReqVO, getLoginUserId());
40 return success(BeanUtils.toBean(pageResult, AiChatRoleRespVO.class)); 42 return success(BeanUtils.toBean(pageResult, AiChatRoleRespVO.class));
@@ -43,6 +45,7 @@ public class AiChatRoleController { @@ -43,6 +45,7 @@ public class AiChatRoleController {
43 @GetMapping("/get-my") 45 @GetMapping("/get-my")
44 @Operation(summary = "获得【我的】聊天角色") 46 @Operation(summary = "获得【我的】聊天角色")
45 @Parameter(name = "id", description = "编号", required = true, example = "1024") 47 @Parameter(name = "id", description = "编号", required = true, example = "1024")
  48 + @TransMethodResult
46 public CommonResult<AiChatRoleRespVO> getChatRoleMy(@RequestParam("id") Long id) { 49 public CommonResult<AiChatRoleRespVO> getChatRoleMy(@RequestParam("id") Long id) {
47 AiChatRoleDO chatRole = chatRoleService.getChatRole(id); 50 AiChatRoleDO chatRole = chatRoleService.getChatRole(id);
48 if (ObjUtil.notEqual(chatRole.getUserId(), getLoginUserId())) { 51 if (ObjUtil.notEqual(chatRole.getUserId(), getLoginUserId())) {
@@ -108,6 +111,7 @@ public class AiChatRoleController { @@ -108,6 +111,7 @@ public class AiChatRoleController {
108 @Operation(summary = "获得聊天角色") 111 @Operation(summary = "获得聊天角色")
109 @Parameter(name = "id", description = "编号", required = true, example = "1024") 112 @Parameter(name = "id", description = "编号", required = true, example = "1024")
110 @PreAuthorize("@ss.hasPermission('ai:chat-role:query')") 113 @PreAuthorize("@ss.hasPermission('ai:chat-role:query')")
  114 + @TransMethodResult
111 public CommonResult<AiChatRoleRespVO> getChatRole(@RequestParam("id") Long id) { 115 public CommonResult<AiChatRoleRespVO> getChatRole(@RequestParam("id") Long id) {
112 AiChatRoleDO chatRole = chatRoleService.getChatRole(id); 116 AiChatRoleDO chatRole = chatRoleService.getChatRole(id);
113 return success(BeanUtils.toBean(chatRole, AiChatRoleRespVO.class)); 117 return success(BeanUtils.toBean(chatRole, AiChatRoleRespVO.class));
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; @@ -5,7 +5,6 @@ import cn.hutool.extra.spring.SpringUtil;
5 import cn.iocoder.yudao.module.ai.framework.ai.core.AiModelFactory; 5 import cn.iocoder.yudao.module.ai.framework.ai.core.AiModelFactory;
6 import cn.iocoder.yudao.module.ai.framework.ai.core.AiModelFactoryImpl; 6 import cn.iocoder.yudao.module.ai.framework.ai.core.AiModelFactoryImpl;
7 import cn.iocoder.yudao.module.ai.framework.ai.core.model.baichuan.BaiChuanChatModel; 7 import cn.iocoder.yudao.module.ai.framework.ai.core.model.baichuan.BaiChuanChatModel;
8 -import cn.iocoder.yudao.module.ai.framework.ai.core.model.deepseek.DeepSeekChatModel;  
9 import cn.iocoder.yudao.module.ai.framework.ai.core.model.doubao.DouBaoChatModel; 8 import cn.iocoder.yudao.module.ai.framework.ai.core.model.doubao.DouBaoChatModel;
10 import cn.iocoder.yudao.module.ai.framework.ai.core.model.hunyuan.HunYuanChatModel; 9 import cn.iocoder.yudao.module.ai.framework.ai.core.model.hunyuan.HunYuanChatModel;
11 import cn.iocoder.yudao.module.ai.framework.ai.core.model.midjourney.api.MidjourneyApi; 10 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 @@ -14,10 +13,6 @@ import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlo
14 import cn.iocoder.yudao.module.ai.framework.ai.core.model.suno.api.SunoApi; 13 import cn.iocoder.yudao.module.ai.framework.ai.core.model.suno.api.SunoApi;
15 import cn.iocoder.yudao.module.ai.framework.ai.core.model.xinghuo.XingHuoChatModel; 14 import cn.iocoder.yudao.module.ai.framework.ai.core.model.xinghuo.XingHuoChatModel;
16 import lombok.extern.slf4j.Slf4j; 15 import lombok.extern.slf4j.Slf4j;
17 -import org.springframework.ai.autoconfigure.vectorstore.milvus.MilvusServiceClientProperties;  
18 -import org.springframework.ai.autoconfigure.vectorstore.milvus.MilvusVectorStoreProperties;  
19 -import org.springframework.ai.autoconfigure.vectorstore.qdrant.QdrantVectorStoreProperties;  
20 -import org.springframework.ai.autoconfigure.vectorstore.redis.RedisVectorStoreProperties;  
21 import org.springframework.ai.embedding.BatchingStrategy; 16 import org.springframework.ai.embedding.BatchingStrategy;
22 import org.springframework.ai.embedding.TokenCountBatchingStrategy; 17 import org.springframework.ai.embedding.TokenCountBatchingStrategy;
23 import org.springframework.ai.model.tool.ToolCallingManager; 18 import org.springframework.ai.model.tool.ToolCallingManager;
@@ -26,6 +21,10 @@ import org.springframework.ai.openai.OpenAiChatOptions; @@ -26,6 +21,10 @@ import org.springframework.ai.openai.OpenAiChatOptions;
26 import org.springframework.ai.openai.api.OpenAiApi; 21 import org.springframework.ai.openai.api.OpenAiApi;
27 import org.springframework.ai.tokenizer.JTokkitTokenCountEstimator; 22 import org.springframework.ai.tokenizer.JTokkitTokenCountEstimator;
28 import org.springframework.ai.tokenizer.TokenCountEstimator; 23 import org.springframework.ai.tokenizer.TokenCountEstimator;
  24 +import org.springframework.ai.vectorstore.milvus.autoconfigure.MilvusServiceClientProperties;
  25 +import org.springframework.ai.vectorstore.milvus.autoconfigure.MilvusVectorStoreProperties;
  26 +import org.springframework.ai.vectorstore.qdrant.autoconfigure.QdrantVectorStoreProperties;
  27 +import org.springframework.ai.vectorstore.redis.autoconfigure.RedisVectorStoreProperties;
29 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 28 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
30 import org.springframework.boot.context.properties.EnableConfigurationProperties; 29 import org.springframework.boot.context.properties.EnableConfigurationProperties;
31 import org.springframework.context.annotation.Bean; 30 import org.springframework.context.annotation.Bean;
@@ -53,33 +52,6 @@ public class AiAutoConfiguration { @@ -53,33 +52,6 @@ public class AiAutoConfiguration {
53 // ========== 各种 AI Client 创建 ========== 52 // ========== 各种 AI Client 创建 ==========
54 53
55 @Bean 54 @Bean
56 - @ConditionalOnProperty(value = "yudao.ai.deepseek.enable", havingValue = "true")  
57 - public DeepSeekChatModel deepSeekChatModel(YudaoAiProperties yudaoAiProperties) {  
58 - YudaoAiProperties.DeepSeekProperties properties = yudaoAiProperties.getDeepseek();  
59 - return buildDeepSeekChatModel(properties);  
60 - }  
61 -  
62 - public DeepSeekChatModel buildDeepSeekChatModel(YudaoAiProperties.DeepSeekProperties properties) {  
63 - if (StrUtil.isEmpty(properties.getModel())) {  
64 - properties.setModel(DeepSeekChatModel.MODEL_DEFAULT);  
65 - }  
66 - OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()  
67 - .openAiApi(OpenAiApi.builder()  
68 - .baseUrl(DeepSeekChatModel.BASE_URL)  
69 - .apiKey(properties.getApiKey())  
70 - .build())  
71 - .defaultOptions(OpenAiChatOptions.builder()  
72 - .model(properties.getModel())  
73 - .temperature(properties.getTemperature())  
74 - .maxTokens(properties.getMaxTokens())  
75 - .topP(properties.getTopP())  
76 - .build())  
77 - .toolCallingManager(getToolCallingManager())  
78 - .build();  
79 - return new DeepSeekChatModel(openAiChatModel);  
80 - }  
81 -  
82 - @Bean  
83 @ConditionalOnProperty(value = "yudao.ai.doubao.enable", havingValue = "true") 55 @ConditionalOnProperty(value = "yudao.ai.doubao.enable", havingValue = "true")
84 public DouBaoChatModel douBaoChatClient(YudaoAiProperties yudaoAiProperties) { 56 public DouBaoChatModel douBaoChatClient(YudaoAiProperties yudaoAiProperties) {
85 YudaoAiProperties.DouBaoProperties properties = yudaoAiProperties.getDoubao(); 57 YudaoAiProperties.DouBaoProperties properties = yudaoAiProperties.getDoubao();
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/config/YudaoAiProperties.java
@@ -14,12 +14,6 @@ import org.springframework.boot.context.properties.ConfigurationProperties; @@ -14,12 +14,6 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
14 public class YudaoAiProperties { 14 public class YudaoAiProperties {
15 15
16 /** 16 /**
17 - * DeepSeek  
18 - */  
19 - @SuppressWarnings("SpellCheckingInspection")  
20 - private DeepSeekProperties deepseek;  
21 -  
22 - /**  
23 * 字节豆包 17 * 字节豆包
24 */ 18 */
25 @SuppressWarnings("SpellCheckingInspection") 19 @SuppressWarnings("SpellCheckingInspection")
@@ -61,19 +55,6 @@ public class YudaoAiProperties { @@ -61,19 +55,6 @@ public class YudaoAiProperties {
61 private SunoProperties suno; 55 private SunoProperties suno;
62 56
63 @Data 57 @Data
64 - public static class DeepSeekProperties {  
65 -  
66 - private String enable;  
67 - private String apiKey;  
68 -  
69 - private String model;  
70 - private Double temperature;  
71 - private Integer maxTokens;  
72 - private Double topP;  
73 -  
74 - }  
75 -  
76 - @Data  
77 public static class DouBaoProperties { 58 public static class DouBaoProperties {
78 59
79 private String enable; 60 private String enable;
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; @@ -8,11 +8,11 @@ import cn.hutool.core.util.ArrayUtil;
8 import cn.hutool.core.util.RuntimeUtil; 8 import cn.hutool.core.util.RuntimeUtil;
9 import cn.hutool.core.util.StrUtil; 9 import cn.hutool.core.util.StrUtil;
10 import cn.hutool.extra.spring.SpringUtil; 10 import cn.hutool.extra.spring.SpringUtil;
  11 +import cn.iocoder.yudao.framework.common.util.spring.SpringUtils;
  12 +import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum;
11 import cn.iocoder.yudao.module.ai.framework.ai.config.AiAutoConfiguration; 13 import cn.iocoder.yudao.module.ai.framework.ai.config.AiAutoConfiguration;
12 import cn.iocoder.yudao.module.ai.framework.ai.config.YudaoAiProperties; 14 import cn.iocoder.yudao.module.ai.framework.ai.config.YudaoAiProperties;
13 -import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum;  
14 import cn.iocoder.yudao.module.ai.framework.ai.core.model.baichuan.BaiChuanChatModel; 15 import cn.iocoder.yudao.module.ai.framework.ai.core.model.baichuan.BaiChuanChatModel;
15 -import cn.iocoder.yudao.module.ai.framework.ai.core.model.deepseek.DeepSeekChatModel;  
16 import cn.iocoder.yudao.module.ai.framework.ai.core.model.doubao.DouBaoChatModel; 16 import cn.iocoder.yudao.module.ai.framework.ai.core.model.doubao.DouBaoChatModel;
17 import cn.iocoder.yudao.module.ai.framework.ai.core.model.hunyuan.HunYuanChatModel; 17 import cn.iocoder.yudao.module.ai.framework.ai.core.model.hunyuan.HunYuanChatModel;
18 import cn.iocoder.yudao.module.ai.framework.ai.core.model.midjourney.api.MidjourneyApi; 18 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 @@ -22,8 +22,9 @@ import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlo
22 import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowImageModel; 22 import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowImageModel;
23 import cn.iocoder.yudao.module.ai.framework.ai.core.model.suno.api.SunoApi; 23 import cn.iocoder.yudao.module.ai.framework.ai.core.model.suno.api.SunoApi;
24 import cn.iocoder.yudao.module.ai.framework.ai.core.model.xinghuo.XingHuoChatModel; 24 import cn.iocoder.yudao.module.ai.framework.ai.core.model.xinghuo.XingHuoChatModel;
25 -import cn.iocoder.yudao.framework.common.util.spring.SpringUtils;  
26 -import com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeAutoConfiguration; 25 +import com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeChatAutoConfiguration;
  26 +import com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeEmbeddingAutoConfiguration;
  27 +import com.alibaba.cloud.ai.autoconfigure.dashscope.DashScopeImageAutoConfiguration;
27 import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; 28 import com.alibaba.cloud.ai.dashscope.api.DashScopeApi;
28 import com.alibaba.cloud.ai.dashscope.api.DashScopeImageApi; 29 import com.alibaba.cloud.ai.dashscope.api.DashScopeImageApi;
29 import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel; 30 import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel;
@@ -32,47 +33,55 @@ import com.alibaba.cloud.ai.dashscope.embedding.DashScopeEmbeddingModel; @@ -32,47 +33,55 @@ import com.alibaba.cloud.ai.dashscope.embedding.DashScopeEmbeddingModel;
32 import com.alibaba.cloud.ai.dashscope.embedding.DashScopeEmbeddingOptions; 33 import com.alibaba.cloud.ai.dashscope.embedding.DashScopeEmbeddingOptions;
33 import com.alibaba.cloud.ai.dashscope.image.DashScopeImageModel; 34 import com.alibaba.cloud.ai.dashscope.image.DashScopeImageModel;
34 import com.azure.ai.openai.OpenAIClientBuilder; 35 import com.azure.ai.openai.OpenAIClientBuilder;
  36 +import com.azure.core.credential.KeyCredential;
35 import io.micrometer.observation.ObservationRegistry; 37 import io.micrometer.observation.ObservationRegistry;
36 import io.milvus.client.MilvusServiceClient; 38 import io.milvus.client.MilvusServiceClient;
37 import io.qdrant.client.QdrantClient; 39 import io.qdrant.client.QdrantClient;
38 import io.qdrant.client.QdrantGrpcClient; 40 import io.qdrant.client.QdrantGrpcClient;
39 import lombok.SneakyThrows; 41 import lombok.SneakyThrows;
40 -import org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiAutoConfiguration;  
41 -import org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiChatProperties;  
42 -import org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiConnectionProperties;  
43 -import org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiEmbeddingProperties;  
44 -import org.springframework.ai.autoconfigure.minimax.MiniMaxAutoConfiguration;  
45 -import org.springframework.ai.autoconfigure.moonshot.MoonshotAutoConfiguration;  
46 -import org.springframework.ai.autoconfigure.ollama.OllamaAutoConfiguration;  
47 -import org.springframework.ai.autoconfigure.openai.OpenAiAutoConfiguration;  
48 -import org.springframework.ai.autoconfigure.qianfan.QianFanAutoConfiguration;  
49 -import org.springframework.ai.autoconfigure.stabilityai.StabilityAiImageAutoConfiguration;  
50 -import org.springframework.ai.autoconfigure.vectorstore.milvus.MilvusServiceClientConnectionDetails;  
51 -import org.springframework.ai.autoconfigure.vectorstore.milvus.MilvusServiceClientProperties;  
52 -import org.springframework.ai.autoconfigure.vectorstore.milvus.MilvusVectorStoreAutoConfiguration;  
53 -import org.springframework.ai.autoconfigure.vectorstore.milvus.MilvusVectorStoreProperties;  
54 -import org.springframework.ai.autoconfigure.vectorstore.qdrant.QdrantVectorStoreAutoConfiguration;  
55 -import org.springframework.ai.autoconfigure.vectorstore.qdrant.QdrantVectorStoreProperties;  
56 -import org.springframework.ai.autoconfigure.vectorstore.redis.RedisVectorStoreAutoConfiguration;  
57 -import org.springframework.ai.autoconfigure.vectorstore.redis.RedisVectorStoreProperties;  
58 -import org.springframework.ai.autoconfigure.zhipuai.ZhiPuAiAutoConfiguration; 42 +import org.springaicommunity.moonshot.MoonshotChatModel;
  43 +import org.springaicommunity.moonshot.MoonshotChatOptions;
  44 +import org.springaicommunity.moonshot.api.MoonshotApi;
  45 +import org.springaicommunity.moonshot.autoconfigure.MoonshotChatAutoConfiguration;
  46 +import org.springaicommunity.qianfan.QianFanChatModel;
  47 +import org.springaicommunity.qianfan.QianFanEmbeddingModel;
  48 +import org.springaicommunity.qianfan.QianFanEmbeddingOptions;
  49 +import org.springaicommunity.qianfan.QianFanImageModel;
  50 +import org.springaicommunity.qianfan.api.QianFanApi;
  51 +import org.springaicommunity.qianfan.api.QianFanImageApi;
  52 +import org.springaicommunity.qianfan.autoconfigure.QianFanChatAutoConfiguration;
  53 +import org.springaicommunity.qianfan.autoconfigure.QianFanEmbeddingAutoConfiguration;
59 import org.springframework.ai.azure.openai.AzureOpenAiChatModel; 54 import org.springframework.ai.azure.openai.AzureOpenAiChatModel;
60 import org.springframework.ai.azure.openai.AzureOpenAiEmbeddingModel; 55 import org.springframework.ai.azure.openai.AzureOpenAiEmbeddingModel;
61 import org.springframework.ai.chat.model.ChatModel; 56 import org.springframework.ai.chat.model.ChatModel;
  57 +import org.springframework.ai.deepseek.DeepSeekChatModel;
  58 +import org.springframework.ai.deepseek.DeepSeekChatOptions;
  59 +import org.springframework.ai.deepseek.api.DeepSeekApi;
62 import org.springframework.ai.document.MetadataMode; 60 import org.springframework.ai.document.MetadataMode;
63 import org.springframework.ai.embedding.BatchingStrategy; 61 import org.springframework.ai.embedding.BatchingStrategy;
64 import org.springframework.ai.embedding.EmbeddingModel; 62 import org.springframework.ai.embedding.EmbeddingModel;
  63 +import org.springframework.ai.embedding.observation.EmbeddingModelObservationConvention;
65 import org.springframework.ai.image.ImageModel; 64 import org.springframework.ai.image.ImageModel;
66 import org.springframework.ai.minimax.MiniMaxChatModel; 65 import org.springframework.ai.minimax.MiniMaxChatModel;
67 import org.springframework.ai.minimax.MiniMaxChatOptions; 66 import org.springframework.ai.minimax.MiniMaxChatOptions;
68 import org.springframework.ai.minimax.MiniMaxEmbeddingModel; 67 import org.springframework.ai.minimax.MiniMaxEmbeddingModel;
69 import org.springframework.ai.minimax.MiniMaxEmbeddingOptions; 68 import org.springframework.ai.minimax.MiniMaxEmbeddingOptions;
70 import org.springframework.ai.minimax.api.MiniMaxApi; 69 import org.springframework.ai.minimax.api.MiniMaxApi;
71 -import org.springframework.ai.model.function.FunctionCallbackResolver; 70 +import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiChatAutoConfiguration;
  71 +import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiEmbeddingAutoConfiguration;
  72 +import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiEmbeddingProperties;
  73 +import org.springframework.ai.model.deepseek.autoconfigure.DeepSeekChatAutoConfiguration;
  74 +import org.springframework.ai.model.minimax.autoconfigure.MiniMaxChatAutoConfiguration;
  75 +import org.springframework.ai.model.minimax.autoconfigure.MiniMaxEmbeddingAutoConfiguration;
  76 +import org.springframework.ai.model.ollama.autoconfigure.OllamaChatAutoConfiguration;
  77 +import org.springframework.ai.model.openai.autoconfigure.OpenAiChatAutoConfiguration;
  78 +import org.springframework.ai.model.openai.autoconfigure.OpenAiEmbeddingAutoConfiguration;
  79 +import org.springframework.ai.model.openai.autoconfigure.OpenAiImageAutoConfiguration;
  80 +import org.springframework.ai.model.stabilityai.autoconfigure.StabilityAiImageAutoConfiguration;
72 import org.springframework.ai.model.tool.ToolCallingManager; 81 import org.springframework.ai.model.tool.ToolCallingManager;
73 -import org.springframework.ai.moonshot.MoonshotChatModel;  
74 -import org.springframework.ai.moonshot.MoonshotChatOptions;  
75 -import org.springframework.ai.moonshot.api.MoonshotApi; 82 +import org.springframework.ai.model.zhipuai.autoconfigure.ZhiPuAiChatAutoConfiguration;
  83 +import org.springframework.ai.model.zhipuai.autoconfigure.ZhiPuAiEmbeddingAutoConfiguration;
  84 +import org.springframework.ai.model.zhipuai.autoconfigure.ZhiPuAiImageAutoConfiguration;
76 import org.springframework.ai.ollama.OllamaChatModel; 85 import org.springframework.ai.ollama.OllamaChatModel;
77 import org.springframework.ai.ollama.OllamaEmbeddingModel; 86 import org.springframework.ai.ollama.OllamaEmbeddingModel;
78 import org.springframework.ai.ollama.api.OllamaApi; 87 import org.springframework.ai.ollama.api.OllamaApi;
@@ -84,21 +93,23 @@ import org.springframework.ai.openai.OpenAiImageModel; @@ -84,21 +93,23 @@ import org.springframework.ai.openai.OpenAiImageModel;
84 import org.springframework.ai.openai.api.OpenAiApi; 93 import org.springframework.ai.openai.api.OpenAiApi;
85 import org.springframework.ai.openai.api.OpenAiImageApi; 94 import org.springframework.ai.openai.api.OpenAiImageApi;
86 import org.springframework.ai.openai.api.common.OpenAiApiConstants; 95 import org.springframework.ai.openai.api.common.OpenAiApiConstants;
87 -import org.springframework.ai.qianfan.QianFanChatModel;  
88 -import org.springframework.ai.qianfan.QianFanEmbeddingModel;  
89 -import org.springframework.ai.qianfan.QianFanEmbeddingOptions;  
90 -import org.springframework.ai.qianfan.QianFanImageModel;  
91 -import org.springframework.ai.qianfan.api.QianFanApi;  
92 -import org.springframework.ai.qianfan.api.QianFanImageApi;  
93 import org.springframework.ai.stabilityai.StabilityAiImageModel; 96 import org.springframework.ai.stabilityai.StabilityAiImageModel;
94 import org.springframework.ai.stabilityai.api.StabilityAiApi; 97 import org.springframework.ai.stabilityai.api.StabilityAiApi;
95 import org.springframework.ai.vectorstore.SimpleVectorStore; 98 import org.springframework.ai.vectorstore.SimpleVectorStore;
96 import org.springframework.ai.vectorstore.VectorStore; 99 import org.springframework.ai.vectorstore.VectorStore;
97 import org.springframework.ai.vectorstore.milvus.MilvusVectorStore; 100 import org.springframework.ai.vectorstore.milvus.MilvusVectorStore;
  101 +import org.springframework.ai.vectorstore.milvus.autoconfigure.MilvusServiceClientConnectionDetails;
  102 +import org.springframework.ai.vectorstore.milvus.autoconfigure.MilvusServiceClientProperties;
  103 +import org.springframework.ai.vectorstore.milvus.autoconfigure.MilvusVectorStoreAutoConfiguration;
  104 +import org.springframework.ai.vectorstore.milvus.autoconfigure.MilvusVectorStoreProperties;
98 import org.springframework.ai.vectorstore.observation.DefaultVectorStoreObservationConvention; 105 import org.springframework.ai.vectorstore.observation.DefaultVectorStoreObservationConvention;
99 import org.springframework.ai.vectorstore.observation.VectorStoreObservationConvention; 106 import org.springframework.ai.vectorstore.observation.VectorStoreObservationConvention;
100 import org.springframework.ai.vectorstore.qdrant.QdrantVectorStore; 107 import org.springframework.ai.vectorstore.qdrant.QdrantVectorStore;
  108 +import org.springframework.ai.vectorstore.qdrant.autoconfigure.QdrantVectorStoreAutoConfiguration;
  109 +import org.springframework.ai.vectorstore.qdrant.autoconfigure.QdrantVectorStoreProperties;
101 import org.springframework.ai.vectorstore.redis.RedisVectorStore; 110 import org.springframework.ai.vectorstore.redis.RedisVectorStore;
  111 +import org.springframework.ai.vectorstore.redis.autoconfigure.RedisVectorStoreAutoConfiguration;
  112 +import org.springframework.ai.vectorstore.redis.autoconfigure.RedisVectorStoreProperties;
102 import org.springframework.ai.zhipuai.*; 113 import org.springframework.ai.zhipuai.*;
103 import org.springframework.ai.zhipuai.api.ZhiPuAiApi; 114 import org.springframework.ai.zhipuai.api.ZhiPuAiApi;
104 import org.springframework.ai.zhipuai.api.ZhiPuAiImageApi; 115 import org.springframework.ai.zhipuai.api.ZhiPuAiImageApi;
@@ -190,7 +201,7 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -190,7 +201,7 @@ public class AiModelFactoryImpl implements AiModelFactory {
190 case XING_HUO: 201 case XING_HUO:
191 return SpringUtil.getBean(XingHuoChatModel.class); 202 return SpringUtil.getBean(XingHuoChatModel.class);
192 case BAI_CHUAN: 203 case BAI_CHUAN:
193 - return SpringUtil.getBean(AzureOpenAiChatModel.class); 204 + return SpringUtil.getBean(BaiChuanChatModel.class);
194 case OPENAI: 205 case OPENAI:
195 return SpringUtil.getBean(OpenAiChatModel.class); 206 return SpringUtil.getBean(OpenAiChatModel.class);
196 case AZURE_OPENAI: 207 case AZURE_OPENAI:
@@ -319,27 +330,34 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -319,27 +330,34 @@ public class AiModelFactoryImpl implements AiModelFactory {
319 // ========== 各种创建 spring-ai 客户端的方法 ========== 330 // ========== 各种创建 spring-ai 客户端的方法 ==========
320 331
321 /** 332 /**
322 - * 可参考 {@link DashScopeAutoConfiguration} 的 dashscopeChatModel 方法 333 + * 可参考 {@link DashScopeChatAutoConfiguration} 的 dashscopeChatModel 方法
323 */ 334 */
324 private static DashScopeChatModel buildTongYiChatModel(String key) { 335 private static DashScopeChatModel buildTongYiChatModel(String key) {
325 - DashScopeApi dashScopeApi = new DashScopeApi(key); 336 + DashScopeApi dashScopeApi = DashScopeApi.builder().apiKey(key).build();
326 DashScopeChatOptions options = DashScopeChatOptions.builder().withModel(DashScopeApi.DEFAULT_CHAT_MODEL) 337 DashScopeChatOptions options = DashScopeChatOptions.builder().withModel(DashScopeApi.DEFAULT_CHAT_MODEL)
327 .withTemperature(0.7).build(); 338 .withTemperature(0.7).build();
328 - return new DashScopeChatModel(dashScopeApi, options, getFunctionCallbackResolver(), DEFAULT_RETRY_TEMPLATE); 339 + return DashScopeChatModel.builder()
  340 + .dashScopeApi(dashScopeApi)
  341 + .defaultOptions(options)
  342 + .toolCallingManager(getToolCallingManager())
  343 + .build();
329 } 344 }
330 345
331 /** 346 /**
332 - * 可参考 {@link DashScopeAutoConfiguration} 的 dashScopeImageModel 方法 347 + * 可参考 {@link DashScopeImageAutoConfiguration} 的 dashScopeImageModel 方法
333 */ 348 */
334 private static DashScopeImageModel buildTongYiImagesModel(String key) { 349 private static DashScopeImageModel buildTongYiImagesModel(String key) {
335 DashScopeImageApi dashScopeImageApi = new DashScopeImageApi(key); 350 DashScopeImageApi dashScopeImageApi = new DashScopeImageApi(key);
336 - return new DashScopeImageModel(dashScopeImageApi); 351 + return DashScopeImageModel.builder()
  352 + .dashScopeApi(dashScopeImageApi)
  353 + .build();
337 } 354 }
338 355
339 /** 356 /**
340 - * 可参考 {@link QianFanAutoConfiguration} 的 qianFanChatModel 方法 357 + * 可参考 {@link QianFanChatAutoConfiguration} 的 qianFanChatModel 方法
341 */ 358 */
342 private static QianFanChatModel buildYiYanChatModel(String key) { 359 private static QianFanChatModel buildYiYanChatModel(String key) {
  360 + // TODO spring ai qianfan 有 bug,无法使用 https://github.com/spring-ai-community/qianfan/issues/6
343 List<String> keys = StrUtil.split(key, '|'); 361 List<String> keys = StrUtil.split(key, '|');
344 Assert.equals(keys.size(), 2, "YiYanChatClient 的密钥需要 (appKey|secretKey) 格式"); 362 Assert.equals(keys.size(), 2, "YiYanChatClient 的密钥需要 (appKey|secretKey) 格式");
345 String appKey = keys.get(0); 363 String appKey = keys.get(0);
@@ -349,9 +367,10 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -349,9 +367,10 @@ public class AiModelFactoryImpl implements AiModelFactory {
349 } 367 }
350 368
351 /** 369 /**
352 - * 可参考 {@link QianFanAutoConfiguration} 的 qianFanImageModel 方法 370 + * 可参考 {@link QianFanEmbeddingAutoConfiguration} 的 qianFanImageModel 方法
353 */ 371 */
354 private QianFanImageModel buildQianFanImageModel(String key) { 372 private QianFanImageModel buildQianFanImageModel(String key) {
  373 + // TODO spring ai qianfan 有 bug,无法使用 https://github.com/spring-ai-community/qianfan/issues/6
355 List<String> keys = StrUtil.split(key, '|'); 374 List<String> keys = StrUtil.split(key, '|');
356 Assert.equals(keys.size(), 2, "YiYanChatClient 的密钥需要 (appKey|secretKey) 格式"); 375 Assert.equals(keys.size(), 2, "YiYanChatClient 的密钥需要 (appKey|secretKey) 格式");
357 String appKey = keys.get(0); 376 String appKey = keys.get(0);
@@ -361,12 +380,17 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -361,12 +380,17 @@ public class AiModelFactoryImpl implements AiModelFactory {
361 } 380 }
362 381
363 /** 382 /**
364 - * 可参考 {@link AiAutoConfiguration#deepSeekChatModel(YudaoAiProperties)} 383 + * 可参考 {@link DeepSeekChatAutoConfiguration} 的 deepSeekChatModel 方法
365 */ 384 */
366 private static DeepSeekChatModel buildDeepSeekChatModel(String apiKey) { 385 private static DeepSeekChatModel buildDeepSeekChatModel(String apiKey) {
367 - YudaoAiProperties.DeepSeekProperties properties = new YudaoAiProperties.DeepSeekProperties()  
368 - .setApiKey(apiKey);  
369 - return new AiAutoConfiguration().buildDeepSeekChatModel(properties); 386 + DeepSeekApi deepSeekApi = DeepSeekApi.builder().apiKey(apiKey).build();
  387 + DeepSeekChatOptions options = DeepSeekChatOptions.builder().model(DeepSeekApi.DEFAULT_CHAT_MODEL)
  388 + .temperature(0.7).build();
  389 + return DeepSeekChatModel.builder()
  390 + .deepSeekApi(deepSeekApi)
  391 + .defaultOptions(options)
  392 + .toolCallingManager(getToolCallingManager())
  393 + .build();
370 } 394 }
371 395
372 /** 396 /**
@@ -397,17 +421,18 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -397,17 +421,18 @@ public class AiModelFactoryImpl implements AiModelFactory {
397 } 421 }
398 422
399 /** 423 /**
400 - * 可参考 {@link ZhiPuAiAutoConfiguration} 的 zhiPuAiChatModel 方法 424 + * 可参考 {@link ZhiPuAiChatAutoConfiguration} 的 zhiPuAiChatModel 方法
401 */ 425 */
402 private ZhiPuAiChatModel buildZhiPuChatModel(String apiKey, String url) { 426 private ZhiPuAiChatModel buildZhiPuChatModel(String apiKey, String url) {
403 ZhiPuAiApi zhiPuAiApi = StrUtil.isEmpty(url) ? new ZhiPuAiApi(apiKey) 427 ZhiPuAiApi zhiPuAiApi = StrUtil.isEmpty(url) ? new ZhiPuAiApi(apiKey)
404 : new ZhiPuAiApi(url, apiKey); 428 : new ZhiPuAiApi(url, apiKey);
405 ZhiPuAiChatOptions options = ZhiPuAiChatOptions.builder().model(ZhiPuAiApi.DEFAULT_CHAT_MODEL).temperature(0.7).build(); 429 ZhiPuAiChatOptions options = ZhiPuAiChatOptions.builder().model(ZhiPuAiApi.DEFAULT_CHAT_MODEL).temperature(0.7).build();
406 - return new ZhiPuAiChatModel(zhiPuAiApi, options, getFunctionCallbackResolver(), DEFAULT_RETRY_TEMPLATE); 430 + return new ZhiPuAiChatModel(zhiPuAiApi, options, getToolCallingManager(), DEFAULT_RETRY_TEMPLATE,
  431 + getObservationRegistry().getIfAvailable());
407 } 432 }
408 433
409 /** 434 /**
410 - * 可参考 {@link ZhiPuAiAutoConfiguration} 的 zhiPuAiImageModel 方法 435 + * 可参考 {@link ZhiPuAiImageAutoConfiguration} 的 zhiPuAiImageModel 方法
411 */ 436 */
412 private ZhiPuAiImageModel buildZhiPuAiImageModel(String apiKey, String url) { 437 private ZhiPuAiImageModel buildZhiPuAiImageModel(String apiKey, String url) {
413 ZhiPuAiImageApi zhiPuAiApi = StrUtil.isEmpty(url) ? new ZhiPuAiImageApi(apiKey) 438 ZhiPuAiImageApi zhiPuAiApi = StrUtil.isEmpty(url) ? new ZhiPuAiImageApi(apiKey)
@@ -416,23 +441,30 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -416,23 +441,30 @@ public class AiModelFactoryImpl implements AiModelFactory {
416 } 441 }
417 442
418 /** 443 /**
419 - * 可参考 {@link MiniMaxAutoConfiguration} 的 miniMaxChatModel 方法 444 + * 可参考 {@link MiniMaxChatAutoConfiguration} 的 miniMaxChatModel 方法
420 */ 445 */
421 private MiniMaxChatModel buildMiniMaxChatModel(String apiKey, String url) { 446 private MiniMaxChatModel buildMiniMaxChatModel(String apiKey, String url) {
422 MiniMaxApi miniMaxApi = StrUtil.isEmpty(url) ? new MiniMaxApi(apiKey) 447 MiniMaxApi miniMaxApi = StrUtil.isEmpty(url) ? new MiniMaxApi(apiKey)
423 : new MiniMaxApi(url, apiKey); 448 : new MiniMaxApi(url, apiKey);
424 MiniMaxChatOptions options = MiniMaxChatOptions.builder().model(MiniMaxApi.DEFAULT_CHAT_MODEL).temperature(0.7).build(); 449 MiniMaxChatOptions options = MiniMaxChatOptions.builder().model(MiniMaxApi.DEFAULT_CHAT_MODEL).temperature(0.7).build();
425 - return new MiniMaxChatModel(miniMaxApi, options, getFunctionCallbackResolver(), DEFAULT_RETRY_TEMPLATE); 450 + return new MiniMaxChatModel(miniMaxApi, options, getToolCallingManager(), DEFAULT_RETRY_TEMPLATE);
426 } 451 }
427 452
428 /** 453 /**
429 - * 可参考 {@link MoonshotAutoConfiguration} 的 moonshotChatModel 方法 454 + * 可参考 {@link MoonshotChatAutoConfiguration} 的 moonshotChatModel 方法
430 */ 455 */
431 private MoonshotChatModel buildMoonshotChatModel(String apiKey, String url) { 456 private MoonshotChatModel buildMoonshotChatModel(String apiKey, String url) {
432 - MoonshotApi moonshotApi = StrUtil.isEmpty(url)? new MoonshotApi(apiKey)  
433 - : new MoonshotApi(url, apiKey); 457 + MoonshotApi.Builder moonshotApiBuilder = MoonshotApi.builder()
  458 + .apiKey(apiKey);
  459 + if (StrUtil.isNotEmpty(url)) {
  460 + moonshotApiBuilder.baseUrl(url);
  461 + }
434 MoonshotChatOptions options = MoonshotChatOptions.builder().model(MoonshotApi.DEFAULT_CHAT_MODEL).build(); 462 MoonshotChatOptions options = MoonshotChatOptions.builder().model(MoonshotApi.DEFAULT_CHAT_MODEL).build();
435 - return new MoonshotChatModel(moonshotApi, options, getFunctionCallbackResolver(), DEFAULT_RETRY_TEMPLATE); 463 + return MoonshotChatModel.builder()
  464 + .moonshotApi(moonshotApiBuilder.build())
  465 + .defaultOptions(options)
  466 + .toolCallingManager(getToolCallingManager())
  467 + .build();
436 } 468 }
437 469
438 /** 470 /**
@@ -456,33 +488,32 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -456,33 +488,32 @@ public class AiModelFactoryImpl implements AiModelFactory {
456 } 488 }
457 489
458 /** 490 /**
459 - * 可参考 {@link OpenAiAutoConfiguration} 的 openAiChatModel 方法 491 + * 可参考 {@link OpenAiChatAutoConfiguration} 的 openAiChatModel 方法
460 */ 492 */
461 private static OpenAiChatModel buildOpenAiChatModel(String openAiToken, String url) { 493 private static OpenAiChatModel buildOpenAiChatModel(String openAiToken, String url) {
462 url = StrUtil.blankToDefault(url, OpenAiApiConstants.DEFAULT_BASE_URL); 494 url = StrUtil.blankToDefault(url, OpenAiApiConstants.DEFAULT_BASE_URL);
463 OpenAiApi openAiApi = OpenAiApi.builder().baseUrl(url).apiKey(openAiToken).build(); 495 OpenAiApi openAiApi = OpenAiApi.builder().baseUrl(url).apiKey(openAiToken).build();
464 - return OpenAiChatModel.builder().openAiApi(openAiApi).toolCallingManager(getToolCallingManager()).build(); 496 + return OpenAiChatModel.builder()
  497 + .openAiApi(openAiApi)
  498 + .toolCallingManager(getToolCallingManager())
  499 + .build();
465 } 500 }
466 501
467 - // TODO @芋艿:手头暂时没密钥,使用建议再测试下  
468 /** 502 /**
469 - * 可参考 {@link AzureOpenAiAutoConfiguration} 503 + * 可参考 {@link AzureOpenAiChatAutoConfiguration}
470 */ 504 */
471 private static AzureOpenAiChatModel buildAzureOpenAiChatModel(String apiKey, String url) { 505 private static AzureOpenAiChatModel buildAzureOpenAiChatModel(String apiKey, String url) {
472 - AzureOpenAiAutoConfiguration azureOpenAiAutoConfiguration = new AzureOpenAiAutoConfiguration();  
473 - // 创建 OpenAIClient 对象  
474 - AzureOpenAiConnectionProperties connectionProperties = new AzureOpenAiConnectionProperties();  
475 - connectionProperties.setApiKey(apiKey);  
476 - connectionProperties.setEndpoint(url);  
477 - OpenAIClientBuilder openAIClient = azureOpenAiAutoConfiguration.openAIClientBuilder(connectionProperties, null);  
478 - // 获取 AzureOpenAiChatProperties 对象  
479 - AzureOpenAiChatProperties chatProperties = SpringUtil.getBean(AzureOpenAiChatProperties.class);  
480 - return azureOpenAiAutoConfiguration.azureOpenAiChatModel(openAIClient, chatProperties,  
481 - getToolCallingManager(), null, null); 506 + // TODO @芋艿:使用前,请测试,暂时没密钥!!!
  507 + OpenAIClientBuilder openAIClientBuilder = new OpenAIClientBuilder()
  508 + .endpoint(url).credential(new KeyCredential(apiKey));
  509 + return AzureOpenAiChatModel.builder()
  510 + .openAIClientBuilder(openAIClientBuilder)
  511 + .toolCallingManager(getToolCallingManager())
  512 + .build();
482 } 513 }
483 514
484 /** 515 /**
485 - * 可参考 {@link OpenAiAutoConfiguration} 的 openAiImageModel 方法 516 + * 可参考 {@link OpenAiImageAutoConfiguration} 的 openAiImageModel 方法
486 */ 517 */
487 private OpenAiImageModel buildOpenAiImageModel(String openAiToken, String url) { 518 private OpenAiImageModel buildOpenAiImageModel(String openAiToken, String url) {
488 url = StrUtil.blankToDefault(url, OpenAiApiConstants.DEFAULT_BASE_URL); 519 url = StrUtil.blankToDefault(url, OpenAiApiConstants.DEFAULT_BASE_URL);
@@ -500,11 +531,14 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -500,11 +531,14 @@ public class AiModelFactoryImpl implements AiModelFactory {
500 } 531 }
501 532
502 /** 533 /**
503 - * 可参考 {@link OllamaAutoConfiguration} 的 ollamaApi 方法 534 + * 可参考 {@link OllamaChatAutoConfiguration} 的 ollamaChatModel 方法
504 */ 535 */
505 private static OllamaChatModel buildOllamaChatModel(String url) { 536 private static OllamaChatModel buildOllamaChatModel(String url) {
506 - OllamaApi ollamaApi = new OllamaApi(url);  
507 - return OllamaChatModel.builder().ollamaApi(ollamaApi).toolCallingManager(getToolCallingManager()).build(); 537 + OllamaApi ollamaApi = OllamaApi.builder().baseUrl(url).build();
  538 + return OllamaChatModel.builder()
  539 + .ollamaApi(ollamaApi)
  540 + .toolCallingManager(getToolCallingManager())
  541 + .build();
508 } 542 }
509 543
510 /** 544 /**
@@ -519,16 +553,16 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -519,16 +553,16 @@ public class AiModelFactoryImpl implements AiModelFactory {
519 // ========== 各种创建 EmbeddingModel 的方法 ========== 553 // ========== 各种创建 EmbeddingModel 的方法 ==========
520 554
521 /** 555 /**
522 - * 可参考 {@link DashScopeAutoConfiguration} 的 dashscopeEmbeddingModel 方法 556 + * 可参考 {@link DashScopeEmbeddingAutoConfiguration} 的 dashscopeEmbeddingModel 方法
523 */ 557 */
524 private DashScopeEmbeddingModel buildTongYiEmbeddingModel(String apiKey, String model) { 558 private DashScopeEmbeddingModel buildTongYiEmbeddingModel(String apiKey, String model) {
525 - DashScopeApi dashScopeApi = new DashScopeApi(apiKey); 559 + DashScopeApi dashScopeApi = DashScopeApi.builder().apiKey(apiKey).build();
526 DashScopeEmbeddingOptions dashScopeEmbeddingOptions = DashScopeEmbeddingOptions.builder().withModel(model).build(); 560 DashScopeEmbeddingOptions dashScopeEmbeddingOptions = DashScopeEmbeddingOptions.builder().withModel(model).build();
527 return new DashScopeEmbeddingModel(dashScopeApi, MetadataMode.EMBED, dashScopeEmbeddingOptions); 561 return new DashScopeEmbeddingModel(dashScopeApi, MetadataMode.EMBED, dashScopeEmbeddingOptions);
528 } 562 }
529 563
530 /** 564 /**
531 - * 可参考 {@link ZhiPuAiAutoConfiguration} 的 zhiPuAiEmbeddingModel 方法 565 + * 可参考 {@link ZhiPuAiEmbeddingAutoConfiguration} 的 zhiPuAiEmbeddingModel 方法
532 */ 566 */
533 private ZhiPuAiEmbeddingModel buildZhiPuEmbeddingModel(String apiKey, String url, String model) { 567 private ZhiPuAiEmbeddingModel buildZhiPuEmbeddingModel(String apiKey, String url, String model) {
534 ZhiPuAiApi zhiPuAiApi = StrUtil.isEmpty(url) ? new ZhiPuAiApi(apiKey) 568 ZhiPuAiApi zhiPuAiApi = StrUtil.isEmpty(url) ? new ZhiPuAiApi(apiKey)
@@ -538,7 +572,7 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -538,7 +572,7 @@ public class AiModelFactoryImpl implements AiModelFactory {
538 } 572 }
539 573
540 /** 574 /**
541 - * 可参考 {@link MiniMaxAutoConfiguration} 的 miniMaxEmbeddingModel 方法 575 + * 可参考 {@link MiniMaxEmbeddingAutoConfiguration} 的 miniMaxEmbeddingModel 方法
542 */ 576 */
543 private EmbeddingModel buildMiniMaxEmbeddingModel(String apiKey, String url, String model) { 577 private EmbeddingModel buildMiniMaxEmbeddingModel(String apiKey, String url, String model) {
544 MiniMaxApi miniMaxApi = StrUtil.isEmpty(url)? new MiniMaxApi(apiKey) 578 MiniMaxApi miniMaxApi = StrUtil.isEmpty(url)? new MiniMaxApi(apiKey)
@@ -548,7 +582,7 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -548,7 +582,7 @@ public class AiModelFactoryImpl implements AiModelFactory {
548 } 582 }
549 583
550 /** 584 /**
551 - * 可参考 {@link QianFanAutoConfiguration} 的 qianFanEmbeddingModel 方法 585 + * 可参考 {@link QianFanEmbeddingAutoConfiguration} 的 qianFanEmbeddingModel 方法
552 */ 586 */
553 private QianFanEmbeddingModel buildYiYanEmbeddingModel(String key, String model) { 587 private QianFanEmbeddingModel buildYiYanEmbeddingModel(String key, String model) {
554 List<String> keys = StrUtil.split(key, '|'); 588 List<String> keys = StrUtil.split(key, '|');
@@ -561,13 +595,16 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -561,13 +595,16 @@ public class AiModelFactoryImpl implements AiModelFactory {
561 } 595 }
562 596
563 private OllamaEmbeddingModel buildOllamaEmbeddingModel(String url, String model) { 597 private OllamaEmbeddingModel buildOllamaEmbeddingModel(String url, String model) {
564 - OllamaApi ollamaApi = new OllamaApi(url); 598 + OllamaApi ollamaApi = OllamaApi.builder().baseUrl(url).build();
565 OllamaOptions ollamaOptions = OllamaOptions.builder().model(model).build(); 599 OllamaOptions ollamaOptions = OllamaOptions.builder().model(model).build();
566 - return OllamaEmbeddingModel.builder().ollamaApi(ollamaApi).defaultOptions(ollamaOptions).build(); 600 + return OllamaEmbeddingModel.builder()
  601 + .ollamaApi(ollamaApi)
  602 + .defaultOptions(ollamaOptions)
  603 + .build();
567 } 604 }
568 605
569 /** 606 /**
570 - * 可参考 {@link OpenAiAutoConfiguration} 的 openAiEmbeddingModel 方法 607 + * 可参考 {@link OpenAiEmbeddingAutoConfiguration} 的 openAiEmbeddingModel 方法
571 */ 608 */
572 private OpenAiEmbeddingModel buildOpenAiEmbeddingModel(String openAiToken, String url, String model) { 609 private OpenAiEmbeddingModel buildOpenAiEmbeddingModel(String openAiToken, String url, String model) {
573 url = StrUtil.blankToDefault(url, OpenAiApiConstants.DEFAULT_BASE_URL); 610 url = StrUtil.blankToDefault(url, OpenAiApiConstants.DEFAULT_BASE_URL);
@@ -576,21 +613,19 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -576,21 +613,19 @@ public class AiModelFactoryImpl implements AiModelFactory {
576 return new OpenAiEmbeddingModel(openAiApi, MetadataMode.EMBED, openAiEmbeddingProperties); 613 return new OpenAiEmbeddingModel(openAiApi, MetadataMode.EMBED, openAiEmbeddingProperties);
577 } 614 }
578 615
579 - // TODO @芋艿:手头暂时没密钥,使用建议再测试下  
580 /** 616 /**
581 - * 可参考 {@link AzureOpenAiAutoConfiguration} 的 azureOpenAiEmbeddingModel 方法 617 + * 可参考 {@link AzureOpenAiEmbeddingAutoConfiguration} 的 azureOpenAiEmbeddingModel 方法
582 */ 618 */
583 private AzureOpenAiEmbeddingModel buildAzureOpenAiEmbeddingModel(String apiKey, String url, String model) { 619 private AzureOpenAiEmbeddingModel buildAzureOpenAiEmbeddingModel(String apiKey, String url, String model) {
584 - AzureOpenAiAutoConfiguration azureOpenAiAutoConfiguration = new AzureOpenAiAutoConfiguration();  
585 - // 创建 OpenAIClient 对象  
586 - AzureOpenAiConnectionProperties connectionProperties = new AzureOpenAiConnectionProperties();  
587 - connectionProperties.setApiKey(apiKey);  
588 - connectionProperties.setEndpoint(url);  
589 - OpenAIClientBuilder openAIClient = azureOpenAiAutoConfiguration.openAIClientBuilder(connectionProperties, null); 620 + // TODO @芋艿:手头暂时没密钥,使用建议再测试下
  621 + AzureOpenAiEmbeddingAutoConfiguration azureOpenAiAutoConfiguration = new AzureOpenAiEmbeddingAutoConfiguration();
  622 + // 创建 OpenAIClientBuilder 对象
  623 + OpenAIClientBuilder openAIClientBuilder = new OpenAIClientBuilder()
  624 + .endpoint(url).credential(new KeyCredential(apiKey));
590 // 获取 AzureOpenAiChatProperties 对象 625 // 获取 AzureOpenAiChatProperties 对象
591 AzureOpenAiEmbeddingProperties embeddingProperties = SpringUtil.getBean(AzureOpenAiEmbeddingProperties.class); 626 AzureOpenAiEmbeddingProperties embeddingProperties = SpringUtil.getBean(AzureOpenAiEmbeddingProperties.class);
592 - return azureOpenAiAutoConfiguration.azureOpenAiEmbeddingModel(openAIClient, embeddingProperties,  
593 - null, null); 627 + return azureOpenAiAutoConfiguration.azureOpenAiEmbeddingModel(openAIClientBuilder, embeddingProperties,
  628 + getObservationRegistry(), getEmbeddingModelObservationConvention());
594 } 629 }
595 630
596 // ========== 各种创建 VectorStore 的方法 ========== 631 // ========== 各种创建 VectorStore 的方法 ==========
@@ -655,12 +690,12 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -655,12 +690,12 @@ public class AiModelFactoryImpl implements AiModelFactory {
655 Map<String, Class<?>> metadataFields) { 690 Map<String, Class<?>> metadataFields) {
656 // 创建 JedisPooled 对象 691 // 创建 JedisPooled 对象
657 RedisProperties redisProperties = SpringUtils.getBean(RedisProperties.class); 692 RedisProperties redisProperties = SpringUtils.getBean(RedisProperties.class);
658 - JedisPooled jedisPooled = new JedisPooled(redisProperties.getHost(), redisProperties.getPort()); 693 + JedisPooled jedisPooled = new JedisPooled(redisProperties.getHost(), redisProperties.getPort(),
  694 + redisProperties.getUsername(), redisProperties.getPassword());
659 // 创建 RedisVectorStoreProperties 对象 695 // 创建 RedisVectorStoreProperties 对象
660 - RedisVectorStoreAutoConfiguration configuration = new RedisVectorStoreAutoConfiguration();  
661 RedisVectorStoreProperties properties = SpringUtil.getBean(RedisVectorStoreProperties.class); 696 RedisVectorStoreProperties properties = SpringUtil.getBean(RedisVectorStoreProperties.class);
662 RedisVectorStore redisVectorStore = RedisVectorStore.builder(jedisPooled, embeddingModel) 697 RedisVectorStore redisVectorStore = RedisVectorStore.builder(jedisPooled, embeddingModel)
663 - .indexName(properties.getIndex()).prefix(properties.getPrefix()) 698 + .indexName(properties.getIndexName()).prefix(properties.getPrefix())
664 .initializeSchema(properties.isInitializeSchema()) 699 .initializeSchema(properties.isInitializeSchema())
665 .metadataFields(convertList(metadataFields.entrySet(), entry -> { 700 .metadataFields(convertList(metadataFields.entrySet(), entry -> {
666 String fieldName = entry.getKey(); 701 String fieldName = entry.getKey();
@@ -730,10 +765,12 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -730,10 +765,12 @@ public class AiModelFactoryImpl implements AiModelFactory {
730 765
731 private static ObjectProvider<VectorStoreObservationConvention> getCustomObservationConvention() { 766 private static ObjectProvider<VectorStoreObservationConvention> getCustomObservationConvention() {
732 return new ObjectProvider<>() { 767 return new ObjectProvider<>() {
  768 +
733 @Override 769 @Override
734 public VectorStoreObservationConvention getObject() throws BeansException { 770 public VectorStoreObservationConvention getObject() throws BeansException {
735 return new DefaultVectorStoreObservationConvention(); 771 return new DefaultVectorStoreObservationConvention();
736 } 772 }
  773 +
737 }; 774 };
738 } 775 }
739 776
@@ -745,8 +782,15 @@ public class AiModelFactoryImpl implements AiModelFactory { @@ -745,8 +782,15 @@ public class AiModelFactoryImpl implements AiModelFactory {
745 return SpringUtil.getBean(ToolCallingManager.class); 782 return SpringUtil.getBean(ToolCallingManager.class);
746 } 783 }
747 784
748 - private static FunctionCallbackResolver getFunctionCallbackResolver() {  
749 - return SpringUtil.getBean(FunctionCallbackResolver.class); 785 + private static ObjectProvider<EmbeddingModelObservationConvention> getEmbeddingModelObservationConvention() {
  786 + return new ObjectProvider<>() {
  787 +
  788 + @Override
  789 + public EmbeddingModelObservationConvention getObject() throws BeansException {
  790 + return SpringUtil.getBean(EmbeddingModelObservationConvention.class);
  791 + }
  792 +
  793 + };
750 } 794 }
751 795
752 } 796 }
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/deepseek/DeepSeekChatModel.java deleted
1 -package cn.iocoder.yudao.module.ai.framework.ai.core.model.deepseek;  
2 -  
3 -import lombok.RequiredArgsConstructor;  
4 -import lombok.extern.slf4j.Slf4j;  
5 -import org.springframework.ai.chat.model.ChatModel;  
6 -import org.springframework.ai.chat.model.ChatResponse;  
7 -import org.springframework.ai.chat.prompt.ChatOptions;  
8 -import org.springframework.ai.chat.prompt.Prompt;  
9 -import org.springframework.ai.openai.OpenAiChatModel;  
10 -import reactor.core.publisher.Flux;  
11 -  
12 -/**  
13 - * DeepSeek {@link ChatModel} 实现类  
14 - *  
15 - * @author fansili  
16 - */  
17 -@Slf4j  
18 -@RequiredArgsConstructor  
19 -public class DeepSeekChatModel implements ChatModel {  
20 -  
21 - public static final String BASE_URL = "https://api.deepseek.com";  
22 -  
23 - public static final String MODEL_DEFAULT = "deepseek-chat";  
24 -  
25 - /**  
26 - * 兼容 OpenAI 接口,进行复用  
27 - */  
28 - private final OpenAiChatModel openAiChatModel;  
29 -  
30 - @Override  
31 - public ChatResponse call(Prompt prompt) {  
32 - return openAiChatModel.call(prompt);  
33 - }  
34 -  
35 - @Override  
36 - public Flux<ChatResponse> stream(Prompt prompt) {  
37 - return openAiChatModel.stream(prompt);  
38 - }  
39 -  
40 - @Override  
41 - public ChatOptions getDefaultOptions() {  
42 - return openAiChatModel.getDefaultOptions();  
43 - }  
44 -  
45 -}  
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 { @@ -89,7 +89,7 @@ public class SiliconFlowImageModel implements ImageModel {
89 var observationContext = ImageModelObservationContext.builder() 89 var observationContext = ImageModelObservationContext.builder()
90 .imagePrompt(imagePrompt) 90 .imagePrompt(imagePrompt)
91 .provider(SiliconFlowApiConstants.PROVIDER_NAME) 91 .provider(SiliconFlowApiConstants.PROVIDER_NAME)
92 - .requestOptions(imagePrompt.getOptions()) 92 + .imagePrompt(imagePrompt)
93 .build(); 93 .build();
94 94
95 return ImageModelObservationDocumentation.IMAGE_MODEL_OPERATION 95 return ImageModelObservationDocumentation.IMAGE_MODEL_OPERATION
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; @@ -9,9 +9,6 @@ import cn.hutool.core.util.ObjUtil;
9 import cn.hutool.core.util.StrUtil; 9 import cn.hutool.core.util.StrUtil;
10 import cn.hutool.extra.spring.SpringUtil; 10 import cn.hutool.extra.spring.SpringUtil;
11 import cn.hutool.http.HttpUtil; 11 import cn.hutool.http.HttpUtil;
12 -import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum;  
13 -import cn.iocoder.yudao.module.ai.framework.ai.core.model.midjourney.api.MidjourneyApi;  
14 -import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowImageOptions;  
15 import cn.iocoder.yudao.framework.common.pojo.PageResult; 12 import cn.iocoder.yudao.framework.common.pojo.PageResult;
16 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 13 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
17 import cn.iocoder.yudao.module.ai.controller.admin.image.vo.AiImageDrawReqVO; 14 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; @@ -24,17 +21,20 @@ import cn.iocoder.yudao.module.ai.dal.dataobject.image.AiImageDO;
24 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO; 21 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
25 import cn.iocoder.yudao.module.ai.dal.mysql.image.AiImageMapper; 22 import cn.iocoder.yudao.module.ai.dal.mysql.image.AiImageMapper;
26 import cn.iocoder.yudao.module.ai.enums.image.AiImageStatusEnum; 23 import cn.iocoder.yudao.module.ai.enums.image.AiImageStatusEnum;
  24 +import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum;
  25 +import cn.iocoder.yudao.module.ai.framework.ai.core.model.midjourney.api.MidjourneyApi;
  26 +import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowImageOptions;
27 import cn.iocoder.yudao.module.ai.service.model.AiModelService; 27 import cn.iocoder.yudao.module.ai.service.model.AiModelService;
28 import cn.iocoder.yudao.module.infra.api.file.FileApi; 28 import cn.iocoder.yudao.module.infra.api.file.FileApi;
29 import com.alibaba.cloud.ai.dashscope.image.DashScopeImageOptions; 29 import com.alibaba.cloud.ai.dashscope.image.DashScopeImageOptions;
30 import jakarta.annotation.Resource; 30 import jakarta.annotation.Resource;
31 import lombok.extern.slf4j.Slf4j; 31 import lombok.extern.slf4j.Slf4j;
  32 +import org.springaicommunity.qianfan.QianFanImageOptions;
32 import org.springframework.ai.image.ImageModel; 33 import org.springframework.ai.image.ImageModel;
33 import org.springframework.ai.image.ImageOptions; 34 import org.springframework.ai.image.ImageOptions;
34 import org.springframework.ai.image.ImagePrompt; 35 import org.springframework.ai.image.ImagePrompt;
35 import org.springframework.ai.image.ImageResponse; 36 import org.springframework.ai.image.ImageResponse;
36 import org.springframework.ai.openai.OpenAiImageOptions; 37 import org.springframework.ai.openai.OpenAiImageOptions;
37 -import org.springframework.ai.qianfan.QianFanImageOptions;  
38 import org.springframework.ai.stabilityai.api.StabilityAiImageOptions; 38 import org.springframework.ai.stabilityai.api.StabilityAiImageOptions;
39 import org.springframework.ai.zhipuai.ZhiPuAiImageOptions; 39 import org.springframework.ai.zhipuai.ZhiPuAiImageOptions;
40 import org.springframework.scheduling.annotation.Async; 40 import org.springframework.scheduling.annotation.Async;
@@ -140,10 +140,10 @@ public class AiImageServiceImpl implements AiImageService { @@ -140,10 +140,10 @@ public class AiImageServiceImpl implements AiImageService {
140 private static ImageOptions buildImageOptions(AiImageDrawReqVO draw, AiModelDO model) { 140 private static ImageOptions buildImageOptions(AiImageDrawReqVO draw, AiModelDO model) {
141 if (ObjUtil.equal(model.getPlatform(), AiPlatformEnum.OPENAI.getPlatform())) { 141 if (ObjUtil.equal(model.getPlatform(), AiPlatformEnum.OPENAI.getPlatform())) {
142 // https://platform.openai.com/docs/api-reference/images/create 142 // https://platform.openai.com/docs/api-reference/images/create
143 - return OpenAiImageOptions.builder().withModel(model.getModel())  
144 - .withHeight(draw.getHeight()).withWidth(draw.getWidth())  
145 - .withStyle(MapUtil.getStr(draw.getOptions(), "style")) // 风格  
146 - .withResponseFormat("b64_json") 143 + return OpenAiImageOptions.builder().model(model.getModel())
  144 + .height(draw.getHeight()).width(draw.getWidth())
  145 + .style(MapUtil.getStr(draw.getOptions(), "style")) // 风格
  146 + .responseFormat("b64_json")
147 .build(); 147 .build();
148 } else if (ObjUtil.equal(model.getPlatform(), AiPlatformEnum.SILICON_FLOW.getPlatform())) { 148 } else if (ObjUtil.equal(model.getPlatform(), AiPlatformEnum.SILICON_FLOW.getPlatform())) {
149 // https://docs.siliconflow.cn/cn/api-reference/images/images-generations 149 // https://docs.siliconflow.cn/cn/api-reference/images/images-generations
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; @@ -7,6 +7,8 @@ import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
7 import cn.iocoder.yudao.module.system.api.user.AdminUserApi; 7 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
8 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; 8 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
9 import com.fasterxml.jackson.annotation.JsonClassDescription; 9 import com.fasterxml.jackson.annotation.JsonClassDescription;
  10 +import com.fasterxml.jackson.annotation.JsonProperty;
  11 +import com.fasterxml.jackson.annotation.JsonPropertyDescription;
10 import jakarta.annotation.Resource; 12 import jakarta.annotation.Resource;
11 import lombok.AllArgsConstructor; 13 import lombok.AllArgsConstructor;
12 import lombok.Data; 14 import lombok.Data;
@@ -17,7 +19,7 @@ import org.springframework.stereotype.Component; @@ -17,7 +19,7 @@ import org.springframework.stereotype.Component;
17 import java.util.function.BiFunction; 19 import java.util.function.BiFunction;
18 20
19 /** 21 /**
20 - * 工具:当前用户信息查询 22 + * 工具:用户信息查询
21 * 23 *
22 * 同时,也是展示 ToolContext 上下文的使用 24 * 同时,也是展示 ToolContext 上下文的使用
23 * 25 *
@@ -31,8 +33,17 @@ public class UserProfileQueryToolFunction @@ -31,8 +33,17 @@ public class UserProfileQueryToolFunction
31 private AdminUserApi adminUserApi; 33 private AdminUserApi adminUserApi;
32 34
33 @Data 35 @Data
34 - @JsonClassDescription("当前用户信息查询")  
35 - public static class Request { } 36 + @JsonClassDescription("用户信息查询")
  37 + public static class Request {
  38 +
  39 + /**
  40 + * 用户编号
  41 + */
  42 + @JsonProperty(value = "id")
  43 + @JsonPropertyDescription("用户编号,例如说:1。如果查询自己,则 id 为空")
  44 + private Long id;
  45 +
  46 + }
36 47
37 @Data 48 @Data
38 @AllArgsConstructor 49 @AllArgsConstructor
@@ -61,13 +72,19 @@ public class UserProfileQueryToolFunction @@ -61,13 +72,19 @@ public class UserProfileQueryToolFunction
61 72
62 @Override 73 @Override
63 public Response apply(Request request, ToolContext toolContext) { 74 public Response apply(Request request, ToolContext toolContext) {
64 - LoginUser loginUser = (LoginUser) toolContext.getContext().get(AiUtils.TOOL_CONTEXT_LOGIN_USER);  
65 Long tenantId = (Long) toolContext.getContext().get(AiUtils.TOOL_CONTEXT_TENANT_ID); 75 Long tenantId = (Long) toolContext.getContext().get(AiUtils.TOOL_CONTEXT_TENANT_ID);
66 - if (loginUser == null | tenantId == null) {  
67 - return null; 76 + if (tenantId == null) {
  77 + return new Response();
  78 + }
  79 + if (request.getId() == null) {
  80 + LoginUser loginUser = (LoginUser) toolContext.getContext().get(AiUtils.TOOL_CONTEXT_LOGIN_USER);
  81 + if (loginUser == null) {
  82 + return new Response();
  83 + }
  84 + request.setId(loginUser.getId());
68 } 85 }
69 return TenantUtils.execute(tenantId, () -> { 86 return TenantUtils.execute(tenantId, () -> {
70 - AdminUserRespDTO user = adminUserApi.getUser(loginUser.getId()); 87 + AdminUserRespDTO user = adminUserApi.getUser(request.getId());
71 return BeanUtils.toBean(user, Response.class); 88 return BeanUtils.toBean(user, Response.class);
72 }); 89 });
73 } 90 }
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; @@ -2,18 +2,18 @@ package cn.iocoder.yudao.module.ai.util;
2 2
3 import cn.hutool.core.util.ObjUtil; 3 import cn.hutool.core.util.ObjUtil;
4 import cn.hutool.core.util.StrUtil; 4 import cn.hutool.core.util.StrUtil;
5 -import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum;  
6 import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; 5 import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
7 import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; 6 import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
  7 +import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum;
8 import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions; 8 import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions;
  9 +import org.springaicommunity.moonshot.MoonshotChatOptions;
  10 +import org.springaicommunity.qianfan.QianFanChatOptions;
9 import org.springframework.ai.azure.openai.AzureOpenAiChatOptions; 11 import org.springframework.ai.azure.openai.AzureOpenAiChatOptions;
10 import org.springframework.ai.chat.messages.*; 12 import org.springframework.ai.chat.messages.*;
11 import org.springframework.ai.chat.prompt.ChatOptions; 13 import org.springframework.ai.chat.prompt.ChatOptions;
12 import org.springframework.ai.minimax.MiniMaxChatOptions; 14 import org.springframework.ai.minimax.MiniMaxChatOptions;
13 -import org.springframework.ai.moonshot.MoonshotChatOptions;  
14 import org.springframework.ai.ollama.api.OllamaOptions; 15 import org.springframework.ai.ollama.api.OllamaOptions;
15 import org.springframework.ai.openai.OpenAiChatOptions; 16 import org.springframework.ai.openai.OpenAiChatOptions;
16 -import org.springframework.ai.qianfan.QianFanChatOptions;  
17 import org.springframework.ai.zhipuai.ZhiPuAiChatOptions; 17 import org.springframework.ai.zhipuai.ZhiPuAiChatOptions;
18 18
19 import java.util.Collections; 19 import java.util.Collections;
@@ -43,18 +43,18 @@ public class AiUtils { @@ -43,18 +43,18 @@ public class AiUtils {
43 switch (platform) { 43 switch (platform) {
44 case TONG_YI: 44 case TONG_YI:
45 return DashScopeChatOptions.builder().withModel(model).withTemperature(temperature).withMaxToken(maxTokens) 45 return DashScopeChatOptions.builder().withModel(model).withTemperature(temperature).withMaxToken(maxTokens)
46 - .withFunctions(toolNames).withToolContext(toolContext).build(); 46 + .withToolNames(toolNames).withToolContext(toolContext).build();
47 case YI_YAN: 47 case YI_YAN:
48 return QianFanChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens).build(); 48 return QianFanChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens).build();
49 case ZHI_PU: 49 case ZHI_PU:
50 return ZhiPuAiChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens) 50 return ZhiPuAiChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens)
51 - .functions(toolNames).toolContext(toolContext).build(); 51 + .toolNames(toolNames).toolContext(toolContext).build();
52 case MINI_MAX: 52 case MINI_MAX:
53 return MiniMaxChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens) 53 return MiniMaxChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens)
54 - .functions(toolNames).toolContext(toolContext).build(); 54 + .toolNames(toolNames).toolContext(toolContext).build();
55 case MOONSHOT: 55 case MOONSHOT:
56 return MoonshotChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens) 56 return MoonshotChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens)
57 - .functions(toolNames).toolContext(toolContext).build(); 57 + .toolNames(toolNames).toolContext(toolContext).build();
58 case OPENAI: 58 case OPENAI:
59 case DEEP_SEEK: // 复用 OpenAI 客户端 59 case DEEP_SEEK: // 复用 OpenAI 客户端
60 case DOU_BAO: // 复用 OpenAI 客户端 60 case DOU_BAO: // 复用 OpenAI 客户端
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; @@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
2 2
3 import com.azure.ai.openai.OpenAIClientBuilder; 3 import com.azure.ai.openai.OpenAIClientBuilder;
4 import com.azure.core.credential.AzureKeyCredential; 4 import com.azure.core.credential.AzureKeyCredential;
5 -import com.azure.core.util.ClientOptions;  
6 import org.junit.jupiter.api.Disabled; 5 import org.junit.jupiter.api.Disabled;
7 import org.junit.jupiter.api.Test; 6 import org.junit.jupiter.api.Test;
8 import org.springframework.ai.azure.openai.AzureOpenAiChatModel; 7 import org.springframework.ai.azure.openai.AzureOpenAiChatModel;
@@ -17,7 +16,7 @@ import reactor.core.publisher.Flux; @@ -17,7 +16,7 @@ import reactor.core.publisher.Flux;
17 import java.util.ArrayList; 16 import java.util.ArrayList;
18 import java.util.List; 17 import java.util.List;
19 18
20 -import static org.springframework.ai.autoconfigure.azure.openai.AzureOpenAiChatProperties.DEFAULT_DEPLOYMENT_NAME; 19 +import static org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiChatProperties.DEFAULT_DEPLOYMENT_NAME;
21 20
22 /** 21 /**
23 * {@link AzureOpenAiChatModel} 集成测试 22 * {@link AzureOpenAiChatModel} 集成测试
@@ -29,10 +28,13 @@ public class AzureOpenAIChatModelTests { @@ -29,10 +28,13 @@ public class AzureOpenAIChatModelTests {
29 // TODO @芋艿:晚点在调整 28 // TODO @芋艿:晚点在调整
30 private final OpenAIClientBuilder openAiApi = new OpenAIClientBuilder() 29 private final OpenAIClientBuilder openAiApi = new OpenAIClientBuilder()
31 .endpoint("https://eastusprejade.openai.azure.com") 30 .endpoint("https://eastusprejade.openai.azure.com")
32 - .credential(new AzureKeyCredential("xxx"))  
33 - .clientOptions((new ClientOptions()).setApplicationId("spring-ai"));  
34 - private final AzureOpenAiChatModel chatModel = new AzureOpenAiChatModel(openAiApi,  
35 - AzureOpenAiChatOptions.builder().deploymentName(DEFAULT_DEPLOYMENT_NAME).build()); 31 + .credential(new AzureKeyCredential("xxx"));
  32 + private final AzureOpenAiChatModel chatModel = AzureOpenAiChatModel.builder()
  33 + .openAIClientBuilder(openAiApi)
  34 + .defaultOptions(AzureOpenAiChatOptions.builder()
  35 + .deploymentName(DEFAULT_DEPLOYMENT_NAME)
  36 + .build())
  37 + .build();
36 38
37 @Test 39 @Test
38 @Disabled 40 @Disabled
yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/BaiChuanChatModelTests.java
1 package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat; 1 package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
2 2
3 import cn.iocoder.yudao.module.ai.framework.ai.core.model.baichuan.BaiChuanChatModel; 3 import cn.iocoder.yudao.module.ai.framework.ai.core.model.baichuan.BaiChuanChatModel;
4 -import cn.iocoder.yudao.module.ai.framework.ai.core.model.deepseek.DeepSeekChatModel;  
5 import org.junit.jupiter.api.Disabled; 4 import org.junit.jupiter.api.Disabled;
6 import org.junit.jupiter.api.Test; 5 import org.junit.jupiter.api.Test;
7 import org.springframework.ai.chat.messages.Message; 6 import org.springframework.ai.chat.messages.Message;
@@ -35,7 +34,7 @@ public class BaiChuanChatModelTests { @@ -35,7 +34,7 @@ public class BaiChuanChatModelTests {
35 .build()) 34 .build())
36 .build(); 35 .build();
37 36
38 - private final DeepSeekChatModel chatModel = new DeepSeekChatModel(openAiChatModel); 37 + private final BaiChuanChatModel chatModel = new BaiChuanChatModel(openAiChatModel);
39 38
40 @Test 39 @Test
41 @Disabled 40 @Disabled
yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/DeepSeekChatModelTests.java
1 package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat; 1 package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
2 2
3 -import cn.iocoder.yudao.module.ai.framework.ai.core.model.deepseek.DeepSeekChatModel;  
4 import org.junit.jupiter.api.Disabled; 3 import org.junit.jupiter.api.Disabled;
5 import org.junit.jupiter.api.Test; 4 import org.junit.jupiter.api.Test;
6 import org.springframework.ai.chat.messages.Message; 5 import org.springframework.ai.chat.messages.Message;
@@ -8,9 +7,9 @@ import org.springframework.ai.chat.messages.SystemMessage; @@ -8,9 +7,9 @@ import org.springframework.ai.chat.messages.SystemMessage;
8 import org.springframework.ai.chat.messages.UserMessage; 7 import org.springframework.ai.chat.messages.UserMessage;
9 import org.springframework.ai.chat.model.ChatResponse; 8 import org.springframework.ai.chat.model.ChatResponse;
10 import org.springframework.ai.chat.prompt.Prompt; 9 import org.springframework.ai.chat.prompt.Prompt;
11 -import org.springframework.ai.openai.OpenAiChatModel;  
12 -import org.springframework.ai.openai.OpenAiChatOptions;  
13 -import org.springframework.ai.openai.api.OpenAiApi; 10 +import org.springframework.ai.deepseek.DeepSeekChatModel;
  11 +import org.springframework.ai.deepseek.DeepSeekChatOptions;
  12 +import org.springframework.ai.deepseek.api.DeepSeekApi;
14 import reactor.core.publisher.Flux; 13 import reactor.core.publisher.Flux;
15 14
16 import java.util.ArrayList; 15 import java.util.ArrayList;
@@ -23,19 +22,16 @@ import java.util.List; @@ -23,19 +22,16 @@ import java.util.List;
23 */ 22 */
24 public class DeepSeekChatModelTests { 23 public class DeepSeekChatModelTests {
25 24
26 - private final OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()  
27 - .openAiApi(OpenAiApi.builder()  
28 - .baseUrl(DeepSeekChatModel.BASE_URL)  
29 - .apiKey("sk-e52047409b144d97b791a6a46a2d") // apiKey 25 + private final DeepSeekChatModel chatModel = DeepSeekChatModel.builder()
  26 + .deepSeekApi(DeepSeekApi.builder()
  27 + .apiKey("sk-eaf4172a057344dd9bc64b1f806b6axx") // apiKey
30 .build()) 28 .build())
31 - .defaultOptions(OpenAiChatOptions.builder() 29 + .defaultOptions(DeepSeekChatOptions.builder()
32 .model("deepseek-chat") // 模型 30 .model("deepseek-chat") // 模型
33 .temperature(0.7) 31 .temperature(0.7)
34 .build()) 32 .build())
35 .build(); 33 .build();
36 34
37 - private final DeepSeekChatModel chatModel = new DeepSeekChatModel(openAiChatModel);  
38 -  
39 @Test 35 @Test
40 @Disabled 36 @Disabled
41 public void testCall() { 37 public void testCall() {
yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/LlamaChatModelTests.java
1 package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat; 1 package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
2 2
3 -import org.junit.jupiter.api.Disabled;  
4 -import org.junit.jupiter.api.Test;  
5 -import org.springframework.ai.chat.messages.Message;  
6 -import org.springframework.ai.chat.messages.SystemMessage;  
7 -import org.springframework.ai.chat.messages.UserMessage;  
8 -import org.springframework.ai.chat.model.ChatResponse;  
9 -import org.springframework.ai.chat.prompt.Prompt;  
10 import org.springframework.ai.ollama.OllamaChatModel; 3 import org.springframework.ai.ollama.OllamaChatModel;
11 -import org.springframework.ai.ollama.api.OllamaApi;  
12 -import org.springframework.ai.ollama.api.OllamaModel;  
13 -import org.springframework.ai.ollama.api.OllamaOptions;  
14 -import reactor.core.publisher.Flux;  
15 -  
16 -import java.util.ArrayList;  
17 -import java.util.List;  
18 4
19 /** 5 /**
20 * {@link OllamaChatModel} 集成测试 6 * {@link OllamaChatModel} 集成测试
@@ -23,43 +9,43 @@ import java.util.List; @@ -23,43 +9,43 @@ import java.util.List;
23 */ 9 */
24 public class LlamaChatModelTests { 10 public class LlamaChatModelTests {
25 11
26 - private final OllamaChatModel chatModel = OllamaChatModel.builder()  
27 - .ollamaApi(new OllamaApi("http://127.0.0.1:11434")) // Ollama 服务地址  
28 - .defaultOptions(OllamaOptions.builder()  
29 - .model(OllamaModel.LLAMA3.getName()) // 模型  
30 - .build())  
31 - .build();  
32 -  
33 - @Test  
34 - @Disabled  
35 - public void testCall() {  
36 - // 准备参数  
37 - List<Message> messages = new ArrayList<>();  
38 - messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));  
39 - messages.add(new UserMessage("1 + 1 = ?"));  
40 -  
41 - // 调用  
42 - ChatResponse response = chatModel.call(new Prompt(messages));  
43 - // 打印结果  
44 - System.out.println(response);  
45 - System.out.println(response.getResult().getOutput());  
46 - }  
47 -  
48 - @Test  
49 - @Disabled  
50 - public void testStream() {  
51 - // 准备参数  
52 - List<Message> messages = new ArrayList<>();  
53 - messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));  
54 - messages.add(new UserMessage("1 + 1 = ?"));  
55 -  
56 - // 调用  
57 - Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages));  
58 - // 打印结果  
59 - flux.doOnNext(response -> {  
60 -// System.out.println(response);  
61 - System.out.println(response.getResult().getOutput());  
62 - }).then().block();  
63 - } 12 +// private final OllamaChatModel chatModel = OllamaChatModel.builder()
  13 +// .ollamaApi(new OllamaApi("http://127.0.0.1:11434")) // Ollama 服务地址
  14 +// .defaultOptions(OllamaOptions.builder()
  15 +// .model(OllamaModel.LLAMA3.getName()) // 模型
  16 +// .build())
  17 +// .build();
  18 +//
  19 +// @Test
  20 +// @Disabled
  21 +// public void testCall() {
  22 +// // 准备参数
  23 +// List<Message> messages = new ArrayList<>();
  24 +// messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
  25 +// messages.add(new UserMessage("1 + 1 = ?"));
  26 +//
  27 +// // 调用
  28 +// ChatResponse response = chatModel.call(new Prompt(messages));
  29 +// // 打印结果
  30 +// System.out.println(response);
  31 +// System.out.println(response.getResult().getOutput());
  32 +// }
  33 +//
  34 +// @Test
  35 +// @Disabled
  36 +// public void testStream() {
  37 +// // 准备参数
  38 +// List<Message> messages = new ArrayList<>();
  39 +// messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
  40 +// messages.add(new UserMessage("1 + 1 = ?"));
  41 +//
  42 +// // 调用
  43 +// Flux<ChatResponse> flux = chatModel.stream(new Prompt(messages));
  44 +// // 打印结果
  45 +// flux.doOnNext(response -> {
  46 +//// System.out.println(response);
  47 +// System.out.println(response.getResult().getOutput());
  48 +// }).then().block();
  49 +// }
64 50
65 } 51 }
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; @@ -2,14 +2,14 @@ package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
2 2
3 import org.junit.jupiter.api.Disabled; 3 import org.junit.jupiter.api.Disabled;
4 import org.junit.jupiter.api.Test; 4 import org.junit.jupiter.api.Test;
  5 +import org.springaicommunity.moonshot.MoonshotChatModel;
  6 +import org.springaicommunity.moonshot.MoonshotChatOptions;
  7 +import org.springaicommunity.moonshot.api.MoonshotApi;
5 import org.springframework.ai.chat.messages.Message; 8 import org.springframework.ai.chat.messages.Message;
6 import org.springframework.ai.chat.messages.SystemMessage; 9 import org.springframework.ai.chat.messages.SystemMessage;
7 import org.springframework.ai.chat.messages.UserMessage; 10 import org.springframework.ai.chat.messages.UserMessage;
8 import org.springframework.ai.chat.model.ChatResponse; 11 import org.springframework.ai.chat.model.ChatResponse;
9 import org.springframework.ai.chat.prompt.Prompt; 12 import org.springframework.ai.chat.prompt.Prompt;
10 -import org.springframework.ai.moonshot.MoonshotChatModel;  
11 -import org.springframework.ai.moonshot.MoonshotChatOptions;  
12 -import org.springframework.ai.moonshot.api.MoonshotApi;  
13 import reactor.core.publisher.Flux; 13 import reactor.core.publisher.Flux;
14 14
15 import java.util.ArrayList; 15 import java.util.ArrayList;
@@ -22,11 +22,15 @@ import java.util.List; @@ -22,11 +22,15 @@ import java.util.List;
22 */ 22 */
23 public class MoonshotChatModelTests { 23 public class MoonshotChatModelTests {
24 24
25 - private final MoonshotChatModel chatModel = new MoonshotChatModel(  
26 - new MoonshotApi("sk-aHYYV1SARscItye5QQRRNbXij4fy65Ee7pNZlC9gsSQnUKXA"), // 密钥  
27 - MoonshotChatOptions.builder()  
28 - .model("moonshot-v1-8k") // 模型  
29 - .build()); 25 + private final MoonshotChatModel chatModel = MoonshotChatModel.builder()
  26 + .moonshotApi(MoonshotApi.builder()
  27 + .apiKey("sk-aHYYV1SARscItye5QQRRNbXij4fy65Ee7pNZlC9gsSQnUKXA") // 密钥
  28 + .build())
  29 + .defaultOptions(MoonshotChatOptions.builder()
  30 + .model("kimi-k2-0711-preview") // 模型
  31 + .build())
  32 + .build();
  33 +
30 @Test 34 @Test
31 @Disabled 35 @Disabled
32 public void testCall() { 36 public void testCall() {
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; @@ -23,7 +23,9 @@ import java.util.List;
23 public class OllamaChatModelTests { 23 public class OllamaChatModelTests {
24 24
25 private final OllamaChatModel chatModel = OllamaChatModel.builder() 25 private final OllamaChatModel chatModel = OllamaChatModel.builder()
26 - .ollamaApi(new OllamaApi("http://127.0.0.1:11434")) // Ollama 服务地址 26 + .ollamaApi(OllamaApi.builder()
  27 + .baseUrl("http://127.0.0.1:11434") // Ollama 服务地址
  28 + .build())
27 .defaultOptions(OllamaOptions.builder() 29 .defaultOptions(OllamaOptions.builder()
28 // .model("qwen") // 模型(https://ollama.com/library/qwen) 30 // .model("qwen") // 模型(https://ollama.com/library/qwen)
29 .model("deepseek-r1") // 模型(https://ollama.com/library/deepseek-r1) 31 .model("deepseek-r1") // 模型(https://ollama.com/library/deepseek-r1)
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 { @@ -25,10 +25,10 @@ public class OpenAIChatModelTests {
25 private final OpenAiChatModel chatModel = OpenAiChatModel.builder() 25 private final OpenAiChatModel chatModel = OpenAiChatModel.builder()
26 .openAiApi(OpenAiApi.builder() 26 .openAiApi(OpenAiApi.builder()
27 .baseUrl("https://api.holdai.top") 27 .baseUrl("https://api.holdai.top")
28 - .apiKey("sk-aN6nWn3fILjrgLFT0fC4Aa60B72e4253826c77B29dC94f17") // apiKey 28 + .apiKey("sk-PytRecQlmjEteoa2RRN6cGnwslo72UUPLQVNEMS6K9yjbmpD") // apiKey
29 .build()) 29 .build())
30 .defaultOptions(OpenAiChatOptions.builder() 30 .defaultOptions(OpenAiChatOptions.builder()
31 - .model(OpenAiApi.ChatModel.GPT_4_O) // 模型 31 + .model(OpenAiApi.ChatModel.GPT_4_1_NANO) // 模型
32 .temperature(0.7) 32 .temperature(0.7)
33 .build()) 33 .build())
34 .build(); 34 .build();
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; @@ -22,14 +22,17 @@ import java.util.List;
22 */ 22 */
23 public class TongYiChatModelTests { 23 public class TongYiChatModelTests {
24 24
25 - private final DashScopeChatModel chatModel = new DashScopeChatModel(  
26 - new DashScopeApi("sk-7d903764249848cfa912733146da12d1"),  
27 - DashScopeChatOptions.builder() 25 + private final DashScopeChatModel chatModel = DashScopeChatModel.builder()
  26 + .dashScopeApi(DashScopeApi.builder()
  27 + .apiKey("sk-47aa124781be4bfb95244cc62f63f7d0")
  28 + .build())
  29 + .defaultOptions( DashScopeChatOptions.builder()
28 .withModel("qwen1.5-72b-chat") // 模型 30 .withModel("qwen1.5-72b-chat") // 模型
29 // .withModel("deepseek-r1") // 模型(deepseek-r1) 31 // .withModel("deepseek-r1") // 模型(deepseek-r1)
30 // .withModel("deepseek-v3") // 模型(deepseek-v3) 32 // .withModel("deepseek-v3") // 模型(deepseek-v3)
31 // .withModel("deepseek-r1-distill-qwen-1.5b") // 模型(deepseek-r1-distill-qwen-1.5b) 33 // .withModel("deepseek-r1-distill-qwen-1.5b") // 模型(deepseek-r1-distill-qwen-1.5b)
32 - .build()); 34 + .build())
  35 + .build();
33 36
34 @Test 37 @Test
35 @Disabled 38 @Disabled
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; @@ -2,13 +2,13 @@ package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
2 2
3 import org.junit.jupiter.api.Disabled; 3 import org.junit.jupiter.api.Disabled;
4 import org.junit.jupiter.api.Test; 4 import org.junit.jupiter.api.Test;
  5 +import org.springaicommunity.qianfan.QianFanChatModel;
  6 +import org.springaicommunity.qianfan.QianFanChatOptions;
  7 +import org.springaicommunity.qianfan.api.QianFanApi;
5 import org.springframework.ai.chat.messages.Message; 8 import org.springframework.ai.chat.messages.Message;
6 import org.springframework.ai.chat.messages.UserMessage; 9 import org.springframework.ai.chat.messages.UserMessage;
7 import org.springframework.ai.chat.model.ChatResponse; 10 import org.springframework.ai.chat.model.ChatResponse;
8 import org.springframework.ai.chat.prompt.Prompt; 11 import org.springframework.ai.chat.prompt.Prompt;
9 -import org.springframework.ai.qianfan.QianFanChatModel;  
10 -import org.springframework.ai.qianfan.QianFanChatOptions;  
11 -import org.springframework.ai.qianfan.api.QianFanApi;  
12 import reactor.core.publisher.Flux; 12 import reactor.core.publisher.Flux;
13 13
14 import java.util.ArrayList; 14 import java.util.ArrayList;
@@ -23,9 +23,9 @@ import java.util.List; @@ -23,9 +23,9 @@ import java.util.List;
23 public class YiYanChatModelTests { 23 public class YiYanChatModelTests {
24 24
25 private final QianFanChatModel chatModel = new QianFanChatModel( 25 private final QianFanChatModel chatModel = new QianFanChatModel(
26 - new QianFanApi("qS8k8dYr2nXunagK4SSU8Xjj", "pHGbx51ql2f0hOyabQvSZezahVC3hh3e"), // 密钥 26 + new QianFanApi("DGnyzREuaY7av7c38bOM9Ji2", "9aR8myflEOPDrEeLhoXv0FdqANOAyIZW"), // 密钥
27 QianFanChatOptions.builder() 27 QianFanChatOptions.builder()
28 - .model(QianFanApi.ChatModel.ERNIE_4_0_8K_Preview.getValue()) 28 + .model("ERNIE-4.5-8K-Preview")
29 .build() 29 .build()
30 ); 30 );
31 31
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 { @@ -18,7 +18,7 @@ public class OpenAiImageModelTests {
18 18
19 private final OpenAiImageModel imageModel = new OpenAiImageModel(OpenAiImageApi.builder() 19 private final OpenAiImageModel imageModel = new OpenAiImageModel(OpenAiImageApi.builder()
20 .baseUrl("https://api.holdai.top") // apiKey 20 .baseUrl("https://api.holdai.top") // apiKey
21 - .apiKey("sk-aN6nWn3fILjrgLFT0fC4Aa60B72e4253826c77B29dC94f17") 21 + .apiKey("sk-PytRecQlmjEteoa2RRN6cGnwslo72UUPLQVNEMS6K9yjbmpD")
22 .build()); 22 .build());
23 23
24 @Test 24 @Test
@@ -26,8 +26,8 @@ public class OpenAiImageModelTests { @@ -26,8 +26,8 @@ public class OpenAiImageModelTests {
26 public void testCall() { 26 public void testCall() {
27 // 准备参数 27 // 准备参数
28 ImageOptions options = OpenAiImageOptions.builder() 28 ImageOptions options = OpenAiImageOptions.builder()
29 - .withModel(OpenAiImageApi.ImageModel.DALL_E_2.getValue()) // 这个模型比较便宜  
30 - .withHeight(256).withWidth(256) 29 + .model(OpenAiImageApi.ImageModel.DALL_E_2.getValue()) // 这个模型比较便宜
  30 + .height(256).width(256)
31 .build(); 31 .build();
32 ImagePrompt prompt = new ImagePrompt("中国长城!", options); 32 ImagePrompt prompt = new ImagePrompt("中国长城!", options);
33 33
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; @@ -2,11 +2,11 @@ package cn.iocoder.yudao.module.ai.framework.ai.core.model.image;
2 2
3 import org.junit.jupiter.api.Disabled; 3 import org.junit.jupiter.api.Disabled;
4 import org.junit.jupiter.api.Test; 4 import org.junit.jupiter.api.Test;
  5 +import org.springaicommunity.qianfan.QianFanImageModel;
  6 +import org.springaicommunity.qianfan.QianFanImageOptions;
  7 +import org.springaicommunity.qianfan.api.QianFanImageApi;
5 import org.springframework.ai.image.ImagePrompt; 8 import org.springframework.ai.image.ImagePrompt;
6 import org.springframework.ai.image.ImageResponse; 9 import org.springframework.ai.image.ImageResponse;
7 -import org.springframework.ai.qianfan.QianFanImageModel;  
8 -import org.springframework.ai.qianfan.QianFanImageOptions;  
9 -import org.springframework.ai.qianfan.api.QianFanImageApi;  
10 10
11 import static cn.iocoder.yudao.module.ai.framework.ai.core.model.image.StabilityAiImageModelTests.viewImage; 11 import static cn.iocoder.yudao.module.ai.framework.ai.core.model.image.StabilityAiImageModelTests.viewImage;
12 12
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 { @@ -31,8 +31,8 @@ public class StabilityAiImageModelTests {
31 public void testCall() { 31 public void testCall() {
32 // 准备参数 32 // 准备参数
33 ImageOptions options = OpenAiImageOptions.builder() 33 ImageOptions options = OpenAiImageOptions.builder()
34 - .withModel("stable-diffusion-v1-6")  
35 - .withHeight(320).withWidth(320) 34 + .model("stable-diffusion-v1-6")
  35 + .height(320).width(320)
36 .build(); 36 .build();
37 ImagePrompt prompt = new ImagePrompt("great wall", options); 37 ImagePrompt prompt = new ImagePrompt("great wall", options);
38 38
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/expression/BpmProcessExpressionRespVO.java
1 package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.expression; 1 package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.expression;
2 2
3 -import com.alibaba.excel.annotation.ExcelProperty; 3 +import cn.idev.excel.annotation.ExcelProperty;
4 import io.swagger.v3.oas.annotations.media.Schema; 4 import io.swagger.v3.oas.annotations.media.Schema;
5 import lombok.Data; 5 import lombok.Data;
6 6
@@ -27,4 +27,4 @@ public class BpmProcessExpressionRespVO { @@ -27,4 +27,4 @@ public class BpmProcessExpressionRespVO {
27 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) 27 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
28 private LocalDateTime createTime; 28 private LocalDateTime createTime;
29 29
30 -}  
31 \ No newline at end of file 30 \ No newline at end of file
  31 +}
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 { @@ -71,6 +71,9 @@ public class BpmSimpleModelNodeVO {
71 @Schema(description = "是否填写审批意见", example = "false") 71 @Schema(description = "是否填写审批意见", example = "false")
72 private Boolean reasonRequire; 72 private Boolean reasonRequire;
73 73
  74 + @Schema(description = "跳过表达式", example = "{true}")
  75 + private String skipExpression; // 用于审批节点
  76 +
74 /** 77 /**
75 * 审批节点拒绝处理 78 * 审批节点拒绝处理
76 */ 79 */
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 { @@ -73,7 +73,7 @@ public class BpmApprovalDetailRespVO {
73 private List<UserSimpleBaseVO> candidateUsers; // 只包含未生成 ApprovalTaskInfo 的用户列表 73 private List<UserSimpleBaseVO> candidateUsers; // 只包含未生成 ApprovalTaskInfo 的用户列表
74 74
75 @Schema(description = "流程编号", example = "8761d8e0-0922-11f0-bd37-00ff1db677bf") 75 @Schema(description = "流程编号", example = "8761d8e0-0922-11f0-bd37-00ff1db677bf")
76 - private String processInstanceId; // 当且仅当,该节点是子流程节点时,才会有值(CallActivity 的 processInstanceId 字段) 76 + private String processInstanceId; // 当且仅当,该节点是子流程节点时,才会有值(CallActivity 的 calledProcessInstanceId 字段)
77 77
78 } 78 }
79 79
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java
@@ -40,7 +40,7 @@ public interface ErrorCodeConstants { @@ -40,7 +40,7 @@ public interface ErrorCodeConstants {
40 ErrorCode PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS = new ErrorCode(1_009_004_004, "任务({})的候选人({})不存在"); 40 ErrorCode PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS = new ErrorCode(1_009_004_004, "任务({})的候选人({})不存在");
41 ErrorCode PROCESS_INSTANCE_START_USER_CAN_START = new ErrorCode(1_009_004_005, "发起流程失败,你没有权限发起该流程"); 41 ErrorCode PROCESS_INSTANCE_START_USER_CAN_START = new ErrorCode(1_009_004_005, "发起流程失败,你没有权限发起该流程");
42 ErrorCode PROCESS_INSTANCE_CANCEL_FAIL_NOT_ALLOW = new ErrorCode(1_009_004_005, "流程取消失败,该流程不允许取消"); 42 ErrorCode PROCESS_INSTANCE_CANCEL_FAIL_NOT_ALLOW = new ErrorCode(1_009_004_005, "流程取消失败,该流程不允许取消");
43 - ErrorCode PROCESS_INSTANCE_HTTP_TRIGGER_CALL_ERROR = new ErrorCode(1_009_004_006, "流程 Http 触发器请求调用失败"); 43 + ErrorCode PROCESS_INSTANCE_HTTP_CALL_ERROR = new ErrorCode(1_009_004_006, "流程 Http 请求调用失败");
44 ErrorCode PROCESS_INSTANCE_APPROVE_USER_SELECT_ASSIGNEES_NOT_CONFIG = new ErrorCode(1_009_004_007, "下一个任务({})的审批人未配置"); 44 ErrorCode PROCESS_INSTANCE_APPROVE_USER_SELECT_ASSIGNEES_NOT_CONFIG = new ErrorCode(1_009_004_007, "下一个任务({})的审批人未配置");
45 ErrorCode PROCESS_INSTANCE_CANCEL_CHILD_FAIL_NOT_ALLOW = new ErrorCode(1_009_004_008, "子流程取消失败,子流程不允许取消"); 45 ErrorCode PROCESS_INSTANCE_CANCEL_CHILD_FAIL_NOT_ALLOW = new ErrorCode(1_009_004_008, "子流程取消失败,子流程不允许取消");
46 46
@@ -58,7 +58,6 @@ public interface ErrorCodeConstants { @@ -58,7 +58,6 @@ public interface ErrorCodeConstants {
58 ErrorCode TASK_SIGN_DELETE_NO_PARENT = new ErrorCode(1_009_005_012, "任务减签失败,被减签的任务必须是通过加签生成的任务"); 58 ErrorCode TASK_SIGN_DELETE_NO_PARENT = new ErrorCode(1_009_005_012, "任务减签失败,被减签的任务必须是通过加签生成的任务");
59 ErrorCode TASK_TRANSFER_FAIL_USER_REPEAT = new ErrorCode(1_009_005_013, "任务转办失败,转办人和当前审批人为同一人"); 59 ErrorCode TASK_TRANSFER_FAIL_USER_REPEAT = new ErrorCode(1_009_005_013, "任务转办失败,转办人和当前审批人为同一人");
60 ErrorCode TASK_TRANSFER_FAIL_USER_NOT_EXISTS = new ErrorCode(1_009_005_014, "任务转办失败,转办人不存在"); 60 ErrorCode TASK_TRANSFER_FAIL_USER_NOT_EXISTS = new ErrorCode(1_009_005_014, "任务转办失败,转办人不存在");
61 - ErrorCode TASK_CREATE_FAIL_NO_CANDIDATE_USER = new ErrorCode(1_009_006_003, "操作失败,原因:找不到任务的审批人!");  
62 ErrorCode TASK_SIGNATURE_NOT_EXISTS = new ErrorCode(1_009_005_015, "签名不能为空!"); 61 ErrorCode TASK_SIGNATURE_NOT_EXISTS = new ErrorCode(1_009_005_015, "签名不能为空!");
63 ErrorCode TASK_REASON_REQUIRE = new ErrorCode(1_009_005_016, "审批意见不能为空!"); 62 ErrorCode TASK_REASON_REQUIRE = new ErrorCode(1_009_005_016, "审批意见不能为空!");
64 63
@@ -74,6 +73,7 @@ public interface ErrorCodeConstants { @@ -74,6 +73,7 @@ public interface ErrorCodeConstants {
74 ErrorCode CATEGORY_NOT_EXISTS = new ErrorCode(1_009_012_000, "流程分类不存在"); 73 ErrorCode CATEGORY_NOT_EXISTS = new ErrorCode(1_009_012_000, "流程分类不存在");
75 ErrorCode CATEGORY_NAME_DUPLICATE = new ErrorCode(1_009_012_001, "流程分类名字【{}】重复"); 74 ErrorCode CATEGORY_NAME_DUPLICATE = new ErrorCode(1_009_012_001, "流程分类名字【{}】重复");
76 ErrorCode CATEGORY_CODE_DUPLICATE = new ErrorCode(1_009_012_002, "流程分类编码【{}】重复"); 75 ErrorCode CATEGORY_CODE_DUPLICATE = new ErrorCode(1_009_012_002, "流程分类编码【{}】重复");
  76 + ErrorCode CATEGORY_DELETE_FAIL_MODEL_USED = new ErrorCode(1_009_012_003, "删除失败,流程分类【{}】已被流程模型使用,请先删除对应的流程模型");
77 77
78 // ========== BPM 流程监听器 1-009-013-000 ========== 78 // ========== BPM 流程监听器 1-009-013-000 ==========
79 ErrorCode PROCESS_LISTENER_NOT_EXISTS = new ErrorCode(1_009_013_000, "流程监听器不存在"); 79 ErrorCode PROCESS_LISTENER_NOT_EXISTS = new ErrorCode(1_009_013_000, "流程监听器不存在");
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeTypeEnum.java
@@ -35,7 +35,7 @@ public enum BpmSimpleModelNodeTypeEnum implements ArrayValuable&lt;Integer&gt; { @@ -35,7 +35,7 @@ public enum BpmSimpleModelNodeTypeEnum implements ArrayValuable&lt;Integer&gt; {
35 // 50 ~ 条件分支 35 // 50 ~ 条件分支
36 CONDITION_NODE(50, "条件", "sequenceFlow"), // 用于构建流转条件的表达式 36 CONDITION_NODE(50, "条件", "sequenceFlow"), // 用于构建流转条件的表达式
37 CONDITION_BRANCH_NODE(51, "条件分支", "exclusiveGateway"), 37 CONDITION_BRANCH_NODE(51, "条件分支", "exclusiveGateway"),
38 - PARALLEL_BRANCH_NODE(52, "并行分支", "parallelGateway"), 38 + PARALLEL_BRANCH_NODE(52, "并行分支", "inclusiveGateway"), // 并行分支使用包容网关实现,条件表达式结果设置为 true
39 INCLUSIVE_BRANCH_NODE(53, "包容分支", "inclusiveGateway"), 39 INCLUSIVE_BRANCH_NODE(53, "包容分支", "inclusiveGateway"),
40 ROUTER_BRANCH_NODE(54, "路由分支", "exclusiveGateway") 40 ROUTER_BRANCH_NODE(54, "路由分支", "exclusiveGateway")
41 ; 41 ;
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmTaskStatusEnum.java
@@ -14,6 +14,7 @@ import lombok.Getter; @@ -14,6 +14,7 @@ import lombok.Getter;
14 @AllArgsConstructor 14 @AllArgsConstructor
15 public enum BpmTaskStatusEnum { 15 public enum BpmTaskStatusEnum {
16 16
  17 + SKIP(-2, "跳过"),
17 NOT_START(-1, "未开始"), 18 NOT_START(-1, "未开始"),
18 RUNNING(1, "审批中"), 19 RUNNING(1, "审批中"),
19 APPROVE(2, "审批通过"), 20 APPROVE(2, "审批通过"),
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; @@ -18,10 +18,7 @@ import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
18 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; 18 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
19 import com.google.common.annotations.VisibleForTesting; 19 import com.google.common.annotations.VisibleForTesting;
20 import lombok.extern.slf4j.Slf4j; 20 import lombok.extern.slf4j.Slf4j;
21 -import org.flowable.bpmn.model.BpmnModel;  
22 -import org.flowable.bpmn.model.CallActivity;  
23 -import org.flowable.bpmn.model.FlowElement;  
24 -import org.flowable.bpmn.model.UserTask; 21 +import org.flowable.bpmn.model.*;
25 import org.flowable.engine.delegate.DelegateExecution; 22 import org.flowable.engine.delegate.DelegateExecution;
26 import org.flowable.engine.runtime.ProcessInstance; 23 import org.flowable.engine.runtime.ProcessInstance;
27 24
@@ -132,7 +129,7 @@ public class BpmTaskCandidateInvoker { @@ -132,7 +129,7 @@ public class BpmTaskCandidateInvoker {
132 Long startUserId, String processDefinitionId, Map<String, Object> processVariables) { 129 Long startUserId, String processDefinitionId, Map<String, Object> processVariables) {
133 // 如果是 CallActivity 子流程,不进行计算候选人 130 // 如果是 CallActivity 子流程,不进行计算候选人
134 FlowElement flowElement = BpmnModelUtils.getFlowElementById(bpmnModel, activityId); 131 FlowElement flowElement = BpmnModelUtils.getFlowElementById(bpmnModel, activityId);
135 - if (flowElement instanceof CallActivity) { 132 + if (flowElement instanceof CallActivity || flowElement instanceof SubProcess) {
136 return new HashSet<>(); 133 return new HashSet<>();
137 } 134 }
138 // 审批类型非人工审核时,不进行计算候选人。原因是:后续会自动通过、不通过 135 // 审批类型非人工审核时,不进行计算候选人。原因是:后续会自动通过、不通过
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; @@ -24,6 +24,7 @@ import static java.util.Collections.emptySet;
24 * @author 芋道源码 24 * @author 芋道源码
25 */ 25 */
26 @Component 26 @Component
  27 +@Deprecated // 仅仅是表达式的示例,建议使用 BpmTaskCandidateStartUserDeptLeaderStrategy 替代
27 public class BpmTaskAssignLeaderExpression { 28 public class BpmTaskAssignLeaderExpression {
28 29
29 @Resource 30 @Resource
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; @@ -16,6 +16,7 @@ import java.util.Set;
16 * @author 芋道源码 16 * @author 芋道源码
17 */ 17 */
18 @Component 18 @Component
  19 +@Deprecated // 仅仅是表达式的示例,建议使用 BpmTaskCandidateStartUserStrategy 替代
19 public class BpmTaskAssignStartUserExpression { 20 public class BpmTaskAssignStartUserExpression {
20 21
21 @Resource 22 @Resource
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; @@ -8,6 +8,7 @@ import com.google.common.collect.Sets;
8 import lombok.extern.slf4j.Slf4j; 8 import lombok.extern.slf4j.Slf4j;
9 import org.flowable.bpmn.model.BpmnModel; 9 import org.flowable.bpmn.model.BpmnModel;
10 import org.flowable.common.engine.api.FlowableException; 10 import org.flowable.common.engine.api.FlowableException;
  11 +import org.flowable.common.engine.impl.javax.el.PropertyNotFoundException;
11 import org.flowable.engine.delegate.DelegateExecution; 12 import org.flowable.engine.delegate.DelegateExecution;
12 import org.springframework.stereotype.Component; 13 import org.springframework.stereotype.Component;
13 14
@@ -48,10 +49,12 @@ public class BpmTaskCandidateExpressionStrategy implements BpmTaskCandidateStrat @@ -48,10 +49,12 @@ public class BpmTaskCandidateExpressionStrategy implements BpmTaskCandidateStrat
48 Object result = FlowableUtils.getExpressionValue(variables, param); 49 Object result = FlowableUtils.getExpressionValue(variables, param);
49 return CollectionUtils.toLinkedHashSet(Long.class, result); 50 return CollectionUtils.toLinkedHashSet(Long.class, result);
50 } catch (FlowableException ex) { 51 } catch (FlowableException ex) {
51 - // 预测未运行的节点时候,表达式如果包含 execution 或者不存在的流程变量会抛异常,  
52 - log.warn("[calculateUsersByActivity][表达式({}) 变量({}) 解析报错", param, variables, ex);  
53 - // 不能预测候选人,返回空列表, 避免流程无法进行  
54 - return Sets.newHashSet(); 52 + // 预测未运行的节点时候,表达式如果包含 execution 或者不存在的流程变量会抛异常,此时忽略该异常!相当于说,不做流程预测!!!
  53 + if (ex.getCause() != null && ex.getCause() instanceof PropertyNotFoundException) {
  54 + return Sets.newHashSet();
  55 + }
  56 + log.error("[calculateUsersByActivity][表达式({}) 变量({}) 解析报错", param, variables, ex);
  57 + throw ex;
55 } 58 }
56 } 59 }
57 60
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/listener/BpmProcessInstanceEventListener.java
1 package cn.iocoder.yudao.module.bpm.framework.flowable.core.listener; 1 package cn.iocoder.yudao.module.bpm.framework.flowable.core.listener;
2 2
  3 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
3 import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; 4 import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
4 import com.google.common.collect.ImmutableSet; 5 import com.google.common.collect.ImmutableSet;
5 import jakarta.annotation.Resource; 6 import jakarta.annotation.Resource;
@@ -37,18 +38,26 @@ public class BpmProcessInstanceEventListener extends AbstractFlowableEngineEvent @@ -37,18 +38,26 @@ public class BpmProcessInstanceEventListener extends AbstractFlowableEngineEvent
37 38
38 @Override 39 @Override
39 protected void processCreated(FlowableEngineEntityEvent event) { 40 protected void processCreated(FlowableEngineEntityEvent event) {
40 - processInstanceService.processProcessInstanceCreated((ProcessInstance)event.getEntity()); 41 + ProcessInstance processInstance = (ProcessInstance) event.getEntity();
  42 + FlowableUtils.execute(processInstance.getTenantId(),
  43 + () -> processInstanceService.processProcessInstanceCreated(processInstance));
41 } 44 }
42 45
43 @Override 46 @Override
44 protected void processCompleted(FlowableEngineEntityEvent event) { 47 protected void processCompleted(FlowableEngineEntityEvent event) {
45 - processInstanceService.processProcessInstanceCompleted((ProcessInstance)event.getEntity()); 48 + ProcessInstance processInstance = (ProcessInstance) event.getEntity();
  49 + FlowableUtils.execute(processInstance.getTenantId(),
  50 + () -> processInstanceService.processProcessInstanceCompleted(processInstance));
46 } 51 }
47 52
48 - @Override // 特殊情况:当跳转到 EndEvent 流程实例未结束, 会执行 deleteProcessInstance 方法 53 + @Override
49 protected void processCancelled(FlowableCancelledEvent event) { 54 protected void processCancelled(FlowableCancelledEvent event) {
  55 + // 特殊情况:当跳转到 EndEvent 流程实例未结束, 会执行 deleteProcessInstance 方法
50 ProcessInstance processInstance = processInstanceService.getProcessInstance(event.getProcessInstanceId()); 56 ProcessInstance processInstance = processInstanceService.getProcessInstance(event.getProcessInstanceId());
51 - processInstanceService.processProcessInstanceCompleted(processInstance); 57 + if (processInstance != null) {
  58 + FlowableUtils.execute(processInstance.getTenantId(),
  59 + () -> processInstanceService.processProcessInstanceCompleted(processInstance));
  60 + }
52 } 61 }
53 62
54 } 63 }
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; @@ -3,10 +3,12 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.listener;
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.util.ObjectUtil; 4 import cn.hutool.core.util.ObjectUtil;
5 import cn.hutool.core.util.StrUtil; 5 import cn.hutool.core.util.StrUtil;
  6 +import cn.hutool.json.JSONObject;
6 import cn.iocoder.yudao.framework.common.util.number.NumberUtils; 7 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
7 import cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventTypeEnum; 8 import cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventTypeEnum;
8 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; 9 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
9 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; 10 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
  11 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
10 import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService; 12 import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService;
11 import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService; 13 import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService;
12 import com.google.common.collect.ImmutableSet; 14 import com.google.common.collect.ImmutableSet;
@@ -58,17 +60,20 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener { @@ -58,17 +60,20 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener {
58 60
59 @Override 61 @Override
60 protected void taskCreated(FlowableEngineEntityEvent event) { 62 protected void taskCreated(FlowableEngineEntityEvent event) {
61 - taskService.processTaskCreated((Task) event.getEntity()); 63 + Task entity = (Task) event.getEntity();
  64 + FlowableUtils.execute(entity.getTenantId(), () -> taskService.processTaskCreated(entity));
62 } 65 }
63 66
64 @Override 67 @Override
65 protected void taskAssigned(FlowableEngineEntityEvent event) { 68 protected void taskAssigned(FlowableEngineEntityEvent event) {
66 - taskService.processTaskAssigned((Task) event.getEntity()); 69 + Task entity = (Task) event.getEntity();
  70 + FlowableUtils.execute(entity.getTenantId(), () -> taskService.processTaskAssigned(entity));
67 } 71 }
68 72
69 @Override 73 @Override
70 protected void taskCompleted(FlowableEngineEntityEvent event) { 74 protected void taskCompleted(FlowableEngineEntityEvent event) {
71 - taskService.processTaskCompleted((Task) event.getEntity()); 75 + Task entity = (Task) event.getEntity();
  76 + FlowableUtils.execute(entity.getTenantId(), () -> taskService.processTaskCompleted(entity));
72 } 77 }
73 78
74 @Override 79 @Override
@@ -94,6 +99,23 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener { @@ -94,6 +99,23 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener {
94 String processDefinitionId = event.getProcessDefinitionId(); 99 String processDefinitionId = event.getProcessDefinitionId();
95 BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processDefinitionId); 100 BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processDefinitionId);
96 Job entity = (Job) event.getEntity(); 101 Job entity = (Job) event.getEntity();
  102 + // 特殊 from https://t.zsxq.com/h6oWr :当 elementId 为空时,尝试从 JobHandlerConfiguration 中解析 JSON 获取
  103 + String elementId = entity.getElementId();
  104 + if (elementId == null && entity.getJobHandlerConfiguration() != null) {
  105 + try {
  106 + String handlerConfig = entity.getJobHandlerConfiguration();
  107 + if (handlerConfig.startsWith("{") && handlerConfig.contains("activityId")) {
  108 + elementId = new JSONObject(handlerConfig).getStr("activityId");
  109 + }
  110 + } catch (Exception e) {
  111 + log.error("[timerFired][解析 entity({}) 失败]", entity, e);
  112 + return;
  113 + }
  114 + }
  115 + if (elementId == null) {
  116 + log.error("[timerFired][解析 entity({}) elementId 为空,跳过处理]", entity);
  117 + return;
  118 + }
97 FlowElement element = BpmnModelUtils.getFlowElementById(bpmnModel, entity.getElementId()); 119 FlowElement element = BpmnModelUtils.getFlowElementById(bpmnModel, entity.getElementId());
98 if (!(element instanceof BoundaryEvent)) { 120 if (!(element instanceof BoundaryEvent)) {
99 return; 121 return;
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; @@ -6,15 +6,15 @@ import cn.iocoder.yudao.framework.common.core.KeyValue;
6 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 6 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
7 import cn.iocoder.yudao.framework.common.util.json.JsonUtils; 7 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
8 import cn.iocoder.yudao.framework.common.util.spring.SpringUtils; 8 import cn.iocoder.yudao.framework.common.util.spring.SpringUtils;
  9 +import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
  10 +import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEvent;
9 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; 11 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
10 import cn.iocoder.yudao.module.bpm.enums.definition.BpmHttpRequestParamTypeEnum; 12 import cn.iocoder.yudao.module.bpm.enums.definition.BpmHttpRequestParamTypeEnum;
11 import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; 13 import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
12 import com.fasterxml.jackson.core.type.TypeReference; 14 import com.fasterxml.jackson.core.type.TypeReference;
13 import lombok.extern.slf4j.Slf4j; 15 import lombok.extern.slf4j.Slf4j;
14 import org.flowable.engine.runtime.ProcessInstance; 16 import org.flowable.engine.runtime.ProcessInstance;
15 -import org.springframework.http.HttpEntity;  
16 -import org.springframework.http.HttpMethod;  
17 -import org.springframework.http.ResponseEntity; 17 +import org.springframework.http.*;
18 import org.springframework.util.LinkedMultiValueMap; 18 import org.springframework.util.LinkedMultiValueMap;
19 import org.springframework.util.MultiValueMap; 19 import org.springframework.util.MultiValueMap;
20 import org.springframework.web.client.RestClientException; 20 import org.springframework.web.client.RestClientException;
@@ -26,7 +26,7 @@ import java.util.Map; @@ -26,7 +26,7 @@ import java.util.Map;
26 26
27 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 27 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
28 import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.HEADER_TENANT_ID; 28 import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.HEADER_TENANT_ID;
29 -import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.PROCESS_INSTANCE_HTTP_TRIGGER_CALL_ERROR; 29 +import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.PROCESS_INSTANCE_HTTP_CALL_ERROR;
30 30
31 /** 31 /**
32 * 工作流发起 HTTP 请求工具类 32 * 工作流发起 HTTP 请求工具类
@@ -42,7 +42,6 @@ public class BpmHttpRequestUtils { @@ -42,7 +42,6 @@ public class BpmHttpRequestUtils {
42 List<BpmSimpleModelNodeVO.HttpRequestParam> bodyParams, 42 List<BpmSimpleModelNodeVO.HttpRequestParam> bodyParams,
43 Boolean handleResponse, 43 Boolean handleResponse,
44 List<KeyValue<String, String>> response) { 44 List<KeyValue<String, String>> response) {
45 - RestTemplate restTemplate = SpringUtils.getBean(RestTemplate.class);  
46 BpmProcessInstanceService processInstanceService = SpringUtils.getBean(BpmProcessInstanceService.class); 45 BpmProcessInstanceService processInstanceService = SpringUtils.getBean(BpmProcessInstanceService.class);
47 46
48 // 1.1 设置请求头 47 // 1.1 设置请求头
@@ -51,6 +50,7 @@ public class BpmHttpRequestUtils { @@ -51,6 +50,7 @@ public class BpmHttpRequestUtils {
51 MultiValueMap<String, String> body = buildHttpBody(processInstance, bodyParams); 50 MultiValueMap<String, String> body = buildHttpBody(processInstance, bodyParams);
52 51
53 // 2. 发起请求 52 // 2. 发起请求
  53 + RestTemplate restTemplate = SpringUtils.getBean(RestTemplate.class);
54 ResponseEntity<String> responseEntity = sendHttpRequest(url, headers, body, restTemplate); 54 ResponseEntity<String> responseEntity = sendHttpRequest(url, headers, body, restTemplate);
55 55
56 // 3. 处理返回 56 // 3. 处理返回
@@ -78,27 +78,55 @@ public class BpmHttpRequestUtils { @@ -78,27 +78,55 @@ public class BpmHttpRequestUtils {
78 } 78 }
79 } 79 }
80 80
  81 + public static void executeBpmHttpRequest(BpmProcessInstanceStatusEvent event,
  82 + String url) {
  83 + // 1.1 设置请求头
  84 + HttpHeaders headers = new HttpHeaders();
  85 + headers.setContentType(MediaType.APPLICATION_JSON);
  86 + if (TenantContextHolder.getTenantId() != null) {
  87 + headers.add(HEADER_TENANT_ID, String.valueOf(TenantContextHolder.getTenantId()));
  88 + } else {
  89 + BpmProcessInstanceService processInstanceService = SpringUtils.getBean(BpmProcessInstanceService.class);
  90 + ProcessInstance processInstance = processInstanceService.getProcessInstance(event.getId());
  91 + if (processInstance != null) {
  92 + headers.add(HEADER_TENANT_ID, String.valueOf(TenantContextHolder.getTenantId()));
  93 + }
  94 + }
  95 + // 1.2 设置请求体
  96 +// MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
  97 +// body.add("id", event.getId());
  98 +// body.add("processDefinitionKey", event.getProcessDefinitionKey());
  99 +// body.add("status", event.getStatus().toString());
  100 +// if (StrUtil.isNotEmpty(event.getBusinessKey())) {
  101 +// body.add("businessKey", event.getBusinessKey());
  102 +// }
  103 +
  104 + // 2. 发起请求
  105 + RestTemplate restTemplate = SpringUtils.getBean(RestTemplate.class);
  106 + sendHttpRequest(url, headers, event, restTemplate);
  107 + }
  108 +
81 public static ResponseEntity<String> sendHttpRequest(String url, 109 public static ResponseEntity<String> sendHttpRequest(String url,
82 MultiValueMap<String, String> headers, 110 MultiValueMap<String, String> headers,
83 - MultiValueMap<String, String> body, 111 + Object body,
84 RestTemplate restTemplate) { 112 RestTemplate restTemplate) {
85 - HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(body, headers); 113 + HttpEntity<Object> requestEntity = new HttpEntity<>(body, headers);
86 ResponseEntity<String> responseEntity; 114 ResponseEntity<String> responseEntity;
87 try { 115 try {
88 responseEntity = restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class); 116 responseEntity = restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class);
89 - log.info("[sendHttpRequest][HTTP 触发器,请求头:{},请求体:{},响应结果:{}]", headers, body, responseEntity); 117 + log.info("[sendHttpRequest][HTTP 请求,请求头:{},请求体:{},响应结果:{}]", headers, body, responseEntity);
90 } catch (RestClientException e) { 118 } catch (RestClientException e) {
91 - log.error("[sendHttpRequest][HTTP 触发器,请求头:{},请求体:{},请求出错:{}]", headers, body, e.getMessage());  
92 - throw exception(PROCESS_INSTANCE_HTTP_TRIGGER_CALL_ERROR); 119 + log.error("[sendHttpRequest][HTTP 请求,请求头:{},请求体:{},请求出错:{}]", headers, body, e.getMessage());
  120 + throw exception(PROCESS_INSTANCE_HTTP_CALL_ERROR);
93 } 121 }
94 return responseEntity; 122 return responseEntity;
95 } 123 }
96 124
97 public static MultiValueMap<String, String> buildHttpHeaders(ProcessInstance processInstance, 125 public static MultiValueMap<String, String> buildHttpHeaders(ProcessInstance processInstance,
98 List<BpmSimpleModelNodeVO.HttpRequestParam> headerSettings) { 126 List<BpmSimpleModelNodeVO.HttpRequestParam> headerSettings) {
99 - Map<String, Object> processVariables = processInstance.getProcessVariables();  
100 MultiValueMap<String, String> headers = new LinkedMultiValueMap<>(); 127 MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
101 headers.add(HEADER_TENANT_ID, processInstance.getTenantId()); 128 headers.add(HEADER_TENANT_ID, processInstance.getTenantId());
  129 + Map<String, Object> processVariables = processInstance.getProcessVariables();
102 addHttpRequestParam(headers, headerSettings, processVariables); 130 addHttpRequestParam(headers, headerSettings, processVariables);
103 return headers; 131 return headers;
104 } 132 }
@@ -108,7 +136,9 @@ public class BpmHttpRequestUtils { @@ -108,7 +136,9 @@ public class BpmHttpRequestUtils {
108 Map<String, Object> processVariables = processInstance.getProcessVariables(); 136 Map<String, Object> processVariables = processInstance.getProcessVariables();
109 MultiValueMap<String, String> body = new LinkedMultiValueMap<>(); 137 MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
110 addHttpRequestParam(body, bodySettings, processVariables); 138 addHttpRequestParam(body, bodySettings, processVariables);
111 - body.add("processInstanceId", processInstance.getId()); 139 + if (!body.containsKey("processInstanceId")) { // 避免重复添加
  140 + body.add("processInstanceId", processInstance.getId());
  141 + }
112 return body; 142 return body;
113 } 143 }
114 144
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java
@@ -478,7 +478,11 @@ public class BpmnModelUtils { @@ -478,7 +478,11 @@ public class BpmnModelUtils {
478 */ 478 */
479 public static FlowElement getFlowElementById(BpmnModel model, String flowElementId) { 479 public static FlowElement getFlowElementById(BpmnModel model, String flowElementId) {
480 Process process = model.getMainProcess(); 480 Process process = model.getMainProcess();
481 - return process.getFlowElement(flowElementId); 481 + FlowElement flowElement = process.getFlowElement(flowElementId);
  482 + if (flowElement != null) {
  483 + return flowElement;
  484 + }
  485 + return model.getFlowElement(flowElementId);
482 } 486 }
483 487
484 /** 488 /**
@@ -796,9 +800,10 @@ public class BpmnModelUtils { @@ -796,9 +800,10 @@ public class BpmnModelUtils {
796 || currentElement instanceof EndEvent 800 || currentElement instanceof EndEvent
797 || currentElement instanceof UserTask 801 || currentElement instanceof UserTask
798 || currentElement instanceof ServiceTask) { 802 || currentElement instanceof ServiceTask) {
799 - // 添加元素 803 + // 添加节点
800 FlowNode flowNode = (FlowNode) currentElement; 804 FlowNode flowNode = (FlowNode) currentElement;
801 resultElements.add(flowNode); 805 resultElements.add(flowNode);
  806 +
802 // 遍历子节点 807 // 遍历子节点
803 flowNode.getOutgoingFlows().forEach( 808 flowNode.getOutgoingFlows().forEach(
804 nextElement -> simulateNextFlowElements(nextElement.getTargetFlowElement(), variables, resultElements, visitElements)); 809 nextElement -> simulateNextFlowElements(nextElement.getTargetFlowElement(), variables, resultElements, visitElements));
@@ -832,6 +837,31 @@ public class BpmnModelUtils { @@ -832,6 +837,31 @@ public class BpmnModelUtils {
832 } 837 }
833 838
834 /** 839 /**
  840 + * 判断是否跳过此节点
  841 + *
  842 + * @param flowNode 节点
  843 + * @param variables 流程变量
  844 + */
  845 + public static boolean isSkipNode(FlowElement flowNode, Map<String, Object> variables) {
  846 + // 1. 检查节点是否有跳过表达式(支持多种任务节点类型)
  847 + String skipExpression = null;
  848 + if (flowNode instanceof UserTask) {
  849 + skipExpression = ((UserTask) flowNode).getSkipExpression();
  850 + } else if (flowNode instanceof ServiceTask) {
  851 + skipExpression = ((ServiceTask) flowNode).getSkipExpression();
  852 + } else if (flowNode instanceof ScriptTask) {
  853 + skipExpression = ((ScriptTask) flowNode).getSkipExpression();
  854 + }
  855 +
  856 + if (StrUtil.isEmpty(skipExpression)) {
  857 + return false;
  858 + }
  859 +
  860 + // 2. 计算跳过表达式的值
  861 + return evalConditionExpress(variables, skipExpression);
  862 + }
  863 +
  864 + /**
835 * 根据当前节点,获取下一个节点 865 * 根据当前节点,获取下一个节点
836 * 866 *
837 * @param currentElement 当前节点 867 * @param currentElement 当前节点
@@ -993,7 +1023,7 @@ public class BpmnModelUtils { @@ -993,7 +1023,7 @@ public class BpmnModelUtils {
993 * @return 是否满足条件 1023 * @return 是否满足条件
994 */ 1024 */
995 public static boolean evalConditionExpress(Map<String, Object> variables, String expression) { 1025 public static boolean evalConditionExpress(Map<String, Object> variables, String expression) {
996 - if (expression == null) { 1026 + if (StrUtil.isEmpty(expression)) {
997 return Boolean.FALSE; 1027 return Boolean.FALSE;
998 } 1028 }
999 // 如果 variables 为空,则创建一个的原因?可能 expression 的计算,不依赖于 variables 1029 // 如果 variables 为空,则创建一个的原因?可能 expression 的计算,不依赖于 variables
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java
@@ -4,7 +4,6 @@ import cn.hutool.core.collection.CollUtil; @@ -4,7 +4,6 @@ import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.lang.Assert; 4 import cn.hutool.core.lang.Assert;
5 import cn.hutool.core.map.MapUtil; 5 import cn.hutool.core.map.MapUtil;
6 import cn.hutool.core.util.*; 6 import cn.hutool.core.util.*;
7 -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;  
8 import cn.iocoder.yudao.framework.common.util.json.JsonUtils; 7 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
9 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; 8 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
10 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.ConditionGroups; 9 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.ConditionGroups;
@@ -17,13 +16,14 @@ import cn.iocoder.yudao.module.bpm.service.task.listener.BpmCallActivityListener @@ -17,13 +16,14 @@ import cn.iocoder.yudao.module.bpm.service.task.listener.BpmCallActivityListener
17 import cn.iocoder.yudao.module.bpm.service.task.listener.BpmUserTaskListener; 16 import cn.iocoder.yudao.module.bpm.service.task.listener.BpmUserTaskListener;
18 import org.flowable.bpmn.BpmnAutoLayout; 17 import org.flowable.bpmn.BpmnAutoLayout;
19 import org.flowable.bpmn.constants.BpmnXMLConstants; 18 import org.flowable.bpmn.constants.BpmnXMLConstants;
20 -import org.flowable.bpmn.model.Process;  
21 import org.flowable.bpmn.model.*; 19 import org.flowable.bpmn.model.*;
  20 +import org.flowable.bpmn.model.Process;
22 import org.flowable.engine.delegate.ExecutionListener; 21 import org.flowable.engine.delegate.ExecutionListener;
23 import org.flowable.engine.delegate.TaskListener; 22 import org.flowable.engine.delegate.TaskListener;
24 23
25 import java.util.*; 24 import java.util.*;
26 25
  26 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
27 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*; 27 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*;
28 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils.*; 28 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils.*;
29 import static java.util.Arrays.asList; 29 import static java.util.Arrays.asList;
@@ -239,13 +239,13 @@ public class SimpleModelUtils { @@ -239,13 +239,13 @@ public class SimpleModelUtils {
239 // 3.1 分支有后续节点。即分支 1: A->B->C->D 的情况 239 // 3.1 分支有后续节点。即分支 1: A->B->C->D 的情况
240 if (isValidNode(conditionChildNode)) { 240 if (isValidNode(conditionChildNode)) {
241 // 3.1.1 建立与后续的节点的连线。例如说,建立 A->B 的连线 241 // 3.1.1 建立与后续的节点的连线。例如说,建立 A->B 的连线
242 - SequenceFlow sequenceFlow = ConditionNodeConvert.buildSequenceFlow(node.getId(), conditionChildNode.getId(), item); 242 + SequenceFlow sequenceFlow = ConditionNodeConvert.buildSequenceFlow(node.getId(), conditionChildNode.getId(), nodeType, item);
243 process.addFlowElement(sequenceFlow); 243 process.addFlowElement(sequenceFlow);
244 // 3.1.2 递归调用后续节点连线。例如说,建立 B->C->D 的连线 244 // 3.1.2 递归调用后续节点连线。例如说,建立 B->C->D 的连线
245 traverseNodeToBuildSequenceFlow(process, conditionChildNode, branchEndNodeId); 245 traverseNodeToBuildSequenceFlow(process, conditionChildNode, branchEndNodeId);
246 } else { 246 } else {
247 // 3.2 分支没有后续节点。例如说,建立 A->D 的连线 247 // 3.2 分支没有后续节点。例如说,建立 A->D 的连线
248 - SequenceFlow sequenceFlow = ConditionNodeConvert.buildSequenceFlow(node.getId(), branchEndNodeId, item); 248 + SequenceFlow sequenceFlow = ConditionNodeConvert.buildSequenceFlow(node.getId(), branchEndNodeId, nodeType, item);
249 process.addFlowElement(sequenceFlow); 249 process.addFlowElement(sequenceFlow);
250 } 250 }
251 } 251 }
@@ -464,6 +464,10 @@ public class SimpleModelUtils { @@ -464,6 +464,10 @@ public class SimpleModelUtils {
464 addReasonRequire(node.getReasonRequire(), userTask); 464 addReasonRequire(node.getReasonRequire(), userTask);
465 // 节点类型 465 // 节点类型
466 addNodeType(node.getType(), userTask); 466 addNodeType(node.getType(), userTask);
  467 + // 添加跳过表达式
  468 + if (StrUtil.isNotEmpty(node.getSkipExpression())) {
  469 + userTask.setSkipExpression(node.getSkipExpression());
  470 + }
467 return userTask; 471 return userTask;
468 } 472 }
469 473
@@ -591,17 +595,23 @@ public class SimpleModelUtils { @@ -591,17 +595,23 @@ public class SimpleModelUtils {
591 595
592 private static class ParallelBranchNodeConvert implements NodeConvert { 596 private static class ParallelBranchNodeConvert implements NodeConvert {
593 597
  598 + /**
  599 + * 并行分支使用包容网关。需要设置所有出口条件表达式的值为 true 。原因是,解决 https://t.zsxq.com/m6GXh 反馈问题
  600 + *
  601 + * @see {@link ConditionNodeConvert#buildSequenceFlow}
  602 + */
594 @Override 603 @Override
595 - public List<ParallelGateway> convertList(BpmSimpleModelNodeVO node) {  
596 - ParallelGateway parallelGateway = new ParallelGateway();  
597 - parallelGateway.setId(node.getId()); 604 + public List<InclusiveGateway> convertList(BpmSimpleModelNodeVO node) {
  605 +
  606 + InclusiveGateway inclusiveGateway = new InclusiveGateway();
  607 + inclusiveGateway.setId(node.getId());
598 // TODO @jason:setName 608 // TODO @jason:setName
599 609
600 - // 并行聚合网关由程序创建,前端不需要传入  
601 - ParallelGateway joinParallelGateway = new ParallelGateway(); 610 + // 合并网关 由程序创建,前端不需要传入
  611 + InclusiveGateway joinParallelGateway = new InclusiveGateway();
602 joinParallelGateway.setId(buildGatewayJoinId(node.getId())); 612 joinParallelGateway.setId(buildGatewayJoinId(node.getId()));
603 // TODO @jason:setName 613 // TODO @jason:setName
604 - return CollUtil.newArrayList(parallelGateway, joinParallelGateway); 614 + return CollUtil.newArrayList(inclusiveGateway, joinParallelGateway);
605 } 615 }
606 616
607 @Override 617 @Override
@@ -652,8 +662,14 @@ public class SimpleModelUtils { @@ -652,8 +662,14 @@ public class SimpleModelUtils {
652 } 662 }
653 663
654 public static SequenceFlow buildSequenceFlow(String sourceId, String targetId, 664 public static SequenceFlow buildSequenceFlow(String sourceId, String targetId,
655 - BpmSimpleModelNodeVO node) {  
656 - String conditionExpression = buildConditionExpression(node.getConditionSetting()); 665 + BpmSimpleModelNodeTypeEnum nodeType, BpmSimpleModelNodeVO node) {
  666 + String conditionExpression;
  667 + // 并行分支,使用包容网关实现,强制设置条件表达式为 true
  668 + if (BpmSimpleModelNodeTypeEnum.PARALLEL_BRANCH_NODE == nodeType) {
  669 + conditionExpression ="${true}";
  670 + } else {
  671 + conditionExpression = buildConditionExpression(node.getConditionSetting());
  672 + }
657 return buildBpmnSequenceFlow(sourceId, targetId, node.getId(), node.getName(), conditionExpression); 673 return buildBpmnSequenceFlow(sourceId, targetId, node.getId(), node.getName(), conditionExpression);
658 } 674 }
659 } 675 }
@@ -662,7 +678,6 @@ public class SimpleModelUtils { @@ -662,7 +678,6 @@ public class SimpleModelUtils {
662 * 构造条件表达式 678 * 构造条件表达式
663 */ 679 */
664 public static String buildConditionExpression(BpmSimpleModelNodeVO.ConditionSetting conditionSetting) { 680 public static String buildConditionExpression(BpmSimpleModelNodeVO.ConditionSetting conditionSetting) {
665 - // 并行网关不需要设置条件  
666 if (conditionSetting == null) { 681 if (conditionSetting == null) {
667 return null; 682 return null;
668 } 683 }
@@ -684,15 +699,18 @@ public class SimpleModelUtils { @@ -684,15 +699,18 @@ public class SimpleModelUtils {
684 if (conditionGroups == null || CollUtil.isEmpty(conditionGroups.getConditions())) { 699 if (conditionGroups == null || CollUtil.isEmpty(conditionGroups.getConditions())) {
685 return null; 700 return null;
686 } 701 }
687 - List<String> strConditionGroups = CollectionUtils.convertList(conditionGroups.getConditions(), item -> { 702 + List<String> strConditionGroups = convertList(conditionGroups.getConditions(), item -> {
688 if (CollUtil.isEmpty(item.getRules())) { 703 if (CollUtil.isEmpty(item.getRules())) {
689 return ""; 704 return "";
690 } 705 }
691 // 构造规则表达式 706 // 构造规则表达式
692 - List<String> list = CollectionUtils.convertList(item.getRules(), (rule) -> { 707 + List<String> list = convertList(item.getRules(), (rule) -> {
693 String rightSide = NumberUtil.isNumber(rule.getRightSide()) ? rule.getRightSide() 708 String rightSide = NumberUtil.isNumber(rule.getRightSide()) ? rule.getRightSide()
694 : "\"" + rule.getRightSide() + "\""; // 如果非数值类型加引号 709 : "\"" + rule.getRightSide() + "\""; // 如果非数值类型加引号
695 - return String.format(" %s %s var:convertByType(%s,%s)", rule.getLeftSide(), rule.getOpCode(), rule.getLeftSide(), rightSide); 710 + return String.format(" vars:getOrDefault(%s, null) %s var:convertByType(%s,%s) ",
  711 + rule.getLeftSide(), // 左侧:读取变量
  712 + rule.getOpCode(), // 中间:操作符,比较
  713 + rule.getLeftSide(), rightSide); // 右侧:转换变量,VariableConvertByTypeExpressionFunction
696 }); 714 });
697 // 构造条件组的表达式 715 // 构造条件组的表达式
698 Boolean and = item.getAnd(); 716 Boolean and = item.getAnd();
@@ -954,7 +972,7 @@ public class SimpleModelUtils { @@ -954,7 +972,7 @@ public class SimpleModelUtils {
954 || nodeType == BpmSimpleModelNodeTypeEnum.COPY_NODE 972 || nodeType == BpmSimpleModelNodeTypeEnum.COPY_NODE
955 || nodeType == BpmSimpleModelNodeTypeEnum.CHILD_PROCESS 973 || nodeType == BpmSimpleModelNodeTypeEnum.CHILD_PROCESS
956 || nodeType == BpmSimpleModelNodeTypeEnum.END_NODE) { 974 || nodeType == BpmSimpleModelNodeTypeEnum.END_NODE) {
957 - // 添加元素 975 + // 添加此节点
958 resultNodes.add(currentNode); 976 resultNodes.add(currentNode);
959 } 977 }
960 978
@@ -1000,6 +1018,16 @@ public class SimpleModelUtils { @@ -1000,6 +1018,16 @@ public class SimpleModelUtils {
1000 simulateNextNode(currentNode.getChildNode(), variables, resultNodes); 1018 simulateNextNode(currentNode.getChildNode(), variables, resultNodes);
1001 } 1019 }
1002 1020
  1021 + /**
  1022 + * 根据跳过表达式,判断是否跳过此节点
  1023 + */
  1024 + public static boolean isSkipNode(BpmSimpleModelNodeVO currentNode, Map<String, Object> variables) {
  1025 + if (StrUtil.isEmpty(currentNode.getSkipExpression())) {
  1026 + return false;
  1027 + }
  1028 + return BpmnModelUtils.evalConditionExpress(variables, currentNode.getSkipExpression());
  1029 + }
  1030 +
1003 public static boolean evalConditionExpress(Map<String, Object> variables, BpmSimpleModelNodeVO.ConditionSetting conditionSetting) { 1031 public static boolean evalConditionExpress(Map<String, Object> variables, BpmSimpleModelNodeVO.ConditionSetting conditionSetting) {
1004 return BpmnModelUtils.evalConditionExpress(variables, buildConditionExpression(conditionSetting)); 1032 return BpmnModelUtils.evalConditionExpress(variables, buildConditionExpression(conditionSetting));
1005 } 1033 }
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmCategoryServiceImpl.java
@@ -34,6 +34,9 @@ public class BpmCategoryServiceImpl implements BpmCategoryService { @@ -34,6 +34,9 @@ public class BpmCategoryServiceImpl implements BpmCategoryService {
34 @Resource 34 @Resource
35 private BpmCategoryMapper bpmCategoryMapper; 35 private BpmCategoryMapper bpmCategoryMapper;
36 36
  37 + @Resource
  38 + private BpmModelService modelService;
  39 +
37 @Override 40 @Override
38 public Long createCategory(BpmCategorySaveReqVO createReqVO) { 41 public Long createCategory(BpmCategorySaveReqVO createReqVO) {
39 // 校验唯一 42 // 校验唯一
@@ -77,15 +80,22 @@ public class BpmCategoryServiceImpl implements BpmCategoryService { @@ -77,15 +80,22 @@ public class BpmCategoryServiceImpl implements BpmCategoryService {
77 @Override 80 @Override
78 public void deleteCategory(Long id) { 81 public void deleteCategory(Long id) {
79 // 校验存在 82 // 校验存在
80 - validateCategoryExists(id); 83 + BpmCategoryDO category = validateCategoryExists(id);
  84 + // 校验是否被流程模型使用
  85 + Long count = modelService.getModelCountByCategory(category.getCode());
  86 + if (count > 0) {
  87 + throw exception(CATEGORY_DELETE_FAIL_MODEL_USED, category.getName());
  88 + }
81 // 删除 89 // 删除
82 bpmCategoryMapper.deleteById(id); 90 bpmCategoryMapper.deleteById(id);
83 } 91 }
84 92
85 - private void validateCategoryExists(Long id) {  
86 - if (bpmCategoryMapper.selectById(id) == null) { 93 + private BpmCategoryDO validateCategoryExists(Long id) {
  94 + BpmCategoryDO category = bpmCategoryMapper.selectById(id);
  95 + if (category == null) {
87 throw exception(CATEGORY_NOT_EXISTS); 96 throw exception(CATEGORY_NOT_EXISTS);
88 } 97 }
  98 + return category;
89 } 99 }
90 100
91 @Override 101 @Override
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java
@@ -25,6 +25,14 @@ public interface BpmModelService { @@ -25,6 +25,14 @@ public interface BpmModelService {
25 List<Model> getModelList(String name); 25 List<Model> getModelList(String name);
26 26
27 /** 27 /**
  28 + * 根据分类编码获得流程模型数量
  29 + *
  30 + * @param category 分类编码
  31 + * @return 流程模型数量
  32 + */
  33 + Long getModelCountByCategory(String category);
  34 +
  35 + /**
28 * 创建流程模型 36 * 创建流程模型
29 * 37 *
30 * @param modelVO 创建信息 38 * @param modelVO 创建信息
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java
@@ -89,6 +89,14 @@ public class BpmModelServiceImpl implements BpmModelService { @@ -89,6 +89,14 @@ public class BpmModelServiceImpl implements BpmModelService {
89 } 89 }
90 90
91 @Override 91 @Override
  92 + public Long getModelCountByCategory(String category) {
  93 + return repositoryService.createModelQuery()
  94 + .modelCategory(category)
  95 + .modelTenantId(FlowableUtils.getTenantId())
  96 + .count();
  97 + }
  98 +
  99 + @Override
92 @Transactional(rollbackFor = Exception.class) 100 @Transactional(rollbackFor = Exception.class)
93 public String createModel(@Valid BpmModelSaveReqVO createReqVO) { 101 public String createModel(@Valid BpmModelSaveReqVO createReqVO) {
94 if (!ValidationUtils.isXmlNCName(createReqVO.getKey())) { 102 if (!ValidationUtils.isXmlNCName(createReqVO.getKey())) {
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java
@@ -4,11 +4,9 @@ import cn.hutool.core.collection.CollUtil; @@ -4,11 +4,9 @@ import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.collection.ListUtil; 4 import cn.hutool.core.collection.ListUtil;
5 import cn.hutool.core.date.DateUtil; 5 import cn.hutool.core.date.DateUtil;
6 import cn.hutool.core.lang.Assert; 6 import cn.hutool.core.lang.Assert;
7 -import cn.hutool.core.util.ArrayUtil;  
8 -import cn.hutool.core.util.ObjUtil;  
9 -import cn.hutool.core.util.ObjectUtil;  
10 -import cn.hutool.core.util.StrUtil; 7 +import cn.hutool.core.util.*;
11 import cn.iocoder.yudao.framework.common.pojo.PageResult; 8 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  9 +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
12 import cn.iocoder.yudao.framework.common.util.date.DateUtils; 10 import cn.iocoder.yudao.framework.common.util.date.DateUtils;
13 import cn.iocoder.yudao.framework.common.util.json.JsonUtils; 11 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
14 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; 12 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
@@ -61,12 +59,15 @@ import org.flowable.task.api.history.HistoricTaskInstance; @@ -61,12 +59,15 @@ import org.flowable.task.api.history.HistoricTaskInstance;
61 import org.springframework.context.annotation.Lazy; 59 import org.springframework.context.annotation.Lazy;
62 import org.springframework.stereotype.Service; 60 import org.springframework.stereotype.Service;
63 import org.springframework.transaction.annotation.Transactional; 61 import org.springframework.transaction.annotation.Transactional;
  62 +import org.springframework.transaction.support.TransactionSynchronization;
  63 +import org.springframework.transaction.support.TransactionSynchronizationManager;
64 import org.springframework.validation.annotation.Validated; 64 import org.springframework.validation.annotation.Validated;
65 65
66 import java.util.*; 66 import java.util.*;
67 67
68 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 68 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
69 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; 69 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
  70 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
70 import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ActivityNode; 71 import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ActivityNode;
71 import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; 72 import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*;
72 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID; 73 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID;
@@ -264,7 +265,9 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @@ -264,7 +265,9 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
264 // 3. 获取下一个将要执行的节点集合 265 // 3. 获取下一个将要执行的节点集合
265 FlowElement flowElement = bpmnModel.getFlowElement(task.getTaskDefinitionKey()); 266 FlowElement flowElement = bpmnModel.getFlowElement(task.getTaskDefinitionKey());
266 List<FlowNode> nextFlowNodes = BpmnModelUtils.getNextFlowNodes(flowElement, bpmnModel, processVariables); 267 List<FlowNode> nextFlowNodes = BpmnModelUtils.getNextFlowNodes(flowElement, bpmnModel, processVariables);
267 - List<ActivityNode> nextActivityNodes = convertList(nextFlowNodes, node -> new ActivityNode().setId(node.getId()) 268 + // 仅仅获取 UserTask 节点 TODO add from jason:如果网关节点和网关节点相连,获取下个 UserTask. 貌似有点不准。
  269 + List<FlowNode> nextUserTaskList = CollectionUtils.filterList(nextFlowNodes, node -> node instanceof UserTask);
  270 + List<ActivityNode> nextActivityNodes = convertList(nextUserTaskList, node -> new ActivityNode().setId(node.getId())
268 .setName(node.getName()).setNodeType(BpmSimpleModelNodeTypeEnum.APPROVE_NODE.getType()) 271 .setName(node.getName()).setNodeType(BpmSimpleModelNodeTypeEnum.APPROVE_NODE.getType())
269 .setStatus(BpmTaskStatusEnum.RUNNING.getStatus()) 272 .setStatus(BpmTaskStatusEnum.RUNNING.getStatus())
270 .setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(node)) 273 .setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(node))
@@ -395,7 +398,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @@ -395,7 +398,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
395 ? BpmSimpleModelNodeTypeEnum.START_USER_NODE.getType() 398 ? BpmSimpleModelNodeTypeEnum.START_USER_NODE.getType()
396 : ObjUtil.defaultIfNull(parseNodeType(flowNode), // 目的:解决“办理节点”的识别 399 : ObjUtil.defaultIfNull(parseNodeType(flowNode), // 目的:解决“办理节点”的识别
397 BpmSimpleModelNodeTypeEnum.APPROVE_NODE.getType())) 400 BpmSimpleModelNodeTypeEnum.APPROVE_NODE.getType()))
398 - .setStatus(FlowableUtils.getTaskStatus(task)) 401 + .setStatus(getEndActivityNodeStatus(task))
399 .setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(flowNode)) 402 .setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(flowNode))
400 .setStartTime(DateUtils.of(task.getCreateTime())).setEndTime(DateUtils.of(task.getEndTime())) 403 .setStartTime(DateUtils.of(task.getCreateTime())).setEndTime(DateUtils.of(task.getEndTime()))
401 .setTasks(singletonList(BpmProcessInstanceConvert.INSTANCE.buildApprovalTaskInfo(task))); 404 .setTasks(singletonList(BpmProcessInstanceConvert.INSTANCE.buildApprovalTaskInfo(task)));
@@ -449,7 +452,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @@ -449,7 +452,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
449 .setNodeType(BpmSimpleModelNodeTypeEnum.CHILD_PROCESS.getType()).setStatus(processInstanceStatus) 452 .setNodeType(BpmSimpleModelNodeTypeEnum.CHILD_PROCESS.getType()).setStatus(processInstanceStatus)
450 .setStartTime(DateUtils.of(activity.getStartTime())) 453 .setStartTime(DateUtils.of(activity.getStartTime()))
451 .setEndTime(DateUtils.of(activity.getEndTime())) 454 .setEndTime(DateUtils.of(activity.getEndTime()))
452 - .setProcessInstanceId(activity.getProcessInstanceId()); 455 + .setProcessInstanceId(activity.getCalledProcessInstanceId());
453 approvalNodes.add(callActivity); 456 approvalNodes.add(callActivity);
454 } 457 }
455 }); 458 });
@@ -460,6 +463,18 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @@ -460,6 +463,18 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
460 } 463 }
461 464
462 /** 465 /**
  466 + * 获取结束节点的状态
  467 + */
  468 + private Integer getEndActivityNodeStatus(HistoricTaskInstance task) {
  469 + Integer status = FlowableUtils.getTaskStatus(task);
  470 + if (status != null) {
  471 + return status;
  472 + }
  473 + // 结束节点未获取到状态,为跳过状态。可见 bpmn 或者 simple 的 skipExpression
  474 + return BpmTaskStatusEnum.SKIP.getStatus();
  475 + }
  476 +
  477 + /**
463 * 获得【进行中】的活动节点们 478 * 获得【进行中】的活动节点们
464 */ 479 */
465 private List<ActivityNode> getRunApproveNodeList(Long startUserId, 480 private List<ActivityNode> getRunApproveNodeList(Long startUserId,
@@ -521,7 +536,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @@ -521,7 +536,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
521 activityNode.setCandidateUserIds(CollUtil.sub(candidateUserIds, index + 1, candidateUserIds.size())); 536 activityNode.setCandidateUserIds(CollUtil.sub(candidateUserIds, index + 1, candidateUserIds.size()));
522 } 537 }
523 if (BpmSimpleModelNodeTypeEnum.CHILD_PROCESS.getType().equals(activityNode.getNodeType())) { 538 if (BpmSimpleModelNodeTypeEnum.CHILD_PROCESS.getType().equals(activityNode.getNodeType())) {
524 - activityNode.setProcessInstanceId(firstActivity.getProcessInstanceId()); 539 + activityNode.setProcessInstanceId(firstActivity.getCalledProcessInstanceId());
525 } 540 }
526 return activityNode; 541 return activityNode;
527 }); 542 });
@@ -562,10 +577,14 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @@ -562,10 +577,14 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
562 if (runActivityIds.contains(node.getId())) { 577 if (runActivityIds.contains(node.getId())) {
563 return null; 578 return null;
564 } 579 }
565 - 580 + Integer status = BpmTaskStatusEnum.NOT_START.getStatus();
  581 + // 如果节点被跳过。设置状态为跳过
  582 + if (SimpleModelUtils.isSkipNode(node, processVariables)) {
  583 + status = BpmTaskStatusEnum.SKIP.getStatus();
  584 + }
566 ActivityNode activityNode = new ActivityNode().setId(node.getId()).setName(node.getName()) 585 ActivityNode activityNode = new ActivityNode().setId(node.getId()).setName(node.getName())
567 .setNodeType(node.getType()).setCandidateStrategy(node.getCandidateStrategy()) 586 .setNodeType(node.getType()).setCandidateStrategy(node.getCandidateStrategy())
568 - .setStatus(BpmTaskStatusEnum.NOT_START.getStatus()); 587 + .setStatus(status);
569 588
570 // 1. 开始节点/审批节点 589 // 1. 开始节点/审批节点
571 if (ObjectUtils.equalsAny(node.getType(), 590 if (ObjectUtils.equalsAny(node.getType(),
@@ -605,8 +624,13 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @@ -605,8 +624,13 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
605 if (runActivityIds.contains(node.getId())) { 624 if (runActivityIds.contains(node.getId())) {
606 return null; 625 return null;
607 } 626 }
  627 + Integer status = BpmTaskStatusEnum.NOT_START.getStatus();
  628 + // 如果节点被跳过,状态设置为跳过
  629 + if(BpmnModelUtils.isSkipNode(node, processVariables)){
  630 + status = BpmTaskStatusEnum.SKIP.getStatus();
  631 + }
608 ActivityNode activityNode = new ActivityNode().setId(node.getId()) 632 ActivityNode activityNode = new ActivityNode().setId(node.getId())
609 - .setStatus(BpmTaskStatusEnum.NOT_START.getStatus()); 633 + .setStatus(status);
610 634
611 // 1. 开始节点 635 // 1. 开始节点
612 if (node instanceof StartEvent) { 636 if (node instanceof StartEvent) {
@@ -771,17 +795,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @@ -771,17 +795,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
771 processInstanceBuilder.predefineProcessInstanceId(processIdRedisDAO.generate(processIdRule)); 795 processInstanceBuilder.predefineProcessInstanceId(processIdRedisDAO.generate(processIdRule));
772 } 796 }
773 // 3.2 流程名称 797 // 3.2 流程名称
774 - BpmModelMetaInfoVO.TitleSetting titleSetting = processDefinitionInfo.getTitleSetting();  
775 - if (titleSetting != null && Boolean.TRUE.equals(titleSetting.getEnable())) {  
776 - AdminUserRespDTO user = adminUserApi.getUser(userId);  
777 - Map<String, Object> cloneVariables = new HashMap<>(variables);  
778 - cloneVariables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_ID, user.getNickname());  
779 - cloneVariables.put(BpmnVariableConstants.PROCESS_START_TIME, DateUtil.now());  
780 - cloneVariables.put(BpmnVariableConstants.PROCESS_DEFINITION_NAME, definition.getName().trim());  
781 - processInstanceBuilder.name(StrUtil.format(titleSetting.getTitle(), cloneVariables));  
782 - } else {  
783 - processInstanceBuilder.name(definition.getName().trim());  
784 - } 798 + processInstanceBuilder.name(generateProcessInstanceName(userId, definition, processDefinitionInfo, variables));
785 // 3.3 发起流程实例 799 // 3.3 发起流程实例
786 ProcessInstance instance = processInstanceBuilder.start(); 800 ProcessInstance instance = processInstanceBuilder.start();
787 return instance.getId(); 801 return instance.getId();
@@ -817,6 +831,25 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @@ -817,6 +831,25 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
817 }); 831 });
818 } 832 }
819 833
  834 + private String generateProcessInstanceName(Long userId,
  835 + ProcessDefinition definition,
  836 + BpmProcessDefinitionInfoDO definitionInfo,
  837 + Map<String, Object> variables) {
  838 + if (definition == null || definitionInfo == null) {
  839 + return null;
  840 + }
  841 + BpmModelMetaInfoVO.TitleSetting titleSetting = definitionInfo.getTitleSetting();
  842 + if (titleSetting == null || !BooleanUtil.isTrue(titleSetting.getEnable())) {
  843 + return definition.getName();
  844 + }
  845 + AdminUserRespDTO user = adminUserApi.getUser(userId);
  846 + Map<String, Object> cloneVariables = new HashMap<>(variables);
  847 + cloneVariables.put(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_ID, user.getNickname());
  848 + cloneVariables.put(BpmnVariableConstants.PROCESS_START_TIME, DateUtil.now());
  849 + cloneVariables.put(BpmnVariableConstants.PROCESS_DEFINITION_NAME, definition.getName().trim());
  850 + return StrUtil.format(definitionInfo.getTitleSetting().getTitle(), cloneVariables);
  851 + }
  852 +
820 @Override 853 @Override
821 public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { 854 public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) {
822 // 1.1 校验流程实例存在 855 // 1.1 校验流程实例存在
@@ -833,7 +866,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @@ -833,7 +866,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
833 .getProcessDefinitionInfo(instance.getProcessDefinitionId()); 866 .getProcessDefinitionInfo(instance.getProcessDefinitionId());
834 Assert.notNull(processDefinitionInfo, "流程定义({})不存在", processDefinitionInfo); 867 Assert.notNull(processDefinitionInfo, "流程定义({})不存在", processDefinitionInfo);
835 if (processDefinitionInfo.getAllowCancelRunningProcess() != null // 防止未配置 AllowCancelRunningProcess , 默认为可取消 868 if (processDefinitionInfo.getAllowCancelRunningProcess() != null // 防止未配置 AllowCancelRunningProcess , 默认为可取消
836 - && Boolean.FALSE.equals(processDefinitionInfo.getAllowCancelRunningProcess())) { 869 + && BooleanUtil.isFalse(processDefinitionInfo.getAllowCancelRunningProcess())) {
837 throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_ALLOW); 870 throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_ALLOW);
838 } 871 }
839 // 1.4 子流程不允许取消 872 // 1.4 子流程不允许取消
@@ -900,64 +933,77 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @@ -900,64 +933,77 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
900 933
901 @Override 934 @Override
902 public void processProcessInstanceCompleted(ProcessInstance instance) { 935 public void processProcessInstanceCompleted(ProcessInstance instance) {
903 - // 注意:需要基于 instance 设置租户编号,避免 Flowable 内部异步时,丢失租户编号  
904 - FlowableUtils.execute(instance.getTenantId(), () -> {  
905 - // 1.1 获取当前状态  
906 - Integer status = (Integer) instance.getProcessVariables()  
907 - .get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS);  
908 - String reason = (String) instance.getProcessVariables()  
909 - .get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON);  
910 - // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态  
911 - // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了  
912 - if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) {  
913 - status = BpmProcessInstanceStatusEnum.APPROVE.getStatus();  
914 - runtimeService.setVariable(instance.getId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS,  
915 - status);  
916 - } 936 + // 1.1 获取当前状态
  937 + Integer status = (Integer) instance.getProcessVariables()
  938 + .get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS);
  939 + String reason = (String) instance.getProcessVariables()
  940 + .get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON);
  941 + // 1.2 当流程状态还是审批状态中,说明审批通过了,则变更下它的状态
  942 + // 为什么这么处理?因为流程完成,并且完成了,说明审批通过了
  943 + if (Objects.equals(status, BpmProcessInstanceStatusEnum.RUNNING.getStatus())) {
  944 + status = BpmProcessInstanceStatusEnum.APPROVE.getStatus();
  945 + runtimeService.setVariable(instance.getId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS,
  946 + status);
  947 + }
  948 +
  949 + // 2. 发送对应的消息通知
  950 + if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) {
  951 + messageService.sendMessageWhenProcessInstanceApprove(
  952 + BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance));
  953 + } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) {
  954 + messageService.sendMessageWhenProcessInstanceReject(
  955 + BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(instance, reason));
  956 + }
  957 +
  958 + // 3. 发送流程实例的状态事件
  959 + processInstanceEventPublisher.sendProcessInstanceResultEvent(
  960 + BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status));
  961 +
  962 + // 4. 流程后置通知
  963 + if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) {
  964 + BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.
  965 + getProcessDefinitionInfo(instance.getProcessDefinitionId());
  966 + if (ObjUtil.isNotNull(processDefinitionInfo) &&
  967 + ObjUtil.isNotNull(processDefinitionInfo.getProcessAfterTriggerSetting())) {
  968 + BpmModelMetaInfoVO.HttpRequestSetting setting = processDefinitionInfo.getProcessAfterTriggerSetting();
917 969
918 - // 2. 发送对应的消息通知  
919 - if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) {  
920 - messageService.sendMessageWhenProcessInstanceApprove(  
921 - BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceApproveMessage(instance));  
922 - } else if (Objects.equals(status, BpmProcessInstanceStatusEnum.REJECT.getStatus())) {  
923 - messageService.sendMessageWhenProcessInstanceReject(  
924 - BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceRejectMessage(instance, reason)); 970 + BpmHttpRequestUtils.executeBpmHttpRequest(instance,
  971 + setting.getUrl(), setting.getHeader(), setting.getBody(), true, setting.getResponse());
925 } 972 }
  973 + }
  974 + }
926 975
927 - // 3. 发送流程实例的状态事件  
928 - processInstanceEventPublisher.sendProcessInstanceResultEvent(  
929 - BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status)); 976 + @Override
  977 + public void processProcessInstanceCreated(ProcessInstance instance) {
  978 + BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.
  979 + getProcessDefinitionInfo(instance.getProcessDefinitionId());
  980 + ProcessDefinition processDefinition = processDefinitionService.getProcessDefinition(instance.getProcessDefinitionId());
  981 + if (processDefinition == null || processDefinitionInfo == null) {
  982 + return;
  983 + }
930 984
931 - // 4. 流程后置通知  
932 - if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) {  
933 - BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.  
934 - getProcessDefinitionInfo(instance.getProcessDefinitionId());  
935 - if (ObjUtil.isNotNull(processDefinitionInfo) &&  
936 - ObjUtil.isNotNull(processDefinitionInfo.getProcessAfterTriggerSetting())) {  
937 - BpmModelMetaInfoVO.HttpRequestSetting setting = processDefinitionInfo.getProcessAfterTriggerSetting(); 985 + // 自定义标题。目的:主要处理子流程的标题无法处理
  986 + // 注意:必须使用 TransactionSynchronizationManager 事务提交后,否则不生效!!!
  987 + TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
938 988
939 - BpmHttpRequestUtils.executeBpmHttpRequest(instance,  
940 - setting.getUrl(), setting.getHeader(), setting.getBody(), true, setting.getResponse()); 989 + @Override
  990 + public void afterCommit() {
  991 + String name = generateProcessInstanceName(Long.valueOf(instance.getStartUserId()),
  992 + processDefinition, processDefinitionInfo, instance.getProcessVariables());
  993 + if (ObjUtil.notEqual(instance.getName(), name)) {
  994 + runtimeService.setProcessInstanceName(instance.getProcessInstanceId(), name);
941 } 995 }
942 } 996 }
943 - });  
944 - }  
945 997
946 - @Override  
947 - public void processProcessInstanceCreated(ProcessInstance instance) {  
948 - // 注意:需要基于 instance 设置租户编号,避免 Flowable 内部异步时,丢失租户编号  
949 - FlowableUtils.execute(instance.getTenantId(), () -> {  
950 - // 流程前置通知  
951 - BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.  
952 - getProcessDefinitionInfo(instance.getProcessDefinitionId());  
953 - if (ObjUtil.isNull(processDefinitionInfo) ||  
954 - ObjUtil.isNull(processDefinitionInfo.getProcessBeforeTriggerSetting())) {  
955 - return;  
956 - }  
957 - BpmModelMetaInfoVO.HttpRequestSetting setting = processDefinitionInfo.getProcessBeforeTriggerSetting();  
958 - BpmHttpRequestUtils.executeBpmHttpRequest(instance,  
959 - setting.getUrl(), setting.getHeader(), setting.getBody(), true, setting.getResponse());  
960 }); 998 });
  999 +
  1000 + // 流程前置通知
  1001 + if (ObjUtil.isNull(processDefinitionInfo.getProcessBeforeTriggerSetting())) {
  1002 + return;
  1003 + }
  1004 + BpmModelMetaInfoVO.HttpRequestSetting setting = processDefinitionInfo.getProcessBeforeTriggerSetting();
  1005 + BpmHttpRequestUtils.executeBpmHttpRequest(instance,
  1006 + setting.getUrl(), setting.getHeader(), setting.getBody(), true, setting.getResponse());
961 } 1007 }
962 1008
963 } 1009 }
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java
@@ -230,10 +230,10 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -230,10 +230,10 @@ public class BpmTaskServiceImpl implements BpmTaskService {
230 if (StrUtil.isNotBlank(pageVO.getName())) { 230 if (StrUtil.isNotBlank(pageVO.getName())) {
231 taskQuery.taskNameLike("%" + pageVO.getName() + "%"); 231 taskQuery.taskNameLike("%" + pageVO.getName() + "%");
232 } 232 }
233 - if (ArrayUtil.isNotEmpty(pageVO.getCreateTime())) {  
234 - taskQuery.taskCreatedAfter(DateUtils.of(pageVO.getCreateTime()[0]));  
235 - taskQuery.taskCreatedBefore(DateUtils.of(pageVO.getCreateTime()[1]));  
236 - } 233 +// if (ArrayUtil.isNotEmpty(pageVO.getCreateTime())) {
  234 +// taskQuery.taskCreatedAfter(DateUtils.of(pageVO.getCreateTime()[0]));
  235 +// taskQuery.taskCreatedBefore(DateUtils.of(pageVO.getCreateTime()[1]));
  236 +// }
237 // 执行查询 237 // 执行查询
238 long count = taskQuery.count(); 238 long count = taskQuery.count();
239 if (count == 0) { 239 if (count == 0) {
@@ -244,6 +244,12 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -244,6 +244,12 @@ public class BpmTaskServiceImpl implements BpmTaskService {
244 // 特殊:强制移除自动完成的“发起人”节点 244 // 特殊:强制移除自动完成的“发起人”节点
245 // 补充说明:由于 taskQuery 无法方面的过滤,所以暂时通过内存过滤 245 // 补充说明:由于 taskQuery 无法方面的过滤,所以暂时通过内存过滤
246 tasks.removeIf(task -> task.getTaskDefinitionKey().equals(START_USER_NODE_ID)); 246 tasks.removeIf(task -> task.getTaskDefinitionKey().equals(START_USER_NODE_ID));
  247 + // TODO @芋艿:https://t.zsxq.com/MNzqp 【flowable bug】:taskCreatedAfter、taskCreatedBefore 拼接的是 OR
  248 + if (ArrayUtil.isNotEmpty(pageVO.getCreateTime())) {
  249 + tasks.removeIf(task -> task.getCreateTime() == null
  250 + || task.getCreateTime().before(DateUtils.of(pageVO.getCreateTime()[0]))
  251 + || task.getCreateTime().after(DateUtils.of(pageVO.getCreateTime()[1])));
  252 + }
247 return new PageResult<>(tasks, count); 253 return new PageResult<>(tasks, count);
248 } 254 }
249 255
@@ -259,16 +265,22 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -259,16 +265,22 @@ public class BpmTaskServiceImpl implements BpmTaskService {
259 if (StrUtil.isNotEmpty(pageVO.getCategory())) { 265 if (StrUtil.isNotEmpty(pageVO.getCategory())) {
260 taskQuery.taskCategory(pageVO.getCategory()); 266 taskQuery.taskCategory(pageVO.getCategory());
261 } 267 }
262 - if (ArrayUtil.isNotEmpty(pageVO.getCreateTime())) {  
263 - taskQuery.taskCreatedAfter(DateUtils.of(pageVO.getCreateTime()[0]));  
264 - taskQuery.taskCreatedBefore(DateUtils.of(pageVO.getCreateTime()[1]));  
265 - } 268 +// if (ArrayUtil.isNotEmpty(pageVO.getCreateTime())) {
  269 +// taskQuery.taskCreatedAfter(DateUtils.of(pageVO.getCreateTime()[0]));
  270 +// taskQuery.taskCreatedBefore(DateUtils.of(pageVO.getCreateTime()[1]));
  271 +// }
266 // 执行查询 272 // 执行查询
267 long count = taskQuery.count(); 273 long count = taskQuery.count();
268 if (count == 0) { 274 if (count == 0) {
269 return PageResult.empty(); 275 return PageResult.empty();
270 } 276 }
271 List<HistoricTaskInstance> tasks = taskQuery.listPage(PageUtils.getStart(pageVO), pageVO.getPageSize()); 277 List<HistoricTaskInstance> tasks = taskQuery.listPage(PageUtils.getStart(pageVO), pageVO.getPageSize());
  278 + // TODO @芋艿:https://t.zsxq.com/MNzqp 【flowable bug】:taskCreatedAfter、taskCreatedBefore 拼接的是 OR
  279 + if (ArrayUtil.isNotEmpty(pageVO.getCreateTime())) {
  280 + tasks.removeIf(task -> task.getCreateTime() == null
  281 + || task.getCreateTime().before(DateUtils.of(pageVO.getCreateTime()[0]))
  282 + || task.getCreateTime().after(DateUtils.of(pageVO.getCreateTime()[1])));
  283 + }
272 return new PageResult<>(tasks, count); 284 return new PageResult<>(tasks, count);
273 } 285 }
274 286
@@ -886,7 +898,9 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -886,7 +898,9 @@ public class BpmTaskServiceImpl implements BpmTaskService {
886 if (!returnTaskKeyList.contains(task.getTaskDefinitionKey())) { 898 if (!returnTaskKeyList.contains(task.getTaskDefinitionKey())) {
887 return; 899 return;
888 } 900 }
889 - runExecutionIds.add(task.getExecutionId()); 901 + if (task.getExecutionId() != null) {
  902 + runExecutionIds.add(task.getExecutionId());
  903 + }
890 904
891 // 判断是否分配给自己任务,因为会签任务,一个节点会有多个任务 905 // 判断是否分配给自己任务,因为会签任务,一个节点会有多个任务
892 if (isAssignUserTask(userId, task)) { // 情况一:自己的任务,进行 RETURN 标记 906 if (isAssignUserTask(userId, task)) { // 情况一:自己的任务,进行 RETURN 标记
@@ -933,7 +947,10 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -933,7 +947,10 @@ public class BpmTaskServiceImpl implements BpmTaskService {
933 BpmCommentTypeEnum.DELEGATE_START.formatComment(currentUser.getNickname(), delegateUser.getNickname(), reqVO.getReason())); 947 BpmCommentTypeEnum.DELEGATE_START.formatComment(currentUser.getNickname(), delegateUser.getNickname(), reqVO.getReason()));
934 948
935 // 3.1 设置任务所有人 (owner) 为原任务的处理人 (assignee) 949 // 3.1 设置任务所有人 (owner) 为原任务的处理人 (assignee)
936 - taskService.setOwner(taskId, task.getAssignee()); 950 + // 特殊:如果已经被委派(owner 非空),则不需要更新 owner:https://gitee.com/zhijiantianya/yudao-cloud/issues/ICJ153
  951 + if (StrUtil.isEmpty(task.getOwner())) {
  952 + taskService.setOwner(taskId, task.getAssignee());
  953 + }
937 // 3.2 执行委派,将任务委派给 delegateUser 954 // 3.2 执行委派,将任务委派给 delegateUser
938 taskService.delegateTask(taskId, reqVO.getDelegateUserId().toString()); 955 taskService.delegateTask(taskId, reqVO.getDelegateUserId().toString());
939 // 补充说明:委托不单独设置状态。如果需要,可通过 Task 的 DelegationState 字段,判断是否为 DelegationState.PENDING 委托中 956 // 补充说明:委托不单独设置状态。如果需要,可通过 Task 的 DelegationState 字段,判断是否为 DelegationState.PENDING 委托中
@@ -959,7 +976,10 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -959,7 +976,10 @@ public class BpmTaskServiceImpl implements BpmTaskService {
959 BpmCommentTypeEnum.TRANSFER.formatComment(currentUser.getNickname(), assigneeUser.getNickname(), reqVO.getReason())); 976 BpmCommentTypeEnum.TRANSFER.formatComment(currentUser.getNickname(), assigneeUser.getNickname(), reqVO.getReason()));
960 977
961 // 3.1 设置任务所有人 (owner) 为原任务的处理人 (assignee) 978 // 3.1 设置任务所有人 (owner) 为原任务的处理人 (assignee)
962 - taskService.setOwner(taskId, task.getAssignee()); 979 + // 特殊:如果已经被转派(owner 非空),则不需要更新 owner:https://gitee.com/zhijiantianya/yudao-cloud/issues/ICJ153
  980 + if (StrUtil.isEmpty(task.getOwner())) {
  981 + taskService.setOwner(taskId, task.getAssignee());
  982 + }
963 // 3.2 执行转派(审批人),将任务转派给 assigneeUser 983 // 3.2 执行转派(审批人),将任务转派给 assigneeUser
964 // 委托( delegate)和转派(transfer)的差别,就在这块的调用!!!! 984 // 委托( delegate)和转派(transfer)的差别,就在这块的调用!!!!
965 taskService.setAssignee(taskId, reqVO.getAssigneeUserId().toString()); 985 taskService.setAssignee(taskId, reqVO.getAssigneeUserId().toString());
@@ -1367,7 +1387,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -1367,7 +1387,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
1367 PROCESS_INSTANCE_VARIABLE_SKIP_START_USER_NODE, String.class)); 1387 PROCESS_INSTANCE_VARIABLE_SKIP_START_USER_NODE, String.class));
1368 if (userTaskElement.getId().equals(START_USER_NODE_ID) 1388 if (userTaskElement.getId().equals(START_USER_NODE_ID)
1369 && (skipStartUserNodeFlag == null // 目的:一般是“主流程”,发起人节点,自动通过审核 1389 && (skipStartUserNodeFlag == null // 目的:一般是“主流程”,发起人节点,自动通过审核
1370 - || Boolean.TRUE.equals(skipStartUserNodeFlag)) // 目的:一般是“子流程”,发起人节点,按配置自动通过审核 1390 + || BooleanUtil.isTrue(skipStartUserNodeFlag)) // 目的:一般是“子流程”,发起人节点,按配置自动通过审核
1371 && ObjUtil.notEqual(returnTaskFlag, Boolean.TRUE)) { 1391 && ObjUtil.notEqual(returnTaskFlag, Boolean.TRUE)) {
1372 getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId()) 1392 getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId())
1373 .setReason(BpmReasonEnum.ASSIGN_START_USER_APPROVE_WHEN_SKIP_START_USER_NODE.getReason())); 1393 .setReason(BpmReasonEnum.ASSIGN_START_USER_APPROVE_WHEN_SKIP_START_USER_NODE.getReason()));
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/listener/BpmCallActivityListener.java
@@ -85,10 +85,15 @@ public class BpmCallActivityListener implements ExecutionListener { @@ -85,10 +85,15 @@ public class BpmCallActivityListener implements ExecutionListener {
85 // 2.2 使用表单值,并兜底字符串转 Long 失败时使用主流程发起人 85 // 2.2 使用表单值,并兜底字符串转 Long 失败时使用主流程发起人
86 try { 86 try {
87 FlowableUtils.setAuthenticatedUserId(Long.parseLong(formFieldValue)); 87 FlowableUtils.setAuthenticatedUserId(Long.parseLong(formFieldValue));
88 - } catch (Exception e) {  
89 - log.error("[notify][监听器:{},子流程监听器设置流程的发起人字符串转 Long 失败,字符串:{}]",  
90 - DELEGATE_EXPRESSION, formFieldValue);  
91 - FlowableUtils.setAuthenticatedUserId(Long.parseLong(processInstance.getStartUserId())); 88 + } catch (NumberFormatException ex) {
  89 + try {
  90 + List<Long> formFieldValues = JsonUtils.parseArray(formFieldValue, Long.class);
  91 + FlowableUtils.setAuthenticatedUserId(formFieldValues.get(0));
  92 + } catch (Exception e) {
  93 + log.error("[notify][监听器:{},子流程监听器设置流程的发起人字符串转 Long 失败,字符串:{}]",
  94 + DELEGATE_EXPRESSION, formFieldValue);
  95 + FlowableUtils.setAuthenticatedUserId(Long.parseLong(processInstance.getStartUserId()));
  96 + }
92 } 97 }
93 } 98 }
94 } 99 }
yudao-module-bpm/src/test/java/cn/iocoder/yudao/module/bpm/service/category/BpmCategoryServiceImplTest.java
@@ -8,9 +8,11 @@ import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.category.BpmCa @@ -8,9 +8,11 @@ import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.category.BpmCa
8 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO; 8 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO;
9 import cn.iocoder.yudao.module.bpm.dal.mysql.category.BpmCategoryMapper; 9 import cn.iocoder.yudao.module.bpm.dal.mysql.category.BpmCategoryMapper;
10 import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryServiceImpl; 10 import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryServiceImpl;
  11 +import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService;
11 import jakarta.annotation.Resource; 12 import jakarta.annotation.Resource;
12 import org.junit.jupiter.api.Test; 13 import org.junit.jupiter.api.Test;
13 import org.springframework.context.annotation.Import; 14 import org.springframework.context.annotation.Import;
  15 +import org.springframework.test.context.bean.override.mockito.MockitoBean;
14 16
15 import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime; 17 import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
16 import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime; 18 import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime;
@@ -32,6 +34,9 @@ public class BpmCategoryServiceImplTest extends BaseDbUnitTest { @@ -32,6 +34,9 @@ public class BpmCategoryServiceImplTest extends BaseDbUnitTest {
32 @Resource 34 @Resource
33 private BpmCategoryServiceImpl categoryService; 35 private BpmCategoryServiceImpl categoryService;
34 36
  37 + @MockitoBean
  38 + private BpmModelService modelService;
  39 +
35 @Resource 40 @Resource
36 private BpmCategoryMapper categoryMapper; 41 private BpmCategoryMapper categoryMapper;
37 42
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessRespVO.java
1 package cn.iocoder.yudao.module.crm.controller.admin.business.vo.business; 1 package cn.iocoder.yudao.module.crm.controller.admin.business.vo.business;
2 2
3 -import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;  
4 -import com.alibaba.excel.annotation.ExcelProperty; 3 +import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
  4 +import cn.idev.excel.annotation.ExcelProperty;
5 import io.swagger.v3.oas.annotations.media.Schema; 5 import io.swagger.v3.oas.annotations.media.Schema;
6 import lombok.AllArgsConstructor; 6 import lombok.AllArgsConstructor;
7 import lombok.Data; 7 import lombok.Data;
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueRespVO.java
@@ -3,8 +3,8 @@ package cn.iocoder.yudao.module.crm.controller.admin.clue.vo; @@ -3,8 +3,8 @@ package cn.iocoder.yudao.module.crm.controller.admin.clue.vo;
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 import cn.iocoder.yudao.module.infra.enums.DictTypeConstants; 5 import cn.iocoder.yudao.module.infra.enums.DictTypeConstants;
6 -import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;  
7 -import com.alibaba.excel.annotation.ExcelProperty; 6 +import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
  7 +import cn.idev.excel.annotation.ExcelProperty;
8 import io.swagger.v3.oas.annotations.media.Schema; 8 import io.swagger.v3.oas.annotations.media.Schema;
9 import lombok.Data; 9 import lombok.Data;
10 import lombok.ToString; 10 import lombok.ToString;
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactRespVO.java
@@ -3,8 +3,8 @@ package cn.iocoder.yudao.module.crm.controller.admin.contact.vo; @@ -3,8 +3,8 @@ package cn.iocoder.yudao.module.crm.controller.admin.contact.vo;
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 import cn.iocoder.yudao.module.infra.enums.DictTypeConstants; 5 import cn.iocoder.yudao.module.infra.enums.DictTypeConstants;
6 -import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;  
7 -import com.alibaba.excel.annotation.ExcelProperty; 6 +import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
  7 +import cn.idev.excel.annotation.ExcelProperty;
8 import io.swagger.v3.oas.annotations.media.Schema; 8 import io.swagger.v3.oas.annotations.media.Schema;
9 import lombok.Data; 9 import lombok.Data;
10 import lombok.ToString; 10 import lombok.ToString;
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/contract/CrmContractRespVO.java
1 package cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract; 1 package cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract;
2 2
3 -import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;  
4 -import com.alibaba.excel.annotation.ExcelProperty; 3 +import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
  4 +import cn.idev.excel.annotation.ExcelProperty;
5 import io.swagger.v3.oas.annotations.media.Schema; 5 import io.swagger.v3.oas.annotations.media.Schema;
6 import lombok.AllArgsConstructor; 6 import lombok.AllArgsConstructor;
7 import lombok.Data; 7 import lombok.Data;
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerImportExcelVO.java
@@ -5,7 +5,7 @@ import cn.iocoder.yudao.framework.excel.core.annotations.ExcelColumnSelect; @@ -5,7 +5,7 @@ import cn.iocoder.yudao.framework.excel.core.annotations.ExcelColumnSelect;
5 import cn.iocoder.yudao.framework.excel.core.convert.AreaConvert; 5 import cn.iocoder.yudao.framework.excel.core.convert.AreaConvert;
6 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 6 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
7 import cn.iocoder.yudao.module.crm.framework.excel.core.AreaExcelColumnSelectFunction; 7 import cn.iocoder.yudao.module.crm.framework.excel.core.AreaExcelColumnSelectFunction;
8 -import com.alibaba.excel.annotation.ExcelProperty; 8 +import cn.idev.excel.annotation.ExcelProperty;
9 import lombok.AllArgsConstructor; 9 import lombok.AllArgsConstructor;
10 import lombok.Builder; 10 import lombok.Builder;
11 import lombok.Data; 11 import lombok.Data;
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerRespVO.java
@@ -3,8 +3,8 @@ package cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer; @@ -3,8 +3,8 @@ package cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer;
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 import cn.iocoder.yudao.module.infra.enums.DictTypeConstants; 5 import cn.iocoder.yudao.module.infra.enums.DictTypeConstants;
6 -import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;  
7 -import com.alibaba.excel.annotation.ExcelProperty; 6 +import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
  7 +import cn.idev.excel.annotation.ExcelProperty;
8 import io.swagger.v3.oas.annotations.media.Schema; 8 import io.swagger.v3.oas.annotations.media.Schema;
9 import lombok.Data; 9 import lombok.Data;
10 10
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/vo/CrmFollowUpRecordRespVO.java
@@ -2,8 +2,8 @@ package cn.iocoder.yudao.module.crm.controller.admin.followup.vo; @@ -2,8 +2,8 @@ package cn.iocoder.yudao.module.crm.controller.admin.followup.vo;
2 2
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessRespVO; 4 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessRespVO;
5 -import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;  
6 -import com.alibaba.excel.annotation.ExcelProperty; 5 +import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
  6 +import cn.idev.excel.annotation.ExcelProperty;
7 import io.swagger.v3.oas.annotations.media.Schema; 7 import io.swagger.v3.oas.annotations.media.Schema;
8 import lombok.Data; 8 import lombok.Data;
9 9
@@ -61,4 +61,4 @@ public class CrmFollowUpRecordRespVO { @@ -61,4 +61,4 @@ public class CrmFollowUpRecordRespVO {
61 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) 61 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
62 private LocalDateTime createTime; 62 private LocalDateTime createTime;
63 63
64 -}  
65 \ No newline at end of file 64 \ No newline at end of file
  65 +}
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/operatelog/vo/CrmOperateLogRespVO.java
1 package cn.iocoder.yudao.module.crm.controller.admin.operatelog.vo; 1 package cn.iocoder.yudao.module.crm.controller.admin.operatelog.vo;
2 2
3 -import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; 3 +import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
4 import io.swagger.v3.oas.annotations.media.Schema; 4 import io.swagger.v3.oas.annotations.media.Schema;
5 import lombok.Data; 5 import lombok.Data;
6 6
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/CrmProductController.java
@@ -14,6 +14,7 @@ import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO; @@ -14,6 +14,7 @@ import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO;
14 import cn.iocoder.yudao.module.crm.enums.product.CrmProductStatusEnum; 14 import cn.iocoder.yudao.module.crm.enums.product.CrmProductStatusEnum;
15 import cn.iocoder.yudao.module.crm.service.product.CrmProductService; 15 import cn.iocoder.yudao.module.crm.service.product.CrmProductService;
16 import cn.iocoder.yudao.module.system.api.user.AdminUserApi; 16 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  17 +import com.fhs.core.trans.anno.TransMethodResult;
17 import io.swagger.v3.oas.annotations.Operation; 18 import io.swagger.v3.oas.annotations.Operation;
18 import io.swagger.v3.oas.annotations.Parameter; 19 import io.swagger.v3.oas.annotations.Parameter;
19 import io.swagger.v3.oas.annotations.tags.Tag; 20 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -70,6 +71,7 @@ public class CrmProductController { @@ -70,6 +71,7 @@ public class CrmProductController {
70 @Operation(summary = "获得产品") 71 @Operation(summary = "获得产品")
71 @Parameter(name = "id", description = "编号", required = true, example = "1024") 72 @Parameter(name = "id", description = "编号", required = true, example = "1024")
72 @PreAuthorize("@ss.hasPermission('crm:product:query')") 73 @PreAuthorize("@ss.hasPermission('crm:product:query')")
  74 + @TransMethodResult
73 public CommonResult<CrmProductRespVO> getProduct(@RequestParam("id") Long id) { 75 public CommonResult<CrmProductRespVO> getProduct(@RequestParam("id") Long id) {
74 CrmProductDO product = productService.getProduct(id); 76 CrmProductDO product = productService.getProduct(id);
75 return success(BeanUtils.toBean(product, CrmProductRespVO.class)); 77 return success(BeanUtils.toBean(product, CrmProductRespVO.class));
@@ -86,6 +88,7 @@ public class CrmProductController { @@ -86,6 +88,7 @@ public class CrmProductController {
86 @GetMapping("/page") 88 @GetMapping("/page")
87 @Operation(summary = "获得产品分页") 89 @Operation(summary = "获得产品分页")
88 @PreAuthorize("@ss.hasPermission('crm:product:query')") 90 @PreAuthorize("@ss.hasPermission('crm:product:query')")
  91 + @TransMethodResult
89 public CommonResult<PageResult<CrmProductRespVO>> getProductPage(@Valid CrmProductPageReqVO pageVO) { 92 public CommonResult<PageResult<CrmProductRespVO>> getProductPage(@Valid CrmProductPageReqVO pageVO) {
90 PageResult<CrmProductDO> pageResult = productService.getProductPage(pageVO); 93 PageResult<CrmProductDO> pageResult = productService.getProductPage(pageVO);
91 return success(BeanUtils.toBean(pageResult, CrmProductRespVO.class)); 94 return success(BeanUtils.toBean(pageResult, CrmProductRespVO.class));
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/vo/category/CrmProductCategoryListReqVO.java
1 package cn.iocoder.yudao.module.crm.controller.admin.product.vo.category; 1 package cn.iocoder.yudao.module.crm.controller.admin.product.vo.category;
2 2
3 -import com.alibaba.excel.annotation.ExcelProperty; 3 +import cn.idev.excel.annotation.ExcelProperty;
4 import io.swagger.v3.oas.annotations.media.Schema; 4 import io.swagger.v3.oas.annotations.media.Schema;
5 import lombok.Data; 5 import lombok.Data;
6 6
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/vo/product/CrmProductRespVO.java
@@ -4,8 +4,8 @@ import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; @@ -4,8 +4,8 @@ import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductCategoryDO; 5 import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductCategoryDO;
6 import cn.iocoder.yudao.module.crm.enums.DictTypeConstants; 6 import cn.iocoder.yudao.module.crm.enums.DictTypeConstants;
7 -import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;  
8 -import com.alibaba.excel.annotation.ExcelProperty; 7 +import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
  8 +import cn.idev.excel.annotation.ExcelProperty;
9 import com.fhs.core.trans.anno.Trans; 9 import com.fhs.core.trans.anno.Trans;
10 import com.fhs.core.trans.constant.TransType; 10 import com.fhs.core.trans.constant.TransType;
11 import com.fhs.core.trans.vo.VO; 11 import com.fhs.core.trans.vo.VO;
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanRespVO.java
1 package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan; 1 package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan;
2 2
3 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableRespVO; 3 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableRespVO;
4 -import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;  
5 -import com.alibaba.excel.annotation.ExcelProperty; 4 +import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
  5 +import cn.idev.excel.annotation.ExcelProperty;
6 import io.swagger.v3.oas.annotations.media.Schema; 6 import io.swagger.v3.oas.annotations.media.Schema;
7 import lombok.Data; 7 import lombok.Data;
8 8
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivableRespVO.java
@@ -4,8 +4,8 @@ import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; @@ -4,8 +4,8 @@ import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractRespVO; 5 import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractRespVO;
6 import cn.iocoder.yudao.module.crm.enums.DictTypeConstants; 6 import cn.iocoder.yudao.module.crm.enums.DictTypeConstants;
7 -import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;  
8 -import com.alibaba.excel.annotation.ExcelProperty; 7 +import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
  8 +import cn.idev.excel.annotation.ExcelProperty;
9 import io.swagger.v3.oas.annotations.media.Schema; 9 import io.swagger.v3.oas.annotations.media.Schema;
10 import lombok.Data; 10 import lombok.Data;
11 11
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java
@@ -142,6 +142,7 @@ public class CrmBusinessServiceImpl implements CrmBusinessService { @@ -142,6 +142,7 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
142 updateBusinessProduct(updateObj.getId(), businessProducts); 142 updateBusinessProduct(updateObj.getId(), businessProducts);
143 143
144 // 3. 记录操作日志上下文 144 // 3. 记录操作日志上下文
  145 + updateReqVO.setOwnerUserId(oldBusiness.getOwnerUserId()); // 避免操作日志出现“删除负责人”的情况
145 LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldBusiness, CrmBusinessSaveReqVO.class)); 146 LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldBusiness, CrmBusinessSaveReqVO.class));
146 LogRecordContext.putVariable("businessName", oldBusiness.getName()); 147 LogRecordContext.putVariable("businessName", oldBusiness.getName());
147 } 148 }
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueServiceImpl.java
@@ -92,19 +92,20 @@ public class CrmClueServiceImpl implements CrmClueService { @@ -92,19 +92,20 @@ public class CrmClueServiceImpl implements CrmClueService {
92 @Transactional(rollbackFor = Exception.class) 92 @Transactional(rollbackFor = Exception.class)
93 @LogRecord(type = CRM_CLUE_TYPE, subType = CRM_CLUE_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}", 93 @LogRecord(type = CRM_CLUE_TYPE, subType = CRM_CLUE_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}",
94 success = CRM_CLUE_UPDATE_SUCCESS) 94 success = CRM_CLUE_UPDATE_SUCCESS)
95 - @CrmPermission(bizType = CrmBizTypeEnum.CRM_CLUE, bizId = "#updateReq.id", level = CrmPermissionLevelEnum.OWNER)  
96 - public void updateClue(CrmClueSaveReqVO updateReq) {  
97 - Assert.notNull(updateReq.getId(), "线索编号不能为空"); 95 + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CLUE, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.OWNER)
  96 + public void updateClue(CrmClueSaveReqVO updateReqVO) {
  97 + Assert.notNull(updateReqVO.getId(), "线索编号不能为空");
98 // 1.1 校验线索是否存在 98 // 1.1 校验线索是否存在
99 - CrmClueDO oldClue = validateClueExists(updateReq.getId()); 99 + CrmClueDO oldClue = validateClueExists(updateReqVO.getId());
100 // 1.2 校验关联数据 100 // 1.2 校验关联数据
101 - validateRelationDataExists(updateReq); 101 + validateRelationDataExists(updateReqVO);
102 102
103 // 2. 更新线索 103 // 2. 更新线索
104 - CrmClueDO updateObj = BeanUtils.toBean(updateReq, CrmClueDO.class); 104 + CrmClueDO updateObj = BeanUtils.toBean(updateReqVO, CrmClueDO.class);
105 clueMapper.updateById(updateObj); 105 clueMapper.updateById(updateObj);
106 106
107 // 3. 记录操作日志上下文 107 // 3. 记录操作日志上下文
  108 + updateReqVO.setOwnerUserId(oldClue.getOwnerUserId()); // 避免操作日志出现“删除负责人”的情况
108 LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldClue, CrmCustomerSaveReqVO.class)); 109 LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldClue, CrmCustomerSaveReqVO.class));
109 LogRecordContext.putVariable("clueName", oldClue.getName()); 110 LogRecordContext.putVariable("clueName", oldClue.getName());
110 } 111 }
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactServiceImpl.java
@@ -114,6 +114,7 @@ public class CrmContactServiceImpl implements CrmContactService { @@ -114,6 +114,7 @@ public class CrmContactServiceImpl implements CrmContactService {
114 contactMapper.updateById(updateObj); 114 contactMapper.updateById(updateObj);
115 115
116 // 3. 记录操作日志 116 // 3. 记录操作日志
  117 + updateReqVO.setOwnerUserId(oldContact.getOwnerUserId()); // 避免操作日志出现“删除负责人”的情况
117 LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldContact, CrmContactSaveReqVO.class)); 118 LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldContact, CrmContactSaveReqVO.class));
118 LogRecordContext.putVariable("contactName", oldContact.getName()); 119 LogRecordContext.putVariable("contactName", oldContact.getName());
119 } 120 }
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java
@@ -140,9 +140,9 @@ public class CrmContractServiceImpl implements CrmContractService { @@ -140,9 +140,9 @@ public class CrmContractServiceImpl implements CrmContractService {
140 Assert.notNull(updateReqVO.getId(), "合同编号不能为空"); 140 Assert.notNull(updateReqVO.getId(), "合同编号不能为空");
141 updateReqVO.setOwnerUserId(null); // 不允许更新的字段 141 updateReqVO.setOwnerUserId(null); // 不允许更新的字段
142 // 1.1 校验存在 142 // 1.1 校验存在
143 - CrmContractDO contract = validateContractExists(updateReqVO.getId()); 143 + CrmContractDO oldContract = validateContractExists(updateReqVO.getId());
144 // 1.2 只有草稿、审批中,可以编辑; 144 // 1.2 只有草稿、审批中,可以编辑;
145 - if (!ObjectUtils.equalsAny(contract.getAuditStatus(), CrmAuditStatusEnum.DRAFT.getStatus(), 145 + if (!ObjectUtils.equalsAny(oldContract.getAuditStatus(), CrmAuditStatusEnum.DRAFT.getStatus(),
146 CrmAuditStatusEnum.PROCESS.getStatus())) { 146 CrmAuditStatusEnum.PROCESS.getStatus())) {
147 throw exception(CONTRACT_UPDATE_FAIL_NOT_DRAFT); 147 throw exception(CONTRACT_UPDATE_FAIL_NOT_DRAFT);
148 } 148 }
@@ -159,8 +159,9 @@ public class CrmContractServiceImpl implements CrmContractService { @@ -159,8 +159,9 @@ public class CrmContractServiceImpl implements CrmContractService {
159 updateContractProduct(updateReqVO.getId(), contractProducts); 159 updateContractProduct(updateReqVO.getId(), contractProducts);
160 160
161 // 3. 记录操作日志上下文 161 // 3. 记录操作日志上下文
162 - LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(contract, CrmContractSaveReqVO.class));  
163 - LogRecordContext.putVariable("contractName", contract.getName()); 162 + updateReqVO.setOwnerUserId(oldContract.getOwnerUserId()); // 避免操作日志出现“删除负责人”的情况
  163 + LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldContract, CrmContractSaveReqVO.class));
  164 + LogRecordContext.putVariable("contractName", oldContract.getName());
164 } 165 }
165 166
166 private void updateContractProduct(Long id, List<CrmContractProductDO> newList) { 167 private void updateContractProduct(Long id, List<CrmContractProductDO> newList) {
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java
@@ -137,6 +137,7 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { @@ -137,6 +137,7 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
137 customerMapper.updateById(updateObj); 137 customerMapper.updateById(updateObj);
138 138
139 // 3. 记录操作日志上下文 139 // 3. 记录操作日志上下文
  140 + updateReqVO.setOwnerUserId(oldCustomer.getOwnerUserId()); // 避免操作日志出现“删除负责人”的情况
140 LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldCustomer, CrmCustomerSaveReqVO.class)); 141 LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldCustomer, CrmCustomerSaveReqVO.class));
141 LogRecordContext.putVariable("customerName", oldCustomer.getName()); 142 LogRecordContext.putVariable("customerName", oldCustomer.getName());
142 } 143 }
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionServiceImpl.java
@@ -210,12 +210,12 @@ public class CrmPermissionServiceImpl implements CrmPermissionService { @@ -210,12 +210,12 @@ public class CrmPermissionServiceImpl implements CrmPermissionService {
210 CrmPermissionDO oldPermission = permissionMapper.selectByBizTypeAndBizIdByUserId( 210 CrmPermissionDO oldPermission = permissionMapper.selectByBizTypeAndBizIdByUserId(
211 transferReqBO.getBizType(), transferReqBO.getBizId(), transferReqBO.getUserId()); 211 transferReqBO.getBizType(), transferReqBO.getBizId(), transferReqBO.getUserId());
212 String bizTypeName = CrmBizTypeEnum.getNameByType(transferReqBO.getBizType()); 212 String bizTypeName = CrmBizTypeEnum.getNameByType(transferReqBO.getBizType());
213 - if (oldPermission == null // 不是拥有者,并且不是超管  
214 - || (!isOwner(oldPermission.getLevel()) && !CrmPermissionUtils.isCrmAdmin())) { 213 + if ((oldPermission == null || !isOwner(oldPermission.getLevel()))
  214 + && !CrmPermissionUtils.isCrmAdmin()) { // 并且不是超管
215 throw exception(CRM_PERMISSION_DENIED, bizTypeName); 215 throw exception(CRM_PERMISSION_DENIED, bizTypeName);
216 } 216 }
217 // 1.1 校验转移对象是否已经是该负责人 217 // 1.1 校验转移对象是否已经是该负责人
218 - if (ObjUtil.equal(transferReqBO.getNewOwnerUserId(), oldPermission.getUserId())) { 218 + if (oldPermission != null && ObjUtil.equal(transferReqBO.getNewOwnerUserId(), oldPermission.getUserId())) {
219 throw exception(CRM_PERMISSION_MODEL_TRANSFER_FAIL_OWNER_USER_EXISTS, bizTypeName); 219 throw exception(CRM_PERMISSION_MODEL_TRANSFER_FAIL_OWNER_USER_EXISTS, bizTypeName);
220 } 220 }
221 // 1.2 校验新负责人是否存在 221 // 1.2 校验新负责人是否存在
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivablePlanServiceImpl.java
@@ -104,6 +104,7 @@ public class CrmReceivablePlanServiceImpl implements CrmReceivablePlanService { @@ -104,6 +104,7 @@ public class CrmReceivablePlanServiceImpl implements CrmReceivablePlanService {
104 receivablePlanMapper.updateById(updateObj); 104 receivablePlanMapper.updateById(updateObj);
105 105
106 // 3. 记录操作日志上下文 106 // 3. 记录操作日志上下文
  107 + updateReqVO.setOwnerUserId(oldReceivablePlan.getOwnerUserId()); // 避免操作日志出现“删除负责人”的情况
107 LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldReceivablePlan, CrmReceivablePlanSaveReqVO.class)); 108 LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldReceivablePlan, CrmReceivablePlanSaveReqVO.class));
108 LogRecordContext.putVariable("receivablePlan", oldReceivablePlan); 109 LogRecordContext.putVariable("receivablePlan", oldReceivablePlan);
109 } 110 }
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableServiceImpl.java
@@ -162,14 +162,14 @@ public class CrmReceivableServiceImpl implements CrmReceivableService { @@ -162,14 +162,14 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
162 Assert.notNull(updateReqVO.getId(), "回款编号不能为空"); 162 Assert.notNull(updateReqVO.getId(), "回款编号不能为空");
163 updateReqVO.setOwnerUserId(null).setCustomerId(null).setContractId(null).setPlanId(null); // 不允许修改的字段 163 updateReqVO.setOwnerUserId(null).setCustomerId(null).setContractId(null).setPlanId(null); // 不允许修改的字段
164 // 1.1 校验存在 164 // 1.1 校验存在
165 - CrmReceivableDO receivable = validateReceivableExists(updateReqVO.getId());  
166 - updateReqVO.setOwnerUserId(receivable.getOwnerUserId()).setCustomerId(receivable.getCustomerId())  
167 - .setContractId(receivable.getContractId()).setPlanId(receivable.getPlanId()); // 设置已存在的值 165 + CrmReceivableDO oldReceivable = validateReceivableExists(updateReqVO.getId());
  166 + updateReqVO.setOwnerUserId(oldReceivable.getOwnerUserId()).setCustomerId(oldReceivable.getCustomerId())
  167 + .setContractId(oldReceivable.getContractId()).setPlanId(oldReceivable.getPlanId()); // 设置已存在的值
168 // 1.2 校验可回款金额超过上限 168 // 1.2 校验可回款金额超过上限
169 validateReceivablePriceExceedsLimit(updateReqVO); 169 validateReceivablePriceExceedsLimit(updateReqVO);
170 170
171 // 1.3 只有草稿、审批中,可以编辑; 171 // 1.3 只有草稿、审批中,可以编辑;
172 - if (!ObjectUtils.equalsAny(receivable.getAuditStatus(), CrmAuditStatusEnum.DRAFT.getStatus(), 172 + if (!ObjectUtils.equalsAny(oldReceivable.getAuditStatus(), CrmAuditStatusEnum.DRAFT.getStatus(),
173 CrmAuditStatusEnum.PROCESS.getStatus())) { 173 CrmAuditStatusEnum.PROCESS.getStatus())) {
174 throw exception(RECEIVABLE_UPDATE_FAIL_EDITING_PROHIBITED); 174 throw exception(RECEIVABLE_UPDATE_FAIL_EDITING_PROHIBITED);
175 } 175 }
@@ -179,9 +179,10 @@ public class CrmReceivableServiceImpl implements CrmReceivableService { @@ -179,9 +179,10 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
179 receivableMapper.updateById(updateObj); 179 receivableMapper.updateById(updateObj);
180 180
181 // 3. 记录操作日志上下文 181 // 3. 记录操作日志上下文
182 - LogRecordContext.putVariable("receivable", receivable);  
183 - LogRecordContext.putVariable("period", getReceivablePeriod(receivable.getPlanId()));  
184 - LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(receivable, CrmReceivableSaveReqVO.class)); 182 + updateReqVO.setOwnerUserId(oldReceivable.getOwnerUserId()); // 避免操作日志出现“删除负责人”的情况
  183 + LogRecordContext.putVariable("oldReceivable", oldReceivable);
  184 + LogRecordContext.putVariable("period", getReceivablePeriod(oldReceivable.getPlanId()));
  185 + LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldReceivable, CrmReceivableSaveReqVO.class));
185 } 186 }
186 187
187 private Integer getReceivablePeriod(Long planId) { 188 private Integer getReceivablePeriod(Long planId) {
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/util/CrmPermissionUtils.java
@@ -2,11 +2,11 @@ package cn.iocoder.yudao.module.crm.util; @@ -2,11 +2,11 @@ package cn.iocoder.yudao.module.crm.util;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.extra.spring.SpringUtil; 4 import cn.hutool.extra.spring.SpringUtil;
  5 +import cn.iocoder.yudao.framework.common.biz.system.permission.PermissionCommonApi;
5 import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO; 6 import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO;
6 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; 7 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
7 import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum; 8 import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum;
8 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; 9 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
9 -import cn.iocoder.yudao.module.system.api.permission.PermissionApi;  
10 import cn.iocoder.yudao.module.system.api.user.AdminUserApi; 10 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
11 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; 11 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
12 import cn.iocoder.yudao.module.system.enums.permission.RoleCodeEnum; 12 import cn.iocoder.yudao.module.system.enums.permission.RoleCodeEnum;
@@ -32,7 +32,7 @@ public class CrmPermissionUtils { @@ -32,7 +32,7 @@ public class CrmPermissionUtils {
32 * @return 是/否 32 * @return 是/否
33 */ 33 */
34 public static boolean isCrmAdmin() { 34 public static boolean isCrmAdmin() {
35 - PermissionApi permissionApi = SpringUtil.getBean(PermissionApi.class); 35 + PermissionCommonApi permissionApi = SpringUtil.getBean(PermissionCommonApi.class);
36 return permissionApi.hasAnyRoles(getLoginUserId(), RoleCodeEnum.CRM_ADMIN.getCode()); 36 return permissionApi.hasAnyRoles(getLoginUserId(), RoleCodeEnum.CRM_ADMIN.getCode());
37 } 37 }
38 38