# Conflicts:
#	yudao-dependencies/pom.xml
#	yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/api/event/CrmContractStatusListener.java
#	yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/api/event/CrmReceivableStatusListener.java
This commit is contained in:
YunaiV 2025-11-24 11:26:03 +08:00
commit 4f1de29748
47 changed files with 420 additions and 153 deletions

View File

@ -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,

View File

@ -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<LocalDateTi
@Override
public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
// LocalDateTime 对象转换为 Long 时间戳
String fieldName = gen.getOutputContext().getCurrentName();
Class<?> 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());
}

View File

@ -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<DataPermissionRule> 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;
}
}

View File

@ -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 <T> List<T> read(MultipartFile file, Class<T> 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();
}
}
}

View File

@ -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<String, Object> map = new HashMap<>();
map.put(ID_TYPE_KEY, idType);
environment.getPropertySources().addFirst(new MapPropertySource("mybatisPlusIdType", map));
log.info("[setIdType][修改 MyBatis Plus 的 idType 为({})]", idType);
}

View File

@ -68,6 +68,29 @@ public interface BaseMapperX<T> extends MPJBaseMapper<T> {
return new PageResult<>(mpPage.getRecords(), mpPage.getTotal());
}
/**
* 执行分页查询并返回结果
*
* @param pageParam 分页参数包含页码每页条数和排序字段信息如果 pageSize {@link PageParam#PAGE_SIZE_NONE}则不分页直接查询所有数据
* @param clazz 结果集的类类型
* @param lambdaWrapper MyBatis Plus Join 查询条件包装器
* @param <D> 结果集的泛型类型
* @return 返回分页查询的结果包括总记录数和当前页的数据列表
*/
default <D> PageResult<D> selectJoinPage(SortablePageParam pageParam, Class<D> clazz, MPJLambdaWrapper<T> lambdaWrapper) {
// 特殊不分页直接查询全部
if (PageParam.PAGE_SIZE_NONE.equals(pageParam.getPageSize())) {
List<D> list = selectJoinList(clazz, lambdaWrapper);
return new PageResult<>(list, (long) list.size());
}
// MyBatis Plus Join 查询
IPage<D> mpPage = MyBatisUtils.buildPage(pageParam, pageParam.getSortingFields());
mpPage = selectJoinPage(mpPage, clazz, lambdaWrapper);
// 转换返回
return new PageResult<>(mpPage.getRecords(), mpPage.getTotal());
}
default <DTO> PageResult<DTO> selectJoinPage(PageParam pageParam, Class<DTO> resultTypeClass, MPJBaseJoin<T> joinQueryWrapper) {
IPage<DTO> mpPage = MyBatisUtils.buildPage(pageParam);
selectJoinPage(mpPage, resultTypeClass, joinQueryWrapper);

View File

@ -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

View File

@ -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<CorsFilter> 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();
}
}

View File

@ -40,6 +40,7 @@ public enum AiPlatformEnum implements ArrayValuable<String> {
STABLE_DIFFUSION("StableDiffusion", "StableDiffusion"), // Stability AI
MIDJOURNEY("Midjourney", "Midjourney"), // Midjourney
SUNO("Suno", "Suno"), // Suno AI
GROK("Grok","Grok"), // Grok
;

View File

@ -19,9 +19,9 @@
国外OpenAI、Ollama、Midjourney、StableDiffusion、Suno
</description>
<properties>
<spring-ai.version>1.0.1</spring-ai.version>
<alibaba-ai.version>1.0.0.3</alibaba-ai.version>
<tinyflow.version>1.0.2</tinyflow.version>
<spring-ai.version>1.1.0</spring-ai.version>
<alibaba-ai.version>1.0.0.4</alibaba-ai.version>
<tinyflow.version>1.2.6</tinyflow.version>
</properties>
<dependencies>

View File

@ -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 创建报错相关 issuehttps://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

View File

@ -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 {

View File

@ -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)

View File

@ -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<ChatResponse> stream(Prompt prompt) {
return openAiChatModel.stream(prompt);
}
@Override
public ChatOptions getDefaultOptions() {
return openAiChatModel.getDefaultOptions();
}
}

View File

@ -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<McpServerProperties> serverProperties;
private Optional<McpServerSseProperties> mcpServerSseProperties;
@Resource
private Optional<McpServerStreamableHttpProperties> 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());
}
};

View File

@ -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;

View File

@ -227,6 +227,9 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService
// 2. 检索
List<Document> documents = searchDocument(knowledge, reqBO);
if (CollUtil.isEmpty(documents)) {
return ListUtil.empty();
}
// 3.1 段落召回
List<AiKnowledgeSegmentDO> segments = segmentMapper

View File

@ -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));

View File

@ -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:

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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";
/**
* 流程实例的变量 - 是否跳过表达式

View File

@ -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);
}

View File

@ -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<String, Object> 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()));

View File

@ -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

View File

@ -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<String> needSimulateTaskDefKeysByReturn = new HashSet<>();
if (StrUtil.isNotEmpty(reqVO.getProcessInstanceId())) {
Map<String, Object> variables = runtimeService.getVariables(reqVO.getProcessInstanceId());
Map<String, Object> 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());

View File

@ -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 过滤只能退回到已经处理过的节点排除审批未经过的节点相关 issuehttps://github.com/YunaiV/ruoyi-vue-pro/issues/982
List<HistoricTaskInstance> finishedTasks = getFinishedTaskListByProcessInstanceIdWithoutCancel(task.getProcessInstanceId());
Set<String> 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<String> 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<String> needSimulateTaskDefinitionKeys = getNeedSimulateTaskDefinitionKeys(bpmnModel, currentTask, targetElement);
Map<String, Object> 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<Task> 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()

View File

@ -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;

View File

@ -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) {

View File

@ -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")

View File

@ -19,10 +19,18 @@ public abstract class AbstractFileClient<Config extends FileClientConfig> implem
* 文件配置
*/
protected Config config;
/**
* 原始的文件配置
*
* 原因{@link #config} 可能被子类所修改无法用于判断配置是否变更
* @link <a href="https://t.zsxq.com/29wkW">相关案例</a>
*/
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<Config extends FileClientConfig> 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();
}

View File

@ -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 {}

View File

@ -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',

View File

@ -33,7 +33,7 @@ public interface IotAlertConfigMapper extends BaseMapperX<IotAlertConfigDO> {
default List<IotAlertConfigDO> selectListBySceneRuleIdAndStatus(Long sceneRuleId, Integer status) {
return selectList(new LambdaQueryWrapperX<IotAlertConfigDO>()
.eq(IotAlertConfigDO::getStatus, status)
.apply(MyBatisUtils.findInSet("scene_rule_id", sceneRuleId)));
.apply(MyBatisUtils.findInSet("scene_rule_ids", sceneRuleId)));
}
}

View File

@ -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 升级记录

View File

@ -43,9 +43,6 @@ public class IotSceneRuleMessageHandler implements IotMessageSubscriber<IotDevic
@Override
public void onMessage(IotDeviceMessage message) {
if (true) {
return;
}
log.info("[onMessage][消息内容({})]", message);
sceneRuleService.executeSceneRuleByDevice(message);
}

View File

@ -282,10 +282,11 @@ public class CouponServiceImpl implements CouponService {
if (ObjUtil.notEqual(couponTemplate.getTakeType(), takeType.getType())) {
throw exception(COUPON_TEMPLATE_CANNOT_TAKE);
}
// 校验发放数量不能过小仅在 CouponTakeTypeEnum.USER 用户领取时
// 校验剩余发放数量是否充足仅在 CouponTakeTypeEnum.USER 用户领取时
// 关联案例https://t.zsxq.com/mElGQhttps://t.zsxq.com/6pLzr
if (CouponTakeTypeEnum.isUser(couponTemplate.getTakeType())
&& ObjUtil.notEqual(couponTemplate.getTakeLimitCount(), CouponTemplateDO.TAKE_LIMIT_COUNT_MAX) // 校验不限制领取数
&& ObjUtil.notEqual(couponTemplate.getTotalCount(), CouponTemplateDO.TOTAL_COUNT_MAX)) { // 校验不限制发放数量
&& couponTemplate.getTakeCount() > couponTemplate.getTotalCount()) { // 已领取数量 >= 发放数量
throw exception(COUPON_TEMPLATE_NOT_ENOUGH);
}
// 校验"固定日期"的有效期类型是否过期

View File

@ -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;
}

View File

@ -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;
}

View File

@ -29,7 +29,7 @@ public class BrokerageRecordDO extends BaseDO {
* 编号
*/
@TableId
private Integer id;
private Long id;
/**
* 用户编号
* <p>

View File

@ -44,7 +44,7 @@ public interface BrokerageRecordMapper extends BaseMapperX<BrokerageRecordDO> {
.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<BrokerageRecordDO>()
.eq(BrokerageRecordDO::getId, id)
.eq(BrokerageRecordDO::getStatus, status));

View File

@ -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) {

View File

@ -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 校验转账订单匹配

View File

@ -70,7 +70,7 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
payConfig.setPrivateKeyPath(FileUtils.createTempFile(config.getPrivateKeyContent()).getPath());
// 参考 https://gitee.com/yudaocode/yudao-ui-admin-vue3/issues/ICUE53 https://t.zsxq.com/ODR5V
if (StrUtil.isNotBlank(config.getPublicKeyContent())) {
payConfig.setPrivateCertPath(FileUtils.createTempFile(Base64.decode(config.getPublicKeyContent())).getPath());
payConfig.setPublicKeyPath(FileUtils.createTempFile(config.getPublicKeyContent()).getPath());
}
// 特殊强制使用微信公钥模式避免灰度期间的问题
payConfig.setStrictlyNeedWechatPaySerial(true);

View File

@ -153,6 +153,7 @@ public class SocialUserServiceImpl implements SocialUserService {
if (socialUser.getId() == null) {
socialUserMapper.insert(socialUser);
} else {
socialUser.clean(); // 避免 updateTime 不更新https://gitee.com/yudaocode/yudao-boot-mini/issues/ID7FUL
socialUserMapper.updateById(socialUser);
}
return socialUser;

View File

@ -276,7 +276,9 @@ public class AdminUserServiceImpl implements AdminUserService {
// 如果有角色编号查询角色对应的用户编号
Set<Long> 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);
}

View File

@ -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: