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 32 <url>https://github.com/YunaiV/ruoyi-vue-pro</url>
33 33  
34 34 <properties>
35   - <revision>2.6.0-SNAPSHOT</revision>
  35 + <revision>2025.08-SNAPSHOT</revision>
36 36 <!-- Maven 相关 -->
37 37 <java.version>17</java.version>
38 38 <maven.compiler.source>${java.version}</maven.compiler.source>
... ... @@ -88,6 +88,13 @@
88 88 <version>${lombok.version}</version>
89 89 </path>
90 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 98 <groupId>org.mapstruct</groupId>
92 99 <artifactId>mapstruct-processor</artifactId>
93 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 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 17 import argparse
18 18 import pathlib
19 19 import re
  20 +import sys
20 21 import time
21 22 from abc import ABC, abstractmethod
22 23 from typing import Dict, Generator, Optional, Tuple, Union
... ... @@ -293,8 +294,10 @@ class Convertor(ABC):
293 294  
294 295 # 将parse失败的脚本打印出来
295 296 if error_scripts:
  297 + print("!!! 以下内容无法正常解析", file=sys.stderr)
296 298 for script in error_scripts:
297   - print(script)
  299 + # print to stderr
  300 + print(script, file=sys.stderr)
298 301  
299 302  
300 303 class PostgreSQLConvertor(Convertor):
... ...
yudao-dependencies/pom.xml
... ... @@ -14,7 +14,7 @@
14 14 <url>https://github.com/YunaiV/ruoyi-vue-pro</url>
15 15  
16 16 <properties>
17   - <revision>2.6.0-SNAPSHOT</revision>
  17 + <revision>2025.08-SNAPSHOT</revision>
18 18 <flatten-maven-plugin.version>1.6.0</flatten-maven-plugin.version>
19 19 <!-- 统一依赖管理 -->
20 20 <spring.boot.version>3.4.5</spring.boot.version>
... ... @@ -24,9 +24,9 @@
24 24 <!-- DB 相关 -->
25 25 <druid.version>1.2.24</druid.version>
26 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 29 <dynamic-datasource.version>4.3.1</dynamic-datasource.version>
29   - <mybatis-plus-join.version>1.4.13</mybatis-plus-join.version>
30 30 <easy-trans.version>3.0.6</easy-trans.version>
31 31 <redisson.version>3.41.0</redisson.version>
32 32 <dm8.jdbc.version>8.1.3.140</dm8.jdbc.version>
... ... @@ -54,7 +54,7 @@
54 54 <mapstruct.version>1.6.3</mapstruct.version>
55 55 <hutool-5.version>5.8.35</hutool-5.version>
56 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 58 <velocity.version>2.4.1</velocity.version>
59 59 <fastjson.version>1.2.83</fastjson.version>
60 60 <guava.version>33.4.8-jre</guava.version>
... ... @@ -69,12 +69,11 @@
69 69 <pf4j-spring.version>0.9.0</pf4j-spring.version>
70 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 72 <awssdk.version>2.30.14</awssdk.version>
75 73 <justauth.version>1.16.7</justauth.version>
76 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 77 <weixin-java.version>4.7.5.B</weixin-java.version>
79 78 </properties>
80 79  
... ... @@ -479,20 +478,11 @@
479 478 </dependency>
480 479  
481 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 484 </dependency>
  485 +
496 486 <dependency>
497 487 <groupId>org.apache.tika</groupId>
498 488 <artifactId>tika-core</artifactId> <!-- 文件类型的识别 -->
... ... @@ -603,7 +593,7 @@
603 593 <dependency>
604 594 <groupId>org.jeecgframework.jimureport</groupId>
605 595 <artifactId>jimubi-spring-boot3-starter</artifactId>
606   - <version>${jimureport.version}</version>
  596 + <version>${jimubi.version}</version>
607 597 <exclusions>
608 598 <exclusion>
609 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 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 25 * 构建异步刷新的 LoadingCache 对象
19 26 *
20 27 * 注意:如果你的缓存和 ThreadLocal 有关系,要么自己处理 ThreadLocal 的传递,要么使用 {@link #buildCache(Duration, CacheLoader)} 方法
... ... @@ -29,6 +36,7 @@ public class CacheUtils {
29 36 */
30 37 public static <K, V> LoadingCache<K, V> buildAsyncReloadingCache(Duration duration, CacheLoader<K, V> loader) {
31 38 return CacheBuilder.newBuilder()
  39 + .maximumSize(CACHE_MAX_SIZE)
32 40 // 只阻塞当前数据加载线程,其他线程返回旧值
33 41 .refreshAfterWrite(duration)
34 42 // 通过 asyncReloading 实现全异步加载,包括 refreshAfterWrite 被阻塞的加载线程
... ... @@ -43,7 +51,11 @@ public class CacheUtils {
43 51 * @return LoadingCache 对象
44 52 */
45 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 74 * 创建指定时间
75 75 *
76 76 * @param year 年
77   - * @param mouth 月
  77 + * @param month 月
78 78 * @param day 日
79 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 88 * @param year 年
89   - * @param mouth 月
  89 + * @param month 月
90 90 * @param day 日
91 91 * @param hour 小时
92 92 * @param minute 分钟
93 93 * @param second 秒
94 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 97 int hour, int minute, int second) {
98 98 Calendar calendar = Calendar.getInstance();
99 99 calendar.set(Calendar.YEAR, year);
100   - calendar.set(Calendar.MONTH, mouth - 1);
  100 + calendar.set(Calendar.MONTH, month - 1);
101 101 calendar.set(Calendar.DAY_OF_MONTH, day);
102 102 calendar.set(Calendar.HOUR_OF_DAY, hour);
103 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 69 * 创建指定时间
70 70 *
71 71 * @param year 年
72   - * @param mouth 月
  72 + * @param month 月
73 73 * @param day 日
74 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 18 */
19 19 @AutoConfiguration
20 20 @ConditionalOnClass(LoginUser.class)
21   -@ConditionalOnBean(value = {PermissionCommonApi.class, DeptDataPermissionRuleCustomizer.class})
  21 +@ConditionalOnBean(value = {DeptDataPermissionRuleCustomizer.class})
22 22 public class YudaoDeptDataPermissionAutoConfiguration {
23 23  
24 24 @Bean
... ...
yudao-framework/yudao-spring-boot-starter-excel/pom.xml
... ... @@ -42,18 +42,19 @@
42 42  
43 43 <!-- 工具类相关 -->
44 44 <dependency>
45   - <groupId>com.alibaba</groupId>
46   - <artifactId>easyexcel</artifactId>
  45 + <groupId>cn.idev.excel</groupId>
  46 + <artifactId>fastexcel</artifactId>
47 47 </dependency>
48 48  
49 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 53 </dependency>
53 54  
54 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 58 </dependency>
58 59  
59 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 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 3 import cn.hutool.core.convert.Convert;
4 4 import cn.iocoder.yudao.framework.ip.core.Area;
5 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 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 3 import cn.hutool.core.convert.Convert;
4 4 import cn.iocoder.yudao.framework.dict.core.DictFrameworkUtils;
5 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 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 1 package cn.iocoder.yudao.framework.excel.core.convert;
2 2  
3 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 11 * Excel Json 转换器
... ...
yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/convert/MoneyConvert.java
1 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 9 import java.math.BigDecimal;
10 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 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 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.lang.Assert;
5 5 import cn.hutool.core.util.ObjectUtil;
6   -import cn.hutool.core.util.ReflectUtil;
7 6 import cn.hutool.core.util.StrUtil;
8 7 import cn.hutool.extra.spring.SpringUtil;
9 8 import cn.hutool.poi.excel.ExcelUtil;
... ... @@ -11,12 +10,12 @@ import cn.iocoder.yudao.framework.common.core.KeyValue;
11 10 import cn.iocoder.yudao.framework.dict.core.DictFrameworkUtils;
12 11 import cn.iocoder.yudao.framework.excel.core.annotations.ExcelColumnSelect;
13 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 19 import lombok.extern.slf4j.Slf4j;
21 20 import org.apache.poi.hssf.usermodel.HSSFDataValidation;
22 21 import org.apache.poi.ss.usermodel.*;
... ... @@ -87,7 +86,7 @@ public class SelectSheetWriteHandler implements SheetWriteHandler {
87 86  
88 87 /**
89 88 * 判断字段是否是静态的、最终的、 transient 的
90   - * 原因:EasyExcel 默认是忽略 static final 或 transient 的字段,所以需要判断
  89 + * 原因:FastExcel 默认是忽略 static final 或 transient 的字段,所以需要判断
91 90 *
92 91 * @param field 字段
93 92 * @return 是否是静态的、最终的、transient 的
... ... @@ -185,4 +184,4 @@ public class SelectSheetWriteHandler implements SheetWriteHandler {
185 184 writeSheetHolder.getSheet().addValidationData(validation);
186 185 }
187 186  
188   -}
189 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 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 5 import cn.iocoder.yudao.framework.common.util.http.HttpUtils;
  6 +import cn.iocoder.yudao.framework.excel.core.handler.ColumnWidthMatchStyleStrategy;
4 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 8 import jakarta.servlet.http.HttpServletResponse;
9 9 import org.springframework.web.multipart.MultipartFile;
10 10  
... ... @@ -32,9 +32,9 @@ public class ExcelUtils {
32 32 public static <T> void write(HttpServletResponse response, String filename, String sheetName,
33 33 Class<T> head, List<T> data) throws IOException {
34 34 // 输出 Excel
35   - EasyExcel.write(response.getOutputStream(), head)
  35 + FastExcelFactory.write(response.getOutputStream(), head)
36 36 .autoCloseStream(false) // 不要自动关闭,交给 Servlet 自己处理
37   - .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) // 基于 column 长度,自动适配。最大 255 宽度
  37 + .registerWriteHandler(new ColumnWidthMatchStyleStrategy()) // 基于 column 长度,自动适配。最大 255 宽度
38 38 .registerWriteHandler(new SelectSheetWriteHandler(head)) // 基于固定 sheet 实现下拉框
39 39 .registerConverter(new LongStringConverter()) // 避免 Long 类型丢失精度
40 40 .sheet(sheetName).doWrite(data);
... ... @@ -44,7 +44,7 @@ public class ExcelUtils {
44 44 }
45 45  
46 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 48 .autoCloseStream(false) // 不要自动关闭,交给 Servlet 自己处理
49 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 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 7 import org.springframework.context.annotation.Bean;
8 8 import org.springframework.scheduling.annotation.EnableAsync;
9 9 import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
  10 +import org.springframework.core.task.SimpleAsyncTaskExecutor;
10 11  
11 12 /**
12 13 * 异步任务 Configuration
... ... @@ -21,13 +22,20 @@ public class YudaoAsyncAutoConfiguration {
21 22  
22 23 @Override
23 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 44 <dependency>
45 45 <groupId>io.opentracing</groupId>
46 46 <artifactId>opentracing-util</artifactId>
  47 + <optional>true</optional>
47 48 </dependency>
48 49 <dependency>
49 50 <groupId>org.apache.skywalking</groupId>
50 51 <artifactId>apm-toolkit-trace</artifactId>
  52 + <optional>true</optional>
51 53 </dependency>
52 54 <dependency>
53 55 <groupId>org.apache.skywalking</groupId>
54 56 <artifactId>apm-toolkit-logback-1.x</artifactId>
  57 + <optional>true</optional>
55 58 </dependency>
56 59 <dependency>
57 60 <groupId>org.apache.skywalking</groupId>
58 61 <artifactId>apm-toolkit-opentracing</artifactId>
  62 + <optional>true</optional>
59 63 </dependency>
60 64  
61 65 <!-- Micrometer 对 Prometheus 的支持 -->
62 66 <dependency>
63 67 <groupId>io.micrometer</groupId>
64 68 <artifactId>micrometer-registry-prometheus</artifactId>
  69 + <optional>true</optional>
65 70 </dependency>
66 71  
67 72 <dependency>
68 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 76 </dependency>
71 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 3 import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum;
4 4 import cn.iocoder.yudao.framework.tracer.core.aop.BizTraceAspect;
5 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 9 import org.springframework.boot.autoconfigure.AutoConfiguration;
7 10 import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
8 11 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
... ... @@ -16,30 +19,32 @@ import org.springframework.context.annotation.Bean;
16 19 * @author mashu
17 20 */
18 21 @AutoConfiguration
19   -@ConditionalOnClass({BizTraceAspect.class})
  22 +@ConditionalOnClass(name = {
  23 + "org.apache.skywalking.apm.toolkit.opentracing.SkywalkingTracer",
  24 + "io.opentracing.Tracer"
  25 +})
20 26 @EnableConfigurationProperties(TracerProperties.class)
21 27 @ConditionalOnProperty(prefix = "yudao.tracer", value = "enable", matchIfMissing = true)
22 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 50 * 创建 TraceFilter 过滤器,响应 header 设置 traceId
... ...
yudao-framework/yudao-spring-boot-starter-mybatis/pom.xml
... ... @@ -24,8 +24,8 @@
24 24 <!-- Web 相关 -->
25 25 <dependency>
26 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 29 </dependency>
30 30  
31 31 <!-- DB 相关 -->
... ...
yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/handler/DefaultDBFieldHandler.java
1 1 package cn.iocoder.yudao.framework.mybatis.core.handler;
2 2  
3 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 5 import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
6 6 import org.apache.ibatis.reflection.MetaObject;
7 7  
... ... @@ -32,7 +32,7 @@ public class DefaultDBFieldHandler implements MetaObjectHandler {
32 32 baseDO.setUpdateTime(current);
33 33 }
34 34  
35   - Long userId = WebFrameworkUtils.getLoginUserId();
  35 + Long userId = SecurityFrameworkUtils.getLoginUserId();
36 36 // 当前登录用户不为空,创建人为空,则当前登录用户为创建人
37 37 if (Objects.nonNull(userId) && Objects.isNull(baseDO.getCreator())) {
38 38 baseDO.setCreator(userId.toString());
... ... @@ -54,7 +54,7 @@ public class DefaultDBFieldHandler implements MetaObjectHandler {
54 54  
55 55 // 当前登录用户不为空,更新人为空,则当前登录用户为更新人
56 56 Object modifier = getFieldValByName("updater", metaObject);
57   - Long userId = WebFrameworkUtils.getLoginUserId();
  57 + Long userId = SecurityFrameworkUtils.getLoginUserId();
58 58 if (Objects.nonNull(userId) && Objects.isNull(modifier)) {
59 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 42 default PageResult<T> selectPage(PageParam pageParam, Collection<SortingField> sortingFields, @Param("ew") Wrapper<T> queryWrapper) {
43 43 // 特殊:不分页,直接查询全部
44 44 if (PageParam.PAGE_SIZE_NONE.equals(pageParam.getPageSize())) {
  45 + MyBatisUtils.addOrder(queryWrapper, sortingFields);
45 46 List<T> list = selectList(queryWrapper);
46 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 118  
119 119 @Override
120 120 public <X> MPJLambdaWrapperX<T> orderByDesc(SFunction<X, ?> column) {
121   - //noinspection unchecked
122 121 super.orderByDesc(true, column);
123 122 return this;
124 123 }
... ... @@ -208,7 +207,7 @@ public class MPJLambdaWrapperX&lt;T&gt; extends MPJLambdaWrapper&lt;T&gt; {
208 207 }
209 208  
210 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 211 super.selectCount(column, alias);
213 212 return this;
214 213 }
... ... @@ -226,7 +225,7 @@ public class MPJLambdaWrapperX&lt;T&gt; extends MPJLambdaWrapper&lt;T&gt; {
226 225 }
227 226  
228 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 229 super.selectSum(column, alias);
231 230 return this;
232 231 }
... ... @@ -244,7 +243,7 @@ public class MPJLambdaWrapperX&lt;T&gt; extends MPJLambdaWrapper&lt;T&gt; {
244 243 }
245 244  
246 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 247 super.selectMax(column, alias);
249 248 return this;
250 249 }
... ... @@ -262,7 +261,7 @@ public class MPJLambdaWrapperX&lt;T&gt; extends MPJLambdaWrapper&lt;T&gt; {
262 261 }
263 262  
264 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 265 super.selectMin(column, alias);
267 266 return this;
268 267 }
... ... @@ -280,7 +279,7 @@ public class MPJLambdaWrapperX&lt;T&gt; extends MPJLambdaWrapper&lt;T&gt; {
280 279 }
281 280  
282 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 283 super.selectAvg(column, alias);
285 284 return this;
286 285 }
... ... @@ -298,7 +297,7 @@ public class MPJLambdaWrapperX&lt;T&gt; extends MPJLambdaWrapper&lt;T&gt; {
298 297 }
299 298  
300 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 301 super.selectLen(column, alias);
303 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 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 4 import cn.hutool.core.lang.func.Func1;
5 5 import cn.hutool.core.lang.func.LambdaUtil;
6 6 import cn.hutool.core.util.StrUtil;
... ... @@ -8,6 +8,8 @@ import cn.iocoder.yudao.framework.common.pojo.PageParam;
8 8 import cn.iocoder.yudao.framework.common.pojo.SortingField;
9 9 import cn.iocoder.yudao.framework.mybatis.core.enums.DbTypeEnum;
10 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 13 import com.baomidou.mybatisplus.core.metadata.OrderItem;
12 14 import com.baomidou.mybatisplus.core.toolkit.StringPool;
13 15 import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
... ... @@ -20,7 +22,6 @@ import net.sf.jsqlparser.schema.Table;
20 22 import java.util.ArrayList;
21 23 import java.util.Collection;
22 24 import java.util.List;
23   -import java.util.stream.Collectors;
24 25  
25 26 /**
26 27 * MyBatis 工具类
... ... @@ -37,15 +38,27 @@ public class MyBatisUtils {
37 38 // 页码 + 数量
38 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 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 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 126  
127 127 // 额外设置到 request 中,用于 ApiAccessLogFilter 可以获取到用户编号;
128 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 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 1 package cn.iocoder.yudao.framework.web.core.filter;
2 2  
  3 +import cn.hutool.core.util.StrUtil;
3 4 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
4 5 import org.springframework.web.filter.OncePerRequestFilter;
5 6  
... ... @@ -16,6 +17,14 @@ import java.io.IOException;
16 17 */
17 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 28 @Override
20 29 protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
21 30 throws IOException, ServletException {
... ... @@ -24,7 +33,13 @@ public class CacheRequestBodyFilter extends OncePerRequestFilter {
24 33  
25 34 @Override
26 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 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 import cn.hutool.core.util.ObjUtil;
7 7 import cn.hutool.core.util.StrUtil;
8 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 11 import cn.iocoder.yudao.framework.common.exception.ServiceException;
10 12 import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
11 13 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
... ... @@ -14,8 +16,6 @@ import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
14 16 import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
15 17 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
16 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 19 import com.fasterxml.jackson.databind.exc.InvalidFormatException;
20 20 import jakarta.servlet.http.HttpServletRequest;
21 21 import jakarta.validation.ConstraintViolation;
... ... @@ -29,6 +29,7 @@ import org.springframework.util.Assert;
29 29 import org.springframework.validation.BindException;
30 30 import org.springframework.validation.FieldError;
31 31 import org.springframework.validation.ObjectError;
  32 +import org.springframework.web.HttpMediaTypeNotSupportedException;
32 33 import org.springframework.web.HttpRequestMethodNotSupportedException;
33 34 import org.springframework.web.bind.MethodArgumentNotValidException;
34 35 import org.springframework.web.bind.MissingServletRequestParameterException;
... ... @@ -101,6 +102,9 @@ public class GlobalExceptionHandler {
101 102 if (ex instanceof HttpRequestMethodNotSupportedException) {
102 103 return httpRequestMethodNotSupportedExceptionHandler((HttpRequestMethodNotSupportedException) ex);
103 104 }
  105 + if (ex instanceof HttpMediaTypeNotSupportedException) {
  106 + return httpMediaTypeNotSupportedExceptionHandler((HttpMediaTypeNotSupportedException) ex);
  107 + }
104 108 if (ex instanceof ServiceException) {
105 109 return serviceExceptionHandler((ServiceException) ex);
106 110 }
... ... @@ -171,17 +175,19 @@ public class GlobalExceptionHandler {
171 175 /**
172 176 * 处理 SpringMVC 请求参数类型错误
173 177 *
174   - * 例如说,接口上设置了 @RequestBody实体中 xx 属性类型为 Integer,结果传递 xx 参数类型为 String
  178 + * 例如说,接口上设置了 @RequestBody 实体中 xx 属性类型为 Integer,结果传递 xx 参数类型为 String
175 179 */
176 180 @ExceptionHandler(HttpMessageNotReadableException.class)
177 181 public CommonResult<?> methodArgumentTypeInvalidFormatExceptionHandler(HttpMessageNotReadableException ex) {
178 182 log.warn("[methodArgumentTypeInvalidFormatExceptionHandler]", ex);
179   - if(ex.getCause() instanceof InvalidFormatException) {
  183 + if (ex.getCause() instanceof InvalidFormatException) {
180 184 InvalidFormatException invalidFormatException = (InvalidFormatException) ex.getCause();
181 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 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 258 * 处理 Spring Security 权限不足的异常
242 259 *
243 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 5 import cn.iocoder.yudao.framework.xss.core.clean.XssCleaner;
6 6 import cn.iocoder.yudao.framework.xss.core.filter.XssFilter;
7 7 import cn.iocoder.yudao.framework.xss.core.json.XssStringJsonDeserializer;
8   -import com.fasterxml.jackson.databind.ObjectMapper;
9 8 import org.springframework.boot.autoconfigure.AutoConfiguration;
10 9 import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
11 10 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
... ... @@ -42,13 +41,13 @@ public class YudaoXssAutoConfiguration implements WebMvcConfigurer {
42 41 */
43 42 @Bean
44 43 @ConditionalOnMissingBean(name = "xssJacksonCustomizer")
45   - @ConditionalOnBean(ObjectMapper.class)
46 44 @ConditionalOnProperty(value = "yudao.xss.enable", havingValue = "true")
47 45 public Jackson2ObjectMapperBuilderCustomizer xssJacksonCustomizer(XssProperties properties,
48 46 PathMatcher pathMatcher,
49 47 XssCleaner xssCleaner) {
50 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 19 国外:OpenAI、Ollama、Midjourney、StableDiffusion、Suno
20 20 </description>
21 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 24 <tinyflow.version>1.0.2</tinyflow.version>
24 25 </properties>
25 26  
... ... @@ -75,65 +76,73 @@
75 76 <!-- Spring AI Model 模型接入 -->
76 77 <dependency>
77 78 <groupId>org.springframework.ai</groupId>
78   - <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
  79 + <artifactId>spring-ai-starter-model-openai</artifactId>
79 80 <version>${spring-ai.version}</version>
80 81 </dependency>
81 82 <dependency>
82 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 85 <version>${spring-ai.version}</version>
85 86 </dependency>
86 87 <dependency>
87 88 <groupId>org.springframework.ai</groupId>
88   - <artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
  89 + <artifactId>spring-ai-starter-model-deepseek</artifactId>
89 90 <version>${spring-ai.version}</version>
90 91 </dependency>
91 92 <dependency>
92 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 95 <version>${spring-ai.version}</version>
95 96 </dependency>
96 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 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 100 <version>${spring-ai.version}</version>
107 101 </dependency>
108 102 <dependency>
109 103 <!-- 智谱 GLM -->
110 104 <groupId>org.springframework.ai</groupId>
111   - <artifactId>spring-ai-zhipuai-spring-boot-starter</artifactId>
  105 + <artifactId>spring-ai-starter-model-zhipuai</artifactId>
112 106 <version>${spring-ai.version}</version>
113 107 </dependency>
114 108 <dependency>
115 109 <groupId>org.springframework.ai</groupId>
116   - <artifactId>spring-ai-minimax-spring-boot-starter</artifactId>
  110 + <artifactId>spring-ai-starter-model-minimax</artifactId>
117 111 <version>${spring-ai.version}</version>
118 112 </dependency>
  113 +
119 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 132 </dependency>
124 133  
125 134 <!-- 向量存储:https://db-engines.com/en/ranking/vector+dbms -->
126 135 <dependency>
127 136 <!-- Qdrant:https://qdrant.tech/ -->
128 137 <groupId>org.springframework.ai</groupId>
129   - <artifactId>spring-ai-qdrant-store</artifactId>
  138 + <artifactId>spring-ai-starter-vector-store-qdrant</artifactId>
130 139 <version>${spring-ai.version}</version>
131 140 </dependency>
132 141  
133 142 <dependency>
134 143 <!-- Redis:https://redis.io/docs/latest/develop/get-started/vector-database/ -->
135 144 <groupId>org.springframework.ai</groupId>
136   - <artifactId>spring-ai-redis-store</artifactId>
  145 + <artifactId>spring-ai-starter-vector-store-redis</artifactId>
137 146 <version>${spring-ai.version}</version>
138 147 </dependency>
139 148 <dependency>
... ... @@ -144,7 +153,7 @@
144 153 <dependency>
145 154 <!-- Milvus:https://milvus.io/ -->
146 155 <groupId>org.springframework.ai</groupId>
147   - <artifactId>spring-ai-milvus-store</artifactId>
  156 + <artifactId>spring-ai-starter-vector-store-milvus</artifactId>
148 157 <version>${spring-ai.version}</version>
149 158 <exclusions>
150 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 12 import cn.iocoder.yudao.module.ai.dal.dataobject.chat.AiChatConversationDO;
13 13 import cn.iocoder.yudao.module.ai.service.chat.AiChatConversationService;
14 14 import cn.iocoder.yudao.module.ai.service.chat.AiChatMessageService;
  15 +import com.fhs.core.trans.anno.TransMethodResult;
15 16 import io.swagger.v3.oas.annotations.Operation;
16 17 import io.swagger.v3.oas.annotations.Parameter;
17 18 import io.swagger.v3.oas.annotations.tags.Tag;
... ... @@ -54,6 +55,7 @@ public class AiChatConversationController {
54 55  
55 56 @GetMapping("/my-list")
56 57 @Operation(summary = "获得【我的】聊天对话列表")
  58 + @TransMethodResult
57 59 public CommonResult<List<AiChatConversationRespVO>> getChatConversationMyList() {
58 60 List<AiChatConversationDO> list = chatConversationService.getChatConversationListByUserId(getLoginUserId());
59 61 return success(BeanUtils.toBean(list, AiChatConversationRespVO.class));
... ... @@ -62,6 +64,7 @@ public class AiChatConversationController {
62 64 @GetMapping("/get-my")
63 65 @Operation(summary = "获得【我的】聊天对话")
64 66 @Parameter(name = "id", required = true, description = "对话编号", example = "1024")
  67 + @TransMethodResult
65 68 public CommonResult<AiChatConversationRespVO> getChatConversationMy(@RequestParam("id") Long id) {
66 69 AiChatConversationDO conversation = chatConversationService.getChatConversation(id);
67 70 if (conversation != null && ObjUtil.notEqual(conversation.getUserId(), getLoginUserId())) {
... ... @@ -90,6 +93,7 @@ public class AiChatConversationController {
90 93 @GetMapping("/page")
91 94 @Operation(summary = "获得对话分页", description = "用于【对话管理】菜单")
92 95 @PreAuthorize("@ss.hasPermission('ai:chat-conversation:query')")
  96 + @TransMethodResult
93 97 public CommonResult<PageResult<AiChatConversationRespVO>> getChatConversationPage(AiChatConversationPageReqVO pageReqVO) {
94 98 PageResult<AiChatConversationDO> pageResult = chatConversationService.getChatConversationPage(pageReqVO);
95 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 10 import cn.iocoder.yudao.module.ai.controller.admin.model.vo.chatRole.AiChatRoleSaveReqVO;
11 11 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO;
12 12 import cn.iocoder.yudao.module.ai.service.model.AiChatRoleService;
  13 +import com.fhs.core.trans.anno.TransMethodResult;
13 14 import io.swagger.v3.oas.annotations.Operation;
14 15 import io.swagger.v3.oas.annotations.Parameter;
15 16 import io.swagger.v3.oas.annotations.tags.Tag;
... ... @@ -35,6 +36,7 @@ public class AiChatRoleController {
35 36  
36 37 @GetMapping("/my-page")
37 38 @Operation(summary = "获得【我的】聊天角色分页")
  39 + @TransMethodResult
38 40 public CommonResult<PageResult<AiChatRoleRespVO>> getChatRoleMyPage(@Valid AiChatRolePageReqVO pageReqVO) {
39 41 PageResult<AiChatRoleDO> pageResult = chatRoleService.getChatRoleMyPage(pageReqVO, getLoginUserId());
40 42 return success(BeanUtils.toBean(pageResult, AiChatRoleRespVO.class));
... ... @@ -43,6 +45,7 @@ public class AiChatRoleController {
43 45 @GetMapping("/get-my")
44 46 @Operation(summary = "获得【我的】聊天角色")
45 47 @Parameter(name = "id", description = "编号", required = true, example = "1024")
  48 + @TransMethodResult
46 49 public CommonResult<AiChatRoleRespVO> getChatRoleMy(@RequestParam("id") Long id) {
47 50 AiChatRoleDO chatRole = chatRoleService.getChatRole(id);
48 51 if (ObjUtil.notEqual(chatRole.getUserId(), getLoginUserId())) {
... ... @@ -108,6 +111,7 @@ public class AiChatRoleController {
108 111 @Operation(summary = "获得聊天角色")
109 112 @Parameter(name = "id", description = "编号", required = true, example = "1024")
110 113 @PreAuthorize("@ss.hasPermission('ai:chat-role:query')")
  114 + @TransMethodResult
111 115 public CommonResult<AiChatRoleRespVO> getChatRole(@RequestParam("id") Long id) {
112 116 AiChatRoleDO chatRole = chatRoleService.getChatRole(id);
113 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 5 import cn.iocoder.yudao.module.ai.framework.ai.core.AiModelFactory;
6 6 import cn.iocoder.yudao.module.ai.framework.ai.core.AiModelFactoryImpl;
7 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 8 import cn.iocoder.yudao.module.ai.framework.ai.core.model.doubao.DouBaoChatModel;
10 9 import cn.iocoder.yudao.module.ai.framework.ai.core.model.hunyuan.HunYuanChatModel;
11 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 13 import cn.iocoder.yudao.module.ai.framework.ai.core.model.suno.api.SunoApi;
15 14 import cn.iocoder.yudao.module.ai.framework.ai.core.model.xinghuo.XingHuoChatModel;
16 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 16 import org.springframework.ai.embedding.BatchingStrategy;
22 17 import org.springframework.ai.embedding.TokenCountBatchingStrategy;
23 18 import org.springframework.ai.model.tool.ToolCallingManager;
... ... @@ -26,6 +21,10 @@ import org.springframework.ai.openai.OpenAiChatOptions;
26 21 import org.springframework.ai.openai.api.OpenAiApi;
27 22 import org.springframework.ai.tokenizer.JTokkitTokenCountEstimator;
28 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 28 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
30 29 import org.springframework.boot.context.properties.EnableConfigurationProperties;
31 30 import org.springframework.context.annotation.Bean;
... ... @@ -53,33 +52,6 @@ public class AiAutoConfiguration {
53 52 // ========== 各种 AI Client 创建 ==========
54 53  
55 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 55 @ConditionalOnProperty(value = "yudao.ai.doubao.enable", havingValue = "true")
84 56 public DouBaoChatModel douBaoChatClient(YudaoAiProperties yudaoAiProperties) {
85 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 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 19 @SuppressWarnings("SpellCheckingInspection")
... ... @@ -61,19 +55,6 @@ public class YudaoAiProperties {
61 55 private SunoProperties suno;
62 56  
63 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 58 public static class DouBaoProperties {
78 59  
79 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 8 import cn.hutool.core.util.RuntimeUtil;
9 9 import cn.hutool.core.util.StrUtil;
10 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 13 import cn.iocoder.yudao.module.ai.framework.ai.config.AiAutoConfiguration;
12 14 import cn.iocoder.yudao.module.ai.framework.ai.config.YudaoAiProperties;
13   -import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum;
14 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 16 import cn.iocoder.yudao.module.ai.framework.ai.core.model.doubao.DouBaoChatModel;
17 17 import cn.iocoder.yudao.module.ai.framework.ai.core.model.hunyuan.HunYuanChatModel;
18 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 22 import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowImageModel;
23 23 import cn.iocoder.yudao.module.ai.framework.ai.core.model.suno.api.SunoApi;
24 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 28 import com.alibaba.cloud.ai.dashscope.api.DashScopeApi;
28 29 import com.alibaba.cloud.ai.dashscope.api.DashScopeImageApi;
29 30 import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel;
... ... @@ -32,47 +33,55 @@ import com.alibaba.cloud.ai.dashscope.embedding.DashScopeEmbeddingModel;
32 33 import com.alibaba.cloud.ai.dashscope.embedding.DashScopeEmbeddingOptions;
33 34 import com.alibaba.cloud.ai.dashscope.image.DashScopeImageModel;
34 35 import com.azure.ai.openai.OpenAIClientBuilder;
  36 +import com.azure.core.credential.KeyCredential;
35 37 import io.micrometer.observation.ObservationRegistry;
36 38 import io.milvus.client.MilvusServiceClient;
37 39 import io.qdrant.client.QdrantClient;
38 40 import io.qdrant.client.QdrantGrpcClient;
39 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 54 import org.springframework.ai.azure.openai.AzureOpenAiChatModel;
60 55 import org.springframework.ai.azure.openai.AzureOpenAiEmbeddingModel;
61 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 60 import org.springframework.ai.document.MetadataMode;
63 61 import org.springframework.ai.embedding.BatchingStrategy;
64 62 import org.springframework.ai.embedding.EmbeddingModel;
  63 +import org.springframework.ai.embedding.observation.EmbeddingModelObservationConvention;
65 64 import org.springframework.ai.image.ImageModel;
66 65 import org.springframework.ai.minimax.MiniMaxChatModel;
67 66 import org.springframework.ai.minimax.MiniMaxChatOptions;
68 67 import org.springframework.ai.minimax.MiniMaxEmbeddingModel;
69 68 import org.springframework.ai.minimax.MiniMaxEmbeddingOptions;
70 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 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 85 import org.springframework.ai.ollama.OllamaChatModel;
77 86 import org.springframework.ai.ollama.OllamaEmbeddingModel;
78 87 import org.springframework.ai.ollama.api.OllamaApi;
... ... @@ -84,21 +93,23 @@ import org.springframework.ai.openai.OpenAiImageModel;
84 93 import org.springframework.ai.openai.api.OpenAiApi;
85 94 import org.springframework.ai.openai.api.OpenAiImageApi;
86 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 96 import org.springframework.ai.stabilityai.StabilityAiImageModel;
94 97 import org.springframework.ai.stabilityai.api.StabilityAiApi;
95 98 import org.springframework.ai.vectorstore.SimpleVectorStore;
96 99 import org.springframework.ai.vectorstore.VectorStore;
97 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 105 import org.springframework.ai.vectorstore.observation.DefaultVectorStoreObservationConvention;
99 106 import org.springframework.ai.vectorstore.observation.VectorStoreObservationConvention;
100 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 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 113 import org.springframework.ai.zhipuai.*;
103 114 import org.springframework.ai.zhipuai.api.ZhiPuAiApi;
104 115 import org.springframework.ai.zhipuai.api.ZhiPuAiImageApi;
... ... @@ -190,7 +201,7 @@ public class AiModelFactoryImpl implements AiModelFactory {
190 201 case XING_HUO:
191 202 return SpringUtil.getBean(XingHuoChatModel.class);
192 203 case BAI_CHUAN:
193   - return SpringUtil.getBean(AzureOpenAiChatModel.class);
  204 + return SpringUtil.getBean(BaiChuanChatModel.class);
194 205 case OPENAI:
195 206 return SpringUtil.getBean(OpenAiChatModel.class);
196 207 case AZURE_OPENAI:
... ... @@ -319,27 +330,34 @@ public class AiModelFactoryImpl implements AiModelFactory {
319 330 // ========== 各种创建 spring-ai 客户端的方法 ==========
320 331  
321 332 /**
322   - * 可参考 {@link DashScopeAutoConfiguration} 的 dashscopeChatModel 方法
  333 + * 可参考 {@link DashScopeChatAutoConfiguration} 的 dashscopeChatModel 方法
323 334 */
324 335 private static DashScopeChatModel buildTongYiChatModel(String key) {
325   - DashScopeApi dashScopeApi = new DashScopeApi(key);
  336 + DashScopeApi dashScopeApi = DashScopeApi.builder().apiKey(key).build();
326 337 DashScopeChatOptions options = DashScopeChatOptions.builder().withModel(DashScopeApi.DEFAULT_CHAT_MODEL)
327 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 349 private static DashScopeImageModel buildTongYiImagesModel(String key) {
335 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 359 private static QianFanChatModel buildYiYanChatModel(String key) {
  360 + // TODO spring ai qianfan 有 bug,无法使用 https://github.com/spring-ai-community/qianfan/issues/6
343 361 List<String> keys = StrUtil.split(key, '|');
344 362 Assert.equals(keys.size(), 2, "YiYanChatClient 的密钥需要 (appKey|secretKey) 格式");
345 363 String appKey = keys.get(0);
... ... @@ -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 372 private QianFanImageModel buildQianFanImageModel(String key) {
  373 + // TODO spring ai qianfan 有 bug,无法使用 https://github.com/spring-ai-community/qianfan/issues/6
355 374 List<String> keys = StrUtil.split(key, '|');
356 375 Assert.equals(keys.size(), 2, "YiYanChatClient 的密钥需要 (appKey|secretKey) 格式");
357 376 String appKey = keys.get(0);
... ... @@ -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 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 421 }
398 422  
399 423 /**
400   - * 可参考 {@link ZhiPuAiAutoConfiguration} 的 zhiPuAiChatModel 方法
  424 + * 可参考 {@link ZhiPuAiChatAutoConfiguration} 的 zhiPuAiChatModel 方法
401 425 */
402 426 private ZhiPuAiChatModel buildZhiPuChatModel(String apiKey, String url) {
403 427 ZhiPuAiApi zhiPuAiApi = StrUtil.isEmpty(url) ? new ZhiPuAiApi(apiKey)
404 428 : new ZhiPuAiApi(url, apiKey);
405 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 437 private ZhiPuAiImageModel buildZhiPuAiImageModel(String apiKey, String url) {
413 438 ZhiPuAiImageApi zhiPuAiApi = StrUtil.isEmpty(url) ? new ZhiPuAiImageApi(apiKey)
... ... @@ -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 446 private MiniMaxChatModel buildMiniMaxChatModel(String apiKey, String url) {
422 447 MiniMaxApi miniMaxApi = StrUtil.isEmpty(url) ? new MiniMaxApi(apiKey)
423 448 : new MiniMaxApi(url, apiKey);
424 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 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 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 488 }
457 489  
458 490 /**
459   - * 可参考 {@link OpenAiAutoConfiguration} 的 openAiChatModel 方法
  491 + * 可参考 {@link OpenAiChatAutoConfiguration} 的 openAiChatModel 方法
460 492 */
461 493 private static OpenAiChatModel buildOpenAiChatModel(String openAiToken, String url) {
462 494 url = StrUtil.blankToDefault(url, OpenAiApiConstants.DEFAULT_BASE_URL);
463 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 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 518 private OpenAiImageModel buildOpenAiImageModel(String openAiToken, String url) {
488 519 url = StrUtil.blankToDefault(url, OpenAiApiConstants.DEFAULT_BASE_URL);
... ... @@ -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 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 553 // ========== 各种创建 EmbeddingModel 的方法 ==========
520 554  
521 555 /**
522   - * 可参考 {@link DashScopeAutoConfiguration} 的 dashscopeEmbeddingModel 方法
  556 + * 可参考 {@link DashScopeEmbeddingAutoConfiguration} 的 dashscopeEmbeddingModel 方法
523 557 */
524 558 private DashScopeEmbeddingModel buildTongYiEmbeddingModel(String apiKey, String model) {
525   - DashScopeApi dashScopeApi = new DashScopeApi(apiKey);
  559 + DashScopeApi dashScopeApi = DashScopeApi.builder().apiKey(apiKey).build();
526 560 DashScopeEmbeddingOptions dashScopeEmbeddingOptions = DashScopeEmbeddingOptions.builder().withModel(model).build();
527 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 567 private ZhiPuAiEmbeddingModel buildZhiPuEmbeddingModel(String apiKey, String url, String model) {
534 568 ZhiPuAiApi zhiPuAiApi = StrUtil.isEmpty(url) ? new ZhiPuAiApi(apiKey)
... ... @@ -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 577 private EmbeddingModel buildMiniMaxEmbeddingModel(String apiKey, String url, String model) {
544 578 MiniMaxApi miniMaxApi = StrUtil.isEmpty(url)? new MiniMaxApi(apiKey)
... ... @@ -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 587 private QianFanEmbeddingModel buildYiYanEmbeddingModel(String key, String model) {
554 588 List<String> keys = StrUtil.split(key, '|');
... ... @@ -561,13 +595,16 @@ public class AiModelFactoryImpl implements AiModelFactory {
561 595 }
562 596  
563 597 private OllamaEmbeddingModel buildOllamaEmbeddingModel(String url, String model) {
564   - OllamaApi ollamaApi = new OllamaApi(url);
  598 + OllamaApi ollamaApi = OllamaApi.builder().baseUrl(url).build();
565 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 609 private OpenAiEmbeddingModel buildOpenAiEmbeddingModel(String openAiToken, String url, String model) {
573 610 url = StrUtil.blankToDefault(url, OpenAiApiConstants.DEFAULT_BASE_URL);
... ... @@ -576,21 +613,19 @@ public class AiModelFactoryImpl implements AiModelFactory {
576 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 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 625 // 获取 AzureOpenAiChatProperties 对象
591 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 631 // ========== 各种创建 VectorStore 的方法 ==========
... ... @@ -655,12 +690,12 @@ public class AiModelFactoryImpl implements AiModelFactory {
655 690 Map<String, Class<?>> metadataFields) {
656 691 // 创建 JedisPooled 对象
657 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 695 // 创建 RedisVectorStoreProperties 对象
660   - RedisVectorStoreAutoConfiguration configuration = new RedisVectorStoreAutoConfiguration();
661 696 RedisVectorStoreProperties properties = SpringUtil.getBean(RedisVectorStoreProperties.class);
662 697 RedisVectorStore redisVectorStore = RedisVectorStore.builder(jedisPooled, embeddingModel)
663   - .indexName(properties.getIndex()).prefix(properties.getPrefix())
  698 + .indexName(properties.getIndexName()).prefix(properties.getPrefix())
664 699 .initializeSchema(properties.isInitializeSchema())
665 700 .metadataFields(convertList(metadataFields.entrySet(), entry -> {
666 701 String fieldName = entry.getKey();
... ... @@ -730,10 +765,12 @@ public class AiModelFactoryImpl implements AiModelFactory {
730 765  
731 766 private static ObjectProvider<VectorStoreObservationConvention> getCustomObservationConvention() {
732 767 return new ObjectProvider<>() {
  768 +
733 769 @Override
734 770 public VectorStoreObservationConvention getObject() throws BeansException {
735 771 return new DefaultVectorStoreObservationConvention();
736 772 }
  773 +
737 774 };
738 775 }
739 776  
... ... @@ -745,8 +782,15 @@ public class AiModelFactoryImpl implements AiModelFactory {
745 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 89 var observationContext = ImageModelObservationContext.builder()
90 90 .imagePrompt(imagePrompt)
91 91 .provider(SiliconFlowApiConstants.PROVIDER_NAME)
92   - .requestOptions(imagePrompt.getOptions())
  92 + .imagePrompt(imagePrompt)
93 93 .build();
94 94  
95 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 import cn.hutool.core.util.StrUtil;
10 10 import cn.hutool.extra.spring.SpringUtil;
11 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 12 import cn.iocoder.yudao.framework.common.pojo.PageResult;
16 13 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
17 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 21 import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO;
25 22 import cn.iocoder.yudao.module.ai.dal.mysql.image.AiImageMapper;
26 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 27 import cn.iocoder.yudao.module.ai.service.model.AiModelService;
28 28 import cn.iocoder.yudao.module.infra.api.file.FileApi;
29 29 import com.alibaba.cloud.ai.dashscope.image.DashScopeImageOptions;
30 30 import jakarta.annotation.Resource;
31 31 import lombok.extern.slf4j.Slf4j;
  32 +import org.springaicommunity.qianfan.QianFanImageOptions;
32 33 import org.springframework.ai.image.ImageModel;
33 34 import org.springframework.ai.image.ImageOptions;
34 35 import org.springframework.ai.image.ImagePrompt;
35 36 import org.springframework.ai.image.ImageResponse;
36 37 import org.springframework.ai.openai.OpenAiImageOptions;
37   -import org.springframework.ai.qianfan.QianFanImageOptions;
38 38 import org.springframework.ai.stabilityai.api.StabilityAiImageOptions;
39 39 import org.springframework.ai.zhipuai.ZhiPuAiImageOptions;
40 40 import org.springframework.scheduling.annotation.Async;
... ... @@ -140,10 +140,10 @@ public class AiImageServiceImpl implements AiImageService {
140 140 private static ImageOptions buildImageOptions(AiImageDrawReqVO draw, AiModelDO model) {
141 141 if (ObjUtil.equal(model.getPlatform(), AiPlatformEnum.OPENAI.getPlatform())) {
142 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 147 .build();
148 148 } else if (ObjUtil.equal(model.getPlatform(), AiPlatformEnum.SILICON_FLOW.getPlatform())) {
149 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 7 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
8 8 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
9 9 import com.fasterxml.jackson.annotation.JsonClassDescription;
  10 +import com.fasterxml.jackson.annotation.JsonProperty;
  11 +import com.fasterxml.jackson.annotation.JsonPropertyDescription;
10 12 import jakarta.annotation.Resource;
11 13 import lombok.AllArgsConstructor;
12 14 import lombok.Data;
... ... @@ -17,7 +19,7 @@ import org.springframework.stereotype.Component;
17 19 import java.util.function.BiFunction;
18 20  
19 21 /**
20   - * 工具:当前用户信息查询
  22 + * 工具:用户信息查询
21 23 *
22 24 * 同时,也是展示 ToolContext 上下文的使用
23 25 *
... ... @@ -31,8 +33,17 @@ public class UserProfileQueryToolFunction
31 33 private AdminUserApi adminUserApi;
32 34  
33 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 48 @Data
38 49 @AllArgsConstructor
... ... @@ -61,13 +72,19 @@ public class UserProfileQueryToolFunction
61 72  
62 73 @Override
63 74 public Response apply(Request request, ToolContext toolContext) {
64   - LoginUser loginUser = (LoginUser) toolContext.getContext().get(AiUtils.TOOL_CONTEXT_LOGIN_USER);
65 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 86 return TenantUtils.execute(tenantId, () -> {
70   - AdminUserRespDTO user = adminUserApi.getUser(loginUser.getId());
  87 + AdminUserRespDTO user = adminUserApi.getUser(request.getId());
71 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 2  
3 3 import cn.hutool.core.util.ObjUtil;
4 4 import cn.hutool.core.util.StrUtil;
5   -import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum;
6 5 import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
7 6 import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
  7 +import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum;
8 8 import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions;
  9 +import org.springaicommunity.moonshot.MoonshotChatOptions;
  10 +import org.springaicommunity.qianfan.QianFanChatOptions;
9 11 import org.springframework.ai.azure.openai.AzureOpenAiChatOptions;
10 12 import org.springframework.ai.chat.messages.*;
11 13 import org.springframework.ai.chat.prompt.ChatOptions;
12 14 import org.springframework.ai.minimax.MiniMaxChatOptions;
13   -import org.springframework.ai.moonshot.MoonshotChatOptions;
14 15 import org.springframework.ai.ollama.api.OllamaOptions;
15 16 import org.springframework.ai.openai.OpenAiChatOptions;
16   -import org.springframework.ai.qianfan.QianFanChatOptions;
17 17 import org.springframework.ai.zhipuai.ZhiPuAiChatOptions;
18 18  
19 19 import java.util.Collections;
... ... @@ -43,18 +43,18 @@ public class AiUtils {
43 43 switch (platform) {
44 44 case TONG_YI:
45 45 return DashScopeChatOptions.builder().withModel(model).withTemperature(temperature).withMaxToken(maxTokens)
46   - .withFunctions(toolNames).withToolContext(toolContext).build();
  46 + .withToolNames(toolNames).withToolContext(toolContext).build();
47 47 case YI_YAN:
48 48 return QianFanChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens).build();
49 49 case ZHI_PU:
50 50 return ZhiPuAiChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens)
51   - .functions(toolNames).toolContext(toolContext).build();
  51 + .toolNames(toolNames).toolContext(toolContext).build();
52 52 case MINI_MAX:
53 53 return MiniMaxChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens)
54   - .functions(toolNames).toolContext(toolContext).build();
  54 + .toolNames(toolNames).toolContext(toolContext).build();
55 55 case MOONSHOT:
56 56 return MoonshotChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens)
57   - .functions(toolNames).toolContext(toolContext).build();
  57 + .toolNames(toolNames).toolContext(toolContext).build();
58 58 case OPENAI:
59 59 case DEEP_SEEK: // 复用 OpenAI 客户端
60 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 2  
3 3 import com.azure.ai.openai.OpenAIClientBuilder;
4 4 import com.azure.core.credential.AzureKeyCredential;
5   -import com.azure.core.util.ClientOptions;
6 5 import org.junit.jupiter.api.Disabled;
7 6 import org.junit.jupiter.api.Test;
8 7 import org.springframework.ai.azure.openai.AzureOpenAiChatModel;
... ... @@ -17,7 +16,7 @@ import reactor.core.publisher.Flux;
17 16 import java.util.ArrayList;
18 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 22 * {@link AzureOpenAiChatModel} 集成测试
... ... @@ -29,10 +28,13 @@ public class AzureOpenAIChatModelTests {
29 28 // TODO @芋艿:晚点在调整
30 29 private final OpenAIClientBuilder openAiApi = new OpenAIClientBuilder()
31 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 39 @Test
38 40 @Disabled
... ...
yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/BaiChuanChatModelTests.java
1 1 package cn.iocoder.yudao.module.ai.framework.ai.core.model.chat;
2 2  
3 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 4 import org.junit.jupiter.api.Disabled;
6 5 import org.junit.jupiter.api.Test;
7 6 import org.springframework.ai.chat.messages.Message;
... ... @@ -35,7 +34,7 @@ public class BaiChuanChatModelTests {
35 34 .build())
36 35 .build();
37 36  
38   - private final DeepSeekChatModel chatModel = new DeepSeekChatModel(openAiChatModel);
  37 + private final BaiChuanChatModel chatModel = new BaiChuanChatModel(openAiChatModel);
39 38  
40 39 @Test
41 40 @Disabled
... ...
yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/DeepSeekChatModelTests.java
1 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 3 import org.junit.jupiter.api.Disabled;
5 4 import org.junit.jupiter.api.Test;
6 5 import org.springframework.ai.chat.messages.Message;
... ... @@ -8,9 +7,9 @@ import org.springframework.ai.chat.messages.SystemMessage;
8 7 import org.springframework.ai.chat.messages.UserMessage;
9 8 import org.springframework.ai.chat.model.ChatResponse;
10 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 13 import reactor.core.publisher.Flux;
15 14  
16 15 import java.util.ArrayList;
... ... @@ -23,19 +22,16 @@ import java.util.List;
23 22 */
24 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 28 .build())
31   - .defaultOptions(OpenAiChatOptions.builder()
  29 + .defaultOptions(DeepSeekChatOptions.builder()
32 30 .model("deepseek-chat") // 模型
33 31 .temperature(0.7)
34 32 .build())
35 33 .build();
36 34  
37   - private final DeepSeekChatModel chatModel = new DeepSeekChatModel(openAiChatModel);
38   -
39 35 @Test
40 36 @Disabled
41 37 public void testCall() {
... ...
yudao-module-ai/src/test/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/chat/LlamaChatModelTests.java
1 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 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 6 * {@link OllamaChatModel} 集成测试
... ... @@ -23,43 +9,43 @@ import java.util.List;
23 9 */
24 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 2  
3 3 import org.junit.jupiter.api.Disabled;
4 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 8 import org.springframework.ai.chat.messages.Message;
6 9 import org.springframework.ai.chat.messages.SystemMessage;
7 10 import org.springframework.ai.chat.messages.UserMessage;
8 11 import org.springframework.ai.chat.model.ChatResponse;
9 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 13 import reactor.core.publisher.Flux;
14 14  
15 15 import java.util.ArrayList;
... ... @@ -22,11 +22,15 @@ import java.util.List;
22 22 */
23 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 34 @Test
31 35 @Disabled
32 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 23 public class OllamaChatModelTests {
24 24  
25 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 29 .defaultOptions(OllamaOptions.builder()
28 30 // .model("qwen") // 模型(https://ollama.com/library/qwen)
29 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 25 private final OpenAiChatModel chatModel = OpenAiChatModel.builder()
26 26 .openAiApi(OpenAiApi.builder()
27 27 .baseUrl("https://api.holdai.top")
28   - .apiKey("sk-aN6nWn3fILjrgLFT0fC4Aa60B72e4253826c77B29dC94f17") // apiKey
  28 + .apiKey("sk-PytRecQlmjEteoa2RRN6cGnwslo72UUPLQVNEMS6K9yjbmpD") // apiKey
29 29 .build())
30 30 .defaultOptions(OpenAiChatOptions.builder()
31   - .model(OpenAiApi.ChatModel.GPT_4_O) // 模型
  31 + .model(OpenAiApi.ChatModel.GPT_4_1_NANO) // 模型
32 32 .temperature(0.7)
33 33 .build())
34 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 22 */
23 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 30 .withModel("qwen1.5-72b-chat") // 模型
29 31 // .withModel("deepseek-r1") // 模型(deepseek-r1)
30 32 // .withModel("deepseek-v3") // 模型(deepseek-v3)
31 33 // .withModel("deepseek-r1-distill-qwen-1.5b") // 模型(deepseek-r1-distill-qwen-1.5b)
32   - .build());
  34 + .build())
  35 + .build();
33 36  
34 37 @Test
35 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 2  
3 3 import org.junit.jupiter.api.Disabled;
4 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 8 import org.springframework.ai.chat.messages.Message;
6 9 import org.springframework.ai.chat.messages.UserMessage;
7 10 import org.springframework.ai.chat.model.ChatResponse;
8 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 12 import reactor.core.publisher.Flux;
13 13  
14 14 import java.util.ArrayList;
... ... @@ -23,9 +23,9 @@ import java.util.List;
23 23 public class YiYanChatModelTests {
24 24  
25 25 private final QianFanChatModel chatModel = new QianFanChatModel(
26   - new QianFanApi("qS8k8dYr2nXunagK4SSU8Xjj", "pHGbx51ql2f0hOyabQvSZezahVC3hh3e"), // 密钥
  26 + new QianFanApi("DGnyzREuaY7av7c38bOM9Ji2", "9aR8myflEOPDrEeLhoXv0FdqANOAyIZW"), // 密钥
27 27 QianFanChatOptions.builder()
28   - .model(QianFanApi.ChatModel.ERNIE_4_0_8K_Preview.getValue())
  28 + .model("ERNIE-4.5-8K-Preview")
29 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 18  
19 19 private final OpenAiImageModel imageModel = new OpenAiImageModel(OpenAiImageApi.builder()
20 20 .baseUrl("https://api.holdai.top") // apiKey
21   - .apiKey("sk-aN6nWn3fILjrgLFT0fC4Aa60B72e4253826c77B29dC94f17")
  21 + .apiKey("sk-PytRecQlmjEteoa2RRN6cGnwslo72UUPLQVNEMS6K9yjbmpD")
22 22 .build());
23 23  
24 24 @Test
... ... @@ -26,8 +26,8 @@ public class OpenAiImageModelTests {
26 26 public void testCall() {
27 27 // 准备参数
28 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 31 .build();
32 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 2  
3 3 import org.junit.jupiter.api.Disabled;
4 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 8 import org.springframework.ai.image.ImagePrompt;
6 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 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 31 public void testCall() {
32 32 // 准备参数
33 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 36 .build();
37 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 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 4 import io.swagger.v3.oas.annotations.media.Schema;
5 5 import lombok.Data;
6 6  
... ... @@ -27,4 +27,4 @@ public class BpmProcessExpressionRespVO {
27 27 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
28 28 private LocalDateTime createTime;
29 29  
30   -}
31 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 71 @Schema(description = "是否填写审批意见", example = "false")
72 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 73 private List<UserSimpleBaseVO> candidateUsers; // 只包含未生成 ApprovalTaskInfo 的用户列表
74 74  
75 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 40 ErrorCode PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS = new ErrorCode(1_009_004_004, "任务({})的候选人({})不存在");
41 41 ErrorCode PROCESS_INSTANCE_START_USER_CAN_START = new ErrorCode(1_009_004_005, "发起流程失败,你没有权限发起该流程");
42 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 44 ErrorCode PROCESS_INSTANCE_APPROVE_USER_SELECT_ASSIGNEES_NOT_CONFIG = new ErrorCode(1_009_004_007, "下一个任务({})的审批人未配置");
45 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 58 ErrorCode TASK_SIGN_DELETE_NO_PARENT = new ErrorCode(1_009_005_012, "任务减签失败,被减签的任务必须是通过加签生成的任务");
59 59 ErrorCode TASK_TRANSFER_FAIL_USER_REPEAT = new ErrorCode(1_009_005_013, "任务转办失败,转办人和当前审批人为同一人");
60 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 61 ErrorCode TASK_SIGNATURE_NOT_EXISTS = new ErrorCode(1_009_005_015, "签名不能为空!");
63 62 ErrorCode TASK_REASON_REQUIRE = new ErrorCode(1_009_005_016, "审批意见不能为空!");
64 63  
... ... @@ -74,6 +73,7 @@ public interface ErrorCodeConstants {
74 73 ErrorCode CATEGORY_NOT_EXISTS = new ErrorCode(1_009_012_000, "流程分类不存在");
75 74 ErrorCode CATEGORY_NAME_DUPLICATE = new ErrorCode(1_009_012_001, "流程分类名字【{}】重复");
76 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 78 // ========== BPM 流程监听器 1-009-013-000 ==========
79 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 35 // 50 ~ 条件分支
36 36 CONDITION_NODE(50, "条件", "sequenceFlow"), // 用于构建流转条件的表达式
37 37 CONDITION_BRANCH_NODE(51, "条件分支", "exclusiveGateway"),
38   - PARALLEL_BRANCH_NODE(52, "并行分支", "parallelGateway"),
  38 + PARALLEL_BRANCH_NODE(52, "并行分支", "inclusiveGateway"), // 并行分支使用包容网关实现,条件表达式结果设置为 true
39 39 INCLUSIVE_BRANCH_NODE(53, "包容分支", "inclusiveGateway"),
40 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 14 @AllArgsConstructor
15 15 public enum BpmTaskStatusEnum {
16 16  
  17 + SKIP(-2, "跳过"),
17 18 NOT_START(-1, "未开始"),
18 19 RUNNING(1, "审批中"),
19 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 18 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
19 19 import com.google.common.annotations.VisibleForTesting;
20 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 22 import org.flowable.engine.delegate.DelegateExecution;
26 23 import org.flowable.engine.runtime.ProcessInstance;
27 24  
... ... @@ -132,7 +129,7 @@ public class BpmTaskCandidateInvoker {
132 129 Long startUserId, String processDefinitionId, Map<String, Object> processVariables) {
133 130 // 如果是 CallActivity 子流程,不进行计算候选人
134 131 FlowElement flowElement = BpmnModelUtils.getFlowElementById(bpmnModel, activityId);
135   - if (flowElement instanceof CallActivity) {
  132 + if (flowElement instanceof CallActivity || flowElement instanceof SubProcess) {
136 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 24 * @author 芋道源码
25 25 */
26 26 @Component
  27 +@Deprecated // 仅仅是表达式的示例,建议使用 BpmTaskCandidateStartUserDeptLeaderStrategy 替代
27 28 public class BpmTaskAssignLeaderExpression {
28 29  
29 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 16 * @author 芋道源码
17 17 */
18 18 @Component
  19 +@Deprecated // 仅仅是表达式的示例,建议使用 BpmTaskCandidateStartUserStrategy 替代
19 20 public class BpmTaskAssignStartUserExpression {
20 21  
21 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 8 import lombok.extern.slf4j.Slf4j;
9 9 import org.flowable.bpmn.model.BpmnModel;
10 10 import org.flowable.common.engine.api.FlowableException;
  11 +import org.flowable.common.engine.impl.javax.el.PropertyNotFoundException;
11 12 import org.flowable.engine.delegate.DelegateExecution;
12 13 import org.springframework.stereotype.Component;
13 14  
... ... @@ -48,10 +49,12 @@ public class BpmTaskCandidateExpressionStrategy implements BpmTaskCandidateStrat
48 49 Object result = FlowableUtils.getExpressionValue(variables, param);
49 50 return CollectionUtils.toLinkedHashSet(Long.class, result);
50 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 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 4 import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
4 5 import com.google.common.collect.ImmutableSet;
5 6 import jakarta.annotation.Resource;
... ... @@ -37,18 +38,26 @@ public class BpmProcessInstanceEventListener extends AbstractFlowableEngineEvent
37 38  
38 39 @Override
39 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 46 @Override
44 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 54 protected void processCancelled(FlowableCancelledEvent event) {
  55 + // 特殊情况:当跳转到 EndEvent 流程实例未结束, 会执行 deleteProcessInstance 方法
50 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 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.util.ObjectUtil;
5 5 import cn.hutool.core.util.StrUtil;
  6 +import cn.hutool.json.JSONObject;
6 7 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
7 8 import cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventTypeEnum;
8 9 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
9 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 12 import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService;
11 13 import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService;
12 14 import com.google.common.collect.ImmutableSet;
... ... @@ -58,17 +60,20 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener {
58 60  
59 61 @Override
60 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 67 @Override
65 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 73 @Override
70 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 79 @Override
... ... @@ -94,6 +99,23 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener {
94 99 String processDefinitionId = event.getProcessDefinitionId();
95 100 BpmnModel bpmnModel = modelService.getBpmnModelByDefinitionId(processDefinitionId);
96 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 119 FlowElement element = BpmnModelUtils.getFlowElementById(bpmnModel, entity.getElementId());
98 120 if (!(element instanceof BoundaryEvent)) {
99 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 6 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
7 7 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
8 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 11 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
10 12 import cn.iocoder.yudao.module.bpm.enums.definition.BpmHttpRequestParamTypeEnum;
11 13 import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
12 14 import com.fasterxml.jackson.core.type.TypeReference;
13 15 import lombok.extern.slf4j.Slf4j;
14 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 18 import org.springframework.util.LinkedMultiValueMap;
19 19 import org.springframework.util.MultiValueMap;
20 20 import org.springframework.web.client.RestClientException;
... ... @@ -26,7 +26,7 @@ import java.util.Map;
26 26  
27 27 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
28 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 32 * 工作流发起 HTTP 请求工具类
... ... @@ -42,7 +42,6 @@ public class BpmHttpRequestUtils {
42 42 List<BpmSimpleModelNodeVO.HttpRequestParam> bodyParams,
43 43 Boolean handleResponse,
44 44 List<KeyValue<String, String>> response) {
45   - RestTemplate restTemplate = SpringUtils.getBean(RestTemplate.class);
46 45 BpmProcessInstanceService processInstanceService = SpringUtils.getBean(BpmProcessInstanceService.class);
47 46  
48 47 // 1.1 设置请求头
... ... @@ -51,6 +50,7 @@ public class BpmHttpRequestUtils {
51 50 MultiValueMap<String, String> body = buildHttpBody(processInstance, bodyParams);
52 51  
53 52 // 2. 发起请求
  53 + RestTemplate restTemplate = SpringUtils.getBean(RestTemplate.class);
54 54 ResponseEntity<String> responseEntity = sendHttpRequest(url, headers, body, restTemplate);
55 55  
56 56 // 3. 处理返回
... ... @@ -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 109 public static ResponseEntity<String> sendHttpRequest(String url,
82 110 MultiValueMap<String, String> headers,
83   - MultiValueMap<String, String> body,
  111 + Object body,
84 112 RestTemplate restTemplate) {
85   - HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(body, headers);
  113 + HttpEntity<Object> requestEntity = new HttpEntity<>(body, headers);
86 114 ResponseEntity<String> responseEntity;
87 115 try {
88 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 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 122 return responseEntity;
95 123 }
96 124  
97 125 public static MultiValueMap<String, String> buildHttpHeaders(ProcessInstance processInstance,
98 126 List<BpmSimpleModelNodeVO.HttpRequestParam> headerSettings) {
99   - Map<String, Object> processVariables = processInstance.getProcessVariables();
100 127 MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
101 128 headers.add(HEADER_TENANT_ID, processInstance.getTenantId());
  129 + Map<String, Object> processVariables = processInstance.getProcessVariables();
102 130 addHttpRequestParam(headers, headerSettings, processVariables);
103 131 return headers;
104 132 }
... ... @@ -108,7 +136,9 @@ public class BpmHttpRequestUtils {
108 136 Map<String, Object> processVariables = processInstance.getProcessVariables();
109 137 MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
110 138 addHttpRequestParam(body, bodySettings, processVariables);
111   - body.add("processInstanceId", processInstance.getId());
  139 + if (!body.containsKey("processInstanceId")) { // 避免重复添加
  140 + body.add("processInstanceId", processInstance.getId());
  141 + }
112 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 478 */
479 479 public static FlowElement getFlowElementById(BpmnModel model, String flowElementId) {
480 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 800 || currentElement instanceof EndEvent
797 801 || currentElement instanceof UserTask
798 802 || currentElement instanceof ServiceTask) {
799   - // 添加元素
  803 + // 添加节点
800 804 FlowNode flowNode = (FlowNode) currentElement;
801 805 resultElements.add(flowNode);
  806 +
802 807 // 遍历子节点
803 808 flowNode.getOutgoingFlows().forEach(
804 809 nextElement -> simulateNextFlowElements(nextElement.getTargetFlowElement(), variables, resultElements, visitElements));
... ... @@ -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 867 * @param currentElement 当前节点
... ... @@ -993,7 +1023,7 @@ public class BpmnModelUtils {
993 1023 * @return 是否满足条件
994 1024 */
995 1025 public static boolean evalConditionExpress(Map<String, Object> variables, String expression) {
996   - if (expression == null) {
  1026 + if (StrUtil.isEmpty(expression)) {
997 1027 return Boolean.FALSE;
998 1028 }
999 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 4 import cn.hutool.core.lang.Assert;
5 5 import cn.hutool.core.map.MapUtil;
6 6 import cn.hutool.core.util.*;
7   -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
8 7 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
9 8 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
10 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 16 import cn.iocoder.yudao.module.bpm.service.task.listener.BpmUserTaskListener;
18 17 import org.flowable.bpmn.BpmnAutoLayout;
19 18 import org.flowable.bpmn.constants.BpmnXMLConstants;
20   -import org.flowable.bpmn.model.Process;
21 19 import org.flowable.bpmn.model.*;
  20 +import org.flowable.bpmn.model.Process;
22 21 import org.flowable.engine.delegate.ExecutionListener;
23 22 import org.flowable.engine.delegate.TaskListener;
24 23  
25 24 import java.util.*;
26 25  
  26 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
27 27 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*;
28 28 import static cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils.*;
29 29 import static java.util.Arrays.asList;
... ... @@ -239,13 +239,13 @@ public class SimpleModelUtils {
239 239 // 3.1 分支有后续节点。即分支 1: A->B->C->D 的情况
240 240 if (isValidNode(conditionChildNode)) {
241 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 243 process.addFlowElement(sequenceFlow);
244 244 // 3.1.2 递归调用后续节点连线。例如说,建立 B->C->D 的连线
245 245 traverseNodeToBuildSequenceFlow(process, conditionChildNode, branchEndNodeId);
246 246 } else {
247 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 249 process.addFlowElement(sequenceFlow);
250 250 }
251 251 }
... ... @@ -464,6 +464,10 @@ public class SimpleModelUtils {
464 464 addReasonRequire(node.getReasonRequire(), userTask);
465 465 // 节点类型
466 466 addNodeType(node.getType(), userTask);
  467 + // 添加跳过表达式
  468 + if (StrUtil.isNotEmpty(node.getSkipExpression())) {
  469 + userTask.setSkipExpression(node.getSkipExpression());
  470 + }
467 471 return userTask;
468 472 }
469 473  
... ... @@ -591,17 +595,23 @@ public class SimpleModelUtils {
591 595  
592 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 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 608 // TODO @jason:setName
599 609  
600   - // 并行聚合网关由程序创建,前端不需要传入
601   - ParallelGateway joinParallelGateway = new ParallelGateway();
  610 + // 合并网关 由程序创建,前端不需要传入
  611 + InclusiveGateway joinParallelGateway = new InclusiveGateway();
602 612 joinParallelGateway.setId(buildGatewayJoinId(node.getId()));
603 613 // TODO @jason:setName
604   - return CollUtil.newArrayList(parallelGateway, joinParallelGateway);
  614 + return CollUtil.newArrayList(inclusiveGateway, joinParallelGateway);
605 615 }
606 616  
607 617 @Override
... ... @@ -652,8 +662,14 @@ public class SimpleModelUtils {
652 662 }
653 663  
654 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 673 return buildBpmnSequenceFlow(sourceId, targetId, node.getId(), node.getName(), conditionExpression);
658 674 }
659 675 }
... ... @@ -662,7 +678,6 @@ public class SimpleModelUtils {
662 678 * 构造条件表达式
663 679 */
664 680 public static String buildConditionExpression(BpmSimpleModelNodeVO.ConditionSetting conditionSetting) {
665   - // 并行网关不需要设置条件
666 681 if (conditionSetting == null) {
667 682 return null;
668 683 }
... ... @@ -684,15 +699,18 @@ public class SimpleModelUtils {
684 699 if (conditionGroups == null || CollUtil.isEmpty(conditionGroups.getConditions())) {
685 700 return null;
686 701 }
687   - List<String> strConditionGroups = CollectionUtils.convertList(conditionGroups.getConditions(), item -> {
  702 + List<String> strConditionGroups = convertList(conditionGroups.getConditions(), item -> {
688 703 if (CollUtil.isEmpty(item.getRules())) {
689 704 return "";
690 705 }
691 706 // 构造规则表达式
692   - List<String> list = CollectionUtils.convertList(item.getRules(), (rule) -> {
  707 + List<String> list = convertList(item.getRules(), (rule) -> {
693 708 String rightSide = NumberUtil.isNumber(rule.getRightSide()) ? rule.getRightSide()
694 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 716 Boolean and = item.getAnd();
... ... @@ -954,7 +972,7 @@ public class SimpleModelUtils {
954 972 || nodeType == BpmSimpleModelNodeTypeEnum.COPY_NODE
955 973 || nodeType == BpmSimpleModelNodeTypeEnum.CHILD_PROCESS
956 974 || nodeType == BpmSimpleModelNodeTypeEnum.END_NODE) {
957   - // 添加元素
  975 + // 添加此节点
958 976 resultNodes.add(currentNode);
959 977 }
960 978  
... ... @@ -1000,6 +1018,16 @@ public class SimpleModelUtils {
1000 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 1031 public static boolean evalConditionExpress(Map<String, Object> variables, BpmSimpleModelNodeVO.ConditionSetting conditionSetting) {
1004 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 34 @Resource
35 35 private BpmCategoryMapper bpmCategoryMapper;
36 36  
  37 + @Resource
  38 + private BpmModelService modelService;
  39 +
37 40 @Override
38 41 public Long createCategory(BpmCategorySaveReqVO createReqVO) {
39 42 // 校验唯一
... ... @@ -77,15 +80,22 @@ public class BpmCategoryServiceImpl implements BpmCategoryService {
77 80 @Override
78 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 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 96 throw exception(CATEGORY_NOT_EXISTS);
88 97 }
  98 + return category;
89 99 }
90 100  
91 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 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 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 89 }
90 90  
91 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 100 @Transactional(rollbackFor = Exception.class)
93 101 public String createModel(@Valid BpmModelSaveReqVO createReqVO) {
94 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 4 import cn.hutool.core.collection.ListUtil;
5 5 import cn.hutool.core.date.DateUtil;
6 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 8 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  9 +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
12 10 import cn.iocoder.yudao.framework.common.util.date.DateUtils;
13 11 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
14 12 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
... ... @@ -61,12 +59,15 @@ import org.flowable.task.api.history.HistoricTaskInstance;
61 59 import org.springframework.context.annotation.Lazy;
62 60 import org.springframework.stereotype.Service;
63 61 import org.springframework.transaction.annotation.Transactional;
  62 +import org.springframework.transaction.support.TransactionSynchronization;
  63 +import org.springframework.transaction.support.TransactionSynchronizationManager;
64 64 import org.springframework.validation.annotation.Validated;
65 65  
66 66 import java.util.*;
67 67  
68 68 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
69 69 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
  70 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
70 71 import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ActivityNode;
71 72 import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*;
72 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 265 // 3. 获取下一个将要执行的节点集合
265 266 FlowElement flowElement = bpmnModel.getFlowElement(task.getTaskDefinitionKey());
266 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 271 .setName(node.getName()).setNodeType(BpmSimpleModelNodeTypeEnum.APPROVE_NODE.getType())
269 272 .setStatus(BpmTaskStatusEnum.RUNNING.getStatus())
270 273 .setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(node))
... ... @@ -395,7 +398,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
395 398 ? BpmSimpleModelNodeTypeEnum.START_USER_NODE.getType()
396 399 : ObjUtil.defaultIfNull(parseNodeType(flowNode), // 目的:解决“办理节点”的识别
397 400 BpmSimpleModelNodeTypeEnum.APPROVE_NODE.getType()))
398   - .setStatus(FlowableUtils.getTaskStatus(task))
  401 + .setStatus(getEndActivityNodeStatus(task))
399 402 .setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(flowNode))
400 403 .setStartTime(DateUtils.of(task.getCreateTime())).setEndTime(DateUtils.of(task.getEndTime()))
401 404 .setTasks(singletonList(BpmProcessInstanceConvert.INSTANCE.buildApprovalTaskInfo(task)));
... ... @@ -449,7 +452,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
449 452 .setNodeType(BpmSimpleModelNodeTypeEnum.CHILD_PROCESS.getType()).setStatus(processInstanceStatus)
450 453 .setStartTime(DateUtils.of(activity.getStartTime()))
451 454 .setEndTime(DateUtils.of(activity.getEndTime()))
452   - .setProcessInstanceId(activity.getProcessInstanceId());
  455 + .setProcessInstanceId(activity.getCalledProcessInstanceId());
453 456 approvalNodes.add(callActivity);
454 457 }
455 458 });
... ... @@ -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 480 private List<ActivityNode> getRunApproveNodeList(Long startUserId,
... ... @@ -521,7 +536,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
521 536 activityNode.setCandidateUserIds(CollUtil.sub(candidateUserIds, index + 1, candidateUserIds.size()));
522 537 }
523 538 if (BpmSimpleModelNodeTypeEnum.CHILD_PROCESS.getType().equals(activityNode.getNodeType())) {
524   - activityNode.setProcessInstanceId(firstActivity.getProcessInstanceId());
  539 + activityNode.setProcessInstanceId(firstActivity.getCalledProcessInstanceId());
525 540 }
526 541 return activityNode;
527 542 });
... ... @@ -562,10 +577,14 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
562 577 if (runActivityIds.contains(node.getId())) {
563 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 585 ActivityNode activityNode = new ActivityNode().setId(node.getId()).setName(node.getName())
567 586 .setNodeType(node.getType()).setCandidateStrategy(node.getCandidateStrategy())
568   - .setStatus(BpmTaskStatusEnum.NOT_START.getStatus());
  587 + .setStatus(status);
569 588  
570 589 // 1. 开始节点/审批节点
571 590 if (ObjectUtils.equalsAny(node.getType(),
... ... @@ -605,8 +624,13 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
605 624 if (runActivityIds.contains(node.getId())) {
606 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 632 ActivityNode activityNode = new ActivityNode().setId(node.getId())
609   - .setStatus(BpmTaskStatusEnum.NOT_START.getStatus());
  633 + .setStatus(status);
610 634  
611 635 // 1. 开始节点
612 636 if (node instanceof StartEvent) {
... ... @@ -771,17 +795,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
771 795 processInstanceBuilder.predefineProcessInstanceId(processIdRedisDAO.generate(processIdRule));
772 796 }
773 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 799 // 3.3 发起流程实例
786 800 ProcessInstance instance = processInstanceBuilder.start();
787 801 return instance.getId();
... ... @@ -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 853 @Override
821 854 public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) {
822 855 // 1.1 校验流程实例存在
... ... @@ -833,7 +866,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
833 866 .getProcessDefinitionInfo(instance.getProcessDefinitionId());
834 867 Assert.notNull(processDefinitionInfo, "流程定义({})不存在", processDefinitionInfo);
835 868 if (processDefinitionInfo.getAllowCancelRunningProcess() != null // 防止未配置 AllowCancelRunningProcess , 默认为可取消
836   - && Boolean.FALSE.equals(processDefinitionInfo.getAllowCancelRunningProcess())) {
  869 + && BooleanUtil.isFalse(processDefinitionInfo.getAllowCancelRunningProcess())) {
837 870 throw exception(PROCESS_INSTANCE_CANCEL_FAIL_NOT_ALLOW);
838 871 }
839 872 // 1.4 子流程不允许取消
... ... @@ -900,64 +933,77 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
900 933  
901 934 @Override
902 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 230 if (StrUtil.isNotBlank(pageVO.getName())) {
231 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 238 long count = taskQuery.count();
239 239 if (count == 0) {
... ... @@ -244,6 +244,12 @@ public class BpmTaskServiceImpl implements BpmTaskService {
244 244 // 特殊:强制移除自动完成的“发起人”节点
245 245 // 补充说明:由于 taskQuery 无法方面的过滤,所以暂时通过内存过滤
246 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 253 return new PageResult<>(tasks, count);
248 254 }
249 255  
... ... @@ -259,16 +265,22 @@ public class BpmTaskServiceImpl implements BpmTaskService {
259 265 if (StrUtil.isNotEmpty(pageVO.getCategory())) {
260 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 273 long count = taskQuery.count();
268 274 if (count == 0) {
269 275 return PageResult.empty();
270 276 }
271 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 284 return new PageResult<>(tasks, count);
273 285 }
274 286  
... ... @@ -886,7 +898,9 @@ public class BpmTaskServiceImpl implements BpmTaskService {
886 898 if (!returnTaskKeyList.contains(task.getTaskDefinitionKey())) {
887 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 906 if (isAssignUserTask(userId, task)) { // 情况一:自己的任务,进行 RETURN 标记
... ... @@ -933,7 +947,10 @@ public class BpmTaskServiceImpl implements BpmTaskService {
933 947 BpmCommentTypeEnum.DELEGATE_START.formatComment(currentUser.getNickname(), delegateUser.getNickname(), reqVO.getReason()));
934 948  
935 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 954 // 3.2 执行委派,将任务委派给 delegateUser
938 955 taskService.delegateTask(taskId, reqVO.getDelegateUserId().toString());
939 956 // 补充说明:委托不单独设置状态。如果需要,可通过 Task 的 DelegationState 字段,判断是否为 DelegationState.PENDING 委托中
... ... @@ -959,7 +976,10 @@ public class BpmTaskServiceImpl implements BpmTaskService {
959 976 BpmCommentTypeEnum.TRANSFER.formatComment(currentUser.getNickname(), assigneeUser.getNickname(), reqVO.getReason()));
960 977  
961 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 983 // 3.2 执行转派(审批人),将任务转派给 assigneeUser
964 984 // 委托( delegate)和转派(transfer)的差别,就在这块的调用!!!!
965 985 taskService.setAssignee(taskId, reqVO.getAssigneeUserId().toString());
... ... @@ -1367,7 +1387,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
1367 1387 PROCESS_INSTANCE_VARIABLE_SKIP_START_USER_NODE, String.class));
1368 1388 if (userTaskElement.getId().equals(START_USER_NODE_ID)
1369 1389 && (skipStartUserNodeFlag == null // 目的:一般是“主流程”,发起人节点,自动通过审核
1370   - || Boolean.TRUE.equals(skipStartUserNodeFlag)) // 目的:一般是“子流程”,发起人节点,按配置自动通过审核
  1390 + || BooleanUtil.isTrue(skipStartUserNodeFlag)) // 目的:一般是“子流程”,发起人节点,按配置自动通过审核
1371 1391 && ObjUtil.notEqual(returnTaskFlag, Boolean.TRUE)) {
1372 1392 getSelf().approveTask(Long.valueOf(task.getAssignee()), new BpmTaskApproveReqVO().setId(task.getId())
1373 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 85 // 2.2 使用表单值,并兜底字符串转 Long 失败时使用主流程发起人
86 86 try {
87 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 8 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO;
9 9 import cn.iocoder.yudao.module.bpm.dal.mysql.category.BpmCategoryMapper;
10 10 import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryServiceImpl;
  11 +import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService;
11 12 import jakarta.annotation.Resource;
12 13 import org.junit.jupiter.api.Test;
13 14 import org.springframework.context.annotation.Import;
  15 +import org.springframework.test.context.bean.override.mockito.MockitoBean;
14 16  
15 17 import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
16 18 import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime;
... ... @@ -32,6 +34,9 @@ public class BpmCategoryServiceImplTest extends BaseDbUnitTest {
32 34 @Resource
33 35 private BpmCategoryServiceImpl categoryService;
34 36  
  37 + @MockitoBean
  38 + private BpmModelService modelService;
  39 +
35 40 @Resource
36 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 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 5 import io.swagger.v3.oas.annotations.media.Schema;
6 6 import lombok.AllArgsConstructor;
7 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 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 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 8 import io.swagger.v3.oas.annotations.media.Schema;
9 9 import lombok.Data;
10 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 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 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 8 import io.swagger.v3.oas.annotations.media.Schema;
9 9 import lombok.Data;
10 10 import lombok.ToString;
... ...
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/contract/CrmContractRespVO.java
1 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 5 import io.swagger.v3.oas.annotations.media.Schema;
6 6 import lombok.AllArgsConstructor;
7 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 5 import cn.iocoder.yudao.framework.excel.core.convert.AreaConvert;
6 6 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
7 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 9 import lombok.AllArgsConstructor;
10 10 import lombok.Builder;
11 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 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 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 8 import io.swagger.v3.oas.annotations.media.Schema;
9 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 2  
3 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 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 7 import io.swagger.v3.oas.annotations.media.Schema;
8 8 import lombok.Data;
9 9  
... ... @@ -61,4 +61,4 @@ public class CrmFollowUpRecordRespVO {
61 61 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
62 62 private LocalDateTime createTime;
63 63  
64   -}
65 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 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 4 import io.swagger.v3.oas.annotations.media.Schema;
5 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 14 import cn.iocoder.yudao.module.crm.enums.product.CrmProductStatusEnum;
15 15 import cn.iocoder.yudao.module.crm.service.product.CrmProductService;
16 16 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  17 +import com.fhs.core.trans.anno.TransMethodResult;
17 18 import io.swagger.v3.oas.annotations.Operation;
18 19 import io.swagger.v3.oas.annotations.Parameter;
19 20 import io.swagger.v3.oas.annotations.tags.Tag;
... ... @@ -70,6 +71,7 @@ public class CrmProductController {
70 71 @Operation(summary = "获得产品")
71 72 @Parameter(name = "id", description = "编号", required = true, example = "1024")
72 73 @PreAuthorize("@ss.hasPermission('crm:product:query')")
  74 + @TransMethodResult
73 75 public CommonResult<CrmProductRespVO> getProduct(@RequestParam("id") Long id) {
74 76 CrmProductDO product = productService.getProduct(id);
75 77 return success(BeanUtils.toBean(product, CrmProductRespVO.class));
... ... @@ -86,6 +88,7 @@ public class CrmProductController {
86 88 @GetMapping("/page")
87 89 @Operation(summary = "获得产品分页")
88 90 @PreAuthorize("@ss.hasPermission('crm:product:query')")
  91 + @TransMethodResult
89 92 public CommonResult<PageResult<CrmProductRespVO>> getProductPage(@Valid CrmProductPageReqVO pageVO) {
90 93 PageResult<CrmProductDO> pageResult = productService.getProductPage(pageVO);
91 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 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 4 import io.swagger.v3.oas.annotations.media.Schema;
5 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 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 5 import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductCategoryDO;
6 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 9 import com.fhs.core.trans.anno.Trans;
10 10 import com.fhs.core.trans.constant.TransType;
11 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 1 package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan;
2 2  
3 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 6 import io.swagger.v3.oas.annotations.media.Schema;
7 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 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 5 import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractRespVO;
6 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 9 import io.swagger.v3.oas.annotations.media.Schema;
10 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 142 updateBusinessProduct(updateObj.getId(), businessProducts);
143 143  
144 144 // 3. 记录操作日志上下文
  145 + updateReqVO.setOwnerUserId(oldBusiness.getOwnerUserId()); // 避免操作日志出现“删除负责人”的情况
145 146 LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldBusiness, CrmBusinessSaveReqVO.class));
146 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 92 @Transactional(rollbackFor = Exception.class)
93 93 @LogRecord(type = CRM_CLUE_TYPE, subType = CRM_CLUE_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}",
94 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 98 // 1.1 校验线索是否存在
99   - CrmClueDO oldClue = validateClueExists(updateReq.getId());
  99 + CrmClueDO oldClue = validateClueExists(updateReqVO.getId());
100 100 // 1.2 校验关联数据
101   - validateRelationDataExists(updateReq);
  101 + validateRelationDataExists(updateReqVO);
102 102  
103 103 // 2. 更新线索
104   - CrmClueDO updateObj = BeanUtils.toBean(updateReq, CrmClueDO.class);
  104 + CrmClueDO updateObj = BeanUtils.toBean(updateReqVO, CrmClueDO.class);
105 105 clueMapper.updateById(updateObj);
106 106  
107 107 // 3. 记录操作日志上下文
  108 + updateReqVO.setOwnerUserId(oldClue.getOwnerUserId()); // 避免操作日志出现“删除负责人”的情况
108 109 LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldClue, CrmCustomerSaveReqVO.class));
109 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 114 contactMapper.updateById(updateObj);
115 115  
116 116 // 3. 记录操作日志
  117 + updateReqVO.setOwnerUserId(oldContact.getOwnerUserId()); // 避免操作日志出现“删除负责人”的情况
117 118 LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldContact, CrmContactSaveReqVO.class));
118 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 140 Assert.notNull(updateReqVO.getId(), "合同编号不能为空");
141 141 updateReqVO.setOwnerUserId(null); // 不允许更新的字段
142 142 // 1.1 校验存在
143   - CrmContractDO contract = validateContractExists(updateReqVO.getId());
  143 + CrmContractDO oldContract = validateContractExists(updateReqVO.getId());
144 144 // 1.2 只有草稿、审批中,可以编辑;
145   - if (!ObjectUtils.equalsAny(contract.getAuditStatus(), CrmAuditStatusEnum.DRAFT.getStatus(),
  145 + if (!ObjectUtils.equalsAny(oldContract.getAuditStatus(), CrmAuditStatusEnum.DRAFT.getStatus(),
146 146 CrmAuditStatusEnum.PROCESS.getStatus())) {
147 147 throw exception(CONTRACT_UPDATE_FAIL_NOT_DRAFT);
148 148 }
... ... @@ -159,8 +159,9 @@ public class CrmContractServiceImpl implements CrmContractService {
159 159 updateContractProduct(updateReqVO.getId(), contractProducts);
160 160  
161 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 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 137 customerMapper.updateById(updateObj);
138 138  
139 139 // 3. 记录操作日志上下文
  140 + updateReqVO.setOwnerUserId(oldCustomer.getOwnerUserId()); // 避免操作日志出现“删除负责人”的情况
140 141 LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldCustomer, CrmCustomerSaveReqVO.class));
141 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 210 CrmPermissionDO oldPermission = permissionMapper.selectByBizTypeAndBizIdByUserId(
211 211 transferReqBO.getBizType(), transferReqBO.getBizId(), transferReqBO.getUserId());
212 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 215 throw exception(CRM_PERMISSION_DENIED, bizTypeName);
216 216 }
217 217 // 1.1 校验转移对象是否已经是该负责人
218   - if (ObjUtil.equal(transferReqBO.getNewOwnerUserId(), oldPermission.getUserId())) {
  218 + if (oldPermission != null && ObjUtil.equal(transferReqBO.getNewOwnerUserId(), oldPermission.getUserId())) {
219 219 throw exception(CRM_PERMISSION_MODEL_TRANSFER_FAIL_OWNER_USER_EXISTS, bizTypeName);
220 220 }
221 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 104 receivablePlanMapper.updateById(updateObj);
105 105  
106 106 // 3. 记录操作日志上下文
  107 + updateReqVO.setOwnerUserId(oldReceivablePlan.getOwnerUserId()); // 避免操作日志出现“删除负责人”的情况
107 108 LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldReceivablePlan, CrmReceivablePlanSaveReqVO.class));
108 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 162 Assert.notNull(updateReqVO.getId(), "回款编号不能为空");
163 163 updateReqVO.setOwnerUserId(null).setCustomerId(null).setContractId(null).setPlanId(null); // 不允许修改的字段
164 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 168 // 1.2 校验可回款金额超过上限
169 169 validateReceivablePriceExceedsLimit(updateReqVO);
170 170  
171 171 // 1.3 只有草稿、审批中,可以编辑;
172   - if (!ObjectUtils.equalsAny(receivable.getAuditStatus(), CrmAuditStatusEnum.DRAFT.getStatus(),
  172 + if (!ObjectUtils.equalsAny(oldReceivable.getAuditStatus(), CrmAuditStatusEnum.DRAFT.getStatus(),
173 173 CrmAuditStatusEnum.PROCESS.getStatus())) {
174 174 throw exception(RECEIVABLE_UPDATE_FAIL_EDITING_PROHIBITED);
175 175 }
... ... @@ -179,9 +179,10 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
179 179 receivableMapper.updateById(updateObj);
180 180  
181 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 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 2  
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.extra.spring.SpringUtil;
  5 +import cn.iocoder.yudao.framework.common.biz.system.permission.PermissionCommonApi;
5 6 import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO;
6 7 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
7 8 import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum;
8 9 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
9   -import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
10 10 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
11 11 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
12 12 import cn.iocoder.yudao.module.system.enums.permission.RoleCodeEnum;
... ... @@ -32,7 +32,7 @@ public class CrmPermissionUtils {
32 32 * @return 是/否
33 33 */
34 34 public static boolean isCrmAdmin() {
35   - PermissionApi permissionApi = SpringUtil.getBean(PermissionApi.class);
  35 + PermissionCommonApi permissionApi = SpringUtil.getBean(PermissionCommonApi.class);
36 36 return permissionApi.hasAnyRoles(getLoginUserId(), RoleCodeEnum.CRM_ADMIN.getCode());
37 37 }
38 38  
... ...