diff --git a/yudao-dependencies/pom.xml b/yudao-dependencies/pom.xml
index cc696f271c..e08f834fb9 100644
--- a/yudao-dependencies/pom.xml
+++ b/yudao-dependencies/pom.xml
@@ -61,7 +61,7 @@
2.14.5
3.11.1
3.18.0
- 0.1.55
+ 2.27.3
3.2.2
2.7.0
3.0.6
@@ -526,7 +526,7 @@
${commons-net.version}
- com.jcraft
+ com.github.mwiede
jsch
${jsch.version}
diff --git a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/config/YudaoMybatisAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/config/YudaoMybatisAutoConfiguration.java
index 4cbb91c2cc..775447a060 100644
--- a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/config/YudaoMybatisAutoConfiguration.java
+++ b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/config/YudaoMybatisAutoConfiguration.java
@@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.mybatis.core.handler.DefaultDBFieldHandler;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration;
+import com.baomidou.mybatisplus.core.handlers.IJsonTypeHandler;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.baomidou.mybatisplus.core.incrementer.IKeyGenerator;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
@@ -80,8 +81,8 @@ public class YudaoMybatisAutoConfiguration {
throw new IllegalArgumentException(StrUtil.format("DbType{} 找不到合适的 IKeyGenerator 实现类", dbType));
}
- @Bean
- public JacksonTypeHandler jacksonTypeHandler(List objectMappers) {
+ @Bean // 特殊:返回结果使用 Object 而不用 JacksonTypeHandler 的原因,避免因为 JacksonTypeHandler 被 mybatis 全局使用!
+ public Object jacksonTypeHandler(List objectMappers) {
// 特殊:设置 JacksonTypeHandler 的 ObjectMapper!
ObjectMapper objectMapper = CollUtil.getFirst(objectMappers);
if (objectMapper == null) {
diff --git a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/dataobject/BaseDO.java b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/dataobject/BaseDO.java
index a79fb2a73c..7e07fd8e32 100644
--- a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/dataobject/BaseDO.java
+++ b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/dataobject/BaseDO.java
@@ -6,6 +6,7 @@ import com.baomidou.mybatisplus.annotation.TableLogic;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fhs.core.trans.vo.TransPojo;
import lombok.Data;
+import org.apache.ibatis.type.JdbcType;
import java.io.Serializable;
import java.time.LocalDateTime;
@@ -37,14 +38,14 @@ public abstract class BaseDO implements Serializable, TransPojo {
*
* 使用 String 类型的原因是,未来可能会存在非数值的情况,留好拓展性。
*/
- @TableField(fill = FieldFill.INSERT)
+ @TableField(fill = FieldFill.INSERT, jdbcType = JdbcType.VARCHAR)
private String creator;
/**
* 更新者,目前使用 SysUser 的 id 编号
*
* 使用 String 类型的原因是,未来可能会存在非数值的情况,留好拓展性。
*/
- @TableField(fill = FieldFill.INSERT_UPDATE)
+ @TableField(fill = FieldFill.INSERT_UPDATE, jdbcType = JdbcType.VARCHAR)
private String updater;
/**
* 是否删除
diff --git a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/swagger/config/YudaoSwaggerAutoConfiguration.java b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/swagger/config/YudaoSwaggerAutoConfiguration.java
index dec79d8eb6..e77a065f99 100644
--- a/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/swagger/config/YudaoSwaggerAutoConfiguration.java
+++ b/yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/swagger/config/YudaoSwaggerAutoConfiguration.java
@@ -1,5 +1,6 @@
package cn.iocoder.yudao.framework.swagger.config;
+import com.github.xiaoymin.knife4j.spring.configuration.Knife4jAutoConfiguration;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
@@ -11,6 +12,7 @@ import io.swagger.v3.oas.models.parameters.Parameter;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import org.springdoc.core.customizers.OpenApiBuilderCustomizer;
+import org.springdoc.core.customizers.OperationCustomizer;
import org.springdoc.core.customizers.ServerBaseUrlCustomizer;
import org.springdoc.core.models.GroupedOpenApi;
import org.springdoc.core.properties.SpringDocConfigProperties;
@@ -43,7 +45,7 @@ import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.HEADER_
*
* @author 芋道源码
*/
-@AutoConfiguration
+@AutoConfiguration(before = Knife4jAutoConfiguration.class) // before 原因,保证覆写的 Knife4jOpenApiCustomizer 先生效!相关 https://github.com/YunaiV/ruoyi-vue-pro/issues/954 讨论
@ConditionalOnClass({OpenAPI.class})
@EnableConfigurationProperties(SwaggerProperties.class)
@ConditionalOnProperty(prefix = "springdoc.api-docs", name = "enabled", havingValue = "true", matchIfMissing = true) // 设置为 false 时,禁用
@@ -127,6 +129,7 @@ public class YudaoSwaggerAutoConfiguration {
.addOperationCustomizer((operation, handlerMethod) -> operation
.addParametersItem(buildTenantHeaderParameter())
.addParametersItem(buildSecurityHeaderParameter()))
+ .addOperationCustomizer(buildOperationIdCustomizer())
.build();
}
@@ -158,5 +161,26 @@ public class YudaoSwaggerAutoConfiguration {
.schema(new StringSchema()._default("Bearer test1").name(HEADER_TENANT_ID).description("认证 Token")); // 默认:使用用户编号为 1
}
+ /**
+ * 核心:自定义OperationId生成规则,组合「类名前缀 + 方法名」
+ *
+ * @see app-api 前缀不生效,都是使用 admin-api
+ */
+ private static OperationCustomizer buildOperationIdCustomizer() {
+ return (operation, handlerMethod) -> {
+ // 1. 获取控制器类名(如 UserController)
+ String className = handlerMethod.getBeanType().getSimpleName();
+ // 2. 提取类名前缀(去除 Controller 后缀,如 UserController -> User)
+ String classPrefix = className.replaceAll("Controller$", "");
+ // 3. 获取方法名(如 list)
+ String methodName = handlerMethod.getMethod().getName();
+ // 4. 组合生成 operationId(如 User_list)
+ String operationId = classPrefix + "_" + methodName;
+ // 5. 设置自定义 operationId
+ operation.setOperationId(operationId);
+ return operation;
+ };
+ }
+
}
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 9126c7e8ea..a781645eba 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
@@ -1,5 +1,6 @@
package cn.iocoder.yudao.framework.web.config;
+import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.biz.infra.logger.ApiErrorLogCommonApi;
import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum;
import cn.iocoder.yudao.framework.web.core.filter.CacheRequestBodyFilter;
@@ -7,6 +8,7 @@ import cn.iocoder.yudao.framework.web.core.filter.DemoFilter;
import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
import cn.iocoder.yudao.framework.web.core.handler.GlobalResponseBodyHandler;
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
+import com.google.common.collect.Maps;
import jakarta.annotation.Resource;
import jakarta.servlet.Filter;
import org.springframework.beans.factory.annotation.Value;
@@ -14,6 +16,7 @@ import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration;
+import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
@@ -26,35 +29,57 @@ import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
+
+import java.util.Map;
+import java.util.function.Predicate;
@AutoConfiguration
@EnableConfigurationProperties(WebProperties.class)
-public class YudaoWebAutoConfiguration implements WebMvcConfigurer {
+public class YudaoWebAutoConfiguration {
- @Resource
- private WebProperties webProperties;
/**
* 应用名
*/
@Value("${spring.application.name}")
private String applicationName;
- @Override
- public void configurePathMatch(PathMatchConfigurer configurer) {
- configurePathMatch(configurer, webProperties.getAdminApi());
- configurePathMatch(configurer, webProperties.getAppApi());
- }
+ @Bean
+ public WebMvcRegistrations webMvcRegistrations(WebProperties webProperties) {
+ return new WebMvcRegistrations() {
- /**
- * 设置 API 前缀,仅仅匹配 controller 包下的
- *
- * @param configurer 配置
- * @param api API 配置
- */
- private void configurePathMatch(PathMatchConfigurer configurer, WebProperties.Api api) {
- AntPathMatcher antPathMatcher = new AntPathMatcher(".");
- configurer.addPathPrefix(api.getPrefix(), clazz -> clazz.isAnnotationPresent(RestController.class)
- && antPathMatcher.match(api.getController(), clazz.getPackage().getName())); // 仅仅匹配 controller 包
+ @Override
+ public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
+ var mapping = new RequestMappingHandlerMapping();
+ // 实例化时就带上前缀
+ mapping.setPathPrefixes(buildPathPrefixes(webProperties));
+ return mapping;
+ }
+
+ /**
+ * 构建 prefix → 匹配条件的映射
+ */
+ private Map>> buildPathPrefixes(WebProperties webProperties) {
+ AntPathMatcher antPathMatcher = new AntPathMatcher(".");
+ Map>> pathPrefixes = Maps.newLinkedHashMapWithExpectedSize(2);
+ putPathPrefix(pathPrefixes, webProperties.getAdminApi(), antPathMatcher);
+ putPathPrefix(pathPrefixes, webProperties.getAppApi(), antPathMatcher);
+ return pathPrefixes;
+ }
+
+ /**
+ * 设置 API 前缀,仅仅匹配 controller 包下的
+ */
+ private void putPathPrefix(Map>> pathPrefixes, WebProperties.Api api, AntPathMatcher matcher) {
+ if (api == null || StrUtil.isEmpty(api.getPrefix())) {
+ return;
+ }
+ pathPrefixes.put(api.getPrefix(), // api 前缀
+ clazz -> clazz.isAnnotationPresent(RestController.class)
+ && matcher.match(api.getController(), clazz.getPackage().getName()));
+ }
+
+ };
}
@Bean
diff --git a/yudao-module-ai/pom.xml b/yudao-module-ai/pom.xml
index 06c9cc0ca9..95a28979a1 100644
--- a/yudao-module-ai/pom.xml
+++ b/yudao-module-ai/pom.xml
@@ -78,6 +78,12 @@
org.springframework.ai
spring-ai-starter-model-openai
${spring-ai.version}
+
+
+ io.swagger.core.v3
+ swagger-annotations-jakarta
+
+
org.springframework.ai
diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/dal/redis/BpmProcessIdRedisDAO.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/dal/redis/BpmProcessIdRedisDAO.java
index 1f9cd5f49d..2a14f51a47 100644
--- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/dal/redis/BpmProcessIdRedisDAO.java
+++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/dal/redis/BpmProcessIdRedisDAO.java
@@ -51,8 +51,8 @@ public class BpmProcessIdRedisDAO {
String noPrefix = processIdRule.getPrefix() + infix + processIdRule.getPostfix();
String key = RedisKeyConstants.BPM_PROCESS_ID + noPrefix;
Long no = stringRedisTemplate.opsForValue().increment(key);
- if (StrUtil.isNotEmpty(infix)) {
- // 特殊:没有前缀,则不能过期,不能每次都是从 0 开始
+ if (StrUtil.isEmpty(infix)) {
+ // 特殊:没有前缀,则不能过期,不能每次都是从 0 开始。可见 https://t.zsxq.com/MU1E2 讨论
stringRedisTemplate.expire(key, Duration.ofDays(1L));
}
return noPrefix + String.format("%0" + processIdRule.getLength() + "d", no);
diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java
index 2582399a81..cfeb2c2ded 100644
--- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java
+++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java
@@ -737,10 +737,10 @@ public class SimpleModelUtils {
BoundaryEvent boundaryEvent = null;
if (node.getDelaySetting().getDelayType().equals(BpmDelayTimerTypeEnum.FIXED_DATE_TIME.getType())) {
boundaryEvent = buildTimeoutBoundaryEvent(receiveTask, BpmBoundaryEventTypeEnum.DELAY_TIMER_TIMEOUT.getType(),
- node.getDelaySetting().getDelayTime(), null, null);
+ null, null, node.getDelaySetting().getDelayTime());
} else if (node.getDelaySetting().getDelayType().equals(BpmDelayTimerTypeEnum.FIXED_TIME_DURATION.getType())) {
boundaryEvent = buildTimeoutBoundaryEvent(receiveTask, BpmBoundaryEventTypeEnum.DELAY_TIMER_TIMEOUT.getType(),
- null, null, node.getDelaySetting().getDelayTime());
+ node.getDelaySetting().getDelayTime(), null, null);
} else {
throw new UnsupportedOperationException("不支持的延迟类型:" + node.getDelaySetting());
}
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmCluePageReqVO.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmCluePageReqVO.java
index d3282f35a3..25a11104d1 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmCluePageReqVO.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmCluePageReqVO.java
@@ -7,6 +7,11 @@ import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 线索分页 Request VO")
@Data
@@ -42,4 +47,8 @@ public class CrmCluePageReqVO extends PageParam {
@Schema(description = "跟进状态", example = "true")
private Boolean followUpStatus;
+ @Schema(description = "创建时间", example = "[2023-01-01 00:00:00, 2023-01-31 23:59:59]")
+ @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+ private LocalDateTime[] createTime;
+
}
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/clue/CrmClueMapper.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/clue/CrmClueMapper.java
index 88650dc899..8e52f9a238 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/clue/CrmClueMapper.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/clue/CrmClueMapper.java
@@ -33,6 +33,7 @@ public interface CrmClueMapper extends BaseMapperX {
.eqIfPresent(CrmClueDO::getLevel, pageReqVO.getLevel())
.eqIfPresent(CrmClueDO::getSource, pageReqVO.getSource())
.eqIfPresent(CrmClueDO::getFollowUpStatus, pageReqVO.getFollowUpStatus())
+ .betweenIfPresent(CrmClueDO::getCreateTime, pageReqVO.getCreateTime())
.orderByDesc(CrmClueDO::getId);
return selectJoinPage(pageReqVO, CrmClueDO.class, query);
}
diff --git a/yudao-module-infra/pom.xml b/yudao-module-infra/pom.xml
index be053b5cae..bd0e6c7408 100644
--- a/yudao-module-infra/pom.xml
+++ b/yudao-module-infra/pom.xml
@@ -101,7 +101,7 @@
commons-net
- com.jcraft
+ com.github.mwiede
jsch
diff --git a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java
index f21e79a188..a12ba3934e 100644
--- a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java
+++ b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java
@@ -26,6 +26,7 @@ import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
+import java.nio.charset.StandardCharsets;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@@ -99,8 +100,10 @@ public class FileController {
if (StrUtil.isEmpty(path)) {
throw new IllegalArgumentException("结尾的 path 路径必须传递");
}
- // 解码,解决中文路径的问题 https://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/807/
- path = URLUtil.decode(path);
+ // 解码,解决中文路径的问题
+ // https://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/807/
+ // https://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/1432/
+ path = URLUtil.decode(path, StandardCharsets.UTF_8, false);
// 读取内容
byte[] content = fileService.getFileContent(configId, path);
diff --git a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/ftp/FtpFileClient.java b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/ftp/FtpFileClient.java
index 4207eb7e15..93bff27ece 100644
--- a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/ftp/FtpFileClient.java
+++ b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/ftp/FtpFileClient.java
@@ -4,6 +4,7 @@ import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.ftp.Ftp;
+import cn.hutool.extra.ftp.FtpConfig;
import cn.hutool.extra.ftp.FtpException;
import cn.hutool.extra.ftp.FtpMode;
import cn.iocoder.yudao.module.infra.framework.file.core.client.AbstractFileClient;
@@ -18,6 +19,15 @@ import java.io.ByteArrayOutputStream;
*/
public class FtpFileClient extends AbstractFileClient {
+ /**
+ * 连接超时时间,单位:毫秒
+ */
+ private static final Long CONNECTION_TIMEOUT = 3000L;
+ /**
+ * 读写超时时间,单位:毫秒
+ */
+ private static final Long SO_TIMEOUT = 10000L;
+
private Ftp ftp;
public FtpFileClient(Long id, FtpFileClientConfig config) {
@@ -26,9 +36,12 @@ public class FtpFileClient extends AbstractFileClient {
@Override
protected void doInit() {
- // 初始化 Ftp 对象
- this.ftp = new Ftp(config.getHost(), config.getPort(), config.getUsername(), config.getPassword(),
- CharsetUtil.CHARSET_UTF_8, null, null, FtpMode.valueOf(config.getMode()));
+ // 初始化 Ftp 对象:https://gitee.com/zhijiantianya/yudao-cloud/pulls/207/
+ FtpConfig ftpConfig = new FtpConfig(config.getHost(), config.getPort(), config.getUsername(), config.getPassword(),
+ CharsetUtil.CHARSET_UTF_8, null, null);
+ ftpConfig.setConnectionTimeout(CONNECTION_TIMEOUT);
+ ftpConfig.setSoTimeout(SO_TIMEOUT);
+ this.ftp = new Ftp(ftpConfig, FtpMode.valueOf(config.getMode()));
}
@Override
@@ -72,4 +85,4 @@ public class FtpFileClient extends AbstractFileClient {
ftp.reconnectIfTimeout();
}
-}
\ No newline at end of file
+}
diff --git a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/ftp/FtpFileClientConfig.java b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/ftp/FtpFileClientConfig.java
index a1b92ffc1c..c1cbb18e7f 100644
--- a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/ftp/FtpFileClientConfig.java
+++ b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/ftp/FtpFileClientConfig.java
@@ -1,11 +1,10 @@
package cn.iocoder.yudao.module.infra.framework.file.core.client.ftp;
import cn.iocoder.yudao.module.infra.framework.file.core.client.FileClientConfig;
-import lombok.Data;
-import org.hibernate.validator.constraints.URL;
-
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import org.hibernate.validator.constraints.URL;
/**
* Ftp 文件客户端的配置类
diff --git a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/sftp/SftpFileClient.java b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/sftp/SftpFileClient.java
index 000cbd10b3..788325f6c2 100644
--- a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/sftp/SftpFileClient.java
+++ b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/sftp/SftpFileClient.java
@@ -1,9 +1,14 @@
package cn.iocoder.yudao.module.infra.framework.file.core.client.sftp;
import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.util.CharsetUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.extra.ftp.FtpConfig;
+import cn.hutool.extra.ssh.JschRuntimeException;
import cn.hutool.extra.ssh.Sftp;
import cn.iocoder.yudao.framework.common.util.io.FileUtils;
import cn.iocoder.yudao.module.infra.framework.file.core.client.AbstractFileClient;
+import com.jcraft.jsch.JSch;
import java.io.File;
@@ -14,6 +19,20 @@ import java.io.File;
*/
public class SftpFileClient extends AbstractFileClient {
+ /**
+ * 连接超时时间,单位:毫秒
+ */
+ private static final Long CONNECTION_TIMEOUT = 3000L;
+ /**
+ * 读写超时时间,单位:毫秒
+ */
+ private static final Long SO_TIMEOUT = 10000L;
+
+ static {
+ // 某些旧的 sftp 服务器仅支持 ssh-dss 协议,该协议并不安全,默认不支持该协议,按需添加
+ JSch.setConfig("server_host_key", JSch.getConfig("server_host_key") + ",ssh-dss");
+ }
+
private Sftp sftp;
public SftpFileClient(Long id, SftpFileClientConfig config) {
@@ -22,18 +41,27 @@ public class SftpFileClient extends AbstractFileClient {
@Override
protected void doInit() {
- // 初始化 Ftp 对象
- this.sftp = new Sftp(config.getHost(), config.getPort(), config.getUsername(), config.getPassword());
+ // 初始化 Sftp 对象
+ FtpConfig ftpConfig = new FtpConfig(config.getHost(), config.getPort(), config.getUsername(), config.getPassword(),
+ CharsetUtil.CHARSET_UTF_8, null, null);
+ ftpConfig.setConnectionTimeout(CONNECTION_TIMEOUT);
+ ftpConfig.setSoTimeout(SO_TIMEOUT);
+ this.sftp = new Sftp(ftpConfig);
}
@Override
public String upload(byte[] content, String path, String type) {
// 执行写入
String filePath = getFilePath(path);
+ String fileName = FileUtil.getName(filePath);
+ String dir = StrUtil.removeSuffix(filePath, fileName);
File file = FileUtils.createTempFile(content);
reconnectIfTimeout();
- sftp.mkDirs(FileUtil.getParent(filePath, 1)); // 需要创建父目录,不然会报错
- sftp.upload(filePath, file);
+ sftp.mkDirs(dir); // 需要创建父目录,不然会报错
+ boolean success = sftp.upload(filePath, file);
+ if (!success) {
+ throw new JschRuntimeException(StrUtil.format("上传文件到目标目录 ({}) 失败", filePath));
+ }
// 拼接返回路径
return super.formatFileUrl(config.getDomain(), path);
}
diff --git a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/sftp/SftpFileClientConfig.java b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/sftp/SftpFileClientConfig.java
index c56d045fef..915c660764 100644
--- a/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/sftp/SftpFileClientConfig.java
+++ b/yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/sftp/SftpFileClientConfig.java
@@ -1,11 +1,10 @@
package cn.iocoder.yudao.module.infra.framework.file.core.client.sftp;
import cn.iocoder.yudao.module.infra.framework.file.core.client.FileClientConfig;
-import lombok.Data;
-import org.hibernate.validator.constraints.URL;
-
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import org.hibernate.validator.constraints.URL;
/**
* Sftp 文件客户端的配置类
diff --git a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/IotDeviceMessageMethodEnum.java b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/IotDeviceMessageMethodEnum.java
index 047fe5ffcd..e62b78e245 100644
--- a/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/IotDeviceMessageMethodEnum.java
+++ b/yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/IotDeviceMessageMethodEnum.java
@@ -43,7 +43,7 @@ public enum IotDeviceMessageMethodEnum implements ArrayValuable {
// ========== 设备配置 ==========
// 可参考:https://help.aliyun.com/zh/iot/user-guide/remote-configuration-1
- CONFIG_PUSH("thing.config.push", "配置推送", true),
+ CONFIG_PUSH("thing.config.push", "配置推送", false),
// ========== OTA 固件 ==========
// 可参考:https://help.aliyun.com/zh/iot/user-guide/perform-ota-updates
diff --git a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/manager/IotMqttConnectionManager.java b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/manager/IotMqttConnectionManager.java
index 3fd1a3a041..d7c4adbd00 100644
--- a/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/manager/IotMqttConnectionManager.java
+++ b/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/manager/IotMqttConnectionManager.java
@@ -125,7 +125,7 @@ public class IotMqttConnectionManager {
*/
public IotMqttConnectionManager.ConnectionInfo getConnectionInfoByDeviceId(Long deviceId) {
// 通过设备 ID 获取连接端点
- var endpoint = getDeviceEndpoint(deviceId);
+ MqttEndpoint endpoint = getDeviceEndpoint(deviceId);
if (endpoint == null) {
return null;
}
diff --git a/yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/reward/vo/RewardActivityRespVO.java b/yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/reward/vo/RewardActivityRespVO.java
index 67bf12153b..ab0cb8810b 100755
--- a/yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/reward/vo/RewardActivityRespVO.java
+++ b/yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/reward/vo/RewardActivityRespVO.java
@@ -14,7 +14,7 @@ import java.time.LocalDateTime;
public class RewardActivityRespVO extends RewardActivityBaseVO {
@Schema(description = "活动编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
- private Integer id;
+ private Long id;
@Schema(description = "活动状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer status;
diff --git a/yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java b/yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java
index fee8a4851f..540cddcf4f 100644
--- a/yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java
+++ b/yudao-module-mall/yudao-module-promotion/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java
@@ -284,8 +284,8 @@ public class CouponServiceImpl implements CouponService {
}
// 校验发放数量不能过小(仅在 CouponTakeTypeEnum.USER 用户领取时)
if (CouponTakeTypeEnum.isUser(couponTemplate.getTakeType())
- && ObjUtil.notEqual(couponTemplate.getTakeLimitCount(), CouponTemplateDO.TAKE_LIMIT_COUNT_MAX) // 非不限制
- && couponTemplate.getTakeCount() + userIds.size() > couponTemplate.getTotalCount()) {
+ && ObjUtil.notEqual(couponTemplate.getTakeLimitCount(), CouponTemplateDO.TAKE_LIMIT_COUNT_MAX) // 校验不限制领取数
+ && ObjUtil.notEqual(couponTemplate.getTotalCount(), CouponTemplateDO.TOTAL_COUNT_MAX) // 校验不限制发放数量
throw exception(COUPON_TEMPLATE_NOT_ENOUGH);
}
// 校验"固定日期"的有效期类型是否过期
diff --git a/yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceTest.java b/yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceTest.java
index cac08e9f65..3172c5e2fd 100755
--- a/yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceTest.java
+++ b/yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceTest.java
@@ -610,7 +610,7 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest {
orderExtensionMapper.insert(orderExtension);
// 重要:需要将 order 的 extensionId 更新下
order.setExtensionId(orderExtension.getId());
- orderMapper.updateById(new PayOrderDO().setId(order.getId()).setExtensionId(orderExtension.getId()));
+ orderMapper.updateById(order);
// 准备参数
PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L));
PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class,
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImpl.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImpl.java
index d80fae07d4..97c65205d8 100644
--- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImpl.java
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImpl.java
@@ -54,6 +54,7 @@ import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.request.AuthRequest;
import me.zhyd.oauth.utils.AuthStateUtils;
+import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.StringRedisTemplate;
@@ -101,7 +102,8 @@ public class SocialClientServiceImpl implements SocialClientService {
@Value("${yudao.wxa-subscribe-message.miniprogram-state:formal}")
public String miniprogramState;
- @Resource
+ @SuppressWarnings("SpringJavaAutowiredFieldsWarningInspection")
+ @Autowired(required = false) // 由于 justauth.enable 配置项,可以关闭 AuthRequestFactory 的功能,所以这里只能不强制注入
private AuthRequestFactory authRequestFactory;
@Resource