diff --git a/sql/postgresql/ruoyi-vue-pro.sql b/sql/postgresql/ruoyi-vue-pro.sql index f85884776..859c6c383 100644 --- a/sql/postgresql/ruoyi-vue-pro.sql +++ b/sql/postgresql/ruoyi-vue-pro.sql @@ -33,7 +33,7 @@ INSERT INTO dual VALUES (1); DROP TABLE IF EXISTS infra_api_access_log; CREATE TABLE infra_api_access_log ( - id int8 NOT NULL, + id int8 NOT NULL DEFAULT NEXTVAL('infra_api_access_log_seq'), trace_id varchar(64) NOT NULL DEFAULT '', user_id int8 NOT NULL DEFAULT 0, user_type int2 NOT NULL DEFAULT 0, @@ -102,7 +102,7 @@ CREATE SEQUENCE infra_api_access_log_seq DROP TABLE IF EXISTS infra_api_error_log; CREATE TABLE infra_api_error_log ( - id int8 NOT NULL, + id int8 NOT NULL DEFAULT NEXTVAL('infra_api_error_log_seq'), trace_id varchar(64) NOT NULL, user_id int8 NOT NULL DEFAULT 0, user_type int2 NOT NULL DEFAULT 0, @@ -175,7 +175,7 @@ CREATE SEQUENCE infra_api_error_log_seq DROP TABLE IF EXISTS infra_codegen_column; CREATE TABLE infra_codegen_column ( - id int8 NOT NULL, + id int8 NOT NULL default nextval('infra_codegen_column_seq'), table_id int8 NOT NULL, column_name varchar(200) NOT NULL, data_type varchar(100) NOT NULL, @@ -238,7 +238,7 @@ CREATE SEQUENCE infra_codegen_column_seq DROP TABLE IF EXISTS infra_codegen_table; CREATE TABLE infra_codegen_table ( - id int8 NOT NULL, + id int8 NOT NULL default nextval('infra_codegen_table_seq'), data_source_config_id int8 NOT NULL, scene int2 NOT NULL DEFAULT 1, table_name varchar(200) NOT NULL DEFAULT '', @@ -303,7 +303,7 @@ CREATE SEQUENCE infra_codegen_table_seq DROP TABLE IF EXISTS infra_config; CREATE TABLE infra_config ( - id int8 NOT NULL, + id int8 NOT NULL default nextval('infra_config_seq'), category varchar(50) NOT NULL, type int2 NOT NULL, name varchar(100) NOT NULL DEFAULT '', @@ -362,7 +362,7 @@ CREATE SEQUENCE infra_config_seq DROP TABLE IF EXISTS infra_data_source_config; CREATE TABLE infra_data_source_config ( - id int8 NOT NULL, + id int8 NOT NULL default nextval('infra_data_source_config_seq'), name varchar(100) NOT NULL DEFAULT '', url varchar(1024) NOT NULL, username varchar(255) NOT NULL, @@ -399,7 +399,7 @@ CREATE SEQUENCE infra_data_source_config_seq DROP TABLE IF EXISTS infra_file; CREATE TABLE infra_file ( - id int8 NOT NULL, + id int8 NOT NULL default nextval('infra_file_seq'), config_id int8 NULL DEFAULT NULL, name varchar(256) NULL DEFAULT NULL, path varchar(512) NOT NULL, @@ -440,7 +440,7 @@ CREATE SEQUENCE infra_file_seq DROP TABLE IF EXISTS infra_file_config; CREATE TABLE infra_file_config ( - id int8 NOT NULL, + id int8 NOT NULL default nextval('infra_file_config_seq'), name varchar(63) NOT NULL, storage int2 NOT NULL, remark varchar(255) NULL DEFAULT NULL, @@ -496,7 +496,7 @@ CREATE SEQUENCE infra_file_config_seq DROP TABLE IF EXISTS infra_file_content; CREATE TABLE infra_file_content ( - id int8 NOT NULL, + id int8 NOT NULL default nextval('infra_file_content_seq'), config_id int8 NOT NULL, path varchar(512) NOT NULL, content bytea NOT NULL, @@ -531,7 +531,7 @@ CREATE SEQUENCE infra_file_content_seq DROP TABLE IF EXISTS infra_job; CREATE TABLE infra_job ( - id int8 NOT NULL, + id int8 NOT NULL default nextval('infra_job_seq'), name varchar(32) NOT NULL, status int2 NOT NULL, handler_name varchar(64) NOT NULL, @@ -597,7 +597,7 @@ CREATE SEQUENCE infra_job_seq DROP TABLE IF EXISTS infra_job_log; CREATE TABLE infra_job_log ( - id int8 NOT NULL, + id int8 NOT NULL default nextval('infra_job_log_seq'), job_id int8 NOT NULL, handler_name varchar(64) NOT NULL, handler_param varchar(255) NULL DEFAULT NULL, @@ -644,7 +644,7 @@ CREATE SEQUENCE infra_job_log_seq DROP TABLE IF EXISTS system_dept; CREATE TABLE system_dept ( - id int8 NOT NULL, + id int8 NOT NULL default nextval('system_dept_seq'), name varchar(30) NOT NULL DEFAULT '', parent_id int8 NOT NULL DEFAULT 0, sort int4 NOT NULL DEFAULT 0, @@ -711,7 +711,7 @@ CREATE SEQUENCE system_dept_seq DROP TABLE IF EXISTS system_dict_data; CREATE TABLE system_dict_data ( - id int8 NOT NULL, + id int8 NOT NULL default nextval('system_dict_data_seq'), sort int4 NOT NULL DEFAULT 0, label varchar(100) NOT NULL DEFAULT '', value varchar(100) NOT NULL DEFAULT '', @@ -1367,7 +1367,7 @@ CREATE SEQUENCE system_dict_data_seq DROP TABLE IF EXISTS system_dict_type; CREATE TABLE system_dict_type ( - id int8 NOT NULL, + id int8 NOT NULL default nextval('system_dict_type_seq'), name varchar(100) NOT NULL DEFAULT '', type varchar(100) NOT NULL DEFAULT '', status int2 NOT NULL DEFAULT 0, @@ -1521,7 +1521,7 @@ CREATE SEQUENCE system_dict_type_seq DROP TABLE IF EXISTS system_login_log; CREATE TABLE system_login_log ( - id int8 NOT NULL, + id int8 NOT NULL default nextval('system_login_log_seq'), log_type int8 NOT NULL, trace_id varchar(64) NOT NULL DEFAULT '', user_id int8 NOT NULL DEFAULT 0, @@ -1568,7 +1568,7 @@ CREATE SEQUENCE system_login_log_seq DROP TABLE IF EXISTS system_mail_account; CREATE TABLE system_mail_account ( - id int8 NOT NULL, + id int8 NOT NULL default nextval('system_mail_account_seq'), mail varchar(255) NOT NULL, username varchar(255) NOT NULL, password varchar(255) NOT NULL, @@ -1623,7 +1623,7 @@ CREATE SEQUENCE system_mail_account_seq DROP TABLE IF EXISTS system_mail_log; CREATE TABLE system_mail_log ( - id int8 NOT NULL, + id int8 NOT NULL default nextval('system_mail_log_seq'), user_id int8 NULL DEFAULT NULL, user_type int2 NULL DEFAULT NULL, to_mail varchar(255) NOT NULL, @@ -1682,7 +1682,7 @@ CREATE SEQUENCE system_mail_log_seq DROP TABLE IF EXISTS system_mail_template; CREATE TABLE system_mail_template ( - id int8 NOT NULL, + id int8 NOT NULL default nextval('system_mail_template_seq'), name varchar(63) NOT NULL, code varchar(63) NOT NULL, account_id int8 NOT NULL, @@ -1740,7 +1740,7 @@ CREATE SEQUENCE system_mail_template_seq DROP TABLE IF EXISTS system_menu; CREATE TABLE system_menu ( - id int8 NOT NULL, + id int8 NOT NULL default nextval('system_menu_seq'), name varchar(50) NOT NULL, permission varchar(100) NOT NULL DEFAULT '', type int2 NOT NULL, @@ -2714,7 +2714,7 @@ CREATE SEQUENCE system_menu_seq DROP TABLE IF EXISTS system_notice; CREATE TABLE system_notice ( - id int8 NOT NULL, + id int8 NOT NULL default nextval('system_notice_seq'), title varchar(50) NOT NULL, content text NOT NULL, type int2 NOT NULL, @@ -2764,7 +2764,7 @@ CREATE SEQUENCE system_notice_seq DROP TABLE IF EXISTS system_notify_message; CREATE TABLE system_notify_message ( - id int8 NOT NULL, + id int8 NOT NULL default nextval('system_notify_message_seq'), user_id int8 NOT NULL, user_type int2 NOT NULL, template_id int8 NOT NULL, @@ -2832,7 +2832,7 @@ CREATE SEQUENCE system_notify_message_seq DROP TABLE IF EXISTS system_notify_template; CREATE TABLE system_notify_template ( - id int8 NOT NULL, + id int8 NOT NULL default nextval('system_notify_template_seq'), name varchar(63) NOT NULL, code varchar(64) NOT NULL, nickname varchar(255) NOT NULL, @@ -2877,7 +2877,7 @@ CREATE SEQUENCE system_notify_template_seq DROP TABLE IF EXISTS system_oauth2_access_token; CREATE TABLE system_oauth2_access_token ( - id int8 NOT NULL, + id int8 NOT NULL default nextval('system_oauth2_access_token_seq'), user_id int8 NOT NULL, user_type int2 NOT NULL, user_info varchar(512) NOT NULL, @@ -2927,7 +2927,7 @@ CREATE SEQUENCE system_oauth2_access_token_seq DROP TABLE IF EXISTS system_oauth2_approve; CREATE TABLE system_oauth2_approve ( - id int8 NOT NULL, + id int8 NOT NULL default nextval('system_oauth2_approve_seq'), user_id int8 NOT NULL, user_type int2 NOT NULL, client_id varchar(255) NOT NULL, @@ -2970,7 +2970,7 @@ CREATE SEQUENCE system_oauth2_approve_seq DROP TABLE IF EXISTS system_oauth2_client; CREATE TABLE system_oauth2_client ( - id int8 NOT NULL, + id int8 NOT NULL default nextval('system_oauth2_client_seq'), client_id varchar(255) NOT NULL, secret varchar(255) NOT NULL, name varchar(255) NOT NULL, @@ -3041,7 +3041,7 @@ CREATE SEQUENCE system_oauth2_client_seq DROP TABLE IF EXISTS system_oauth2_code; CREATE TABLE system_oauth2_code ( - id int8 NOT NULL, + id int8 NOT NULL default nextval('system_oauth2_code_seq'), user_id int8 NOT NULL, user_type int2 NOT NULL, code varchar(32) NOT NULL, @@ -3088,7 +3088,7 @@ CREATE SEQUENCE system_oauth2_code_seq DROP TABLE IF EXISTS system_oauth2_refresh_token; CREATE TABLE system_oauth2_refresh_token ( - id int8 NOT NULL, + id int8 NOT NULL default nextval('system_oauth2_refresh_token_seq'), user_id int8 NOT NULL, refresh_token varchar(32) NOT NULL, user_type int2 NOT NULL, @@ -3131,7 +3131,7 @@ CREATE SEQUENCE system_oauth2_refresh_token_seq DROP TABLE IF EXISTS system_operate_log; CREATE TABLE system_operate_log ( - id int8 NOT NULL, + id int8 NOT NULL default nextval('system_operate_log_seq'), trace_id varchar(64) NOT NULL DEFAULT '', user_id int8 NOT NULL, user_type int2 NOT NULL DEFAULT 0, @@ -3188,7 +3188,7 @@ CREATE SEQUENCE system_operate_log_seq DROP TABLE IF EXISTS system_post; CREATE TABLE system_post ( - id int8 NOT NULL, + id int8 NOT NULL default nextval('system_post_seq'), code varchar(64) NOT NULL, name varchar(50) NOT NULL, sort int4 NOT NULL, @@ -3241,7 +3241,7 @@ CREATE SEQUENCE system_post_seq DROP TABLE IF EXISTS system_role; CREATE TABLE system_role ( - id int8 NOT NULL, + id int8 NOT NULL default nextval('system_role_seq'), name varchar(30) NOT NULL, code varchar(100) NOT NULL, sort int4 NOT NULL, @@ -3304,7 +3304,7 @@ CREATE SEQUENCE system_role_seq DROP TABLE IF EXISTS system_role_menu; CREATE TABLE system_role_menu ( - id int8 NOT NULL, + id int8 NOT NULL default nextval('system_role_menu_seq'), role_id int8 NOT NULL, menu_id int8 NOT NULL, creator varchar(64) NULL DEFAULT '', @@ -4210,7 +4210,7 @@ CREATE SEQUENCE system_role_menu_seq DROP TABLE IF EXISTS system_sms_channel; CREATE TABLE system_sms_channel ( - id int8 NOT NULL, + id int8 NOT NULL default nextval('system_sms_channel_seq'), signature varchar(12) NOT NULL, code varchar(63) NOT NULL, status int2 NOT NULL, @@ -4264,7 +4264,7 @@ CREATE SEQUENCE system_sms_channel_seq DROP TABLE IF EXISTS system_sms_code; CREATE TABLE system_sms_code ( - id int8 NOT NULL, + id int8 NOT NULL default nextval('system_sms_code_seq'), mobile varchar(11) NOT NULL, code varchar(6) NOT NULL, create_ip varchar(15) NOT NULL, @@ -4313,7 +4313,7 @@ CREATE SEQUENCE system_sms_code_seq DROP TABLE IF EXISTS system_sms_log; CREATE TABLE system_sms_log ( - id int8 NOT NULL, + id int8 NOT NULL default nextval('system_sms_log_seq'), channel_id int8 NOT NULL, channel_code varchar(63) NOT NULL, template_id int8 NOT NULL, @@ -4384,7 +4384,7 @@ CREATE SEQUENCE system_sms_log_seq DROP TABLE IF EXISTS system_sms_template; CREATE TABLE system_sms_template ( - id int8 NOT NULL, + id int8 NOT NULL default nextval('system_sms_template_seq'), type int2 NOT NULL, status int2 NOT NULL, code varchar(63) NOT NULL, @@ -4456,7 +4456,7 @@ CREATE SEQUENCE system_sms_template_seq DROP TABLE IF EXISTS system_social_client; CREATE TABLE system_social_client ( - id int8 NOT NULL, + id int8 NOT NULL default nextval('system_social_client_seq'), name varchar(255) NOT NULL, social_type int2 NOT NULL, user_type int2 NOT NULL, @@ -4514,7 +4514,7 @@ CREATE SEQUENCE system_social_client_seq DROP TABLE IF EXISTS system_social_user; CREATE TABLE system_social_user ( - id int8 NOT NULL, + id int8 NOT NULL default nextval('system_social_user_seq'), type int2 NOT NULL, openid varchar(32) NOT NULL, token varchar(256) NULL DEFAULT NULL, @@ -4563,7 +4563,7 @@ CREATE SEQUENCE system_social_user_seq DROP TABLE IF EXISTS system_social_user_bind; CREATE TABLE system_social_user_bind ( - id int8 NOT NULL, + id int8 NOT NULL default nextval('system_social_user_bind_seq'), user_id int8 NOT NULL, user_type int2 NOT NULL, social_type int2 NOT NULL, @@ -4602,7 +4602,7 @@ CREATE SEQUENCE system_social_user_bind_seq DROP TABLE IF EXISTS system_tenant; CREATE TABLE system_tenant ( - id int8 NOT NULL, + id int8 NOT NULL default nextval('system_tenant_seq'), name varchar(30) NOT NULL, contact_user_id int8 NULL DEFAULT NULL, contact_name varchar(30) NOT NULL, @@ -4660,7 +4660,7 @@ CREATE SEQUENCE system_tenant_seq DROP TABLE IF EXISTS system_tenant_package; CREATE TABLE system_tenant_package ( - id int8 NOT NULL, + id int8 NOT NULL default nextval('system_tenant_package_seq'), name varchar(30) NOT NULL, status int2 NOT NULL DEFAULT 0, remark varchar(256) NULL DEFAULT '', @@ -4707,7 +4707,7 @@ CREATE SEQUENCE system_tenant_package_seq DROP TABLE IF EXISTS system_user_post; CREATE TABLE system_user_post ( - id int8 NOT NULL, + id int8 NOT NULL default nextval('system_user_post_seq'), user_id int8 NOT NULL DEFAULT 0, post_id int8 NOT NULL DEFAULT 0, creator varchar(64) NULL DEFAULT '', @@ -4759,7 +4759,7 @@ CREATE SEQUENCE system_user_post_seq DROP TABLE IF EXISTS system_user_role; CREATE TABLE system_user_role ( - id int8 NOT NULL, + id int8 NOT NULL default nextval('system_user_role_seq'), user_id int8 NOT NULL, role_id int8 NOT NULL, creator varchar(64) NULL DEFAULT '', @@ -4819,7 +4819,7 @@ CREATE SEQUENCE system_user_role_seq DROP TABLE IF EXISTS system_users; CREATE TABLE system_users ( - id int8 NOT NULL, + id int8 NOT NULL default nextval('system_users_seq'), username varchar(30) NOT NULL, password varchar(100) NOT NULL DEFAULT '', nickname varchar(30) NOT NULL, @@ -4902,7 +4902,7 @@ CREATE SEQUENCE system_users_seq DROP TABLE IF EXISTS yudao_demo01_contact; CREATE TABLE yudao_demo01_contact ( - id int8 NOT NULL, + id int8 NOT NULL default nextval('yudao_demo01_contact_seq'), name varchar(100) NOT NULL DEFAULT '', sex int2 NOT NULL, birthday timestamp NOT NULL, @@ -4952,7 +4952,7 @@ CREATE SEQUENCE yudao_demo01_contact_seq DROP TABLE IF EXISTS yudao_demo02_category; CREATE TABLE yudao_demo02_category ( - id int8 NOT NULL, + id int8 NOT NULL default nextval('yudao_demo02_category_seq'), name varchar(100) NOT NULL DEFAULT '', parent_id int8 NOT NULL, creator varchar(64) NULL DEFAULT '', @@ -5001,7 +5001,7 @@ CREATE SEQUENCE yudao_demo02_category_seq DROP TABLE IF EXISTS yudao_demo03_course; CREATE TABLE yudao_demo03_course ( - id int8 NOT NULL, + id int8 NOT NULL default nextval('yudao_demo03_course_seq'), student_id int8 NOT NULL, name varchar(100) NOT NULL DEFAULT '', score int2 NOT NULL, @@ -5063,7 +5063,7 @@ CREATE SEQUENCE yudao_demo03_course_seq DROP TABLE IF EXISTS yudao_demo03_grade; CREATE TABLE yudao_demo03_grade ( - id int8 NOT NULL, + id int8 NOT NULL default nextval('yudao_demo03_grade_seq'), student_id int8 NOT NULL, name varchar(100) NOT NULL DEFAULT '', teacher varchar(255) NOT NULL, @@ -5111,7 +5111,7 @@ CREATE SEQUENCE yudao_demo03_grade_seq DROP TABLE IF EXISTS yudao_demo03_student; CREATE TABLE yudao_demo03_student ( - id int8 NOT NULL, + id int8 NOT NULL default nextval('yudao_demo03_student_seq'), name varchar(100) NOT NULL DEFAULT '', sex int2 NOT NULL, birthday timestamp NOT NULL, diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/json/databind/TimestampLocalDateTimeSerializer.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/json/databind/TimestampLocalDateTimeSerializer.java index ef767a558..bed47e93b 100644 --- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/json/databind/TimestampLocalDateTimeSerializer.java +++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/json/databind/TimestampLocalDateTimeSerializer.java @@ -1,12 +1,16 @@ package cn.iocoder.yudao.framework.common.util.json.databind; +import cn.hutool.core.util.ReflectUtil; +import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import java.io.IOException; +import java.lang.reflect.Field; import java.time.LocalDateTime; import java.time.ZoneId; +import java.time.format.DateTimeFormatter; /** * 基于时间戳的 LocalDateTime 序列化器 @@ -19,7 +23,19 @@ public class TimestampLocalDateTimeSerializer extends JsonSerializer clazz = gen.getOutputContext().getCurrentValue().getClass(); + Field field = ReflectUtil.getField(clazz, fieldName); + // 情况一:有 JsonFormat 自定义注解,则使用它。https://github.com/YunaiV/ruoyi-vue-pro/pull/1019 + JsonFormat[] jsonFormats = field.getAnnotationsByType(JsonFormat.class); + if (jsonFormats.length > 0) { + String pattern = jsonFormats[0].pattern(); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern); + gen.writeString(formatter.format(value)); + return; + } + + // 情况二:默认将 LocalDateTime 对象,转换为 Long 时间戳 gen.writeNumber(value.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()); } diff --git a/yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/core/rule/DataPermissionRuleFactoryImpl.java b/yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/core/rule/DataPermissionRuleFactoryImpl.java index eaa6e6aed..8119c7d52 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/core/rule/DataPermissionRuleFactoryImpl.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/core/rule/DataPermissionRuleFactoryImpl.java @@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission; import cn.iocoder.yudao.framework.datapermission.core.aop.DataPermissionContextHolder; +import com.fhs.trans.service.impl.SimpleTransService; import lombok.RequiredArgsConstructor; import java.util.Collections; @@ -31,32 +32,53 @@ public class DataPermissionRuleFactoryImpl implements DataPermissionRuleFactory @Override // mappedStatementId 参数,暂时没有用。以后,可以基于 mappedStatementId + DataPermission 进行缓存 public List getDataPermissionRule(String mappedStatementId) { - // 1. 无数据权限 + // 1.1 无数据权限 if (CollUtil.isEmpty(rules)) { return Collections.emptyList(); } - // 2. 未配置,则默认开启 + // 1.2 未配置,则默认开启 DataPermission dataPermission = DataPermissionContextHolder.get(); if (dataPermission == null) { return rules; } - // 3. 已配置,但禁用 + // 1.3 已配置,但禁用 if (!dataPermission.enable()) { return Collections.emptyList(); } + // 1.4 特殊:数据翻译时,强制忽略数据权限 https://github.com/YunaiV/ruoyi-vue-pro/issues/1007 + if (isTranslateCall()) { + return Collections.emptyList(); + } - // 4. 已配置,只选择部分规则 + // 2.1 情况一:已配置,只选择部分规则 if (ArrayUtil.isNotEmpty(dataPermission.includeRules())) { return rules.stream().filter(rule -> ArrayUtil.contains(dataPermission.includeRules(), rule.getClass())) .collect(Collectors.toList()); // 一般规则不会太多,所以不采用 HashSet 查询 } - // 5. 已配置,只排除部分规则 + // 2.2 已配置,只排除部分规则 if (ArrayUtil.isNotEmpty(dataPermission.excludeRules())) { return rules.stream().filter(rule -> !ArrayUtil.contains(dataPermission.excludeRules(), rule.getClass())) .collect(Collectors.toList()); // 一般规则不会太多,所以不采用 HashSet 查询 } - // 6. 已配置,全部规则 + // 2.3 已配置,全部规则 return rules; } + /** + * 判断是否为数据翻译 {@link com.fhs.core.trans.anno.Trans} 的调用 + * + * 目前暂时只有这个办法,已经和 easy-trans 做过沟通 + * + * @return 是否 + */ + private boolean isTranslateCall() { + StackTraceElement[] stack = Thread.currentThread().getStackTrace(); + for (StackTraceElement e : stack) { + if (SimpleTransService.class.getName().equals(e.getClassName())) { + return true; + } + } + return false; + } + } diff --git a/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/util/ExcelUtils.java b/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/util/ExcelUtils.java index acff70c6e..8e37452a4 100644 --- a/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/util/ExcelUtils.java +++ b/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/util/ExcelUtils.java @@ -2,7 +2,6 @@ package cn.iocoder.yudao.framework.excel.core.util; import cn.idev.excel.FastExcelFactory; import cn.idev.excel.converters.longconverter.LongStringConverter; -import cn.idev.excel.write.style.column.LongestMatchColumnWidthStyleStrategy; import cn.iocoder.yudao.framework.common.util.http.HttpUtils; import cn.iocoder.yudao.framework.excel.core.handler.ColumnWidthMatchStyleStrategy; import cn.iocoder.yudao.framework.excel.core.handler.SelectSheetWriteHandler; @@ -10,6 +9,7 @@ import org.springframework.web.multipart.MultipartFile; import javax.servlet.http.HttpServletResponse; import java.io.IOException; +import java.io.InputStream; import java.util.List; /** @@ -45,9 +45,12 @@ public class ExcelUtils { } public static List read(MultipartFile file, Class head) throws IOException { - return FastExcelFactory.read(file.getInputStream(), head, null) - .autoCloseStream(false) // 不要自动关闭,交给 Servlet 自己处理 - .doReadAllSync(); + // 参考 https://t.zsxq.com/zM77F 帖子,增加 try 处理,兼容 windows 场景 + try (InputStream inputStream = file.getInputStream()) { + return FastExcelFactory.read(inputStream, head, null) + .autoCloseStream(false) // 不要自动关闭,交给 Servlet 自己处理 + .doReadAllSync(); + } } } diff --git a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/config/IdTypeEnvironmentPostProcessor.java b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/config/IdTypeEnvironmentPostProcessor.java index 3a67b905f..0ea6bf252 100644 --- a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/config/IdTypeEnvironmentPostProcessor.java +++ b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/config/IdTypeEnvironmentPostProcessor.java @@ -9,7 +9,10 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.env.EnvironmentPostProcessor; import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.MapPropertySource; +import java.util.HashMap; +import java.util.Map; import java.util.Set; /** @@ -56,11 +59,19 @@ public class IdTypeEnvironmentPostProcessor implements EnvironmentPostProcessor } public IdType getIdType(ConfigurableEnvironment environment) { - return environment.getProperty(ID_TYPE_KEY, IdType.class); + String value = environment.getProperty(ID_TYPE_KEY); + try { + return StrUtil.isNotBlank(value) ? IdType.valueOf(value) : IdType.NONE; + } catch (IllegalArgumentException ex) { + log.error("[getIdType][无法解析 id-type 配置值({})]", value, ex); + return IdType.NONE; + } } public void setIdType(ConfigurableEnvironment environment, IdType idType) { - environment.getSystemProperties().put(ID_TYPE_KEY, idType); + Map map = new HashMap<>(); + map.put(ID_TYPE_KEY, idType); + environment.getPropertySources().addFirst(new MapPropertySource("mybatisPlusIdType", map)); log.info("[setIdType][修改 MyBatis Plus 的 idType 为({})]", idType); } diff --git a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java index 52ca947cc..9858a1aed 100644 --- a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java +++ b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/mapper/BaseMapperX.java @@ -68,6 +68,29 @@ public interface BaseMapperX extends MPJBaseMapper { return new PageResult<>(mpPage.getRecords(), mpPage.getTotal()); } + /** + * 执行分页查询并返回结果。 + * + * @param pageParam 分页参数,包含页码、每页条数和排序字段信息。如果 pageSize 为 {@link PageParam#PAGE_SIZE_NONE},则不分页,直接查询所有数据。 + * @param clazz 结果集的类类型 + * @param lambdaWrapper MyBatis Plus Join 查询条件包装器 + * @param 结果集的泛型类型 + * @return 返回分页查询的结果,包括总记录数和当前页的数据列表 + */ + default PageResult selectJoinPage(SortablePageParam pageParam, Class clazz, MPJLambdaWrapper lambdaWrapper) { + // 特殊:不分页,直接查询全部 + if (PageParam.PAGE_SIZE_NONE.equals(pageParam.getPageSize())) { + List list = selectJoinList(clazz, lambdaWrapper); + return new PageResult<>(list, (long) list.size()); + } + + // MyBatis Plus Join 查询 + IPage mpPage = MyBatisUtils.buildPage(pageParam, pageParam.getSortingFields()); + mpPage = selectJoinPage(mpPage, clazz, lambdaWrapper); + // 转换返回 + return new PageResult<>(mpPage.getRecords(), mpPage.getTotal()); + } + default PageResult selectJoinPage(PageParam pageParam, Class resultTypeClass, MPJBaseJoin joinQueryWrapper) { IPage mpPage = MyBatisUtils.buildPage(pageParam); selectJoinPage(mpPage, resultTypeClass, joinQueryWrapper); diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/encrypt/core/filter/ApiEncryptResponseWrapper.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/encrypt/core/filter/ApiEncryptResponseWrapper.java index fc49442c3..0f5a02cd5 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/encrypt/core/filter/ApiEncryptResponseWrapper.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/encrypt/core/filter/ApiEncryptResponseWrapper.java @@ -42,15 +42,16 @@ public class ApiEncryptResponseWrapper extends HttpServletResponseWrapper { this.flushBuffer(); byte[] body = byteArrayOutputStream.toByteArray(); - // 2. 加密 body - String encryptedBody = symmetricEncryptor != null ? symmetricEncryptor.encryptBase64(body) - : asymmetricEncryptor.encryptBase64(body, KeyType.PublicKey); - response.getWriter().write(encryptedBody); - - // 3. 添加加密 header 标识 + // 2. 添加加密 header 标识 this.addHeader(properties.getHeader(), "true"); // 特殊:特殊:https://juejin.cn/post/6867327674675625992 this.addHeader("Access-Control-Expose-Headers", properties.getHeader()); + + // 3.1 加密 body + String encryptedBody = symmetricEncryptor != null ? symmetricEncryptor.encryptBase64(body) + : asymmetricEncryptor.encryptBase64(body, KeyType.PublicKey); + // 3.2 输出加密后的 body:(设置 header 要放在 response 的 write 之前) + response.getWriter().write(encryptedBody); } @Override diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/config/YudaoWebAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/config/YudaoWebAutoConfiguration.java index 12ea31b6c..63874d248 100644 --- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/config/YudaoWebAutoConfiguration.java +++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/config/YudaoWebAutoConfiguration.java @@ -20,6 +20,8 @@ import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; +import org.springframework.core.annotation.Order; import org.springframework.util.AntPathMatcher; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; @@ -81,6 +83,7 @@ public class YudaoWebAutoConfiguration { } @Bean + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") public GlobalExceptionHandler globalExceptionHandler(ApiErrorLogCommonApi apiErrorLogApi) { return new GlobalExceptionHandler(applicationName, apiErrorLogApi); } @@ -103,6 +106,7 @@ public class YudaoWebAutoConfiguration { * 创建 CorsFilter Bean,解决跨域问题 */ @Bean + @Order(value = WebFilterOrderEnum.CORS_FILTER) // 特殊:修复因执行顺序影响到跨域配置不生效问题 public FilterRegistrationBean corsFilterBean() { // 创建 CorsConfiguration 对象 CorsConfiguration config = new CorsConfiguration(); @@ -146,9 +150,20 @@ public class YudaoWebAutoConfiguration { */ @Bean @ConditionalOnMissingBean - @LoadBalanced + @Primary public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) { return restTemplateBuilder.build(); } + /** + * 创建 RestTemplate 实例(支持负载均衡) + * + * @param restTemplateBuilder {@link RestTemplateAutoConfiguration#restTemplateBuilder} + */ + @Bean + @LoadBalanced + public RestTemplate loadBalancedRestTemplate(RestTemplateBuilder restTemplateBuilder) { + return restTemplateBuilder.build(); + } + } diff --git a/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/model/AiPlatformEnum.java b/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/model/AiPlatformEnum.java index 47a4d2d71..612a91338 100644 --- a/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/model/AiPlatformEnum.java +++ b/yudao-module-ai/yudao-module-ai-api/src/main/java/cn/iocoder/yudao/module/ai/enums/model/AiPlatformEnum.java @@ -40,6 +40,7 @@ public enum AiPlatformEnum implements ArrayValuable { STABLE_DIFFUSION("StableDiffusion", "StableDiffusion"), // Stability AI MIDJOURNEY("Midjourney", "Midjourney"), // Midjourney SUNO("Suno", "Suno"), // Suno AI + GROK("Grok","Grok"), // Grok ; diff --git a/yudao-module-ai/yudao-module-ai-server/pom.xml b/yudao-module-ai/yudao-module-ai-server/pom.xml index a2ce08ba1..d40ba9247 100644 --- a/yudao-module-ai/yudao-module-ai-server/pom.xml +++ b/yudao-module-ai/yudao-module-ai-server/pom.xml @@ -19,9 +19,9 @@ 国外:OpenAI、Ollama、Midjourney、StableDiffusion、Suno - 1.0.1 - 1.0.0.3 - 1.0.2 + 1.1.0 + 1.0.0.4 + 1.2.6 diff --git a/yudao-module-ai/yudao-module-ai-server/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/config/AiAutoConfiguration.java b/yudao-module-ai/yudao-module-ai-server/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/config/AiAutoConfiguration.java index 26fbe0ad4..9009cbc8c 100644 --- a/yudao-module-ai/yudao-module-ai-server/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/config/AiAutoConfiguration.java +++ b/yudao-module-ai/yudao-module-ai-server/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/config/AiAutoConfiguration.java @@ -7,6 +7,7 @@ import cn.iocoder.yudao.module.ai.framework.ai.core.model.AiModelFactoryImpl; import cn.iocoder.yudao.module.ai.framework.ai.core.model.baichuan.BaiChuanChatModel; import cn.iocoder.yudao.module.ai.framework.ai.core.model.doubao.DouBaoChatModel; import cn.iocoder.yudao.module.ai.framework.ai.core.model.gemini.GeminiChatModel; +import cn.iocoder.yudao.module.ai.framework.ai.core.model.grok.GrokChatModel; import cn.iocoder.yudao.module.ai.framework.ai.core.model.hunyuan.HunYuanChatModel; import cn.iocoder.yudao.module.ai.framework.ai.core.model.midjourney.api.MidjourneyApi; import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowApiConstants; @@ -16,7 +17,9 @@ import cn.iocoder.yudao.module.ai.framework.ai.core.model.xinghuo.XingHuoChatMod import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.AiWebSearchClient; import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.bocha.AiBoChaWebSearchClient; import cn.iocoder.yudao.module.ai.tool.method.PersonService; +import io.micrometer.observation.ObservationRegistry; import lombok.extern.slf4j.Slf4j; +import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.deepseek.DeepSeekChatModel; import org.springframework.ai.deepseek.DeepSeekChatOptions; import org.springframework.ai.deepseek.api.DeepSeekApi; @@ -34,12 +37,14 @@ import org.springframework.ai.vectorstore.milvus.autoconfigure.MilvusServiceClie import org.springframework.ai.vectorstore.milvus.autoconfigure.MilvusVectorStoreProperties; import org.springframework.ai.vectorstore.qdrant.autoconfigure.QdrantVectorStoreProperties; import org.springframework.ai.vectorstore.redis.autoconfigure.RedisVectorStoreProperties; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.List; +import java.util.Optional; /** * 芋道 AI 自动配置 @@ -60,6 +65,13 @@ public class AiAutoConfiguration { return new AiModelFactoryImpl(); } + @Bean + @ConditionalOnMissingBean + public ObservationRegistry observationRegistry() { + // 特殊:兜底有 ObservationRegistry Bean,避免相关的 ChatModel 创建报错。相关 issue:https://t.zsxq.com/CuPu4 + return ObservationRegistry.NOOP; + } + // ========== 各种 AI Client 创建 ========== @Bean @@ -252,6 +264,28 @@ public class AiAutoConfiguration { return new SunoApi(yudaoAiProperties.getSuno().getBaseUrl()); } + public ChatModel buildGrokChatClient(YudaoAiProperties.Grok properties) { + if (StrUtil.isEmpty(properties.getModel())) { + properties.setModel(GrokChatModel.MODEL_DEFAULT); + } + OpenAiChatModel openAiChatModel = OpenAiChatModel.builder() + .openAiApi(OpenAiApi.builder() + .baseUrl(Optional.ofNullable(properties.getBaseUrl()) + .orElse(GrokChatModel.BASE_URL)) + .completionsPath(GrokChatModel.COMPLETE_PATH) + .apiKey(properties.getApiKey()) + .build()) + .defaultOptions(OpenAiChatOptions.builder() + .model(properties.getModel()) + .temperature(properties.getTemperature()) + .maxTokens(properties.getMaxTokens()) + .topP(properties.getTopP()) + .build()) + .toolCallingManager(getToolCallingManager()) + .build(); + return new DouBaoChatModel(openAiChatModel); + } + // ========== RAG 相关 ========== @Bean diff --git a/yudao-module-ai/yudao-module-ai-server/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/config/YudaoAiProperties.java b/yudao-module-ai/yudao-module-ai-server/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/config/YudaoAiProperties.java index 67d3bb5f3..986c24c18 100644 --- a/yudao-module-ai/yudao-module-ai-server/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/config/YudaoAiProperties.java +++ b/yudao-module-ai/yudao-module-ai-server/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/config/YudaoAiProperties.java @@ -160,6 +160,20 @@ public class YudaoAiProperties { } + @Data + public static class Grok { + + private String enable; + private String apiKey; + private String baseUrl; + + private String model; + private Double temperature; + private Integer maxTokens; + private Double topP; + + } + @Data public static class WebSearch { diff --git a/yudao-module-ai/yudao-module-ai-server/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/AiModelFactoryImpl.java b/yudao-module-ai/yudao-module-ai-server/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/AiModelFactoryImpl.java index 75798ebd2..f8067dea2 100644 --- a/yudao-module-ai/yudao-module-ai-server/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/AiModelFactoryImpl.java +++ b/yudao-module-ai/yudao-module-ai-server/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/AiModelFactoryImpl.java @@ -87,7 +87,7 @@ import org.springframework.ai.model.zhipuai.autoconfigure.ZhiPuAiImageAutoConfig import org.springframework.ai.ollama.OllamaChatModel; import org.springframework.ai.ollama.OllamaEmbeddingModel; import org.springframework.ai.ollama.api.OllamaApi; -import org.springframework.ai.ollama.api.OllamaOptions; +import org.springframework.ai.ollama.api.OllamaEmbeddingOptions; import org.springframework.ai.openai.OpenAiChatModel; import org.springframework.ai.openai.OpenAiEmbeddingModel; import org.springframework.ai.openai.OpenAiEmbeddingOptions; @@ -178,6 +178,8 @@ public class AiModelFactoryImpl implements AiModelFactory { return buildGeminiChatModel(apiKey); case OLLAMA: return buildOllamaChatModel(url); + case GROK: + return buildGrokChatModel(apiKey,url); default: throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform)); } @@ -436,10 +438,12 @@ public class AiModelFactoryImpl implements AiModelFactory { * 可参考 {@link ZhiPuAiChatAutoConfiguration} 的 zhiPuAiChatModel 方法 */ private ZhiPuAiChatModel buildZhiPuChatModel(String apiKey, String url) { - ZhiPuAiApi zhiPuAiApi = StrUtil.isEmpty(url) ? new ZhiPuAiApi(apiKey) - : new ZhiPuAiApi(url, apiKey); + ZhiPuAiApi.Builder zhiPuAiApiBuilder = ZhiPuAiApi.builder().apiKey(apiKey); + if (StrUtil.isNotEmpty(url)) { + zhiPuAiApiBuilder.baseUrl(url); + } ZhiPuAiChatOptions options = ZhiPuAiChatOptions.builder().model(ZhiPuAiApi.DEFAULT_CHAT_MODEL).temperature(0.7).build(); - return new ZhiPuAiChatModel(zhiPuAiApi, options, getToolCallingManager(), DEFAULT_RETRY_TEMPLATE, + return new ZhiPuAiChatModel(zhiPuAiApiBuilder.build(), options, getToolCallingManager(), DEFAULT_RETRY_TEMPLATE, getObservationRegistry().getIfAvailable()); } @@ -586,6 +590,13 @@ public class AiModelFactoryImpl implements AiModelFactory { return new StabilityAiImageModel(stabilityAiApi); } + private ChatModel buildGrokChatModel(String apiKey,String url) { + YudaoAiProperties.Grok properties = new YudaoAiProperties.Grok() + .setBaseUrl(url) + .setApiKey(apiKey); + return new AiAutoConfiguration().buildGrokChatClient(properties); + } + // ========== 各种创建 EmbeddingModel 的方法 ========== /** @@ -601,10 +612,12 @@ public class AiModelFactoryImpl implements AiModelFactory { * 可参考 {@link ZhiPuAiEmbeddingAutoConfiguration} 的 zhiPuAiEmbeddingModel 方法 */ private ZhiPuAiEmbeddingModel buildZhiPuEmbeddingModel(String apiKey, String url, String model) { - ZhiPuAiApi zhiPuAiApi = StrUtil.isEmpty(url) ? new ZhiPuAiApi(apiKey) - : new ZhiPuAiApi(url, apiKey); + ZhiPuAiApi.Builder zhiPuAiApiBuilder = ZhiPuAiApi.builder().apiKey(apiKey); + if (StrUtil.isNotEmpty(url)) { + zhiPuAiApiBuilder.baseUrl(url); + } ZhiPuAiEmbeddingOptions zhiPuAiEmbeddingOptions = ZhiPuAiEmbeddingOptions.builder().model(model).build(); - return new ZhiPuAiEmbeddingModel(zhiPuAiApi, MetadataMode.EMBED, zhiPuAiEmbeddingOptions); + return new ZhiPuAiEmbeddingModel(zhiPuAiApiBuilder.build(), MetadataMode.EMBED, zhiPuAiEmbeddingOptions); } /** @@ -632,7 +645,7 @@ public class AiModelFactoryImpl implements AiModelFactory { private OllamaEmbeddingModel buildOllamaEmbeddingModel(String url, String model) { OllamaApi ollamaApi = OllamaApi.builder().baseUrl(url).build(); - OllamaOptions ollamaOptions = OllamaOptions.builder().model(model).build(); + OllamaEmbeddingOptions ollamaOptions = OllamaEmbeddingOptions.builder().model(model).build(); return OllamaEmbeddingModel.builder() .ollamaApi(ollamaApi) .defaultOptions(ollamaOptions) diff --git a/yudao-module-ai/yudao-module-ai-server/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/grok/GrokChatModel.java b/yudao-module-ai/yudao-module-ai-server/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/grok/GrokChatModel.java new file mode 100644 index 000000000..06eed2504 --- /dev/null +++ b/yudao-module-ai/yudao-module-ai-server/src/main/java/cn/iocoder/yudao/module/ai/framework/ai/core/model/grok/GrokChatModel.java @@ -0,0 +1,44 @@ +package cn.iocoder.yudao.module.ai.framework.ai.core.model.grok; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.chat.model.ChatResponse; +import org.springframework.ai.chat.prompt.ChatOptions; +import org.springframework.ai.chat.prompt.Prompt; +import reactor.core.publisher.Flux; + +/** + * Grok {@link ChatModel} 实现类 + * + * + */ +@Slf4j +@RequiredArgsConstructor +public class GrokChatModel implements ChatModel { + + public static final String BASE_URL = "https://api.x.ai"; + public static final String COMPLETE_PATH = "/v1/chat/completions"; + public static final String MODEL_DEFAULT = "grok-4-fast-reasoning"; + + /** + * 兼容 OpenAI 接口,进行复用 + */ + private final ChatModel openAiChatModel; + + @Override + public ChatResponse call(Prompt prompt) { + return openAiChatModel.call(prompt); + } + + @Override + public Flux stream(Prompt prompt) { + return openAiChatModel.stream(prompt); + } + + @Override + public ChatOptions getDefaultOptions() { + return openAiChatModel.getDefaultOptions(); + } + +} diff --git a/yudao-module-ai/yudao-module-ai-server/src/main/java/cn/iocoder/yudao/module/ai/framework/security/config/SecurityConfiguration.java b/yudao-module-ai/yudao-module-ai-server/src/main/java/cn/iocoder/yudao/module/ai/framework/security/config/SecurityConfiguration.java index 0e7a8ad45..aedd493c3 100644 --- a/yudao-module-ai/yudao-module-ai-server/src/main/java/cn/iocoder/yudao/module/ai/framework/security/config/SecurityConfiguration.java +++ b/yudao-module-ai/yudao-module-ai-server/src/main/java/cn/iocoder/yudao/module/ai/framework/security/config/SecurityConfiguration.java @@ -3,7 +3,8 @@ package cn.iocoder.yudao.module.ai.framework.security.config; import cn.iocoder.yudao.framework.security.config.AuthorizeRequestsCustomizer; import cn.iocoder.yudao.module.infra.enums.ApiConstants; import jakarta.annotation.Resource; -import org.springframework.ai.mcp.server.autoconfigure.McpServerProperties; +import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerSseProperties; +import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerStreamableHttpProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; @@ -18,7 +19,9 @@ import java.util.Optional; public class SecurityConfiguration { @Resource - private Optional serverProperties; + private Optional mcpServerSseProperties; + @Resource + private Optional mcpServerStreamableHttpProperties; @Bean("aiAuthorizeRequestsCustomizer") public AuthorizeRequestsCustomizer authorizeRequestsCustomizer() { @@ -42,10 +45,12 @@ public class SecurityConfiguration { registry.requestMatchers(ApiConstants.PREFIX + "/**").permitAll(); // MCP Server - serverProperties.ifPresent(properties -> { + mcpServerSseProperties.ifPresent(properties -> { registry.requestMatchers(properties.getSseEndpoint()).permitAll(); registry.requestMatchers(properties.getSseMessageEndpoint()).permitAll(); }); + mcpServerStreamableHttpProperties.ifPresent(properties -> + registry.requestMatchers(properties.getMcpEndpoint()).permitAll()); } }; diff --git a/yudao-module-ai/yudao-module-ai-server/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageServiceImpl.java b/yudao-module-ai/yudao-module-ai-server/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageServiceImpl.java index 0f44eacbf..f29a5c3f9 100644 --- a/yudao-module-ai/yudao-module-ai-server/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageServiceImpl.java +++ b/yudao-module-ai/yudao-module-ai-server/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageServiceImpl.java @@ -49,7 +49,7 @@ import org.springframework.ai.chat.model.StreamingChatModel; import org.springframework.ai.chat.prompt.ChatOptions; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.mcp.SyncMcpToolCallbackProvider; -import org.springframework.ai.mcp.client.autoconfigure.properties.McpClientCommonProperties; +import org.springframework.ai.mcp.client.common.autoconfigure.properties.McpClientCommonProperties; import org.springframework.ai.tool.ToolCallback; import org.springframework.ai.tool.resolution.ToolCallbackResolver; import org.springframework.beans.factory.annotation.Autowired; diff --git a/yudao-module-ai/yudao-module-ai-server/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeSegmentServiceImpl.java b/yudao-module-ai/yudao-module-ai-server/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeSegmentServiceImpl.java index dd0f91315..43c7e9cef 100644 --- a/yudao-module-ai/yudao-module-ai-server/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeSegmentServiceImpl.java +++ b/yudao-module-ai/yudao-module-ai-server/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeSegmentServiceImpl.java @@ -227,6 +227,9 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService // 2. 检索 List documents = searchDocument(knowledge, reqBO); + if (CollUtil.isEmpty(documents)) { + return ListUtil.empty(); + } // 3.1 段落召回 List segments = segmentMapper diff --git a/yudao-module-ai/yudao-module-ai-server/src/main/java/cn/iocoder/yudao/module/ai/util/AiUtils.java b/yudao-module-ai/yudao-module-ai-server/src/main/java/cn/iocoder/yudao/module/ai/util/AiUtils.java index d209c62d4..dab4d5aa4 100644 --- a/yudao-module-ai/yudao-module-ai-server/src/main/java/cn/iocoder/yudao/module/ai/util/AiUtils.java +++ b/yudao-module-ai/yudao-module-ai-server/src/main/java/cn/iocoder/yudao/module/ai/util/AiUtils.java @@ -16,7 +16,7 @@ import org.springframework.ai.chat.prompt.ChatOptions; import org.springframework.ai.deepseek.DeepSeekAssistantMessage; import org.springframework.ai.deepseek.DeepSeekChatOptions; import org.springframework.ai.minimax.MiniMaxChatOptions; -import org.springframework.ai.ollama.api.OllamaOptions; +import org.springframework.ai.ollama.api.OllamaChatOptions; import org.springframework.ai.openai.OpenAiChatOptions; import org.springframework.ai.tool.ToolCallback; import org.springframework.ai.zhipuai.ZhiPuAiChatOptions; @@ -68,6 +68,7 @@ public class AiUtils { case OPENAI: case GEMINI: // 复用 OpenAI 客户端 case BAI_CHUAN: // 复用 OpenAI 客户端 + case GROK: // 复用 OpenAI 客户端 return OpenAiChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens) .toolCallbacks(toolCallbacks).toolContext(toolContext).build(); case AZURE_OPENAI: @@ -77,7 +78,7 @@ public class AiUtils { return AnthropicChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens) .toolCallbacks(toolCallbacks).toolContext(toolContext).build(); case OLLAMA: - return OllamaOptions.builder().model(model).temperature(temperature).numPredict(maxTokens) + return OllamaChatOptions.builder().model(model).temperature(temperature).numPredict(maxTokens) .toolCallbacks(toolCallbacks).toolContext(toolContext).build(); default: throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform)); diff --git a/yudao-module-ai/yudao-module-ai-server/src/main/resources/application.yaml b/yudao-module-ai/yudao-module-ai-server/src/main/resources/application.yaml index 98c2bf95f..236eb8e6d 100644 --- a/yudao-module-ai/yudao-module-ai-server/src/main/resources/application.yaml +++ b/yudao-module-ai/yudao-module-ai-server/src/main/resources/application.yaml @@ -168,6 +168,8 @@ spring: filesystem: url: http://127.0.0.1:8089 sse-endpoint: /sse + annotation-scanner: + enabled: false # TODO @芋艿:有 bug https://github.com/spring-projects/spring-ai/issues/4917 需要官方修复 yudao: ai: diff --git a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/api/event/CrmContractStatusListener.java b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/api/event/CrmContractStatusListener.java index 2bdd124e4..00dc09500 100644 --- a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/api/event/CrmContractStatusListener.java +++ b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/api/event/CrmContractStatusListener.java @@ -1,9 +1,10 @@ package cn.iocoder.yudao.module.bpm.api.event; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmHttpRequestUtils; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; import org.springframework.web.bind.annotation.RequestBody; - -import javax.validation.Valid; +import org.springframework.web.client.RestTemplate; /** * 合同审批的结果的监听器实现类 @@ -12,6 +13,9 @@ import javax.validation.Valid; */ public class CrmContractStatusListener extends BpmProcessInstanceStatusEventListener { + @Resource + private RestTemplate loadBalancedRestTemplate; + @Override public String getProcessDefinitionKey() { return "crm-contract-audit"; @@ -20,7 +24,8 @@ public class CrmContractStatusListener extends BpmProcessInstanceStatusEventList @Override public void onEvent(@RequestBody @Valid BpmProcessInstanceStatusEvent event) { BpmHttpRequestUtils.executeBpmHttpRequest(event, - "http://crm-server/rpc-api/crm/contract/update-audit-status"); + "http://crm-server/rpc-api/crm/contract/update-audit-status", + loadBalancedRestTemplate); } } diff --git a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/api/event/CrmReceivableStatusListener.java b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/api/event/CrmReceivableStatusListener.java index c52047886..336c94cee 100644 --- a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/api/event/CrmReceivableStatusListener.java +++ b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/api/event/CrmReceivableStatusListener.java @@ -1,9 +1,10 @@ package cn.iocoder.yudao.module.bpm.api.event; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmHttpRequestUtils; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; import org.springframework.web.bind.annotation.RequestBody; - -import javax.validation.Valid; +import org.springframework.web.client.RestTemplate; /** * 回款审批的结果的监听器实现类 @@ -12,6 +13,9 @@ import javax.validation.Valid; */ public class CrmReceivableStatusListener extends BpmProcessInstanceStatusEventListener { + @Resource + private RestTemplate loadBalancedRestTemplate; + @Override public String getProcessDefinitionKey() { return "crm-receivable-audit"; @@ -20,7 +24,8 @@ public class CrmReceivableStatusListener extends BpmProcessInstanceStatusEventLi @Override public void onEvent(@RequestBody @Valid BpmProcessInstanceStatusEvent event) { BpmHttpRequestUtils.executeBpmHttpRequest(event, - "http://crm-server/rpc-api/crm/receivable/update-audit-status"); + "http://crm-server/rpc-api/crm/receivable/update-audit-status", + loadBalancedRestTemplate); } } diff --git a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnVariableConstants.java b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnVariableConstants.java index b3ce94614..c1d956836 100644 --- a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnVariableConstants.java +++ b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnVariableConstants.java @@ -54,11 +54,11 @@ public class BpmnVariableConstants { public static final String PROCESS_INSTANCE_VARIABLE_RETURN_FLAG = "RETURN_FLAG_%s"; /** - * 流程实例的变量前缀 - 用于退回操作,记录需要预测的节点:格式 NEED_SIMULATE_TASK_{节点定义 id} + * 流程实例的变量 - 用于退回操作,记录需要预测的节点 ids, 变量值类型为 Set * * 目的是:退回操作,预测节点会不准,在流程变量中记录需要预测的节点,来辅助预测 */ - public static final String PROCESS_INSTANCE_VARIABLE_NEED_SIMULATE_PREFIX = "NEED_SIMULATE_TASK_"; + public static final String PROCESS_INSTANCE_VARIABLE_NEED_SIMULATE_TASK_IDS = "NEED_SIMULATE_TASK_IDS"; /** * 流程实例的变量 - 是否跳过表达式 diff --git a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmHttpRequestUtils.java b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmHttpRequestUtils.java index 15ef72fb3..5b725afc9 100644 --- a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmHttpRequestUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmHttpRequestUtils.java @@ -80,6 +80,13 @@ public class BpmHttpRequestUtils { public static void executeBpmHttpRequest(BpmProcessInstanceStatusEvent event, String url) { + RestTemplate restTemplate = SpringUtils.getBean(RestTemplate.class); + executeBpmHttpRequest(event, url, restTemplate); + } + + public static void executeBpmHttpRequest(BpmProcessInstanceStatusEvent event, + String url, + RestTemplate restTemplate) { // 1.1 设置请求头 HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); @@ -102,7 +109,6 @@ public class BpmHttpRequestUtils { // } // 2. 发起请求 - RestTemplate restTemplate = SpringUtils.getBean(RestTemplate.class); sendHttpRequest(url, headers, event, restTemplate); } diff --git a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java index 349f88048..36ee811b4 100644 --- a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java @@ -18,11 +18,11 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConsta import com.google.common.collect.Maps; import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.converter.BpmnXMLConverter; -import org.flowable.bpmn.model.Process; import org.flowable.bpmn.model.*; +import org.flowable.bpmn.model.Process; import org.flowable.common.engine.api.FlowableException; +import org.flowable.common.engine.api.delegate.Expression; import org.flowable.common.engine.impl.util.io.BytesStreamSource; -import org.flowable.engine.impl.el.FixedValue; import java.util.*; @@ -406,7 +406,7 @@ public class BpmnModelUtils { flowableListener.getFieldExtensions().add(fieldExtension); } - public static BpmSimpleModelNodeVO.ListenerHandler parseListenerConfig(FixedValue fixedValue) { + public static BpmSimpleModelNodeVO.ListenerHandler parseListenerConfig(Expression fixedValue) { String expressionText = fixedValue.getExpressionText(); Assert.notNull(expressionText, "监听器扩展字段({})不能为空", expressionText); return JsonUtils.parseObject(expressionText, BpmSimpleModelNodeVO.ListenerHandler.class); @@ -673,7 +673,7 @@ public class BpmnModelUtils { // 这条线路存在目标节点,直接返回 true FlowElement sourceFlowElement = sequenceFlow.getSourceFlowElement(); if (target.getId().equals(sourceFlowElement.getId())) { - return true; + return true; } // 如果目标节点为并行网关,跳过这个循环 (TODO 疑问:这个判断作用是防止回退到并行网关分支上的节点吗?) if (sourceFlowElement instanceof ParallelGateway) { @@ -798,9 +798,9 @@ public class BpmnModelUtils { // 情况:StartEvent/EndEvent/UserTask/ServiceTask if (currentElement instanceof StartEvent - || currentElement instanceof EndEvent - || currentElement instanceof UserTask - || currentElement instanceof ServiceTask) { + || currentElement instanceof EndEvent + || currentElement instanceof UserTask + || currentElement instanceof ServiceTask) { // 添加节点 FlowNode flowNode = (FlowNode) currentElement; resultElements.add(flowNode); @@ -982,8 +982,8 @@ public class BpmnModelUtils { */ private static SequenceFlow findMatchSequenceFlowByExclusiveGateway(Gateway gateway, Map variables) { SequenceFlow matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(), - flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId()) - && (evalConditionExpress(variables, flow.getConditionExpression()))); + flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId()) + && (evalConditionExpress(variables, flow.getConditionExpression()))); if (matchSequenceFlow == null) { matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(), flow -> ObjUtil.equal(gateway.getDefaultFlow(), flow.getId())); diff --git a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java index f4a8993d3..1ebe6a8c6 100644 --- a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java @@ -54,7 +54,7 @@ public class BpmProcessInstanceCopyServiceImpl implements BpmProcessInstanceCopy } // 执行抄送 createProcessInstanceCopy(userIds, reason, - task.getProcessInstanceId(), task.getTaskDefinitionKey(), task.getId(), task.getName()); + task.getProcessInstanceId(), task.getTaskDefinitionKey(), task.getName(), task.getId()); } @Override diff --git a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java index abb69594a..4f798ffdf 100644 --- a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java @@ -2,9 +2,9 @@ package cn.iocoder.yudao.module.bpm.service.task; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.ListUtil; +import cn.hutool.core.convert.Convert; import cn.hutool.core.date.DateUtil; import cn.hutool.core.lang.Assert; -import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.*; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; @@ -12,6 +12,7 @@ import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils; +import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO; @@ -72,7 +73,6 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils. import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ActivityNode; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID; -import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_NEED_SIMULATE_PREFIX; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils.parseNodeType; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; @@ -227,11 +227,8 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService // 3.2 获取由于退回操作,需要预测的节点。从流程变量中获取,回退操作会设置这些变量 Set needSimulateTaskDefKeysByReturn = new HashSet<>(); if (StrUtil.isNotEmpty(reqVO.getProcessInstanceId())) { - Map variables = runtimeService.getVariables(reqVO.getProcessInstanceId()); - Map simulateTaskVariables = MapUtil.filter(variables, - item -> item.getKey().startsWith(PROCESS_INSTANCE_VARIABLE_NEED_SIMULATE_PREFIX)); - simulateTaskVariables.forEach((key, value) -> - needSimulateTaskDefKeysByReturn.add(StrUtil.removePrefix(key, PROCESS_INSTANCE_VARIABLE_NEED_SIMULATE_PREFIX))); + Object needSimulateTaskIds = runtimeService.getVariable(reqVO.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_NEED_SIMULATE_TASK_IDS); + needSimulateTaskDefKeysByReturn.addAll(Convert.toSet(String.class, needSimulateTaskIds)); } // 移除运行中的节点,运行中的节点无需预测 if (CollUtil.isNotEmpty(runActivityNodes)) { @@ -756,6 +753,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService @Override @Transactional(rollbackFor = Exception.class) + @DataPermission(enable = false) // 关闭数据权限,避免查询不到用户数据。相关案例:https://gitee.com/zhijiantianya/yudao-cloud/issues/ID1UYA public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqVO createReqVO) { // 获得流程定义 ProcessDefinition definition = processDefinitionService @@ -766,6 +764,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService } @Override + @DataPermission(enable = false) // 关闭数据权限,避免查询不到用户数据。相关案例:https://gitee.com/zhijiantianya/yudao-cloud/issues/ID1UYA public String createProcessInstance(Long userId, @Valid BpmProcessInstanceCreateReqDTO createReqDTO) { return FlowableUtils.executeAuthenticatedUserId(userId, () -> { // 获得流程定义 @@ -882,6 +881,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService } @Override + @DataPermission(enable = false) // 关闭数据权限,避免查询不到用户数据。相关案例:https://gitee.com/zhijiantianya/yudao-cloud/issues/ID1UYA public void cancelProcessInstanceByStartUser(Long userId, @Valid BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); @@ -911,6 +911,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService } @Override + @DataPermission(enable = false) // 关闭数据权限,避免查询不到用户数据。相关案例:https://gitee.com/zhijiantianya/yudao-cloud/issues/ID1UYA public void cancelProcessInstanceByAdmin(Long userId, BpmProcessInstanceCancelReqVO cancelReqVO) { // 1.1 校验流程实例存在 ProcessInstance instance = getProcessInstance(cancelReqVO.getId()); diff --git a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java index 8d02c09da..df1e53076 100644 --- a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java @@ -69,7 +69,6 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.START_USER_NODE_ID; -//import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants.*; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils.*; /** @@ -377,6 +376,11 @@ public class BpmTaskServiceImpl implements BpmTaskService { } // 2.2 过滤:只有串行可到达的节点,才可以退回。类似非串行、子流程无法退回 previousUserList.removeIf(userTask -> !BpmnModelUtils.isSequentialReachable(source, userTask, null)); + + // 2.3 过滤:只能退回到已经处理过的节点(排除审批未经过的节点)。相关 issue:https://github.com/YunaiV/ruoyi-vue-pro/issues/982 + List finishedTasks = getFinishedTaskListByProcessInstanceIdWithoutCancel(task.getProcessInstanceId()); + Set finishedTaskDefinitionKeys = convertSet(finishedTasks, HistoricTaskInstance::getTaskDefinitionKey); + previousUserList.removeIf(userTask -> !finishedTaskDefinitionKeys.contains(userTask.getId())); return previousUserList; } @@ -545,6 +549,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { @Override @Transactional(rollbackFor = Exception.class) + @DataPermission(enable = false) // 关闭数据权限,避免查询不到用户数据。相关案例:https://gitee.com/zhijiantianya/yudao-cloud/issues/ID1UYA public void approveTask(Long userId, @Valid BpmTaskApproveReqVO reqVO) { // 1.1 校验任务存在 Task task = validateTask(userId, reqVO.getId()); @@ -604,11 +609,13 @@ public class BpmTaskServiceImpl implements BpmTaskService { bpmnModel, reqVO.getNextAssignees(), instance); runtimeService.setVariables(task.getProcessInstanceId(), variables); - // 5. 移除辅助预测的流程变量,这些变量在回退操作中设置 - // todo @jason:可以直接 + 拼接哈 - String simulateVariableName = StrUtil.concat(false, - BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_NEED_SIMULATE_PREFIX, task.getTaskDefinitionKey()); - runtimeService.removeVariable(task.getProcessInstanceId(), simulateVariableName); + // 5. 如果当前节点 Id 存在于需要预测的流程节点中,从中移除。 流程变量在回退操作中设置 + Object needSimulateTaskIds = runtimeService.getVariable(task.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_NEED_SIMULATE_TASK_IDS); + Set needSimulateTaskIdsByReturn = Convert.toSet(String.class, needSimulateTaskIds); + if (needSimulateTaskIdsByReturn.contains(task.getTaskDefinitionKey())) { + needSimulateTaskIdsByReturn.remove(task.getTaskDefinitionKey()); + runtimeService.setVariable(task.getProcessInstanceId(), BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_NEED_SIMULATE_TASK_IDS, needSimulateTaskIdsByReturn); + } // 6. 调用 BPM complete 去完成任务 taskService.complete(task.getId(), variables, true); @@ -787,6 +794,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { @Override @Transactional(rollbackFor = Exception.class) + @DataPermission(enable = false) // 关闭数据权限,避免查询不到用户数据。相关案例:https://gitee.com/zhijiantianya/yudao-cloud/issues/ID1UYA public void rejectTask(Long userId, @Valid BpmTaskRejectReqVO reqVO) { // 1.1 校验任务存在 Task task = validateTask(userId, reqVO.getId()); @@ -854,6 +862,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { @Override @Transactional(rollbackFor = Exception.class) + @DataPermission(enable = false) // 关闭数据权限,避免查询不到用户数据。相关案例:https://gitee.com/zhijiantianya/yudao-cloud/issues/ID1UYA public void returnTask(Long userId, BpmTaskReturnReqVO reqVO) { // 1.1 当前任务 task Task task = validateTask(userId, reqVO.getId()); @@ -937,11 +946,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { }); // 3. 构建需要预测的任务流程变量 - // TODO @jason:【驳回预测相关】是不是搞成一个变量,里面是 set 更简洁一点呀? Set needSimulateTaskDefinitionKeys = getNeedSimulateTaskDefinitionKeys(bpmnModel, currentTask, targetElement); - Map needSimulateVariables = convertMap(needSimulateTaskDefinitionKeys, - key -> StrUtil.concat(false, BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_NEED_SIMULATE_PREFIX, key), item -> Boolean.TRUE); - // 4. 执行驳回 // 使用 moveExecutionsToSingleActivityId 替换 moveActivityIdsToSingleActivityId 原因: @@ -949,12 +954,11 @@ public class BpmTaskServiceImpl implements BpmTaskService { runtimeService.createChangeActivityStateBuilder() .processInstanceId(currentTask.getProcessInstanceId()) .moveExecutionsToSingleActivityId(runExecutionIds, reqVO.getTargetTaskDefinitionKey()) - // 设置需要预测的任务流程变量,用于辅助预测 - .processVariables(needSimulateVariables) - // 设置流程变量(local)节点退回标记, 用于退回到节点,不执行 BpmUserTaskAssignStartUserHandlerTypeEnum 策略,导致自动通过 + // 设置需要预测的任务 ids 的流程变量,用于辅助预测 + .processVariable(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_NEED_SIMULATE_TASK_IDS, needSimulateTaskDefinitionKeys) + // 设置流程变量(local)节点退回标记, 用于退回到节点,不执行 BpmUserTaskAssignStartUserHandlerTypeEnum 策略,导致自动通过 .localVariable(reqVO.getTargetTaskDefinitionKey(), - String.format(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, reqVO.getTargetTaskDefinitionKey()), - Boolean.TRUE) + String.format(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, reqVO.getTargetTaskDefinitionKey()), Boolean.TRUE) .changeState(); } @@ -987,6 +991,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { @Override @Transactional(rollbackFor = Exception.class) + @DataPermission(enable = false) // 关闭数据权限,避免查询不到用户数据。相关案例:https://gitee.com/zhijiantianya/yudao-cloud/issues/ID1UYA public void delegateTask(Long userId, BpmTaskDelegateReqVO reqVO) { String taskId = reqVO.getId(); // 1.1 校验任务 @@ -1016,6 +1021,8 @@ public class BpmTaskServiceImpl implements BpmTaskService { } @Override + @Transactional(rollbackFor = Exception.class) + @DataPermission(enable = false) // 关闭数据权限,避免查询不到用户数据。相关案例:https://gitee.com/zhijiantianya/yudao-cloud/issues/ID1UYA public void transferTask(Long userId, BpmTaskTransferReqVO reqVO) { String taskId = reqVO.getId(); // 1.1 校验任务 @@ -1046,6 +1053,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { @Override @Transactional(rollbackFor = Exception.class) + @DataPermission(enable = false) // 关闭数据权限,避免查询不到用户数据。相关案例:https://gitee.com/zhijiantianya/yudao-cloud/issues/ID1UYA public void moveTaskToEnd(String processInstanceId, String reason) { List taskList = getRunningTaskListByProcessInstanceId(processInstanceId, null, null); if (CollUtil.isEmpty(taskList)) { @@ -1084,6 +1092,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { @Override @Transactional(rollbackFor = Exception.class) + @DataPermission(enable = false) // 关闭数据权限,避免查询不到用户数据。相关案例:https://gitee.com/zhijiantianya/yudao-cloud/issues/ID1UYA public void createSignTask(Long userId, BpmTaskSignCreateReqVO reqVO) { // 1. 获取和校验任务 TaskEntityImpl taskEntity = validateTaskCanCreateSign(userId, reqVO); @@ -1200,6 +1209,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { @Override @Transactional(rollbackFor = Exception.class) + @DataPermission(enable = false) // 关闭数据权限,避免查询不到用户数据。相关案例:https://gitee.com/zhijiantianya/yudao-cloud/issues/ID1UYA @SuppressWarnings("DataFlowIssue") public void deleteSignTask(Long userId, BpmTaskSignDeleteReqVO reqVO) { // 1.1 校验 task 可以被减签 @@ -1239,6 +1249,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { @Override @Transactional(rollbackFor = Exception.class) + @DataPermission(enable = false) // 关闭数据权限,避免查询不到用户数据。相关案例:https://gitee.com/zhijiantianya/yudao-cloud/issues/ID1UYA public void withdrawTask(Long userId, String taskId) { // 1.1 查询本人已办任务 HistoricTaskInstance taskInstance = historyService.createHistoricTaskInstanceQuery() diff --git a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/service/task/listener/BpmCallActivityListener.java b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/service/task/listener/BpmCallActivityListener.java index 4766f4dd0..5eea679ab 100644 --- a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/service/task/listener/BpmCallActivityListener.java +++ b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/service/task/listener/BpmCallActivityListener.java @@ -13,9 +13,9 @@ import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionServic import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; import lombok.Setter; import lombok.extern.slf4j.Slf4j; +import org.flowable.common.engine.api.delegate.Expression; import org.flowable.engine.delegate.DelegateExecution; import org.flowable.engine.delegate.ExecutionListener; -import org.flowable.engine.impl.el.FixedValue; import org.flowable.engine.runtime.ProcessInstance; import org.springframework.stereotype.Component; @@ -34,7 +34,7 @@ public class BpmCallActivityListener implements ExecutionListener { public static final String DELEGATE_EXPRESSION = "${bpmCallActivityListener}"; @Setter - private FixedValue listenerConfig; + private Expression listenerConfig; @Resource private BpmProcessDefinitionService processDefinitionService; diff --git a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/service/task/listener/BpmUserTaskListener.java b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/service/task/listener/BpmUserTaskListener.java index 646982e38..b76f9eb40 100644 --- a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/service/task/listener/BpmUserTaskListener.java +++ b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/service/task/listener/BpmUserTaskListener.java @@ -6,8 +6,8 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmHttpRequestUt import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService; import lombok.Setter; import lombok.extern.slf4j.Slf4j; +import org.flowable.common.engine.api.delegate.Expression; import org.flowable.engine.delegate.TaskListener; -import org.flowable.engine.impl.el.FixedValue; import org.flowable.engine.runtime.ProcessInstance; import org.flowable.task.service.delegate.DelegateTask; import org.springframework.context.annotation.Scope; @@ -34,7 +34,7 @@ public class BpmUserTaskListener implements TaskListener { private BpmProcessInstanceService processInstanceService; @Setter - private FixedValue listenerConfig; + private Expression listenerConfig; @Override public void notify(DelegateTask delegateTask) { diff --git a/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanRespVO.java b/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanRespVO.java index 2208887db..5feb99c22 100644 --- a/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanRespVO.java +++ b/yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanRespVO.java @@ -59,7 +59,6 @@ public class CrmReceivablePlanRespVO { @ExcelProperty("回款编号") private Long receivableId; @Schema(description = "回款信息") - @ExcelProperty("回款信息") private CrmReceivableRespVO receivable; @Schema(description = "提前几天提醒", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") diff --git a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/AbstractFileClient.java b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/AbstractFileClient.java index 3c7883b83..7ddade3ea 100644 --- a/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/AbstractFileClient.java +++ b/yudao-module-infra/yudao-module-infra-server/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/AbstractFileClient.java @@ -19,10 +19,18 @@ public abstract class AbstractFileClient implem * 文件配置 */ protected Config config; + /** + * 原始的文件配置 + * + * 原因:{@link #config} 可能被子类所修改,无法用于判断配置是否变更 + * @link 相关案例 + */ + private Config originalConfig; public AbstractFileClient(Long id, Config config) { this.id = id; this.config = config; + this.originalConfig = config; } /** @@ -40,11 +48,12 @@ public abstract class AbstractFileClient implem public final void refresh(Config config) { // 判断是否更新 - if (config.equals(this.config)) { + if (config.equals(this.originalConfig)) { return; } log.info("[refresh][配置({})发生变化,重新初始化]", config); this.config = config; + this.originalConfig = config; // 初始化 this.init(); } diff --git a/yudao-module-infra/yudao-module-infra-server/src/main/resources/codegen/vue3/views/index.vue.vm b/yudao-module-infra/yudao-module-infra-server/src/main/resources/codegen/vue3/views/index.vue.vm index fb7485d6d..857972a85 100644 --- a/yudao-module-infra/yudao-module-infra-server/src/main/resources/codegen/vue3/views/index.vue.vm +++ b/yudao-module-infra/yudao-module-infra-server/src/main/resources/codegen/vue3/views/index.vue.vm @@ -353,7 +353,9 @@ const handleDelete = async (id: number) => { // 发起删除 await ${simpleClassName}Api.delete${simpleClassName}(id) message.success(t('common.delSuccess')) +#if ( $table.templateType == 11 ) currentRow.value = {} +#end // 刷新列表 await getList() } catch {} diff --git a/yudao-module-infra/yudao-module-infra-server/src/main/resources/codegen/vue3_vben5_ele/schema/views/data.ts.vm b/yudao-module-infra/yudao-module-infra-server/src/main/resources/codegen/vue3_vben5_ele/schema/views/data.ts.vm index da9582448..c0f5b8e97 100644 --- a/yudao-module-infra/yudao-module-infra-server/src/main/resources/codegen/vue3_vben5_ele/schema/views/data.ts.vm +++ b/yudao-module-infra/yudao-module-infra-server/src/main/resources/codegen/vue3_vben5_ele/schema/views/data.ts.vm @@ -114,6 +114,7 @@ export function useFormSchema(): VbenFormSchema[] { showTime: true, format: 'YYYY-MM-DD HH:mm:ss', valueFormat: 'x', + class: '!w-full', }, #elseif($column.htmlType == "textarea")## 文本域 component: 'Textarea', @@ -317,6 +318,7 @@ export function use${subSimpleClassName}FormSchema(): VbenFormSchema[] { showTime: true, format: 'YYYY-MM-DD HH:mm:ss', valueFormat: 'x', + class: '!w-full', }, #elseif($column.htmlType == "textarea")## 文本域 component: 'Textarea', @@ -552,6 +554,7 @@ export function use${subSimpleClassName}GridColumns(): VxeTableGridOptions<${api showTime: true, format: 'YYYY-MM-DD HH:mm:ss', valueFormat: 'x', + class: '!w-full', }, #elseif($column.htmlType == "textarea")## 文本域 component: 'Textarea', diff --git a/yudao-module-iot/yudao-module-iot-server/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/alert/IotAlertConfigMapper.java b/yudao-module-iot/yudao-module-iot-server/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/alert/IotAlertConfigMapper.java index c5d7154ff..52b0d360d 100644 --- a/yudao-module-iot/yudao-module-iot-server/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/alert/IotAlertConfigMapper.java +++ b/yudao-module-iot/yudao-module-iot-server/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/alert/IotAlertConfigMapper.java @@ -33,7 +33,7 @@ public interface IotAlertConfigMapper extends BaseMapperX { default List selectListBySceneRuleIdAndStatus(Long sceneRuleId, Integer status) { return selectList(new LambdaQueryWrapperX() .eq(IotAlertConfigDO::getStatus, status) - .apply(MyBatisUtils.findInSet("scene_rule_id", sceneRuleId))); + .apply(MyBatisUtils.findInSet("scene_rule_ids", sceneRuleId))); } } \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-server/src/main/java/cn/iocoder/yudao/module/iot/job/ota/IotOtaUpgradeJob.java b/yudao-module-iot/yudao-module-iot-server/src/main/java/cn/iocoder/yudao/module/iot/job/ota/IotOtaUpgradeJob.java index 1567de013..4b73919be 100644 --- a/yudao-module-iot/yudao-module-iot-server/src/main/java/cn/iocoder/yudao/module/iot/job/ota/IotOtaUpgradeJob.java +++ b/yudao-module-iot/yudao-module-iot-server/src/main/java/cn/iocoder/yudao/module/iot/job/ota/IotOtaUpgradeJob.java @@ -36,7 +36,7 @@ public class IotOtaUpgradeJob { @Resource private IotDeviceService deviceService; - @XxlJob("deviceOfflineCheckJob") + @XxlJob("deviceUpgradeJob") @TenantJob // 多租户 public String execute(String param) throws Exception { // 1. 查询待推送的 OTA 升级记录 diff --git a/yudao-module-iot/yudao-module-iot-server/src/main/java/cn/iocoder/yudao/module/iot/mq/consumer/rule/IotSceneRuleMessageHandler.java b/yudao-module-iot/yudao-module-iot-server/src/main/java/cn/iocoder/yudao/module/iot/mq/consumer/rule/IotSceneRuleMessageHandler.java index 278cd775e..800344dd2 100644 --- a/yudao-module-iot/yudao-module-iot-server/src/main/java/cn/iocoder/yudao/module/iot/mq/consumer/rule/IotSceneRuleMessageHandler.java +++ b/yudao-module-iot/yudao-module-iot-server/src/main/java/cn/iocoder/yudao/module/iot/mq/consumer/rule/IotSceneRuleMessageHandler.java @@ -43,9 +43,6 @@ public class IotSceneRuleMessageHandler implements IotMessageSubscriber couponTemplate.getTotalCount()) { // 已领取数量 >= 总发放数量 throw exception(COUPON_TEMPLATE_NOT_ENOUGH); } // 校验"固定日期"的有效期类型是否过期 diff --git a/yudao-module-mall/yudao-module-trade-server/src/main/java/cn/iocoder/yudao/module/trade/convert/delivery/DeliveryExpressTemplateConvert.java b/yudao-module-mall/yudao-module-trade-server/src/main/java/cn/iocoder/yudao/module/trade/convert/delivery/DeliveryExpressTemplateConvert.java index b917d874b..3c02e8917 100644 --- a/yudao-module-mall/yudao-module-trade-server/src/main/java/cn/iocoder/yudao/module/trade/convert/delivery/DeliveryExpressTemplateConvert.java +++ b/yudao-module-mall/yudao-module-trade-server/src/main/java/cn/iocoder/yudao/module/trade/convert/delivery/DeliveryExpressTemplateConvert.java @@ -85,7 +85,9 @@ public interface DeliveryExpressTemplateConvert { .setChargeMode(template.getChargeMode()) .setCharge(convertTemplateCharge(findFirst(templateIdChargeMap.get(template.getId()), charge -> charge.getAreaIds().contains(areaId)))) .setFree(convertTemplateFree(findFirst(templateIdFreeMap.get(template.getId()), free -> free.getAreaIds().contains(areaId)))); - result.put(template.getId(), bo); + if (bo.getCharge() != null || bo.getFree() != null) { + result.put(template.getId(), bo); + } }); return result; } diff --git a/yudao-module-mall/yudao-module-trade-server/src/main/java/cn/iocoder/yudao/module/trade/convert/order/TradeOrderConvert.java b/yudao-module-mall/yudao-module-trade-server/src/main/java/cn/iocoder/yudao/module/trade/convert/order/TradeOrderConvert.java index beacddab8..c90d9ed81 100644 --- a/yudao-module-mall/yudao-module-trade-server/src/main/java/cn/iocoder/yudao/module/trade/convert/order/TradeOrderConvert.java +++ b/yudao-module-mall/yudao-module-trade-server/src/main/java/cn/iocoder/yudao/module/trade/convert/order/TradeOrderConvert.java @@ -268,7 +268,9 @@ public interface TradeOrderConvert { .setTitle(StrUtil.format("{}成功购买{}", user.getNickname(), item.getSpuName())) .setFirstFixedPrice(0).setSecondFixedPrice(0); if (BooleanUtil.isTrue(spu.getSubCommissionType())) { - bo.setFirstFixedPrice(sku.getFirstBrokeragePrice()).setSecondFixedPrice(sku.getSecondBrokeragePrice()); + // 特殊:单独设置的佣金需要乘以购买数量。关联 https://gitee.com/yudaocode/yudao-mall-uniapp/issues/ICY7SJ + bo.setFirstFixedPrice(sku.getFirstBrokeragePrice() * item.getCount()) + .setSecondFixedPrice(sku.getSecondBrokeragePrice() * item.getCount()); } return bo; } diff --git a/yudao-module-mall/yudao-module-trade-server/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/brokerage/BrokerageRecordDO.java b/yudao-module-mall/yudao-module-trade-server/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/brokerage/BrokerageRecordDO.java index c819d723b..7dec2b850 100644 --- a/yudao-module-mall/yudao-module-trade-server/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/brokerage/BrokerageRecordDO.java +++ b/yudao-module-mall/yudao-module-trade-server/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/brokerage/BrokerageRecordDO.java @@ -29,7 +29,7 @@ public class BrokerageRecordDO extends BaseDO { * 编号 */ @TableId - private Integer id; + private Long id; /** * 用户编号 *

diff --git a/yudao-module-mall/yudao-module-trade-server/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/BrokerageRecordMapper.java b/yudao-module-mall/yudao-module-trade-server/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/BrokerageRecordMapper.java index a17c3bfca..8c64354f8 100644 --- a/yudao-module-mall/yudao-module-trade-server/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/BrokerageRecordMapper.java +++ b/yudao-module-mall/yudao-module-trade-server/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/BrokerageRecordMapper.java @@ -44,7 +44,7 @@ public interface BrokerageRecordMapper extends BaseMapperX { .lt(BrokerageRecordDO::getUnfreezeTime, unfreezeTime)); } - default int updateByIdAndStatus(Integer id, Integer status, BrokerageRecordDO updateObj) { + default int updateByIdAndStatus(Long id, Integer status, BrokerageRecordDO updateObj) { return update(updateObj, new LambdaQueryWrapper() .eq(BrokerageRecordDO::getId, id) .eq(BrokerageRecordDO::getStatus, status)); diff --git a/yudao-module-mall/yudao-module-trade-server/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleServiceImpl.java b/yudao-module-mall/yudao-module-trade-server/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleServiceImpl.java index ea326ca5c..c201bb605 100644 --- a/yudao-module-mall/yudao-module-trade-server/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleServiceImpl.java +++ b/yudao-module-mall/yudao-module-trade-server/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleServiceImpl.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.trade.service.aftersale; import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; @@ -352,12 +353,20 @@ public class AfterSaleServiceImpl implements AfterSaleService { throw exception(AFTER_SALE_REFUND_FAIL_STATUS_NOT_WAIT_REFUND); } - // 发起退款单。注意,需要在事务提交后,再进行发起,避免重复发起 - createPayRefund(userIp, afterSale); + Integer newStatus; + if (ObjUtil.equals(afterSale.getRefundPrice(), 0)) { + // 特殊:退款为 0 的订单,直接标记为完成(积分商城)。关联案例:https://t.zsxq.com/AQEvL + updateAfterSaleStatus(afterSale.getId(), AfterSaleStatusEnum.WAIT_REFUND.getStatus(), new AfterSaleDO() + .setStatus(AfterSaleStatusEnum.COMPLETE.getStatus()).setRefundTime(LocalDateTime.now())); + newStatus = AfterSaleStatusEnum.COMPLETE.getStatus(); + } else { + // 发起退款单。注意,需要在事务提交后,再进行发起,避免重复发起 + createPayRefund(userIp, afterSale); + newStatus = afterSale.getStatus(); // 特殊:这里状态不变,而是最终 updateAfterSaleRefunded 处理!!! + } // 记录售后日志 - AfterSaleLogUtils.setAfterSaleInfo(afterSale.getId(), afterSale.getStatus(), - afterSale.getStatus()); // 特殊:这里状态不变,而是最终 updateAfterSaleRefunded 处理!!! + AfterSaleLogUtils.setAfterSaleInfo(afterSale.getId(), afterSale.getStatus(), newStatus); } private void createPayRefund(String userIp, AfterSaleDO afterSale) { diff --git a/yudao-module-mall/yudao-module-trade-server/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawServiceImpl.java b/yudao-module-mall/yudao-module-trade-server/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawServiceImpl.java index e143f5035..c6cdbd89e 100644 --- a/yudao-module-mall/yudao-module-trade-server/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawServiceImpl.java +++ b/yudao-module-mall/yudao-module-trade-server/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawServiceImpl.java @@ -147,9 +147,10 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService { userAccount = wallet.getId().toString(); } // 1.2 构建请求 + Integer transferPrice = withdraw.getPrice() - withdraw.getFeePrice(); // 计算实际转账金额(提现金额 - 手续费) PayTransferCreateReqDTO transferReqDTO = new PayTransferCreateReqDTO() .setAppKey(tradeOrderProperties.getPayAppKey()).setChannelCode(channelCode) - .setMerchantTransferId(withdraw.getId().toString()).setSubject("佣金提现").setPrice(withdraw.getPrice()) + .setMerchantTransferId(withdraw.getId().toString()).setSubject("佣金提现").setPrice(transferPrice) .setUserAccount(userAccount).setUserName(userName).setUserIp(getClientIP()) .setUserId(withdraw.getUserId()).setUserType(UserTypeEnum.MEMBER.getValue()) // 用户信息 .setChannelExtras(channelExtras); @@ -280,9 +281,10 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService { throw exception(BROKERAGE_WITHDRAW_UPDATE_STATUS_FAIL_PAY_TRANSFER_STATUS_NOT_SUCCESS_OR_CLOSED); } // 2.2 校验转账金额一致 - if (ObjectUtil.notEqual(payTransfer.getPrice(), withdraw.getPrice())) { - log.error("[validateBrokerageTransferStatusCanUpdate][withdraw({}) payTransfer({}) 转账金额不匹配,请进行处理!withdraw 数据是:{},payTransfer 数据是:{}]", - withdraw.getId(), payTransferId, JsonUtils.toJsonString(withdraw), JsonUtils.toJsonString(payTransfer)); + Integer expectedTransferPrice = withdraw.getPrice() - withdraw.getFeePrice(); // 转账金额 = 提现金额 - 手续费 + if (ObjectUtil.notEqual(payTransfer.getPrice(), expectedTransferPrice)) { + log.error("[validateBrokerageTransferStatusCanUpdate][withdraw({}) payTransfer({}) 转账金额不匹配,请进行处理!withdraw 数据是:{},payTransfer 数据是:{},期望转账金额:{}]", + withdraw.getId(), payTransferId, JsonUtils.toJsonString(withdraw), JsonUtils.toJsonString(payTransfer), expectedTransferPrice); throw exception(BROKERAGE_WITHDRAW_UPDATE_STATUS_FAIL_PAY_PRICE_NOT_MATCH); } // 2.3 校验转账订单匹配 diff --git a/yudao-module-pay/yudao-module-pay-server/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/AbstractWxPayClient.java b/yudao-module-pay/yudao-module-pay-server/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/AbstractWxPayClient.java index 4ee904bbb..3b54862a7 100644 --- a/yudao-module-pay/yudao-module-pay-server/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/AbstractWxPayClient.java +++ b/yudao-module-pay/yudao-module-pay-server/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/client/impl/weixin/AbstractWxPayClient.java @@ -70,7 +70,7 @@ public abstract class AbstractWxPayClient extends AbstractPayClient userIds = reqVO.getRoleId() != null ? permissionService.getUserRoleIdListByRoleId(singleton(reqVO.getRoleId())) : null; - + if (userIds != null && userIds.isEmpty()) { + return PageResult.empty(); + } // 分页查询 return userMapper.selectPage(reqVO, getDeptCondition(reqVO.getDeptId()), userIds); } diff --git a/yudao-server/src/main/resources/application.yaml b/yudao-server/src/main/resources/application.yaml index d69205ac6..58a66ea6b 100644 --- a/yudao-server/src/main/resources/application.yaml +++ b/yudao-server/src/main/resources/application.yaml @@ -235,6 +235,8 @@ spring: filesystem: url: http://127.0.0.1:8089 sse-endpoint: /sse + annotation-scanner: + enabled: false # TODO @芋艿:有 bug https://github.com/spring-projects/spring-ai/issues/4917 需要官方修复 yudao: ai: