Commit 98f9e44b24022b0cc7d7b743d0aae1d2fc02fda5
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<CrmClueDO> { |
| 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<FtpFileClientConfig> { |
| 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<FtpFileClientConfig> { |
| 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<SftpFileClientConfig> { |
| 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<String> { |
| 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 | ... | ... |