Commit 98f9e44b24022b0cc7d7b743d0aae1d2fc02fda5

Authored by YunaiV
2 parents 341e72c0 772326e7

Merge branch 'master-jdk17' of https://gitee.com/zhijiantianya/ruoyi-vue-pro into feature/iot

Showing 22 changed files with 164 additions and 53 deletions
yudao-dependencies/pom.xml
... ... @@ -61,7 +61,7 @@
61 61 <transmittable-thread-local.version>2.14.5</transmittable-thread-local.version>
62 62 <commons-net.version>3.11.1</commons-net.version>
63 63 <commons-lang3.version>3.18.0</commons-lang3.version>
64   - <jsch.version>0.1.55</jsch.version>
  64 + <jsch.version>2.27.3</jsch.version>
65 65 <tika-core.version>3.2.2</tika-core.version>
66 66 <ip2region.version>2.7.0</ip2region.version>
67 67 <bizlog-sdk.version>3.0.6</bizlog-sdk.version>
... ... @@ -526,7 +526,7 @@
526 526 <version>${commons-net.version}</version>
527 527 </dependency>
528 528 <dependency>
529   - <groupId>com.jcraft</groupId>
  529 + <groupId>com.github.mwiede</groupId>
530 530 <artifactId>jsch</artifactId> <!-- 解决 sftp 连接 -->
531 531 <version>${jsch.version}</version>
532 532 </dependency>
... ...
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;
6 6 import cn.iocoder.yudao.framework.mybatis.core.handler.DefaultDBFieldHandler;
7 7 import com.baomidou.mybatisplus.annotation.DbType;
8 8 import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration;
  9 +import com.baomidou.mybatisplus.core.handlers.IJsonTypeHandler;
9 10 import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
10 11 import com.baomidou.mybatisplus.core.incrementer.IKeyGenerator;
11 12 import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
... ... @@ -80,8 +81,8 @@ public class YudaoMybatisAutoConfiguration {
80 81 throw new IllegalArgumentException(StrUtil.format("DbType{} 找不到合适的 IKeyGenerator 实现类", dbType));
81 82 }
82 83  
83   - @Bean
84   - public JacksonTypeHandler jacksonTypeHandler(List<ObjectMapper> objectMappers) {
  84 + @Bean // 特殊:返回结果使用 Object 而不用 JacksonTypeHandler 的原因,避免因为 JacksonTypeHandler 被 mybatis 全局使用!
  85 + public Object jacksonTypeHandler(List<ObjectMapper> objectMappers) {
85 86 // 特殊:设置 JacksonTypeHandler 的 ObjectMapper!
86 87 ObjectMapper objectMapper = CollUtil.getFirst(objectMappers);
87 88 if (objectMapper == null) {
... ...
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;
6 6 import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
7 7 import com.fhs.core.trans.vo.TransPojo;
8 8 import lombok.Data;
  9 +import org.apache.ibatis.type.JdbcType;
9 10  
10 11 import java.io.Serializable;
11 12 import java.time.LocalDateTime;
... ... @@ -37,14 +38,14 @@ public abstract class BaseDO implements Serializable, TransPojo {
37 38 *
38 39 * 使用 String 类型的原因是,未来可能会存在非数值的情况,留好拓展性。
39 40 */
40   - @TableField(fill = FieldFill.INSERT)
  41 + @TableField(fill = FieldFill.INSERT, jdbcType = JdbcType.VARCHAR)
41 42 private String creator;
42 43 /**
43 44 * 更新者,目前使用 SysUser 的 id 编号
44 45 *
45 46 * 使用 String 类型的原因是,未来可能会存在非数值的情况,留好拓展性。
46 47 */
47   - @TableField(fill = FieldFill.INSERT_UPDATE)
  48 + @TableField(fill = FieldFill.INSERT_UPDATE, jdbcType = JdbcType.VARCHAR)
48 49 private String updater;
49 50 /**
50 51 * 是否删除
... ...
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/swagger/config/YudaoSwaggerAutoConfiguration.java
1 1 package cn.iocoder.yudao.framework.swagger.config;
2 2  
  3 +import com.github.xiaoymin.knife4j.spring.configuration.Knife4jAutoConfiguration;
3 4 import io.swagger.v3.oas.models.Components;
4 5 import io.swagger.v3.oas.models.OpenAPI;
5 6 import io.swagger.v3.oas.models.info.Contact;
... ... @@ -11,6 +12,7 @@ import io.swagger.v3.oas.models.parameters.Parameter;
11 12 import io.swagger.v3.oas.models.security.SecurityRequirement;
12 13 import io.swagger.v3.oas.models.security.SecurityScheme;
13 14 import org.springdoc.core.customizers.OpenApiBuilderCustomizer;
  15 +import org.springdoc.core.customizers.OperationCustomizer;
14 16 import org.springdoc.core.customizers.ServerBaseUrlCustomizer;
15 17 import org.springdoc.core.models.GroupedOpenApi;
16 18 import org.springdoc.core.properties.SpringDocConfigProperties;
... ... @@ -43,7 +45,7 @@ import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.HEADER_
43 45 *
44 46 * @author 芋道源码
45 47 */
46   -@AutoConfiguration
  48 +@AutoConfiguration(before = Knife4jAutoConfiguration.class) // before 原因,保证覆写的 Knife4jOpenApiCustomizer 先生效!相关 https://github.com/YunaiV/ruoyi-vue-pro/issues/954 讨论
47 49 @ConditionalOnClass({OpenAPI.class})
48 50 @EnableConfigurationProperties(SwaggerProperties.class)
49 51 @ConditionalOnProperty(prefix = "springdoc.api-docs", name = "enabled", havingValue = "true", matchIfMissing = true) // 设置为 false 时,禁用
... ... @@ -127,6 +129,7 @@ public class YudaoSwaggerAutoConfiguration {
127 129 .addOperationCustomizer((operation, handlerMethod) -> operation
128 130 .addParametersItem(buildTenantHeaderParameter())
129 131 .addParametersItem(buildSecurityHeaderParameter()))
  132 + .addOperationCustomizer(buildOperationIdCustomizer())
130 133 .build();
131 134 }
132 135  
... ... @@ -158,5 +161,26 @@ public class YudaoSwaggerAutoConfiguration {
158 161 .schema(new StringSchema()._default("Bearer test1").name(HEADER_TENANT_ID).description("认证 Token")); // 默认:使用用户编号为 1
159 162 }
160 163  
  164 + /**
  165 + * 核心:自定义OperationId生成规则,组合「类名前缀 + 方法名」
  166 + *
  167 + * @see <a href="https://github.com/YunaiV/ruoyi-vue-pro/issues/957">app-api 前缀不生效,都是使用 admin-api</a>
  168 + */
  169 + private static OperationCustomizer buildOperationIdCustomizer() {
  170 + return (operation, handlerMethod) -> {
  171 + // 1. 获取控制器类名(如 UserController)
  172 + String className = handlerMethod.getBeanType().getSimpleName();
  173 + // 2. 提取类名前缀(去除 Controller 后缀,如 UserController -> User)
  174 + String classPrefix = className.replaceAll("Controller$", "");
  175 + // 3. 获取方法名(如 list)
  176 + String methodName = handlerMethod.getMethod().getName();
  177 + // 4. 组合生成 operationId(如 User_list)
  178 + String operationId = classPrefix + "_" + methodName;
  179 + // 5. 设置自定义 operationId
  180 + operation.setOperationId(operationId);
  181 + return operation;
  182 + };
  183 + }
  184 +
161 185 }
162 186  
... ...
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/config/YudaoWebAutoConfiguration.java
1 1 package cn.iocoder.yudao.framework.web.config;
2 2  
  3 +import cn.hutool.core.util.StrUtil;
3 4 import cn.iocoder.yudao.framework.common.biz.infra.logger.ApiErrorLogCommonApi;
4 5 import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum;
5 6 import cn.iocoder.yudao.framework.web.core.filter.CacheRequestBodyFilter;
... ... @@ -7,6 +8,7 @@ import cn.iocoder.yudao.framework.web.core.filter.DemoFilter;
7 8 import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
8 9 import cn.iocoder.yudao.framework.web.core.handler.GlobalResponseBodyHandler;
9 10 import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
  11 +import com.google.common.collect.Maps;
10 12 import jakarta.annotation.Resource;
11 13 import jakarta.servlet.Filter;
12 14 import org.springframework.beans.factory.annotation.Value;
... ... @@ -14,6 +16,7 @@ import org.springframework.boot.autoconfigure.AutoConfiguration;
14 16 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
15 17 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
16 18 import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration;
  19 +import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations;
17 20 import org.springframework.boot.context.properties.EnableConfigurationProperties;
18 21 import org.springframework.boot.web.client.RestTemplateBuilder;
19 22 import org.springframework.boot.web.servlet.FilterRegistrationBean;
... ... @@ -26,35 +29,57 @@ import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
26 29 import org.springframework.web.filter.CorsFilter;
27 30 import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
28 31 import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
  32 +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
  33 +
  34 +import java.util.Map;
  35 +import java.util.function.Predicate;
29 36  
30 37 @AutoConfiguration
31 38 @EnableConfigurationProperties(WebProperties.class)
32   -public class YudaoWebAutoConfiguration implements WebMvcConfigurer {
  39 +public class YudaoWebAutoConfiguration {
33 40  
34   - @Resource
35   - private WebProperties webProperties;
36 41 /**
37 42 * 应用名
38 43 */
39 44 @Value("${spring.application.name}")
40 45 private String applicationName;
41 46  
42   - @Override
43   - public void configurePathMatch(PathMatchConfigurer configurer) {
44   - configurePathMatch(configurer, webProperties.getAdminApi());
45   - configurePathMatch(configurer, webProperties.getAppApi());
46   - }
  47 + @Bean
  48 + public WebMvcRegistrations webMvcRegistrations(WebProperties webProperties) {
  49 + return new WebMvcRegistrations() {
47 50  
48   - /**
49   - * 设置 API 前缀,仅仅匹配 controller 包下的
50   - *
51   - * @param configurer 配置
52   - * @param api API 配置
53   - */
54   - private void configurePathMatch(PathMatchConfigurer configurer, WebProperties.Api api) {
55   - AntPathMatcher antPathMatcher = new AntPathMatcher(".");
56   - configurer.addPathPrefix(api.getPrefix(), clazz -> clazz.isAnnotationPresent(RestController.class)
57   - && antPathMatcher.match(api.getController(), clazz.getPackage().getName())); // 仅仅匹配 controller 包
  51 + @Override
  52 + public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
  53 + var mapping = new RequestMappingHandlerMapping();
  54 + // 实例化时就带上前缀
  55 + mapping.setPathPrefixes(buildPathPrefixes(webProperties));
  56 + return mapping;
  57 + }
  58 +
  59 + /**
  60 + * 构建 prefix → 匹配条件的映射
  61 + */
  62 + private Map<String, Predicate<Class<?>>> buildPathPrefixes(WebProperties webProperties) {
  63 + AntPathMatcher antPathMatcher = new AntPathMatcher(".");
  64 + Map<String, Predicate<Class<?>>> pathPrefixes = Maps.newLinkedHashMapWithExpectedSize(2);
  65 + putPathPrefix(pathPrefixes, webProperties.getAdminApi(), antPathMatcher);
  66 + putPathPrefix(pathPrefixes, webProperties.getAppApi(), antPathMatcher);
  67 + return pathPrefixes;
  68 + }
  69 +
  70 + /**
  71 + * 设置 API 前缀,仅仅匹配 controller 包下的
  72 + */
  73 + private void putPathPrefix(Map<String, Predicate<Class<?>>> pathPrefixes, WebProperties.Api api, AntPathMatcher matcher) {
  74 + if (api == null || StrUtil.isEmpty(api.getPrefix())) {
  75 + return;
  76 + }
  77 + pathPrefixes.put(api.getPrefix(), // api 前缀
  78 + clazz -> clazz.isAnnotationPresent(RestController.class)
  79 + && matcher.match(api.getController(), clazz.getPackage().getName()));
  80 + }
  81 +
  82 + };
58 83 }
59 84  
60 85 @Bean
... ...
yudao-module-ai/pom.xml
... ... @@ -78,6 +78,12 @@
78 78 <groupId>org.springframework.ai</groupId>
79 79 <artifactId>spring-ai-starter-model-openai</artifactId>
80 80 <version>${spring-ai.version}</version>
  81 + <exclusions>
  82 + <exclusion>
  83 + <groupId>io.swagger.core.v3</groupId>
  84 + <artifactId>swagger-annotations-jakarta</artifactId>
  85 + </exclusion>
  86 + </exclusions>
81 87 </dependency>
82 88 <dependency>
83 89 <groupId>org.springframework.ai</groupId>
... ...
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/dal/redis/BpmProcessIdRedisDAO.java
... ... @@ -51,8 +51,8 @@ public class BpmProcessIdRedisDAO {
51 51 String noPrefix = processIdRule.getPrefix() + infix + processIdRule.getPostfix();
52 52 String key = RedisKeyConstants.BPM_PROCESS_ID + noPrefix;
53 53 Long no = stringRedisTemplate.opsForValue().increment(key);
54   - if (StrUtil.isNotEmpty(infix)) {
55   - // 特殊:没有前缀,则不能过期,不能每次都是从 0 开始
  54 + if (StrUtil.isEmpty(infix)) {
  55 + // 特殊:没有前缀,则不能过期,不能每次都是从 0 开始。可见 https://t.zsxq.com/MU1E2 讨论
56 56 stringRedisTemplate.expire(key, Duration.ofDays(1L));
57 57 }
58 58 return noPrefix + String.format("%0" + processIdRule.getLength() + "d", no);
... ...
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java
... ... @@ -737,10 +737,10 @@ public class SimpleModelUtils {
737 737 BoundaryEvent boundaryEvent = null;
738 738 if (node.getDelaySetting().getDelayType().equals(BpmDelayTimerTypeEnum.FIXED_DATE_TIME.getType())) {
739 739 boundaryEvent = buildTimeoutBoundaryEvent(receiveTask, BpmBoundaryEventTypeEnum.DELAY_TIMER_TIMEOUT.getType(),
740   - node.getDelaySetting().getDelayTime(), null, null);
  740 + null, null, node.getDelaySetting().getDelayTime());
741 741 } else if (node.getDelaySetting().getDelayType().equals(BpmDelayTimerTypeEnum.FIXED_TIME_DURATION.getType())) {
742 742 boundaryEvent = buildTimeoutBoundaryEvent(receiveTask, BpmBoundaryEventTypeEnum.DELAY_TIMER_TIMEOUT.getType(),
743   - null, null, node.getDelaySetting().getDelayTime());
  743 + node.getDelaySetting().getDelayTime(), null, null);
744 744 } else {
745 745 throw new UnsupportedOperationException("不支持的延迟类型:" + node.getDelaySetting());
746 746 }
... ...
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;
7 7 import lombok.Data;
8 8 import lombok.EqualsAndHashCode;
9 9 import lombok.ToString;
  10 +import org.springframework.format.annotation.DateTimeFormat;
  11 +
  12 +import java.time.LocalDateTime;
  13 +
  14 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
10 15  
11 16 @Schema(description = "管理后台 - 线索分页 Request VO")
12 17 @Data
... ... @@ -42,4 +47,8 @@ public class CrmCluePageReqVO extends PageParam {
42 47 @Schema(description = "跟进状态", example = "true")
43 48 private Boolean followUpStatus;
44 49  
  50 + @Schema(description = "创建时间", example = "[2023-01-01 00:00:00, 2023-01-31 23:59:59]")
  51 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  52 + private LocalDateTime[] createTime;
  53 +
45 54 }
... ...
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&lt;CrmClueDO&gt; {
33 33 .eqIfPresent(CrmClueDO::getLevel, pageReqVO.getLevel())
34 34 .eqIfPresent(CrmClueDO::getSource, pageReqVO.getSource())
35 35 .eqIfPresent(CrmClueDO::getFollowUpStatus, pageReqVO.getFollowUpStatus())
  36 + .betweenIfPresent(CrmClueDO::getCreateTime, pageReqVO.getCreateTime())
36 37 .orderByDesc(CrmClueDO::getId);
37 38 return selectJoinPage(pageReqVO, CrmClueDO.class, query);
38 39 }
... ...
yudao-module-infra/pom.xml
... ... @@ -101,7 +101,7 @@
101 101 <artifactId>commons-net</artifactId> <!-- 文件客户端:解决 ftp 连接 -->
102 102 </dependency>
103 103 <dependency>
104   - <groupId>com.jcraft</groupId>
  104 + <groupId>com.github.mwiede</groupId>
105 105 <artifactId>jsch</artifactId> <!-- 文件客户端:解决 sftp 连接 -->
106 106 </dependency>
107 107 <!-- 文件客户端:解决阿里云、腾讯云、minio 等 S3 连接 -->
... ...
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;
26 26 import org.springframework.web.bind.annotation.*;
27 27 import org.springframework.web.multipart.MultipartFile;
28 28  
  29 +import java.nio.charset.StandardCharsets;
29 30 import java.util.List;
30 31  
31 32 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
... ... @@ -99,8 +100,10 @@ public class FileController {
99 100 if (StrUtil.isEmpty(path)) {
100 101 throw new IllegalArgumentException("结尾的 path 路径必须传递");
101 102 }
102   - // 解码,解决中文路径的问题 https://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/807/
103   - path = URLUtil.decode(path);
  103 + // 解码,解决中文路径的问题
  104 + // https://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/807/
  105 + // https://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/1432/
  106 + path = URLUtil.decode(path, StandardCharsets.UTF_8, false);
104 107  
105 108 // 读取内容
106 109 byte[] content = fileService.getFileContent(configId, path);
... ...
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;
4 4 import cn.hutool.core.util.CharsetUtil;
5 5 import cn.hutool.core.util.StrUtil;
6 6 import cn.hutool.extra.ftp.Ftp;
  7 +import cn.hutool.extra.ftp.FtpConfig;
7 8 import cn.hutool.extra.ftp.FtpException;
8 9 import cn.hutool.extra.ftp.FtpMode;
9 10 import cn.iocoder.yudao.module.infra.framework.file.core.client.AbstractFileClient;
... ... @@ -18,6 +19,15 @@ import java.io.ByteArrayOutputStream;
18 19 */
19 20 public class FtpFileClient extends AbstractFileClient<FtpFileClientConfig> {
20 21  
  22 + /**
  23 + * 连接超时时间,单位:毫秒
  24 + */
  25 + private static final Long CONNECTION_TIMEOUT = 3000L;
  26 + /**
  27 + * 读写超时时间,单位:毫秒
  28 + */
  29 + private static final Long SO_TIMEOUT = 10000L;
  30 +
21 31 private Ftp ftp;
22 32  
23 33 public FtpFileClient(Long id, FtpFileClientConfig config) {
... ... @@ -26,9 +36,12 @@ public class FtpFileClient extends AbstractFileClient&lt;FtpFileClientConfig&gt; {
26 36  
27 37 @Override
28 38 protected void doInit() {
29   - // 初始化 Ftp 对象
30   - this.ftp = new Ftp(config.getHost(), config.getPort(), config.getUsername(), config.getPassword(),
31   - CharsetUtil.CHARSET_UTF_8, null, null, FtpMode.valueOf(config.getMode()));
  39 + // 初始化 Ftp 对象:https://gitee.com/zhijiantianya/yudao-cloud/pulls/207/
  40 + FtpConfig ftpConfig = new FtpConfig(config.getHost(), config.getPort(), config.getUsername(), config.getPassword(),
  41 + CharsetUtil.CHARSET_UTF_8, null, null);
  42 + ftpConfig.setConnectionTimeout(CONNECTION_TIMEOUT);
  43 + ftpConfig.setSoTimeout(SO_TIMEOUT);
  44 + this.ftp = new Ftp(ftpConfig, FtpMode.valueOf(config.getMode()));
32 45 }
33 46  
34 47 @Override
... ... @@ -72,4 +85,4 @@ public class FtpFileClient extends AbstractFileClient&lt;FtpFileClientConfig&gt; {
72 85 ftp.reconnectIfTimeout();
73 86 }
74 87  
75   -}
76 88 \ No newline at end of file
  89 +}
... ...
yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/ftp/FtpFileClientConfig.java
1 1 package cn.iocoder.yudao.module.infra.framework.file.core.client.ftp;
2 2  
3 3 import cn.iocoder.yudao.module.infra.framework.file.core.client.FileClientConfig;
4   -import lombok.Data;
5   -import org.hibernate.validator.constraints.URL;
6   -
7 4 import jakarta.validation.constraints.NotEmpty;
8 5 import jakarta.validation.constraints.NotNull;
  6 +import lombok.Data;
  7 +import org.hibernate.validator.constraints.URL;
9 8  
10 9 /**
11 10 * Ftp 文件客户端的配置类
... ...
yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/sftp/SftpFileClient.java
1 1 package cn.iocoder.yudao.module.infra.framework.file.core.client.sftp;
2 2  
3 3 import cn.hutool.core.io.FileUtil;
  4 +import cn.hutool.core.util.CharsetUtil;
  5 +import cn.hutool.core.util.StrUtil;
  6 +import cn.hutool.extra.ftp.FtpConfig;
  7 +import cn.hutool.extra.ssh.JschRuntimeException;
4 8 import cn.hutool.extra.ssh.Sftp;
5 9 import cn.iocoder.yudao.framework.common.util.io.FileUtils;
6 10 import cn.iocoder.yudao.module.infra.framework.file.core.client.AbstractFileClient;
  11 +import com.jcraft.jsch.JSch;
7 12  
8 13 import java.io.File;
9 14  
... ... @@ -14,6 +19,20 @@ import java.io.File;
14 19 */
15 20 public class SftpFileClient extends AbstractFileClient<SftpFileClientConfig> {
16 21  
  22 + /**
  23 + * 连接超时时间,单位:毫秒
  24 + */
  25 + private static final Long CONNECTION_TIMEOUT = 3000L;
  26 + /**
  27 + * 读写超时时间,单位:毫秒
  28 + */
  29 + private static final Long SO_TIMEOUT = 10000L;
  30 +
  31 + static {
  32 + // 某些旧的 sftp 服务器仅支持 ssh-dss 协议,该协议并不安全,默认不支持该协议,按需添加
  33 + JSch.setConfig("server_host_key", JSch.getConfig("server_host_key") + ",ssh-dss");
  34 + }
  35 +
17 36 private Sftp sftp;
18 37  
19 38 public SftpFileClient(Long id, SftpFileClientConfig config) {
... ... @@ -22,18 +41,27 @@ public class SftpFileClient extends AbstractFileClient&lt;SftpFileClientConfig&gt; {
22 41  
23 42 @Override
24 43 protected void doInit() {
25   - // 初始化 Ftp 对象
26   - this.sftp = new Sftp(config.getHost(), config.getPort(), config.getUsername(), config.getPassword());
  44 + // 初始化 Sftp 对象
  45 + FtpConfig ftpConfig = new FtpConfig(config.getHost(), config.getPort(), config.getUsername(), config.getPassword(),
  46 + CharsetUtil.CHARSET_UTF_8, null, null);
  47 + ftpConfig.setConnectionTimeout(CONNECTION_TIMEOUT);
  48 + ftpConfig.setSoTimeout(SO_TIMEOUT);
  49 + this.sftp = new Sftp(ftpConfig);
27 50 }
28 51  
29 52 @Override
30 53 public String upload(byte[] content, String path, String type) {
31 54 // 执行写入
32 55 String filePath = getFilePath(path);
  56 + String fileName = FileUtil.getName(filePath);
  57 + String dir = StrUtil.removeSuffix(filePath, fileName);
33 58 File file = FileUtils.createTempFile(content);
34 59 reconnectIfTimeout();
35   - sftp.mkDirs(FileUtil.getParent(filePath, 1)); // 需要创建父目录,不然会报错
36   - sftp.upload(filePath, file);
  60 + sftp.mkDirs(dir); // 需要创建父目录,不然会报错
  61 + boolean success = sftp.upload(filePath, file);
  62 + if (!success) {
  63 + throw new JschRuntimeException(StrUtil.format("上传文件到目标目录 ({}) 失败", filePath));
  64 + }
37 65 // 拼接返回路径
38 66 return super.formatFileUrl(config.getDomain(), path);
39 67 }
... ...
yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/sftp/SftpFileClientConfig.java
1 1 package cn.iocoder.yudao.module.infra.framework.file.core.client.sftp;
2 2  
3 3 import cn.iocoder.yudao.module.infra.framework.file.core.client.FileClientConfig;
4   -import lombok.Data;
5   -import org.hibernate.validator.constraints.URL;
6   -
7 4 import jakarta.validation.constraints.NotEmpty;
8 5 import jakarta.validation.constraints.NotNull;
  6 +import lombok.Data;
  7 +import org.hibernate.validator.constraints.URL;
9 8  
10 9 /**
11 10 * Sftp 文件客户端的配置类
... ...
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&lt;String&gt; {
43 43 // ========== 设备配置 ==========
44 44 // 可参考:https://help.aliyun.com/zh/iot/user-guide/remote-configuration-1
45 45  
46   - CONFIG_PUSH("thing.config.push", "配置推送", true),
  46 + CONFIG_PUSH("thing.config.push", "配置推送", false),
47 47  
48 48 // ========== OTA 固件 ==========
49 49 // 可参考:https://help.aliyun.com/zh/iot/user-guide/perform-ota-updates
... ...
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 {
125 125 */
126 126 public IotMqttConnectionManager.ConnectionInfo getConnectionInfoByDeviceId(Long deviceId) {
127 127 // 通过设备 ID 获取连接端点
128   - var endpoint = getDeviceEndpoint(deviceId);
  128 + MqttEndpoint endpoint = getDeviceEndpoint(deviceId);
129 129 if (endpoint == null) {
130 130 return null;
131 131 }
... ...
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;
14 14 public class RewardActivityRespVO extends RewardActivityBaseVO {
15 15  
16 16 @Schema(description = "活动编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
17   - private Integer id;
  17 + private Long id;
18 18  
19 19 @Schema(description = "活动状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
20 20 private Integer status;
... ...
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 {
284 284 }
285 285 // 校验发放数量不能过小(仅在 CouponTakeTypeEnum.USER 用户领取时)
286 286 if (CouponTakeTypeEnum.isUser(couponTemplate.getTakeType())
287   - && ObjUtil.notEqual(couponTemplate.getTakeLimitCount(), CouponTemplateDO.TAKE_LIMIT_COUNT_MAX) // 非不限制
288   - && couponTemplate.getTakeCount() + userIds.size() > couponTemplate.getTotalCount()) {
  287 + && ObjUtil.notEqual(couponTemplate.getTakeLimitCount(), CouponTemplateDO.TAKE_LIMIT_COUNT_MAX) // 校验不限制领取数
  288 + && ObjUtil.notEqual(couponTemplate.getTotalCount(), CouponTemplateDO.TOTAL_COUNT_MAX) // 校验不限制发放数量
289 289 throw exception(COUPON_TEMPLATE_NOT_ENOUGH);
290 290 }
291 291 // 校验"固定日期"的有效期类型是否过期
... ...
yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceTest.java
... ... @@ -610,7 +610,7 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest {
610 610 orderExtensionMapper.insert(orderExtension);
611 611 // 重要:需要将 order 的 extensionId 更新下
612 612 order.setExtensionId(orderExtension.getId());
613   - orderMapper.updateById(new PayOrderDO().setId(order.getId()).setExtensionId(orderExtension.getId()));
  613 + orderMapper.updateById(order);
614 614 // 准备参数
615 615 PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L));
616 616 PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class,
... ...
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;
54 54 import me.zhyd.oauth.model.AuthUser;
55 55 import me.zhyd.oauth.request.AuthRequest;
56 56 import me.zhyd.oauth.utils.AuthStateUtils;
  57 +import org.springframework.beans.factory.annotation.Autowired;
57 58 import org.springframework.beans.factory.annotation.Value;
58 59 import org.springframework.cache.annotation.Cacheable;
59 60 import org.springframework.data.redis.core.StringRedisTemplate;
... ... @@ -101,7 +102,8 @@ public class SocialClientServiceImpl implements SocialClientService {
101 102 @Value("${yudao.wxa-subscribe-message.miniprogram-state:formal}")
102 103 public String miniprogramState;
103 104  
104   - @Resource
  105 + @SuppressWarnings("SpringJavaAutowiredFieldsWarningInspection")
  106 + @Autowired(required = false) // 由于 justauth.enable 配置项,可以关闭 AuthRequestFactory 的功能,所以这里只能不强制注入
105 107 private AuthRequestFactory authRequestFactory;
106 108  
107 109 @Resource
... ...