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,7 +61,7 @@
61 <transmittable-thread-local.version>2.14.5</transmittable-thread-local.version> 61 <transmittable-thread-local.version>2.14.5</transmittable-thread-local.version>
62 <commons-net.version>3.11.1</commons-net.version> 62 <commons-net.version>3.11.1</commons-net.version>
63 <commons-lang3.version>3.18.0</commons-lang3.version> 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 <tika-core.version>3.2.2</tika-core.version> 65 <tika-core.version>3.2.2</tika-core.version>
66 <ip2region.version>2.7.0</ip2region.version> 66 <ip2region.version>2.7.0</ip2region.version>
67 <bizlog-sdk.version>3.0.6</bizlog-sdk.version> 67 <bizlog-sdk.version>3.0.6</bizlog-sdk.version>
@@ -526,7 +526,7 @@ @@ -526,7 +526,7 @@
526 <version>${commons-net.version}</version> 526 <version>${commons-net.version}</version>
527 </dependency> 527 </dependency>
528 <dependency> 528 <dependency>
529 - <groupId>com.jcraft</groupId> 529 + <groupId>com.github.mwiede</groupId>
530 <artifactId>jsch</artifactId> <!-- 解决 sftp 连接 --> 530 <artifactId>jsch</artifactId> <!-- 解决 sftp 连接 -->
531 <version>${jsch.version}</version> 531 <version>${jsch.version}</version>
532 </dependency> 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 +6,7 @@ import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
6 import cn.iocoder.yudao.framework.mybatis.core.handler.DefaultDBFieldHandler; 6 import cn.iocoder.yudao.framework.mybatis.core.handler.DefaultDBFieldHandler;
7 import com.baomidou.mybatisplus.annotation.DbType; 7 import com.baomidou.mybatisplus.annotation.DbType;
8 import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration; 8 import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration;
  9 +import com.baomidou.mybatisplus.core.handlers.IJsonTypeHandler;
9 import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; 10 import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
10 import com.baomidou.mybatisplus.core.incrementer.IKeyGenerator; 11 import com.baomidou.mybatisplus.core.incrementer.IKeyGenerator;
11 import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; 12 import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
@@ -80,8 +81,8 @@ public class YudaoMybatisAutoConfiguration { @@ -80,8 +81,8 @@ public class YudaoMybatisAutoConfiguration {
80 throw new IllegalArgumentException(StrUtil.format("DbType{} 找不到合适的 IKeyGenerator 实现类", dbType)); 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 // 特殊:设置 JacksonTypeHandler 的 ObjectMapper! 86 // 特殊:设置 JacksonTypeHandler 的 ObjectMapper!
86 ObjectMapper objectMapper = CollUtil.getFirst(objectMappers); 87 ObjectMapper objectMapper = CollUtil.getFirst(objectMappers);
87 if (objectMapper == null) { 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 +6,7 @@ import com.baomidou.mybatisplus.annotation.TableLogic;
6 import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 6 import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
7 import com.fhs.core.trans.vo.TransPojo; 7 import com.fhs.core.trans.vo.TransPojo;
8 import lombok.Data; 8 import lombok.Data;
  9 +import org.apache.ibatis.type.JdbcType;
9 10
10 import java.io.Serializable; 11 import java.io.Serializable;
11 import java.time.LocalDateTime; 12 import java.time.LocalDateTime;
@@ -37,14 +38,14 @@ public abstract class BaseDO implements Serializable, TransPojo { @@ -37,14 +38,14 @@ public abstract class BaseDO implements Serializable, TransPojo {
37 * 38 *
38 * 使用 String 类型的原因是,未来可能会存在非数值的情况,留好拓展性。 39 * 使用 String 类型的原因是,未来可能会存在非数值的情况,留好拓展性。
39 */ 40 */
40 - @TableField(fill = FieldFill.INSERT) 41 + @TableField(fill = FieldFill.INSERT, jdbcType = JdbcType.VARCHAR)
41 private String creator; 42 private String creator;
42 /** 43 /**
43 * 更新者,目前使用 SysUser 的 id 编号 44 * 更新者,目前使用 SysUser 的 id 编号
44 * 45 *
45 * 使用 String 类型的原因是,未来可能会存在非数值的情况,留好拓展性。 46 * 使用 String 类型的原因是,未来可能会存在非数值的情况,留好拓展性。
46 */ 47 */
47 - @TableField(fill = FieldFill.INSERT_UPDATE) 48 + @TableField(fill = FieldFill.INSERT_UPDATE, jdbcType = JdbcType.VARCHAR)
48 private String updater; 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 package cn.iocoder.yudao.framework.swagger.config; 1 package cn.iocoder.yudao.framework.swagger.config;
2 2
  3 +import com.github.xiaoymin.knife4j.spring.configuration.Knife4jAutoConfiguration;
3 import io.swagger.v3.oas.models.Components; 4 import io.swagger.v3.oas.models.Components;
4 import io.swagger.v3.oas.models.OpenAPI; 5 import io.swagger.v3.oas.models.OpenAPI;
5 import io.swagger.v3.oas.models.info.Contact; 6 import io.swagger.v3.oas.models.info.Contact;
@@ -11,6 +12,7 @@ import io.swagger.v3.oas.models.parameters.Parameter; @@ -11,6 +12,7 @@ import io.swagger.v3.oas.models.parameters.Parameter;
11 import io.swagger.v3.oas.models.security.SecurityRequirement; 12 import io.swagger.v3.oas.models.security.SecurityRequirement;
12 import io.swagger.v3.oas.models.security.SecurityScheme; 13 import io.swagger.v3.oas.models.security.SecurityScheme;
13 import org.springdoc.core.customizers.OpenApiBuilderCustomizer; 14 import org.springdoc.core.customizers.OpenApiBuilderCustomizer;
  15 +import org.springdoc.core.customizers.OperationCustomizer;
14 import org.springdoc.core.customizers.ServerBaseUrlCustomizer; 16 import org.springdoc.core.customizers.ServerBaseUrlCustomizer;
15 import org.springdoc.core.models.GroupedOpenApi; 17 import org.springdoc.core.models.GroupedOpenApi;
16 import org.springdoc.core.properties.SpringDocConfigProperties; 18 import org.springdoc.core.properties.SpringDocConfigProperties;
@@ -43,7 +45,7 @@ import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.HEADER_ @@ -43,7 +45,7 @@ import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.HEADER_
43 * 45 *
44 * @author 芋道源码 46 * @author 芋道源码
45 */ 47 */
46 -@AutoConfiguration 48 +@AutoConfiguration(before = Knife4jAutoConfiguration.class) // before 原因,保证覆写的 Knife4jOpenApiCustomizer 先生效!相关 https://github.com/YunaiV/ruoyi-vue-pro/issues/954 讨论
47 @ConditionalOnClass({OpenAPI.class}) 49 @ConditionalOnClass({OpenAPI.class})
48 @EnableConfigurationProperties(SwaggerProperties.class) 50 @EnableConfigurationProperties(SwaggerProperties.class)
49 @ConditionalOnProperty(prefix = "springdoc.api-docs", name = "enabled", havingValue = "true", matchIfMissing = true) // 设置为 false 时,禁用 51 @ConditionalOnProperty(prefix = "springdoc.api-docs", name = "enabled", havingValue = "true", matchIfMissing = true) // 设置为 false 时,禁用
@@ -127,6 +129,7 @@ public class YudaoSwaggerAutoConfiguration { @@ -127,6 +129,7 @@ public class YudaoSwaggerAutoConfiguration {
127 .addOperationCustomizer((operation, handlerMethod) -> operation 129 .addOperationCustomizer((operation, handlerMethod) -> operation
128 .addParametersItem(buildTenantHeaderParameter()) 130 .addParametersItem(buildTenantHeaderParameter())
129 .addParametersItem(buildSecurityHeaderParameter())) 131 .addParametersItem(buildSecurityHeaderParameter()))
  132 + .addOperationCustomizer(buildOperationIdCustomizer())
130 .build(); 133 .build();
131 } 134 }
132 135
@@ -158,5 +161,26 @@ public class YudaoSwaggerAutoConfiguration { @@ -158,5 +161,26 @@ public class YudaoSwaggerAutoConfiguration {
158 .schema(new StringSchema()._default("Bearer test1").name(HEADER_TENANT_ID).description("认证 Token")); // 默认:使用用户编号为 1 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 package cn.iocoder.yudao.framework.web.config; 1 package cn.iocoder.yudao.framework.web.config;
2 2
  3 +import cn.hutool.core.util.StrUtil;
3 import cn.iocoder.yudao.framework.common.biz.infra.logger.ApiErrorLogCommonApi; 4 import cn.iocoder.yudao.framework.common.biz.infra.logger.ApiErrorLogCommonApi;
4 import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum; 5 import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum;
5 import cn.iocoder.yudao.framework.web.core.filter.CacheRequestBodyFilter; 6 import cn.iocoder.yudao.framework.web.core.filter.CacheRequestBodyFilter;
@@ -7,6 +8,7 @@ import cn.iocoder.yudao.framework.web.core.filter.DemoFilter; @@ -7,6 +8,7 @@ import cn.iocoder.yudao.framework.web.core.filter.DemoFilter;
7 import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler; 8 import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
8 import cn.iocoder.yudao.framework.web.core.handler.GlobalResponseBodyHandler; 9 import cn.iocoder.yudao.framework.web.core.handler.GlobalResponseBodyHandler;
9 import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils; 10 import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
  11 +import com.google.common.collect.Maps;
10 import jakarta.annotation.Resource; 12 import jakarta.annotation.Resource;
11 import jakarta.servlet.Filter; 13 import jakarta.servlet.Filter;
12 import org.springframework.beans.factory.annotation.Value; 14 import org.springframework.beans.factory.annotation.Value;
@@ -14,6 +16,7 @@ import org.springframework.boot.autoconfigure.AutoConfiguration; @@ -14,6 +16,7 @@ import org.springframework.boot.autoconfigure.AutoConfiguration;
14 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 16 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
15 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 17 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
16 import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration; 18 import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration;
  19 +import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations;
17 import org.springframework.boot.context.properties.EnableConfigurationProperties; 20 import org.springframework.boot.context.properties.EnableConfigurationProperties;
18 import org.springframework.boot.web.client.RestTemplateBuilder; 21 import org.springframework.boot.web.client.RestTemplateBuilder;
19 import org.springframework.boot.web.servlet.FilterRegistrationBean; 22 import org.springframework.boot.web.servlet.FilterRegistrationBean;
@@ -26,35 +29,57 @@ import org.springframework.web.cors.UrlBasedCorsConfigurationSource; @@ -26,35 +29,57 @@ import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
26 import org.springframework.web.filter.CorsFilter; 29 import org.springframework.web.filter.CorsFilter;
27 import org.springframework.web.servlet.config.annotation.PathMatchConfigurer; 30 import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
28 import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 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 @AutoConfiguration 37 @AutoConfiguration
31 @EnableConfigurationProperties(WebProperties.class) 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 @Value("${spring.application.name}") 44 @Value("${spring.application.name}")
40 private String applicationName; 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 @Bean 85 @Bean
yudao-module-ai/pom.xml
@@ -78,6 +78,12 @@ @@ -78,6 +78,12 @@
78 <groupId>org.springframework.ai</groupId> 78 <groupId>org.springframework.ai</groupId>
79 <artifactId>spring-ai-starter-model-openai</artifactId> 79 <artifactId>spring-ai-starter-model-openai</artifactId>
80 <version>${spring-ai.version}</version> 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 </dependency> 87 </dependency>
82 <dependency> 88 <dependency>
83 <groupId>org.springframework.ai</groupId> 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,8 +51,8 @@ public class BpmProcessIdRedisDAO {
51 String noPrefix = processIdRule.getPrefix() + infix + processIdRule.getPostfix(); 51 String noPrefix = processIdRule.getPrefix() + infix + processIdRule.getPostfix();
52 String key = RedisKeyConstants.BPM_PROCESS_ID + noPrefix; 52 String key = RedisKeyConstants.BPM_PROCESS_ID + noPrefix;
53 Long no = stringRedisTemplate.opsForValue().increment(key); 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 stringRedisTemplate.expire(key, Duration.ofDays(1L)); 56 stringRedisTemplate.expire(key, Duration.ofDays(1L));
57 } 57 }
58 return noPrefix + String.format("%0" + processIdRule.getLength() + "d", no); 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,10 +737,10 @@ public class SimpleModelUtils {
737 BoundaryEvent boundaryEvent = null; 737 BoundaryEvent boundaryEvent = null;
738 if (node.getDelaySetting().getDelayType().equals(BpmDelayTimerTypeEnum.FIXED_DATE_TIME.getType())) { 738 if (node.getDelaySetting().getDelayType().equals(BpmDelayTimerTypeEnum.FIXED_DATE_TIME.getType())) {
739 boundaryEvent = buildTimeoutBoundaryEvent(receiveTask, BpmBoundaryEventTypeEnum.DELAY_TIMER_TIMEOUT.getType(), 739 boundaryEvent = buildTimeoutBoundaryEvent(receiveTask, BpmBoundaryEventTypeEnum.DELAY_TIMER_TIMEOUT.getType(),
740 - node.getDelaySetting().getDelayTime(), null, null); 740 + null, null, node.getDelaySetting().getDelayTime());
741 } else if (node.getDelaySetting().getDelayType().equals(BpmDelayTimerTypeEnum.FIXED_TIME_DURATION.getType())) { 741 } else if (node.getDelaySetting().getDelayType().equals(BpmDelayTimerTypeEnum.FIXED_TIME_DURATION.getType())) {
742 boundaryEvent = buildTimeoutBoundaryEvent(receiveTask, BpmBoundaryEventTypeEnum.DELAY_TIMER_TIMEOUT.getType(), 742 boundaryEvent = buildTimeoutBoundaryEvent(receiveTask, BpmBoundaryEventTypeEnum.DELAY_TIMER_TIMEOUT.getType(),
743 - null, null, node.getDelaySetting().getDelayTime()); 743 + node.getDelaySetting().getDelayTime(), null, null);
744 } else { 744 } else {
745 throw new UnsupportedOperationException("不支持的延迟类型:" + node.getDelaySetting()); 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,6 +7,11 @@ import io.swagger.v3.oas.annotations.media.Schema;
7 import lombok.Data; 7 import lombok.Data;
8 import lombok.EqualsAndHashCode; 8 import lombok.EqualsAndHashCode;
9 import lombok.ToString; 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 @Schema(description = "管理后台 - 线索分页 Request VO") 16 @Schema(description = "管理后台 - 线索分页 Request VO")
12 @Data 17 @Data
@@ -42,4 +47,8 @@ public class CrmCluePageReqVO extends PageParam { @@ -42,4 +47,8 @@ public class CrmCluePageReqVO extends PageParam {
42 @Schema(description = "跟进状态", example = "true") 47 @Schema(description = "跟进状态", example = "true")
43 private Boolean followUpStatus; 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,6 +33,7 @@ public interface CrmClueMapper extends BaseMapperX&lt;CrmClueDO&gt; {
33 .eqIfPresent(CrmClueDO::getLevel, pageReqVO.getLevel()) 33 .eqIfPresent(CrmClueDO::getLevel, pageReqVO.getLevel())
34 .eqIfPresent(CrmClueDO::getSource, pageReqVO.getSource()) 34 .eqIfPresent(CrmClueDO::getSource, pageReqVO.getSource())
35 .eqIfPresent(CrmClueDO::getFollowUpStatus, pageReqVO.getFollowUpStatus()) 35 .eqIfPresent(CrmClueDO::getFollowUpStatus, pageReqVO.getFollowUpStatus())
  36 + .betweenIfPresent(CrmClueDO::getCreateTime, pageReqVO.getCreateTime())
36 .orderByDesc(CrmClueDO::getId); 37 .orderByDesc(CrmClueDO::getId);
37 return selectJoinPage(pageReqVO, CrmClueDO.class, query); 38 return selectJoinPage(pageReqVO, CrmClueDO.class, query);
38 } 39 }
yudao-module-infra/pom.xml
@@ -101,7 +101,7 @@ @@ -101,7 +101,7 @@
101 <artifactId>commons-net</artifactId> <!-- 文件客户端:解决 ftp 连接 --> 101 <artifactId>commons-net</artifactId> <!-- 文件客户端:解决 ftp 连接 -->
102 </dependency> 102 </dependency>
103 <dependency> 103 <dependency>
104 - <groupId>com.jcraft</groupId> 104 + <groupId>com.github.mwiede</groupId>
105 <artifactId>jsch</artifactId> <!-- 文件客户端:解决 sftp 连接 --> 105 <artifactId>jsch</artifactId> <!-- 文件客户端:解决 sftp 连接 -->
106 </dependency> 106 </dependency>
107 <!-- 文件客户端:解决阿里云、腾讯云、minio 等 S3 连接 --> 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,6 +26,7 @@ import org.springframework.validation.annotation.Validated;
26 import org.springframework.web.bind.annotation.*; 26 import org.springframework.web.bind.annotation.*;
27 import org.springframework.web.multipart.MultipartFile; 27 import org.springframework.web.multipart.MultipartFile;
28 28
  29 +import java.nio.charset.StandardCharsets;
29 import java.util.List; 30 import java.util.List;
30 31
31 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 32 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@@ -99,8 +100,10 @@ public class FileController { @@ -99,8 +100,10 @@ public class FileController {
99 if (StrUtil.isEmpty(path)) { 100 if (StrUtil.isEmpty(path)) {
100 throw new IllegalArgumentException("结尾的 path 路径必须传递"); 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 byte[] content = fileService.getFileContent(configId, path); 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,6 +4,7 @@ import cn.hutool.core.io.FileUtil;
4 import cn.hutool.core.util.CharsetUtil; 4 import cn.hutool.core.util.CharsetUtil;
5 import cn.hutool.core.util.StrUtil; 5 import cn.hutool.core.util.StrUtil;
6 import cn.hutool.extra.ftp.Ftp; 6 import cn.hutool.extra.ftp.Ftp;
  7 +import cn.hutool.extra.ftp.FtpConfig;
7 import cn.hutool.extra.ftp.FtpException; 8 import cn.hutool.extra.ftp.FtpException;
8 import cn.hutool.extra.ftp.FtpMode; 9 import cn.hutool.extra.ftp.FtpMode;
9 import cn.iocoder.yudao.module.infra.framework.file.core.client.AbstractFileClient; 10 import cn.iocoder.yudao.module.infra.framework.file.core.client.AbstractFileClient;
@@ -18,6 +19,15 @@ import java.io.ByteArrayOutputStream; @@ -18,6 +19,15 @@ import java.io.ByteArrayOutputStream;
18 */ 19 */
19 public class FtpFileClient extends AbstractFileClient<FtpFileClientConfig> { 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 private Ftp ftp; 31 private Ftp ftp;
22 32
23 public FtpFileClient(Long id, FtpFileClientConfig config) { 33 public FtpFileClient(Long id, FtpFileClientConfig config) {
@@ -26,9 +36,12 @@ public class FtpFileClient extends AbstractFileClient&lt;FtpFileClientConfig&gt; { @@ -26,9 +36,12 @@ public class FtpFileClient extends AbstractFileClient&lt;FtpFileClientConfig&gt; {
26 36
27 @Override 37 @Override
28 protected void doInit() { 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 @Override 47 @Override
@@ -72,4 +85,4 @@ public class FtpFileClient extends AbstractFileClient&lt;FtpFileClientConfig&gt; { @@ -72,4 +85,4 @@ public class FtpFileClient extends AbstractFileClient&lt;FtpFileClientConfig&gt; {
72 ftp.reconnectIfTimeout(); 85 ftp.reconnectIfTimeout();
73 } 86 }
74 87
75 -}  
76 \ No newline at end of file 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 package cn.iocoder.yudao.module.infra.framework.file.core.client.ftp; 1 package cn.iocoder.yudao.module.infra.framework.file.core.client.ftp;
2 2
3 import cn.iocoder.yudao.module.infra.framework.file.core.client.FileClientConfig; 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 import jakarta.validation.constraints.NotEmpty; 4 import jakarta.validation.constraints.NotEmpty;
8 import jakarta.validation.constraints.NotNull; 5 import jakarta.validation.constraints.NotNull;
  6 +import lombok.Data;
  7 +import org.hibernate.validator.constraints.URL;
9 8
10 /** 9 /**
11 * Ftp 文件客户端的配置类 10 * Ftp 文件客户端的配置类
yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/sftp/SftpFileClient.java
1 package cn.iocoder.yudao.module.infra.framework.file.core.client.sftp; 1 package cn.iocoder.yudao.module.infra.framework.file.core.client.sftp;
2 2
3 import cn.hutool.core.io.FileUtil; 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 import cn.hutool.extra.ssh.Sftp; 8 import cn.hutool.extra.ssh.Sftp;
5 import cn.iocoder.yudao.framework.common.util.io.FileUtils; 9 import cn.iocoder.yudao.framework.common.util.io.FileUtils;
6 import cn.iocoder.yudao.module.infra.framework.file.core.client.AbstractFileClient; 10 import cn.iocoder.yudao.module.infra.framework.file.core.client.AbstractFileClient;
  11 +import com.jcraft.jsch.JSch;
7 12
8 import java.io.File; 13 import java.io.File;
9 14
@@ -14,6 +19,20 @@ import java.io.File; @@ -14,6 +19,20 @@ import java.io.File;
14 */ 19 */
15 public class SftpFileClient extends AbstractFileClient<SftpFileClientConfig> { 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 private Sftp sftp; 36 private Sftp sftp;
18 37
19 public SftpFileClient(Long id, SftpFileClientConfig config) { 38 public SftpFileClient(Long id, SftpFileClientConfig config) {
@@ -22,18 +41,27 @@ public class SftpFileClient extends AbstractFileClient&lt;SftpFileClientConfig&gt; { @@ -22,18 +41,27 @@ public class SftpFileClient extends AbstractFileClient&lt;SftpFileClientConfig&gt; {
22 41
23 @Override 42 @Override
24 protected void doInit() { 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 @Override 52 @Override
30 public String upload(byte[] content, String path, String type) { 53 public String upload(byte[] content, String path, String type) {
31 // 执行写入 54 // 执行写入
32 String filePath = getFilePath(path); 55 String filePath = getFilePath(path);
  56 + String fileName = FileUtil.getName(filePath);
  57 + String dir = StrUtil.removeSuffix(filePath, fileName);
33 File file = FileUtils.createTempFile(content); 58 File file = FileUtils.createTempFile(content);
34 reconnectIfTimeout(); 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 return super.formatFileUrl(config.getDomain(), path); 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 package cn.iocoder.yudao.module.infra.framework.file.core.client.sftp; 1 package cn.iocoder.yudao.module.infra.framework.file.core.client.sftp;
2 2
3 import cn.iocoder.yudao.module.infra.framework.file.core.client.FileClientConfig; 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 import jakarta.validation.constraints.NotEmpty; 4 import jakarta.validation.constraints.NotEmpty;
8 import jakarta.validation.constraints.NotNull; 5 import jakarta.validation.constraints.NotNull;
  6 +import lombok.Data;
  7 +import org.hibernate.validator.constraints.URL;
9 8
10 /** 9 /**
11 * Sftp 文件客户端的配置类 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,7 +43,7 @@ public enum IotDeviceMessageMethodEnum implements ArrayValuable&lt;String&gt; {
43 // ========== 设备配置 ========== 43 // ========== 设备配置 ==========
44 // 可参考:https://help.aliyun.com/zh/iot/user-guide/remote-configuration-1 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 // ========== OTA 固件 ========== 48 // ========== OTA 固件 ==========
49 // 可参考:https://help.aliyun.com/zh/iot/user-guide/perform-ota-updates 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,7 +125,7 @@ public class IotMqttConnectionManager {
125 */ 125 */
126 public IotMqttConnectionManager.ConnectionInfo getConnectionInfoByDeviceId(Long deviceId) { 126 public IotMqttConnectionManager.ConnectionInfo getConnectionInfoByDeviceId(Long deviceId) {
127 // 通过设备 ID 获取连接端点 127 // 通过设备 ID 获取连接端点
128 - var endpoint = getDeviceEndpoint(deviceId); 128 + MqttEndpoint endpoint = getDeviceEndpoint(deviceId);
129 if (endpoint == null) { 129 if (endpoint == null) {
130 return null; 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,7 +14,7 @@ import java.time.LocalDateTime;
14 public class RewardActivityRespVO extends RewardActivityBaseVO { 14 public class RewardActivityRespVO extends RewardActivityBaseVO {
15 15
16 @Schema(description = "活动编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") 16 @Schema(description = "活动编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
17 - private Integer id; 17 + private Long id;
18 18
19 @Schema(description = "活动状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") 19 @Schema(description = "活动状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
20 private Integer status; 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,8 +284,8 @@ public class CouponServiceImpl implements CouponService {
284 } 284 }
285 // 校验发放数量不能过小(仅在 CouponTakeTypeEnum.USER 用户领取时) 285 // 校验发放数量不能过小(仅在 CouponTakeTypeEnum.USER 用户领取时)
286 if (CouponTakeTypeEnum.isUser(couponTemplate.getTakeType()) 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 throw exception(COUPON_TEMPLATE_NOT_ENOUGH); 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,7 +610,7 @@ public class PayOrderServiceTest extends BaseDbAndRedisUnitTest {
610 orderExtensionMapper.insert(orderExtension); 610 orderExtensionMapper.insert(orderExtension);
611 // 重要:需要将 order 的 extensionId 更新下 611 // 重要:需要将 order 的 extensionId 更新下
612 order.setExtensionId(orderExtension.getId()); 612 order.setExtensionId(orderExtension.getId());
613 - orderMapper.updateById(new PayOrderDO().setId(order.getId()).setExtensionId(orderExtension.getId())); 613 + orderMapper.updateById(order);
614 // 准备参数 614 // 准备参数
615 PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L)); 615 PayChannelDO channel = randomPojo(PayChannelDO.class, o -> o.setId(10L));
616 PayOrderRespDTO notify = randomPojo(PayOrderRespDTO.class, 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,6 +54,7 @@ import me.zhyd.oauth.model.AuthResponse;
54 import me.zhyd.oauth.model.AuthUser; 54 import me.zhyd.oauth.model.AuthUser;
55 import me.zhyd.oauth.request.AuthRequest; 55 import me.zhyd.oauth.request.AuthRequest;
56 import me.zhyd.oauth.utils.AuthStateUtils; 56 import me.zhyd.oauth.utils.AuthStateUtils;
  57 +import org.springframework.beans.factory.annotation.Autowired;
57 import org.springframework.beans.factory.annotation.Value; 58 import org.springframework.beans.factory.annotation.Value;
58 import org.springframework.cache.annotation.Cacheable; 59 import org.springframework.cache.annotation.Cacheable;
59 import org.springframework.data.redis.core.StringRedisTemplate; 60 import org.springframework.data.redis.core.StringRedisTemplate;
@@ -101,7 +102,8 @@ public class SocialClientServiceImpl implements SocialClientService { @@ -101,7 +102,8 @@ public class SocialClientServiceImpl implements SocialClientService {
101 @Value("${yudao.wxa-subscribe-message.miniprogram-state:formal}") 102 @Value("${yudao.wxa-subscribe-message.miniprogram-state:formal}")
102 public String miniprogramState; 103 public String miniprogramState;
103 104
104 - @Resource 105 + @SuppressWarnings("SpringJavaAutowiredFieldsWarningInspection")
  106 + @Autowired(required = false) // 由于 justauth.enable 配置项,可以关闭 AuthRequestFactory 的功能,所以这里只能不强制注入
105 private AuthRequestFactory authRequestFactory; 107 private AuthRequestFactory authRequestFactory;
106 108
107 @Resource 109 @Resource