Commit 53d5c394b6979dac4cfe74eb816a1ca9e98c55eb
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.
pom.xml
| ... | ... | @@ -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 > ../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
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<T> extends MPJBaseMapper<T> { |
| 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<T> extends MPJLambdaWrapper<T> { |
| 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<T> extends MPJLambdaWrapper<T> { |
| 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<T> extends MPJLambdaWrapper<T> { |
| 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<T> extends MPJLambdaWrapper<T> { |
| 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<T> extends MPJLambdaWrapper<T> { |
| 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<T> extends MPJLambdaWrapper<T> { |
| 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<T> extends MPJLambdaWrapper<T> { |
| 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<Integer> { |
| 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
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
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/expression/BpmTaskAssignStartUserExpression.java
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
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
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 | ... | ... |