Commit 28c818e9bcbb62eec212b417e059bdb480ade30e

Authored by puhui999
2 parents ed920f3f 4fb4af2c

Merge remote-tracking branch 'yudao/master-jdk17' into master-jdk17

# Conflicts:
#	yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngine.java
Showing 118 changed files with 1495 additions and 581 deletions

Too many changes.

To preserve performance only 100 of 118 files are displayed.

README.md
1 <p align="center"> 1 <p align="center">
2 - <img src="https://img.shields.io/badge/Spring%20Boot-3.4.1-blue.svg" alt="Downloads"> 2 + <img src="https://img.shields.io/badge/Spring%20Boot-3.4.5-blue.svg" alt="Downloads">
3 <img src="https://img.shields.io/badge/Vue-3.2-blue.svg" alt="Downloads"> 3 <img src="https://img.shields.io/badge/Vue-3.2-blue.svg" alt="Downloads">
4 <img src="https://img.shields.io/github/license/YunaiV/ruoyi-vue-pro" alt="Downloads" /> 4 <img src="https://img.shields.io/github/license/YunaiV/ruoyi-vue-pro" alt="Downloads" />
5 </p> 5 </p>
@@ -308,7 +308,7 @@ @@ -308,7 +308,7 @@
308 308
309 | 框架 | 说明 | 版本 | 学习指南 | 309 | 框架 | 说明 | 版本 | 学习指南 |
310 |---------------------------------------------------------------------------------------------|------------------|----------------|----------------------------------------------------------------| 310 |---------------------------------------------------------------------------------------------|------------------|----------------|----------------------------------------------------------------|
311 -| [Spring Boot](https://spring.io/projects/spring-boot) | 应用开发框架 | 3.4.1 | [文档](https://github.com/YunaiV/SpringBoot-Labs) | 311 +| [Spring Boot](https://spring.io/projects/spring-boot) | 应用开发框架 | 3.4.5 | [文档](https://github.com/YunaiV/SpringBoot-Labs) |
312 | [MySQL](https://www.mysql.com/cn/) | 数据库服务器 | 5.7 / 8.0+ | | 312 | [MySQL](https://www.mysql.com/cn/) | 数据库服务器 | 5.7 / 8.0+ | |
313 | [Druid](https://github.com/alibaba/druid) | JDBC 连接池、监控组件 | 1.2.23 | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) | 313 | [Druid](https://github.com/alibaba/druid) | JDBC 连接池、监控组件 | 1.2.23 | [文档](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) |
314 | [MyBatis Plus](https://mp.baomidou.com/) | MyBatis 增强工具包 | 3.5.7 | [文档](http://www.iocoder.cn/Spring-Boot/MyBatis/?yudao) | 314 | [MyBatis Plus](https://mp.baomidou.com/) | MyBatis 增强工具包 | 3.5.7 | [文档](http://www.iocoder.cn/Spring-Boot/MyBatis/?yudao) |
@@ -42,7 +42,7 @@ @@ -42,7 +42,7 @@
42 <flatten-maven-plugin.version>1.6.0</flatten-maven-plugin.version> 42 <flatten-maven-plugin.version>1.6.0</flatten-maven-plugin.version>
43 <!-- 看看咋放到 bom 里 --> 43 <!-- 看看咋放到 bom 里 -->
44 <lombok.version>1.18.36</lombok.version> 44 <lombok.version>1.18.36</lombok.version>
45 - <spring.boot.version>3.4.1</spring.boot.version> 45 + <spring.boot.version>3.4.5</spring.boot.version>
46 <mapstruct.version>1.6.3</mapstruct.version> 46 <mapstruct.version>1.6.3</mapstruct.version>
47 <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> 47 <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
48 </properties> 48 </properties>
sql/mysql/ruoyi-vue-pro.sql
@@ -1055,7 +1055,7 @@ INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `st @@ -1055,7 +1055,7 @@ INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `st
1055 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2172, 31, 'RABBITMQ', '31', 'iot_data_bridge_type_enum', 0, 'primary', '', '', '1', '2025-03-09 12:41:47', '1', '2025-03-17 09:40:46', b'0'); 1055 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2172, 31, 'RABBITMQ', '31', 'iot_data_bridge_type_enum', 0, 'primary', '', '', '1', '2025-03-09 12:41:47', '1', '2025-03-17 09:40:46', b'0');
1056 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2173, 32, 'KAFKA', '32', 'iot_data_bridge_type_enum', 0, 'primary', '', '', '1', '2025-03-09 12:41:59', '1', '2025-03-17 09:40:46', b'0'); 1056 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2173, 32, 'KAFKA', '32', 'iot_data_bridge_type_enum', 0, 'primary', '', '', '1', '2025-03-09 12:41:59', '1', '2025-03-17 09:40:46', b'0');
1057 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3000, 16, '百川智能', 'BaiChuan', 'ai_platform', 0, '', '', '', '1', '2025-03-23 12:15:46', '1', '2025-03-23 12:15:46', b'0'); 1057 INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3000, 16, '百川智能', 'BaiChuan', 'ai_platform', 0, '', '', '', '1', '2025-03-23 12:15:46', '1', '2025-03-23 12:15:46', b'0');
1058 -INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3001, 50, 'Vben5.0 Ant Design Schema 模版', '50', 'infra_codegen_front_type', 0, '', '', NULL, '1', '2025-04-23 21:47:47', '1', '2025-04-23 21:47:47', b'0'); 1058 +INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (3001, 50, 'Vben5.0 Ant Design Schema 模版', '40', 'infra_codegen_front_type', 0, '', '', NULL, '1', '2025-04-23 21:47:47', '1', '2025-04-23 21:47:47', b'0');
1059 COMMIT; 1059 COMMIT;
1060 1060
1061 -- ---------------------------- 1061 -- ----------------------------
yudao-dependencies/pom.xml
@@ -17,14 +17,13 @@ @@ -17,14 +17,13 @@
17 <revision>2.4.2-SNAPSHOT</revision> 17 <revision>2.4.2-SNAPSHOT</revision>
18 <flatten-maven-plugin.version>1.6.0</flatten-maven-plugin.version> 18 <flatten-maven-plugin.version>1.6.0</flatten-maven-plugin.version>
19 <!-- 统一依赖管理 --> 19 <!-- 统一依赖管理 -->
20 - <spring.boot.version>3.4.1</spring.boot.version> 20 + <spring.boot.version>3.4.5</spring.boot.version>
21 <!-- Web 相关 --> 21 <!-- Web 相关 -->
22 - <springdoc.version>2.7.0</springdoc.version> 22 + <springdoc.version>2.8.3</springdoc.version>
23 <knife4j.version>4.6.0</knife4j.version> 23 <knife4j.version>4.6.0</knife4j.version>
24 <!-- DB 相关 --> 24 <!-- DB 相关 -->
25 <druid.version>1.2.24</druid.version> 25 <druid.version>1.2.24</druid.version>
26 - <mybatis.version>3.5.17</mybatis.version>  
27 - <mybatis-plus.version>3.5.9</mybatis-plus.version> 26 + <mybatis-plus.version>3.5.10.1</mybatis-plus.version>
28 <dynamic-datasource.version>4.3.1</dynamic-datasource.version> 27 <dynamic-datasource.version>4.3.1</dynamic-datasource.version>
29 <mybatis-plus-join.version>1.4.13</mybatis-plus-join.version> 28 <mybatis-plus-join.version>1.4.13</mybatis-plus-join.version>
30 <easy-trans.version>3.0.6</easy-trans.version> 29 <easy-trans.version>3.0.6</easy-trans.version>
@@ -39,7 +38,7 @@ @@ -39,7 +38,7 @@
39 <lock4j.version>2.2.7</lock4j.version> 38 <lock4j.version>2.2.7</lock4j.version>
40 <!-- 监控相关 --> 39 <!-- 监控相关 -->
41 <skywalking.version>9.0.0</skywalking.version> 40 <skywalking.version>9.0.0</skywalking.version>
42 - <spring-boot-admin.version>3.4.1</spring-boot-admin.version> 41 + <spring-boot-admin.version>3.4.5</spring-boot-admin.version>
43 <opentracing.version>0.33.0</opentracing.version> 42 <opentracing.version>0.33.0</opentracing.version>
44 <!-- Test 测试相关 --> 43 <!-- Test 测试相关 -->
45 <podam.version>8.0.2.RELEASE</podam.version> 44 <podam.version>8.0.2.RELEASE</podam.version>
@@ -48,7 +47,7 @@ @@ -48,7 +47,7 @@
48 <!-- Bpm 工作流相关 --> 47 <!-- Bpm 工作流相关 -->
49 <flowable.version>7.0.1</flowable.version> 48 <flowable.version>7.0.1</flowable.version>
50 <!-- 工具类相关 --> 49 <!-- 工具类相关 -->
51 - <captcha-plus.version>2.0.3</captcha-plus.version> 50 + <anji-plus-captcha.version>1.4.0</anji-plus-captcha.version>
52 <jsoup.version>1.18.3</jsoup.version> 51 <jsoup.version>1.18.3</jsoup.version>
53 <lombok.version>1.18.36</lombok.version> 52 <lombok.version>1.18.36</lombok.version>
54 <mapstruct.version>1.6.3</mapstruct.version> 53 <mapstruct.version>1.6.3</mapstruct.version>
@@ -71,9 +70,10 @@ @@ -71,9 +70,10 @@
71 <!-- 三方云服务相关 --> 70 <!-- 三方云服务相关 -->
72 <commons-io.version>2.17.0</commons-io.version> 71 <commons-io.version>2.17.0</commons-io.version>
73 <commons-compress.version>1.27.1</commons-compress.version> 72 <commons-compress.version>1.27.1</commons-compress.version>
74 - <aws-java-sdk-s3.version>1.12.777</aws-java-sdk-s3.version>  
75 - <justauth.version>2.0.5</justauth.version>  
76 - <jimureport.version>1.8.1</jimureport.version> 73 + <awssdk.version>2.30.14</awssdk.version>
  74 + <justauth.version>1.16.7</justauth.version>
  75 + <justauth-starter.version>1.4.0</justauth-starter.version>
  76 + <jimureport.version>1.9.4</jimureport.version>
77 <weixin-java.version>4.7.2.B</weixin-java.version> 77 <weixin-java.version>4.7.2.B</weixin-java.version>
78 </properties> 78 </properties>
79 79
@@ -174,11 +174,6 @@ @@ -174,11 +174,6 @@
174 <version>${druid.version}</version> 174 <version>${druid.version}</version>
175 </dependency> 175 </dependency>
176 <dependency> 176 <dependency>
177 - <groupId>org.mybatis</groupId>  
178 - <artifactId>mybatis</artifactId>  
179 - <version>${mybatis.version}</version>  
180 - </dependency>  
181 - <dependency>  
182 <groupId>com.baomidou</groupId> 177 <groupId>com.baomidou</groupId>
183 <artifactId>mybatis-plus-spring-boot3-starter</artifactId> 178 <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
184 <version>${mybatis-plus.version}</version> 179 <version>${mybatis-plus.version}</version>
@@ -534,9 +529,9 @@ @@ -534,9 +529,9 @@
534 </dependency> 529 </dependency>
535 530
536 <dependency> 531 <dependency>
537 - <groupId>com.xingyuv</groupId>  
538 - <artifactId>spring-boot-starter-captcha-plus</artifactId>  
539 - <version>${captcha-plus.version}</version> 532 + <groupId>com.anji-plus</groupId>
  533 + <artifactId>captcha-spring-boot-starter</artifactId> <!-- 验证码,一般用于登录使用 -->
  534 + <version>${anji-plus-captcha.version}</version>
540 </dependency> 535 </dependency>
541 536
542 <dependency> 537 <dependency>
@@ -553,21 +548,20 @@ @@ -553,21 +548,20 @@
553 548
554 <!-- 三方云服务相关 --> 549 <!-- 三方云服务相关 -->
555 <dependency> 550 <dependency>
556 - <groupId>com.amazonaws</groupId>  
557 - <artifactId>aws-java-sdk-s3</artifactId>  
558 - <version>${aws-java-sdk-s3.version}</version> 551 + <groupId>software.amazon.awssdk</groupId>
  552 + <artifactId>s3</artifactId>
  553 + <version>${awssdk.version}</version>
559 </dependency> 554 </dependency>
560 555
561 <dependency> 556 <dependency>
562 - <groupId>com.xingyuv</groupId>  
563 - <artifactId>spring-boot-starter-justauth</artifactId> <!-- 社交登陆(例如说,个人微信、企业微信等等) --> 557 + <groupId>me.zhyd.oauth</groupId>
  558 + <artifactId>JustAuth</artifactId> <!-- 社交登陆(例如说,个人微信、企业微信等等) -->
564 <version>${justauth.version}</version> 559 <version>${justauth.version}</version>
565 - <exclusions>  
566 - <exclusion>  
567 - <groupId>cn.hutool</groupId>  
568 - <artifactId>hutool-core</artifactId>  
569 - </exclusion>  
570 - </exclusions> 560 + </dependency>
  561 + <dependency>
  562 + <groupId>com.xkcoding.justauth</groupId>
  563 + <artifactId>justauth-spring-boot-starter</artifactId>
  564 + <version>${justauth-starter.version}</version>
571 </dependency> 565 </dependency>
572 566
573 <dependency> 567 <dependency>
@@ -591,10 +585,15 @@ @@ -591,10 +585,15 @@
591 <groupId>org.jeecgframework.jimureport</groupId> 585 <groupId>org.jeecgframework.jimureport</groupId>
592 <artifactId>jimureport-spring-boot3-starter-fastjson2</artifactId> 586 <artifactId>jimureport-spring-boot3-starter-fastjson2</artifactId>
593 <version>${jimureport.version}</version> 587 <version>${jimureport.version}</version>
  588 + </dependency>
  589 + <dependency>
  590 + <groupId>org.jeecgframework.jimureport</groupId>
  591 + <artifactId>jimubi-spring-boot3-starter</artifactId>
  592 + <version>${jimureport.version}</version>
594 <exclusions> 593 <exclusions>
595 <exclusion> 594 <exclusion>
596 - <groupId>com.alibaba</groupId>  
597 - <artifactId>druid</artifactId> 595 + <groupId>com.github.jsqlparser</groupId>
  596 + <artifactId>jsqlparser</artifactId>
598 </exclusion> 597 </exclusion>
599 </exclusions> 598 </exclusions>
600 </dependency> 599 </dependency>
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java
@@ -11,6 +11,7 @@ import java.util.function.*; @@ -11,6 +11,7 @@ import java.util.function.*;
11 import java.util.stream.Collectors; 11 import java.util.stream.Collectors;
12 import java.util.stream.Stream; 12 import java.util.stream.Stream;
13 13
  14 +import static cn.hutool.core.convert.Convert.toCollection;
14 import static java.util.Arrays.asList; 15 import static java.util.Arrays.asList;
15 16
16 /** 17 /**
@@ -335,4 +336,17 @@ public class CollectionUtils { @@ -335,4 +336,17 @@ public class CollectionUtils {
335 return list.stream().filter(Objects::nonNull).flatMap(Collection::stream).collect(Collectors.toList()); 336 return list.stream().filter(Objects::nonNull).flatMap(Collection::stream).collect(Collectors.toList());
336 } 337 }
337 338
  339 + /**
  340 + * 转换为 LinkedHashSet
  341 + *
  342 + * @param <T> 元素类型
  343 + * @param elementType 集合中元素类型
  344 + * @param value 被转换的值
  345 + * @return {@link LinkedHashSet}
  346 + */
  347 + @SuppressWarnings("unchecked")
  348 + public static <T> LinkedHashSet<T> toLinkedHashSet(Class<T> elementType, Object value) {
  349 + return (LinkedHashSet<T>) toCollection(LinkedHashSet.class, elementType, value);
  350 + }
  351 +
338 } 352 }
339 \ No newline at end of file 353 \ No newline at end of file
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/io/FileUtils.java
1 package cn.iocoder.yudao.framework.common.util.io; 1 package cn.iocoder.yudao.framework.common.util.io;
2 2
3 -import cn.hutool.core.io.FileTypeUtil;  
4 import cn.hutool.core.io.FileUtil; 3 import cn.hutool.core.io.FileUtil;
5 -import cn.hutool.core.io.file.FileNameUtil;  
6 import cn.hutool.core.util.IdUtil; 4 import cn.hutool.core.util.IdUtil;
7 -import cn.hutool.core.util.StrUtil;  
8 -import cn.hutool.crypto.digest.DigestUtil;  
9 import lombok.SneakyThrows; 5 import lombok.SneakyThrows;
10 6
11 -import java.io.ByteArrayInputStream;  
12 import java.io.File; 7 import java.io.File;
13 8
14 /** 9 /**
@@ -63,22 +58,4 @@ public class FileUtils { @@ -63,22 +58,4 @@ public class FileUtils {
63 return file; 58 return file;
64 } 59 }
65 60
66 - /**  
67 - * 生成文件路径  
68 - *  
69 - * @param content 文件内容  
70 - * @param originalName 原始文件名  
71 - * @return path,唯一不可重复  
72 - */  
73 - public static String generatePath(byte[] content, String originalName) {  
74 - String sha256Hex = DigestUtil.sha256Hex(content);  
75 - // 情况一:如果存在 name,则优先使用 name 的后缀  
76 - if (StrUtil.isNotBlank(originalName)) {  
77 - String extName = FileNameUtil.extName(originalName);  
78 - return StrUtil.isBlank(extName) ? sha256Hex : sha256Hex + "." + extName;  
79 - }  
80 - // 情况二:基于 content 计算  
81 - return sha256Hex + '.' + FileTypeUtil.getType(new ByteArrayInputStream(content));  
82 - }  
83 -  
84 } 61 }
yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/config/YudaoRedisMQConsumerAutoConfiguration.java
@@ -6,6 +6,7 @@ import cn.hutool.system.SystemUtil; @@ -6,6 +6,7 @@ import cn.hutool.system.SystemUtil;
6 import cn.iocoder.yudao.framework.common.enums.DocumentEnum; 6 import cn.iocoder.yudao.framework.common.enums.DocumentEnum;
7 import cn.iocoder.yudao.framework.mq.redis.core.RedisMQTemplate; 7 import cn.iocoder.yudao.framework.mq.redis.core.RedisMQTemplate;
8 import cn.iocoder.yudao.framework.mq.redis.core.job.RedisPendingMessageResendJob; 8 import cn.iocoder.yudao.framework.mq.redis.core.job.RedisPendingMessageResendJob;
  9 +import cn.iocoder.yudao.framework.mq.redis.core.job.RedisStreamMessageCleanupJob;
9 import cn.iocoder.yudao.framework.mq.redis.core.pubsub.AbstractRedisChannelMessageListener; 10 import cn.iocoder.yudao.framework.mq.redis.core.pubsub.AbstractRedisChannelMessageListener;
10 import cn.iocoder.yudao.framework.mq.redis.core.stream.AbstractRedisStreamMessageListener; 11 import cn.iocoder.yudao.framework.mq.redis.core.stream.AbstractRedisStreamMessageListener;
11 import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration; 12 import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration;
@@ -74,6 +75,17 @@ public class YudaoRedisMQConsumerAutoConfiguration { @@ -74,6 +75,17 @@ public class YudaoRedisMQConsumerAutoConfiguration {
74 } 75 }
75 76
76 /** 77 /**
  78 + * 创建 Redis Stream 消息清理任务
  79 + */
  80 + @Bean
  81 + @ConditionalOnBean(AbstractRedisStreamMessageListener.class)
  82 + public RedisStreamMessageCleanupJob redisStreamMessageCleanupJob(List<AbstractRedisStreamMessageListener<?>> listeners,
  83 + RedisMQTemplate redisTemplate,
  84 + RedissonClient redissonClient) {
  85 + return new RedisStreamMessageCleanupJob(listeners, redisTemplate, redissonClient);
  86 + }
  87 +
  88 + /**
77 * 创建 Redis Stream 集群消费的容器 89 * 创建 Redis Stream 集群消费的容器
78 * 90 *
79 * 基础知识:<a href="https://www.geek-book.com/src/docs/redis/redis/redis.io/commands/xreadgroup.html">Redis Stream 的 xreadgroup 命令</a> 91 * 基础知识:<a href="https://www.geek-book.com/src/docs/redis/redis/redis.io/commands/xreadgroup.html">Redis Stream 的 xreadgroup 命令</a>
yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/core/job/RedisPendingMessageResendJob.java
@@ -23,13 +23,13 @@ import java.util.Objects; @@ -23,13 +23,13 @@ import java.util.Objects;
23 @AllArgsConstructor 23 @AllArgsConstructor
24 public class RedisPendingMessageResendJob { 24 public class RedisPendingMessageResendJob {
25 25
26 - private static final String LOCK_KEY = "redis:pending:msg:lock"; 26 + private static final String LOCK_KEY = "redis:stream:pending-message-resend:lock";
27 27
28 /** 28 /**
29 * 消息超时时间,默认 5 分钟 29 * 消息超时时间,默认 5 分钟
30 * 30 *
31 * 1. 超时的消息才会被重新投递 31 * 1. 超时的消息才会被重新投递
32 - * 2. 由于定时任务 1 分钟一次,消息超时后不会被立即重投,极端情况下消息5分钟过期后,再等 1 分钟才会被扫瞄到 32 + * 2. 由于定时任务 1 分钟一次,消息超时后不会被立即重投,极端情况下消息 5 分钟过期后,再等 1 分钟才会被扫瞄到
33 */ 33 */
34 private static final int EXPIRE_TIME = 5 * 60; 34 private static final int EXPIRE_TIME = 5 * 60;
35 35
@@ -39,7 +39,7 @@ public class RedisPendingMessageResendJob { @@ -39,7 +39,7 @@ public class RedisPendingMessageResendJob {
39 private final RedissonClient redissonClient; 39 private final RedissonClient redissonClient;
40 40
41 /** 41 /**
42 - * 一分钟执行一次,这里选择每分钟的35秒执行,是为了避免整点任务过多的问题 42 + * 一分钟执行一次,这里选择每分钟的 35 秒执行,是为了避免整点任务过多的问题
43 */ 43 */
44 @Scheduled(cron = "35 * * * * ?") 44 @Scheduled(cron = "35 * * * * ?")
45 public void messageResend() { 45 public void messageResend() {
yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/core/job/RedisStreamMessageCleanupJob.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.mq.redis.core.job;
  2 +
  3 +import cn.iocoder.yudao.framework.mq.redis.core.RedisMQTemplate;
  4 +import cn.iocoder.yudao.framework.mq.redis.core.stream.AbstractRedisStreamMessageListener;
  5 +import lombok.AllArgsConstructor;
  6 +import lombok.extern.slf4j.Slf4j;
  7 +import org.redisson.api.RLock;
  8 +import org.redisson.api.RedissonClient;
  9 +import org.springframework.data.redis.core.StreamOperations;
  10 +import org.springframework.scheduling.annotation.Scheduled;
  11 +
  12 +import java.util.List;
  13 +
  14 +/**
  15 + * Redis Stream 消息清理任务
  16 + * 用于定期清理已消费的消息,防止内存占用过大
  17 + *
  18 + * @see <a href="https://www.cnblogs.com/nanxiang/p/16179519.html">记一次 redis stream 数据类型内存不释放问题</a>
  19 + *
  20 + * @author 芋道源码
  21 + */
  22 +@Slf4j
  23 +@AllArgsConstructor
  24 +public class RedisStreamMessageCleanupJob {
  25 +
  26 + private static final String LOCK_KEY = "redis:stream:message-cleanup:lock";
  27 +
  28 + /**
  29 + * 保留的消息数量,默认保留最近 10000 条消息
  30 + */
  31 + private static final long MAX_COUNT = 10000;
  32 +
  33 + private final List<AbstractRedisStreamMessageListener<?>> listeners;
  34 + private final RedisMQTemplate redisTemplate;
  35 + private final RedissonClient redissonClient;
  36 +
  37 + /**
  38 + * 每小时执行一次清理任务
  39 + */
  40 + @Scheduled(cron = "0 0 * * * ?")
  41 + public void cleanup() {
  42 + RLock lock = redissonClient.getLock(LOCK_KEY);
  43 + // 尝试加锁
  44 + if (lock.tryLock()) {
  45 + try {
  46 + execute();
  47 + } catch (Exception ex) {
  48 + log.error("[cleanup][执行异常]", ex);
  49 + } finally {
  50 + lock.unlock();
  51 + }
  52 + }
  53 + }
  54 +
  55 + /**
  56 + * 执行清理逻辑
  57 + */
  58 + private void execute() {
  59 + StreamOperations<String, Object, Object> ops = redisTemplate.getRedisTemplate().opsForStream();
  60 + listeners.forEach(listener -> {
  61 + try {
  62 + // 使用 XTRIM 命令清理消息,只保留最近的 MAX_LEN 条消息
  63 + Long trimCount = ops.trim(listener.getStreamKey(), MAX_COUNT, true);
  64 + if (trimCount != null && trimCount > 0) {
  65 + log.info("[execute][Stream({}) 清理消息数量({})]", listener.getStreamKey(), trimCount);
  66 + }
  67 + } catch (Exception ex) {
  68 + log.error("[execute][Stream({}) 清理异常]", listener.getStreamKey(), ex);
  69 + }
  70 + });
  71 + }
  72 +}
0 \ No newline at end of file 73 \ No newline at end of file
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/form/BpmTaskCandidateFormUserStrategy.java
1 package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.form; 1 package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.form;
2 2
3 -import cn.hutool.core.convert.Convert;  
4 import cn.hutool.core.lang.Assert; 3 import cn.hutool.core.lang.Assert;
  4 +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
5 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; 5 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
6 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.user.BpmTaskCandidateUserStrategy; 6 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.user.BpmTaskCandidateUserStrategy;
7 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; 7 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
@@ -33,7 +33,7 @@ public class BpmTaskCandidateFormUserStrategy implements BpmTaskCandidateStrateg @@ -33,7 +33,7 @@ public class BpmTaskCandidateFormUserStrategy implements BpmTaskCandidateStrateg
33 @Override 33 @Override
34 public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) { 34 public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) {
35 Object result = execution.getVariable(param); 35 Object result = execution.getVariable(param);
36 - return Convert.toSet(Long.class, result); 36 + return CollectionUtils.toLinkedHashSet(Long.class, result);
37 } 37 }
38 38
39 @Override 39 @Override
@@ -41,7 +41,7 @@ public class BpmTaskCandidateFormUserStrategy implements BpmTaskCandidateStrateg @@ -41,7 +41,7 @@ public class BpmTaskCandidateFormUserStrategy implements BpmTaskCandidateStrateg
41 String param, Long startUserId, String processDefinitionId, 41 String param, Long startUserId, String processDefinitionId,
42 Map<String, Object> processVariables) { 42 Map<String, Object> processVariables) {
43 Object result = processVariables == null ? null : processVariables.get(param); 43 Object result = processVariables == null ? null : processVariables.get(param);
44 - return Convert.toSet(Long.class, result); 44 + return CollectionUtils.toLinkedHashSet(Long.class, result);
45 } 45 }
46 46
47 } 47 }
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/other/BpmTaskCandidateExpressionStrategy.java
1 package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.other; 1 package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.other;
2 2
3 -import cn.hutool.core.convert.Convert; 3 +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
4 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy; 4 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
5 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum; 5 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
6 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils; 6 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
@@ -37,7 +37,7 @@ public class BpmTaskCandidateExpressionStrategy implements BpmTaskCandidateStrat @@ -37,7 +37,7 @@ public class BpmTaskCandidateExpressionStrategy implements BpmTaskCandidateStrat
37 @Override 37 @Override
38 public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) { 38 public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) {
39 Object result = FlowableUtils.getExpressionValue(execution, param); 39 Object result = FlowableUtils.getExpressionValue(execution, param);
40 - return Convert.toSet(Long.class, result); 40 + return CollectionUtils.toLinkedHashSet(Long.class, result);
41 } 41 }
42 42
43 @Override 43 @Override
@@ -46,7 +46,7 @@ public class BpmTaskCandidateExpressionStrategy implements BpmTaskCandidateStrat @@ -46,7 +46,7 @@ public class BpmTaskCandidateExpressionStrategy implements BpmTaskCandidateStrat
46 Map<String, Object> variables = processVariables == null ? new HashMap<>() : processVariables; 46 Map<String, Object> variables = processVariables == null ? new HashMap<>() : processVariables;
47 try { 47 try {
48 Object result = FlowableUtils.getExpressionValue(variables, param); 48 Object result = FlowableUtils.getExpressionValue(variables, param);
49 - return Convert.toSet(Long.class, result); 49 + return CollectionUtils.toLinkedHashSet(Long.class, result);
50 } catch (FlowableException ex) { 50 } catch (FlowableException ex) {
51 // 预测未运行的节点时候,表达式如果包含 execution 或者不存在的流程变量会抛异常, 51 // 预测未运行的节点时候,表达式如果包含 execution 或者不存在的流程变量会抛异常,
52 log.warn("[calculateUsersByActivity][表达式({}) 变量({}) 解析报错", param, variables, ex); 52 log.warn("[calculateUsersByActivity][表达式({}) 变量({}) 解析报错", param, variables, ex);
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java
@@ -6,7 +6,6 @@ import cn.hutool.core.lang.Assert; @@ -6,7 +6,6 @@ import cn.hutool.core.lang.Assert;
6 import cn.hutool.core.util.*; 6 import cn.hutool.core.util.*;
7 import cn.hutool.extra.spring.SpringUtil; 7 import cn.hutool.extra.spring.SpringUtil;
8 import cn.iocoder.yudao.framework.common.pojo.PageResult; 8 import cn.iocoder.yudao.framework.common.pojo.PageResult;
9 -import cn.iocoder.yudao.framework.common.util.collection.MapUtils;  
10 import cn.iocoder.yudao.framework.common.util.date.DateUtils; 9 import cn.iocoder.yudao.framework.common.util.date.DateUtils;
11 import cn.iocoder.yudao.framework.common.util.number.NumberUtils; 10 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
12 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; 11 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
@@ -882,6 +881,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -882,6 +881,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
882 return; 881 return;
883 } 882 }
884 runExecutionIds.add(task.getExecutionId()); 883 runExecutionIds.add(task.getExecutionId());
  884 +
885 // 判断是否分配给自己任务,因为会签任务,一个节点会有多个任务 885 // 判断是否分配给自己任务,因为会签任务,一个节点会有多个任务
886 if (isAssignUserTask(userId, task)) { // 情况一:自己的任务,进行 RETURN 标记 886 if (isAssignUserTask(userId, task)) { // 情况一:自己的任务,进行 RETURN 标记
887 // 2.1.1 添加评论 887 // 2.1.1 添加评论
yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/api/file/FileApi.java
1 package cn.iocoder.yudao.module.infra.api.file; 1 package cn.iocoder.yudao.module.infra.api.file;
2 2
  3 +import jakarta.validation.constraints.NotEmpty;
  4 +
3 /** 5 /**
4 * 文件 API 接口 6 * 文件 API 接口
5 * 7 *
@@ -14,28 +16,30 @@ public interface FileApi { @@ -14,28 +16,30 @@ public interface FileApi {
14 * @return 文件路径 16 * @return 文件路径
15 */ 17 */
16 default String createFile(byte[] content) { 18 default String createFile(byte[] content) {
17 - return createFile(null, null, content); 19 + return createFile(content, null, null, null);
18 } 20 }
19 21
20 /** 22 /**
21 * 保存文件,并返回文件的访问路径 23 * 保存文件,并返回文件的访问路径
22 * 24 *
23 - * @param path 文件路径  
24 * @param content 文件内容 25 * @param content 文件内容
  26 + * @param name 文件名称,允许空
25 * @return 文件路径 27 * @return 文件路径
26 */ 28 */
27 - default String createFile(String path, byte[] content) {  
28 - return createFile(null, path, content); 29 + default String createFile(byte[] content, String name) {
  30 + return createFile(content, name, null, null);
29 } 31 }
30 32
31 /** 33 /**
32 * 保存文件,并返回文件的访问路径 34 * 保存文件,并返回文件的访问路径
33 * 35 *
34 - * @param name 文件名称  
35 - * @param path 文件路径  
36 * @param content 文件内容 36 * @param content 文件内容
  37 + * @param name 文件名称,允许空
  38 + * @param directory 目录,允许空
  39 + * @param type 文件的 MIME 类型,允许空
37 * @return 文件路径 40 * @return 文件路径
38 */ 41 */
39 - String createFile(String name, String path, byte[] content); 42 + String createFile(@NotEmpty(message = "文件内容不能为空") byte[] content,
  43 + String name, String directory, String type);
40 44
41 } 45 }
yudao-module-infra/yudao-module-infra-biz/pom.xml
@@ -115,9 +115,10 @@ @@ -115,9 +115,10 @@
115 <groupId>com.jcraft</groupId> 115 <groupId>com.jcraft</groupId>
116 <artifactId>jsch</artifactId> <!-- 文件客户端:解决 sftp 连接 --> 116 <artifactId>jsch</artifactId> <!-- 文件客户端:解决 sftp 连接 -->
117 </dependency> 117 </dependency>
  118 + <!-- 文件客户端:解决阿里云、腾讯云、minio 等 S3 连接 -->
118 <dependency> 119 <dependency>
119 - <groupId>com.amazonaws</groupId>  
120 - <artifactId>aws-java-sdk-s3</artifactId><!-- 文件客户端:解决阿里云、腾讯云、minio 等 S3 连接 --> 120 + <groupId>software.amazon.awssdk</groupId>
  121 + <artifactId>s3</artifactId>
121 </dependency> 122 </dependency>
122 123
123 <dependency> 124 <dependency>
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/api/file/FileApiImpl.java
1 package cn.iocoder.yudao.module.infra.api.file; 1 package cn.iocoder.yudao.module.infra.api.file;
2 2
3 import cn.iocoder.yudao.module.infra.service.file.FileService; 3 import cn.iocoder.yudao.module.infra.service.file.FileService;
  4 +import jakarta.annotation.Resource;
4 import org.springframework.stereotype.Service; 5 import org.springframework.stereotype.Service;
5 import org.springframework.validation.annotation.Validated; 6 import org.springframework.validation.annotation.Validated;
6 7
7 -import jakarta.annotation.Resource;  
8 -  
9 /** 8 /**
10 * 文件 API 实现类 9 * 文件 API 实现类
11 * 10 *
@@ -19,8 +18,8 @@ public class FileApiImpl implements FileApi { @@ -19,8 +18,8 @@ public class FileApiImpl implements FileApi {
19 private FileService fileService; 18 private FileService fileService;
20 19
21 @Override 20 @Override
22 - public String createFile(String name, String path, byte[] content) {  
23 - return fileService.createFile(name, path, content); 21 + public String createFile(byte[] content, String name, String directory, String type) {
  22 + return fileService.createFile(content, name, directory, type);
24 } 23 }
25 24
26 } 25 }
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java
@@ -11,6 +11,7 @@ import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO; @@ -11,6 +11,7 @@ import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO;
11 import cn.iocoder.yudao.module.infra.service.file.FileService; 11 import cn.iocoder.yudao.module.infra.service.file.FileService;
12 import io.swagger.v3.oas.annotations.Operation; 12 import io.swagger.v3.oas.annotations.Operation;
13 import io.swagger.v3.oas.annotations.Parameter; 13 import io.swagger.v3.oas.annotations.Parameter;
  14 +import io.swagger.v3.oas.annotations.Parameters;
14 import io.swagger.v3.oas.annotations.tags.Tag; 15 import io.swagger.v3.oas.annotations.tags.Tag;
15 import jakarta.annotation.Resource; 16 import jakarta.annotation.Resource;
16 import jakarta.annotation.security.PermitAll; 17 import jakarta.annotation.security.PermitAll;
@@ -41,14 +42,21 @@ public class FileController { @@ -41,14 +42,21 @@ public class FileController {
41 @Operation(summary = "上传文件", description = "模式一:后端上传文件") 42 @Operation(summary = "上传文件", description = "模式一:后端上传文件")
42 public CommonResult<String> uploadFile(FileUploadReqVO uploadReqVO) throws Exception { 43 public CommonResult<String> uploadFile(FileUploadReqVO uploadReqVO) throws Exception {
43 MultipartFile file = uploadReqVO.getFile(); 44 MultipartFile file = uploadReqVO.getFile();
44 - String path = uploadReqVO.getPath();  
45 - return success(fileService.createFile(file.getOriginalFilename(), path, IoUtil.readBytes(file.getInputStream()))); 45 + byte[] content = IoUtil.readBytes(file.getInputStream());
  46 + return success(fileService.createFile(content, file.getOriginalFilename(),
  47 + uploadReqVO.getDirectory(), file.getContentType()));
46 } 48 }
47 49
48 @GetMapping("/presigned-url") 50 @GetMapping("/presigned-url")
49 @Operation(summary = "获取文件预签名地址", description = "模式二:前端上传文件:用于前端直接上传七牛、阿里云 OSS 等文件存储器") 51 @Operation(summary = "获取文件预签名地址", description = "模式二:前端上传文件:用于前端直接上传七牛、阿里云 OSS 等文件存储器")
50 - public CommonResult<FilePresignedUrlRespVO> getFilePresignedUrl(@RequestParam("path") String path) throws Exception {  
51 - return success(fileService.getFilePresignedUrl(path)); 52 + @Parameters({
  53 + @Parameter(name = "name", description = "文件名称", required = true),
  54 + @Parameter(name = "directory", description = "文件目录")
  55 + })
  56 + public CommonResult<FilePresignedUrlRespVO> getFilePresignedUrl(
  57 + @RequestParam("name") String name,
  58 + @RequestParam(value = "directory", required = false) String directory) {
  59 + return success(fileService.getFilePresignedUrl(name, directory));
52 } 60 }
53 61
54 @PostMapping("/create") 62 @PostMapping("/create")
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/FilePresignedUrlRespVO.java
@@ -14,7 +14,8 @@ public class FilePresignedUrlRespVO { @@ -14,7 +14,8 @@ public class FilePresignedUrlRespVO {
14 @Schema(description = "配置编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11") 14 @Schema(description = "配置编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11")
15 private Long configId; 15 private Long configId;
16 16
17 - @Schema(description = "文件上传 URL", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://s3.cn-south-1.qiniucs.com/ruoyi-vue-pro/758d3a5387507358c7236de4c8f96de1c7f5097ff6a7722b34772fb7b76b140f.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=3TvrJ70gl2Gt6IBe7_IZT1F6i_k0iMuRtyEv4EyS%2F20240217%2Fcn-south-1%2Fs3%2Faws4_request&X-Amz-Date=20240217T123222Z&X-Amz-Expires=600&X-Amz-SignedHeaders=host&X-Amz-Signature=a29f33770ab79bf523ccd4034d0752ac545f3c2a3b17baa1eb4e280cfdccfda5") 17 + @Schema(description = "文件上传 URL", requiredMode = Schema.RequiredMode.REQUIRED,
  18 + example = "https://s3.cn-south-1.qiniucs.com/ruoyi-vue-pro/758d3a5387507358c7236de4c8f96de1c7f5097ff6a7722b34772fb7b76b140f.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=3TvrJ70gl2Gt6IBe7_IZT1F6i_k0iMuRtyEv4EyS%2F20240217%2Fcn-south-1%2Fs3%2Faws4_request&X-Amz-Date=20240217T123222Z&X-Amz-Expires=600&X-Amz-SignedHeaders=host&X-Amz-Signature=a29f33770ab79bf523ccd4034d0752ac545f3c2a3b17baa1eb4e280cfdccfda5")
18 private String uploadUrl; 19 private String uploadUrl;
19 20
20 /** 21 /**
@@ -26,4 +27,12 @@ public class FilePresignedUrlRespVO { @@ -26,4 +27,12 @@ public class FilePresignedUrlRespVO {
26 example = "https://test.yudao.iocoder.cn/758d3a5387507358c7236de4c8f96de1c7f5097ff6a7722b34772fb7b76b140f.png") 27 example = "https://test.yudao.iocoder.cn/758d3a5387507358c7236de4c8f96de1c7f5097ff6a7722b34772fb7b76b140f.png")
27 private String url; 28 private String url;
28 29
  30 + /**
  31 + * 为什么要返回 path 字段?
  32 + *
  33 + * 前端上传完文件后,需要调用 createFile 记录下 path 路径
  34 + */
  35 + @Schema(description = "文件路径", requiredMode = Schema.RequiredMode.REQUIRED, example = "xxx.png")
  36 + private String path;
  37 +
29 } 38 }
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/vo/file/FileUploadReqVO.java
@@ -14,7 +14,7 @@ public class FileUploadReqVO { @@ -14,7 +14,7 @@ public class FileUploadReqVO {
14 @NotNull(message = "文件附件不能为空") 14 @NotNull(message = "文件附件不能为空")
15 private MultipartFile file; 15 private MultipartFile file;
16 16
17 - @Schema(description = "文件附件", example = "yudaoyuanma.png")  
18 - private String path; 17 + @Schema(description = "文件目录", example = "XXX/YYY")
  18 + private String directory;
19 19
20 } 20 }
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/app/file/AppFileController.java
@@ -7,6 +7,8 @@ import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePresigned @@ -7,6 +7,8 @@ import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePresigned
7 import cn.iocoder.yudao.module.infra.controller.app.file.vo.AppFileUploadReqVO; 7 import cn.iocoder.yudao.module.infra.controller.app.file.vo.AppFileUploadReqVO;
8 import cn.iocoder.yudao.module.infra.service.file.FileService; 8 import cn.iocoder.yudao.module.infra.service.file.FileService;
9 import io.swagger.v3.oas.annotations.Operation; 9 import io.swagger.v3.oas.annotations.Operation;
  10 +import io.swagger.v3.oas.annotations.Parameter;
  11 +import io.swagger.v3.oas.annotations.Parameters;
10 import io.swagger.v3.oas.annotations.tags.Tag; 12 import io.swagger.v3.oas.annotations.tags.Tag;
11 import jakarta.annotation.Resource; 13 import jakarta.annotation.Resource;
12 import jakarta.annotation.security.PermitAll; 14 import jakarta.annotation.security.PermitAll;
@@ -33,15 +35,21 @@ public class AppFileController { @@ -33,15 +35,21 @@ public class AppFileController {
33 @PermitAll 35 @PermitAll
34 public CommonResult<String> uploadFile(AppFileUploadReqVO uploadReqVO) throws Exception { 36 public CommonResult<String> uploadFile(AppFileUploadReqVO uploadReqVO) throws Exception {
35 MultipartFile file = uploadReqVO.getFile(); 37 MultipartFile file = uploadReqVO.getFile();
36 - String path = uploadReqVO.getPath();  
37 - return success(fileService.createFile(file.getOriginalFilename(), path, IoUtil.readBytes(file.getInputStream()))); 38 + byte[] content = IoUtil.readBytes(file.getInputStream());
  39 + return success(fileService.createFile(content, file.getOriginalFilename(),
  40 + uploadReqVO.getDirectory(), file.getContentType()));
38 } 41 }
39 42
40 @GetMapping("/presigned-url") 43 @GetMapping("/presigned-url")
41 @Operation(summary = "获取文件预签名地址", description = "模式二:前端上传文件:用于前端直接上传七牛、阿里云 OSS 等文件存储器") 44 @Operation(summary = "获取文件预签名地址", description = "模式二:前端上传文件:用于前端直接上传七牛、阿里云 OSS 等文件存储器")
42 - @PermitAll  
43 - public CommonResult<FilePresignedUrlRespVO> getFilePresignedUrl(@RequestParam("path") String path) throws Exception {  
44 - return success(fileService.getFilePresignedUrl(path)); 45 + @Parameters({
  46 + @Parameter(name = "name", description = "文件名称", required = true),
  47 + @Parameter(name = "directory", description = "文件目录")
  48 + })
  49 + public CommonResult<FilePresignedUrlRespVO> getFilePresignedUrl(
  50 + @RequestParam("name") String name,
  51 + @RequestParam(value = "directory", required = false) String directory) {
  52 + return success(fileService.getFilePresignedUrl(name, directory));
45 } 53 }
46 54
47 @PostMapping("/create") 55 @PostMapping("/create")
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/app/file/vo/AppFileUploadReqVO.java
@@ -14,7 +14,7 @@ public class AppFileUploadReqVO { @@ -14,7 +14,7 @@ public class AppFileUploadReqVO {
14 @NotNull(message = "文件附件不能为空") 14 @NotNull(message = "文件附件不能为空")
15 private MultipartFile file; 15 private MultipartFile file;
16 16
17 - @Schema(description = "文件附件", example = "yudaoyuanma.png")  
18 - private String path; 17 + @Schema(description = "文件目录", example = "XXX/YYY")
  18 + private String directory;
19 19
20 } 20 }
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/codegen/CodegenTableMapper.java
@@ -23,8 +23,7 @@ public interface CodegenTableMapper extends BaseMapperX&lt;CodegenTableDO&gt; { @@ -23,8 +23,7 @@ public interface CodegenTableMapper extends BaseMapperX&lt;CodegenTableDO&gt; {
23 .likeIfPresent(CodegenTableDO::getTableComment, pageReqVO.getTableComment()) 23 .likeIfPresent(CodegenTableDO::getTableComment, pageReqVO.getTableComment())
24 .likeIfPresent(CodegenTableDO::getClassName, pageReqVO.getClassName()) 24 .likeIfPresent(CodegenTableDO::getClassName, pageReqVO.getClassName())
25 .betweenIfPresent(CodegenTableDO::getCreateTime, pageReqVO.getCreateTime()) 25 .betweenIfPresent(CodegenTableDO::getCreateTime, pageReqVO.getCreateTime())
26 - .orderByDesc(CodegenTableDO::getUpdateTime)  
27 - ); 26 + .orderByDesc(CodegenTableDO::getUpdateTime));
28 } 27 }
29 28
30 default List<CodegenTableDO> selectListByDataSourceConfigId(Long dataSourceConfigId) { 29 default List<CodegenTableDO> selectListByDataSourceConfigId(Long dataSourceConfigId) {
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/config/ConfigMapper.java
@@ -19,7 +19,8 @@ public interface ConfigMapper extends BaseMapperX&lt;ConfigDO&gt; { @@ -19,7 +19,8 @@ public interface ConfigMapper extends BaseMapperX&lt;ConfigDO&gt; {
19 .likeIfPresent(ConfigDO::getName, reqVO.getName()) 19 .likeIfPresent(ConfigDO::getName, reqVO.getName())
20 .likeIfPresent(ConfigDO::getConfigKey, reqVO.getKey()) 20 .likeIfPresent(ConfigDO::getConfigKey, reqVO.getKey())
21 .eqIfPresent(ConfigDO::getType, reqVO.getType()) 21 .eqIfPresent(ConfigDO::getType, reqVO.getType())
22 - .betweenIfPresent(ConfigDO::getCreateTime, reqVO.getCreateTime())); 22 + .betweenIfPresent(ConfigDO::getCreateTime, reqVO.getCreateTime())
  23 + .orderByDesc(ConfigDO::getId));
23 } 24 }
24 25
25 } 26 }
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/mysql/job/JobMapper.java
@@ -24,7 +24,7 @@ public interface JobMapper extends BaseMapperX&lt;JobDO&gt; { @@ -24,7 +24,7 @@ public interface JobMapper extends BaseMapperX&lt;JobDO&gt; {
24 .likeIfPresent(JobDO::getName, reqVO.getName()) 24 .likeIfPresent(JobDO::getName, reqVO.getName())
25 .eqIfPresent(JobDO::getStatus, reqVO.getStatus()) 25 .eqIfPresent(JobDO::getStatus, reqVO.getStatus())
26 .likeIfPresent(JobDO::getHandlerName, reqVO.getHandlerName()) 26 .likeIfPresent(JobDO::getHandlerName, reqVO.getHandlerName())
27 - ); 27 + .orderByDesc(JobDO::getId));
28 } 28 }
29 29
30 } 30 }
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/enums/codegen/CodegenFrontTypeEnum.java
@@ -13,10 +13,14 @@ import lombok.Getter; @@ -13,10 +13,14 @@ import lombok.Getter;
13 public enum CodegenFrontTypeEnum { 13 public enum CodegenFrontTypeEnum {
14 14
15 VUE2_ELEMENT_UI(10), // Vue2 Element UI 标准模版 15 VUE2_ELEMENT_UI(10), // Vue2 Element UI 标准模版
  16 +
16 VUE3_ELEMENT_PLUS(20), // Vue3 Element Plus 标准模版 17 VUE3_ELEMENT_PLUS(20), // Vue3 Element Plus 标准模版
  18 +
17 VUE3_VBEN2_ANTD_SCHEMA(30), // Vue3 VBEN2 + ANTD + Schema 模版 19 VUE3_VBEN2_ANTD_SCHEMA(30), // Vue3 VBEN2 + ANTD + Schema 模版
  20 +
18 VUE3_VBEN5_ANTD_SCHEMA(40), // Vue3 VBEN5 + ANTD + schema 模版 21 VUE3_VBEN5_ANTD_SCHEMA(40), // Vue3 VBEN5 + ANTD + schema 模版
19 - VUE3_VBEN5_ANTD(50), // Vue3 VBEN5 + ANTD 模版 22 +
  23 + VUE3_VBEN5_ANTD_GENERAL(41), // Vue3 VBEN5 + ANTD 标准模版
20 ; 24 ;
21 25
22 /** 26 /**
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/ftp/FtpFileClient.java
@@ -26,12 +26,6 @@ public class FtpFileClient extends AbstractFileClient&lt;FtpFileClientConfig&gt; { @@ -26,12 +26,6 @@ public class FtpFileClient extends AbstractFileClient&lt;FtpFileClientConfig&gt; {
26 26
27 @Override 27 @Override
28 protected void doInit() { 28 protected void doInit() {
29 - // 把配置的 \ 替换成 /, 如果路径配置 \a\test, 替换成 /a/test, 替换方法已经处理 null 情况  
30 - config.setBasePath(StrUtil.replace(config.getBasePath(), StrUtil.BACKSLASH, StrUtil.SLASH));  
31 - // ftp的路径是 / 结尾  
32 - if (!config.getBasePath().endsWith(StrUtil.SLASH)) {  
33 - config.setBasePath(config.getBasePath() + StrUtil.SLASH);  
34 - }  
35 // 初始化 Ftp 对象 29 // 初始化 Ftp 对象
36 this.ftp = new Ftp(config.getHost(), config.getPort(), config.getUsername(), config.getPassword(), 30 this.ftp = new Ftp(config.getHost(), config.getPort(), config.getUsername(), config.getPassword(),
37 CharsetUtil.CHARSET_UTF_8, null, null, FtpMode.valueOf(config.getMode())); 31 CharsetUtil.CHARSET_UTF_8, null, null, FtpMode.valueOf(config.getMode()));
@@ -43,8 +37,8 @@ public class FtpFileClient extends AbstractFileClient&lt;FtpFileClientConfig&gt; { @@ -43,8 +37,8 @@ public class FtpFileClient extends AbstractFileClient&lt;FtpFileClientConfig&gt; {
43 String filePath = getFilePath(path); 37 String filePath = getFilePath(path);
44 String fileName = FileUtil.getName(filePath); 38 String fileName = FileUtil.getName(filePath);
45 String dir = StrUtil.removeSuffix(filePath, fileName); 39 String dir = StrUtil.removeSuffix(filePath, fileName);
46 - ftp.reconnectIfTimeout();  
47 - boolean success = ftp.upload(dir, fileName, new ByteArrayInputStream(content)); 40 + reconnectIfTimeout();
  41 + boolean success = ftp.upload(dir, fileName, new ByteArrayInputStream(content)); // 不需要主动创建目录,ftp 内部已经处理(见源码)
48 if (!success) { 42 if (!success) {
49 throw new FtpException(StrUtil.format("上传文件到目标目录 ({}) 失败", filePath)); 43 throw new FtpException(StrUtil.format("上传文件到目标目录 ({}) 失败", filePath));
50 } 44 }
@@ -55,7 +49,7 @@ public class FtpFileClient extends AbstractFileClient&lt;FtpFileClientConfig&gt; { @@ -55,7 +49,7 @@ public class FtpFileClient extends AbstractFileClient&lt;FtpFileClientConfig&gt; {
55 @Override 49 @Override
56 public void delete(String path) { 50 public void delete(String path) {
57 String filePath = getFilePath(path); 51 String filePath = getFilePath(path);
58 - ftp.reconnectIfTimeout(); 52 + reconnectIfTimeout();
59 ftp.delFile(filePath); 53 ftp.delFile(filePath);
60 } 54 }
61 55
@@ -65,13 +59,17 @@ public class FtpFileClient extends AbstractFileClient&lt;FtpFileClientConfig&gt; { @@ -65,13 +59,17 @@ public class FtpFileClient extends AbstractFileClient&lt;FtpFileClientConfig&gt; {
65 String fileName = FileUtil.getName(filePath); 59 String fileName = FileUtil.getName(filePath);
66 String dir = StrUtil.removeSuffix(filePath, fileName); 60 String dir = StrUtil.removeSuffix(filePath, fileName);
67 ByteArrayOutputStream out = new ByteArrayOutputStream(); 61 ByteArrayOutputStream out = new ByteArrayOutputStream();
68 - ftp.reconnectIfTimeout(); 62 + reconnectIfTimeout();
69 ftp.download(dir, fileName, out); 63 ftp.download(dir, fileName, out);
70 return out.toByteArray(); 64 return out.toByteArray();
71 } 65 }
72 66
73 private String getFilePath(String path) { 67 private String getFilePath(String path) {
74 - return config.getBasePath() + path; 68 + return config.getBasePath() + StrUtil.SLASH + path;
  69 + }
  70 +
  71 + private synchronized void reconnectIfTimeout() {
  72 + ftp.reconnectIfTimeout();
75 } 73 }
76 74
77 } 75 }
78 \ No newline at end of file 76 \ No newline at end of file
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/local/LocalFileClient.java
@@ -18,10 +18,6 @@ public class LocalFileClient extends AbstractFileClient&lt;LocalFileClientConfig&gt; { @@ -18,10 +18,6 @@ public class LocalFileClient extends AbstractFileClient&lt;LocalFileClientConfig&gt; {
18 18
19 @Override 19 @Override
20 protected void doInit() { 20 protected void doInit() {
21 - // 补全风格。例如说 Linux 是 /,Windows 是 \  
22 - if (!config.getBasePath().endsWith(File.separator)) {  
23 - config.setBasePath(config.getBasePath() + File.separator);  
24 - }  
25 } 21 }
26 22
27 @Override 23 @Override
@@ -46,7 +42,7 @@ public class LocalFileClient extends AbstractFileClient&lt;LocalFileClientConfig&gt; { @@ -46,7 +42,7 @@ public class LocalFileClient extends AbstractFileClient&lt;LocalFileClientConfig&gt; {
46 } 42 }
47 43
48 private String getFilePath(String path) { 44 private String getFilePath(String path) {
49 - return config.getBasePath() + path; 45 + return config.getBasePath() + File.separator + path;
50 } 46 }
51 47
52 } 48 }
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/s3/S3FileClient.java
@@ -4,29 +4,31 @@ import cn.hutool.core.io.IoUtil; @@ -4,29 +4,31 @@ import cn.hutool.core.io.IoUtil;
4 import cn.hutool.core.util.StrUtil; 4 import cn.hutool.core.util.StrUtil;
5 import cn.hutool.http.HttpUtil; 5 import cn.hutool.http.HttpUtil;
6 import cn.iocoder.yudao.module.infra.framework.file.core.client.AbstractFileClient; 6 import cn.iocoder.yudao.module.infra.framework.file.core.client.AbstractFileClient;
7 -import com.amazonaws.HttpMethod;  
8 -import com.amazonaws.auth.AWSStaticCredentialsProvider;  
9 -import com.amazonaws.auth.BasicAWSCredentials;  
10 -import com.amazonaws.client.builder.AwsClientBuilder;  
11 -import com.amazonaws.services.s3.AmazonS3Client;  
12 -import com.amazonaws.services.s3.AmazonS3ClientBuilder;  
13 -import com.amazonaws.services.s3.model.ObjectMetadata;  
14 -import com.amazonaws.services.s3.model.S3Object; 7 +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
  8 +import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
  9 +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
  10 +import software.amazon.awssdk.core.sync.RequestBody;
  11 +import software.amazon.awssdk.regions.Region;
  12 +import software.amazon.awssdk.services.s3.S3Client;
  13 +import software.amazon.awssdk.services.s3.S3Configuration;
  14 +import software.amazon.awssdk.services.s3.model.DeleteObjectRequest;
  15 +import software.amazon.awssdk.services.s3.model.GetObjectRequest;
  16 +import software.amazon.awssdk.services.s3.model.PutObjectRequest;
  17 +import software.amazon.awssdk.services.s3.presigner.S3Presigner;
  18 +import software.amazon.awssdk.services.s3.presigner.model.PutObjectPresignRequest;
15 19
16 -import java.io.ByteArrayInputStream;  
17 -import java.util.Date;  
18 -import java.util.concurrent.TimeUnit; 20 +import java.net.URI;
  21 +import java.time.Duration;
19 22
20 /** 23 /**
21 * 基于 S3 协议的文件客户端,实现 MinIO、阿里云、腾讯云、七牛云、华为云等云服务 24 * 基于 S3 协议的文件客户端,实现 MinIO、阿里云、腾讯云、七牛云、华为云等云服务
22 - * <p>  
23 - * S3 协议的客户端,采用亚马逊提供的 software.amazon.awssdk.s3 库  
24 * 25 *
25 * @author 芋道源码 26 * @author 芋道源码
26 */ 27 */
27 public class S3FileClient extends AbstractFileClient<S3FileClientConfig> { 28 public class S3FileClient extends AbstractFileClient<S3FileClientConfig> {
28 29
29 - private AmazonS3Client client; 30 + private S3Client client;
  31 + private S3Presigner presigner;
30 32
31 public S3FileClient(Long id, S3FileClientConfig config) { 33 public S3FileClient(Long id, S3FileClientConfig config) {
32 super(id, config); 34 super(id, config);
@@ -38,31 +40,80 @@ public class S3FileClient extends AbstractFileClient&lt;S3FileClientConfig&gt; { @@ -38,31 +40,80 @@ public class S3FileClient extends AbstractFileClient&lt;S3FileClientConfig&gt; {
38 if (StrUtil.isEmpty(config.getDomain())) { 40 if (StrUtil.isEmpty(config.getDomain())) {
39 config.setDomain(buildDomain()); 41 config.setDomain(buildDomain());
40 } 42 }
41 - // 初始化客户端  
42 - client = (AmazonS3Client)AmazonS3ClientBuilder.standard()  
43 - .withCredentials(buildCredentials())  
44 - .withEndpointConfiguration(buildEndpointConfiguration()) 43 + // 初始化 S3 客户端
  44 + Region region = Region.of("us-east-1"); // 必须填,但填什么都行,常见的值有 "us-east-1",不填会报错
  45 + AwsCredentialsProvider credentialsProvider = StaticCredentialsProvider.create(
  46 + AwsBasicCredentials.create(config.getAccessKey(), config.getAccessSecret()));
  47 + URI endpoint = URI.create(buildEndpoint());
  48 + S3Configuration serviceConfiguration = S3Configuration.builder() // Path-style 访问
  49 + .pathStyleAccessEnabled(Boolean.TRUE.equals(config.getEnablePathStyleAccess()))
  50 + .chunkedEncodingEnabled(false) // 禁用分块编码,参见 https://t.zsxq.com/kBy57
  51 + .build();
  52 + client = S3Client.builder()
  53 + .credentialsProvider(credentialsProvider)
  54 + .region(region)
  55 + .endpointOverride(endpoint)
  56 + .serviceConfiguration(serviceConfiguration)
  57 + .build();
  58 + presigner = S3Presigner.builder()
  59 + .credentialsProvider(credentialsProvider)
  60 + .region(region)
  61 + .endpointOverride(endpoint)
  62 + .serviceConfiguration(serviceConfiguration)
45 .build(); 63 .build();
46 } 64 }
47 65
48 - /**  
49 - * 基于 config 秘钥,构建 S3 客户端的认证信息  
50 - *  
51 - * @return S3 客户端的认证信息  
52 - */  
53 - private AWSStaticCredentialsProvider buildCredentials() {  
54 - return new AWSStaticCredentialsProvider(  
55 - new BasicAWSCredentials(config.getAccessKey(), config.getAccessSecret())); 66 + @Override
  67 + public String upload(byte[] content, String path, String type) {
  68 + // 构造 PutObjectRequest
  69 + PutObjectRequest putRequest = PutObjectRequest.builder()
  70 + .bucket(config.getBucket())
  71 + .key(path)
  72 + .contentType(type)
  73 + .contentLength((long) content.length)
  74 + .build();
  75 + // 上传文件
  76 + client.putObject(putRequest, RequestBody.fromBytes(content));
  77 + // 拼接返回路径
  78 + return config.getDomain() + "/" + path;
  79 + }
  80 +
  81 + @Override
  82 + public void delete(String path) {
  83 + DeleteObjectRequest deleteRequest = DeleteObjectRequest.builder()
  84 + .bucket(config.getBucket())
  85 + .key(path)
  86 + .build();
  87 + client.deleteObject(deleteRequest);
  88 + }
  89 +
  90 + @Override
  91 + public byte[] getContent(String path) {
  92 + GetObjectRequest getRequest = GetObjectRequest.builder()
  93 + .bucket(config.getBucket())
  94 + .key(path)
  95 + .build();
  96 + return IoUtil.readBytes(client.getObject(getRequest));
  97 + }
  98 +
  99 + @Override
  100 + public FilePresignedUrlRespDTO getPresignedObjectUrl(String path) {
  101 + Duration expiration = Duration.ofHours(24);
  102 + return new FilePresignedUrlRespDTO(getPresignedUrl(path, expiration), config.getDomain() + "/" + path);
56 } 103 }
57 104
58 /** 105 /**
59 - * 构建 S3 客户端的 Endpoint 配置,包括 region、endpoint 106 + * 生成动态的预签名上传 URL
60 * 107 *
61 - * @return S3 客户端的 EndpointConfiguration 配置 108 + * @param path 相对路径
  109 + * @param expiration 过期时间
  110 + * @return 生成的上传 URL
62 */ 111 */
63 - private AwsClientBuilder.EndpointConfiguration buildEndpointConfiguration() {  
64 - return new AwsClientBuilder.EndpointConfiguration(config.getEndpoint(),  
65 - null); // 无需设置 region 112 + private String getPresignedUrl(String path, Duration expiration) {
  113 + return presigner.presignPutObject(PutObjectPresignRequest.builder()
  114 + .signatureDuration(expiration)
  115 + .putObjectRequest(b -> b.bucket(config.getBucket()).key(path))
  116 + .build()).url().toString();
66 } 117 }
67 118
68 /** 119 /**
@@ -79,40 +130,17 @@ public class S3FileClient extends AbstractFileClient&lt;S3FileClientConfig&gt; { @@ -79,40 +130,17 @@ public class S3FileClient extends AbstractFileClient&lt;S3FileClientConfig&gt; {
79 return StrUtil.format("https://{}.{}", config.getBucket(), config.getEndpoint()); 130 return StrUtil.format("https://{}.{}", config.getBucket(), config.getEndpoint());
80 } 131 }
81 132
82 - @Override  
83 - public String upload(byte[] content, String path, String type) throws Exception {  
84 - // 元数据,主要用于设置文件类型  
85 - ObjectMetadata objectMetadata = new ObjectMetadata();  
86 - objectMetadata.setContentType(type);  
87 - objectMetadata.setContentLength(content.length); // 如果不设置,会有 “ No content length specified for stream data” 警告日志  
88 - // 执行上传  
89 - client.putObject(config.getBucket(),  
90 - path, // 相对路径  
91 - new ByteArrayInputStream(content), // 文件内容  
92 - objectMetadata);  
93 -  
94 - // 拼接返回路径  
95 - return config.getDomain() + "/" + path;  
96 - }  
97 -  
98 - @Override  
99 - public void delete(String path) throws Exception {  
100 - client.deleteObject(config.getBucket(), path);  
101 - }  
102 -  
103 - @Override  
104 - public byte[] getContent(String path) throws Exception {  
105 - S3Object tempS3Object = client.getObject(config.getBucket(), path);  
106 - return IoUtil.readBytes(tempS3Object.getObjectContent());  
107 - }  
108 -  
109 - @Override  
110 - public FilePresignedUrlRespDTO getPresignedObjectUrl(String path) throws Exception {  
111 - // 设定过期时间为 10 分钟。取值范围:1 秒 ~ 7 天  
112 - Date expiration = new Date(System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(10));  
113 - // 生成上传 URL  
114 - String uploadUrl = String.valueOf(client.generatePresignedUrl(config.getBucket(), path, expiration , HttpMethod.PUT));  
115 - return new FilePresignedUrlRespDTO(uploadUrl, config.getDomain() + "/" + path); 133 + /**
  134 + * 节点地址补全协议头
  135 + *
  136 + * @return 节点地址
  137 + */
  138 + private String buildEndpoint() {
  139 + // 如果已经是 http 或者 https,则不进行拼接
  140 + if (HttpUtil.isHttp(config.getEndpoint()) || HttpUtil.isHttps(config.getEndpoint())) {
  141 + return config.getEndpoint();
  142 + }
  143 + return StrUtil.format("https://{}", config.getEndpoint());
116 } 144 }
117 145
118 } 146 }
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/s3/S3FileClientConfig.java
@@ -67,6 +67,12 @@ public class S3FileClientConfig implements FileClientConfig { @@ -67,6 +67,12 @@ public class S3FileClientConfig implements FileClientConfig {
67 @NotNull(message = "accessSecret 不能为空") 67 @NotNull(message = "accessSecret 不能为空")
68 private String accessSecret; 68 private String accessSecret;
69 69
  70 + /**
  71 + * 是否启用 PathStyle 访问
  72 + */
  73 + @NotNull(message = "enablePathStyleAccess 不能为空")
  74 + private Boolean enablePathStyleAccess;
  75 +
70 @SuppressWarnings("RedundantIfStatement") 76 @SuppressWarnings("RedundantIfStatement")
71 @AssertTrue(message = "domain 不能为空") 77 @AssertTrue(message = "domain 不能为空")
72 @JsonIgnore 78 @JsonIgnore
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/sftp/SftpFileClient.java
@@ -22,10 +22,6 @@ public class SftpFileClient extends AbstractFileClient&lt;SftpFileClientConfig&gt; { @@ -22,10 +22,6 @@ public class SftpFileClient extends AbstractFileClient&lt;SftpFileClientConfig&gt; {
22 22
23 @Override 23 @Override
24 protected void doInit() { 24 protected void doInit() {
25 - // 补全风格。例如说 Linux 是 /,Windows 是 \  
26 - if (!config.getBasePath().endsWith(File.separator)) {  
27 - config.setBasePath(config.getBasePath() + File.separator);  
28 - }  
29 // 初始化 Ftp 对象 25 // 初始化 Ftp 对象
30 this.sftp = new Sftp(config.getHost(), config.getPort(), config.getUsername(), config.getPassword()); 26 this.sftp = new Sftp(config.getHost(), config.getPort(), config.getUsername(), config.getPassword());
31 } 27 }
@@ -35,6 +31,8 @@ public class SftpFileClient extends AbstractFileClient&lt;SftpFileClientConfig&gt; { @@ -35,6 +31,8 @@ public class SftpFileClient extends AbstractFileClient&lt;SftpFileClientConfig&gt; {
35 // 执行写入 31 // 执行写入
36 String filePath = getFilePath(path); 32 String filePath = getFilePath(path);
37 File file = FileUtils.createTempFile(content); 33 File file = FileUtils.createTempFile(content);
  34 + reconnectIfTimeout();
  35 + sftp.mkDirs(FileUtil.getParent(filePath, 1)); // 需要创建父目录,不然会报错
38 sftp.upload(filePath, file); 36 sftp.upload(filePath, file);
39 // 拼接返回路径 37 // 拼接返回路径
40 return super.formatFileUrl(config.getDomain(), path); 38 return super.formatFileUrl(config.getDomain(), path);
@@ -43,6 +41,7 @@ public class SftpFileClient extends AbstractFileClient&lt;SftpFileClientConfig&gt; { @@ -43,6 +41,7 @@ public class SftpFileClient extends AbstractFileClient&lt;SftpFileClientConfig&gt; {
43 @Override 41 @Override
44 public void delete(String path) { 42 public void delete(String path) {
45 String filePath = getFilePath(path); 43 String filePath = getFilePath(path);
  44 + reconnectIfTimeout();
46 sftp.delFile(filePath); 45 sftp.delFile(filePath);
47 } 46 }
48 47
@@ -50,12 +49,17 @@ public class SftpFileClient extends AbstractFileClient&lt;SftpFileClientConfig&gt; { @@ -50,12 +49,17 @@ public class SftpFileClient extends AbstractFileClient&lt;SftpFileClientConfig&gt; {
50 public byte[] getContent(String path) { 49 public byte[] getContent(String path) {
51 String filePath = getFilePath(path); 50 String filePath = getFilePath(path);
52 File destFile = FileUtils.createTempFile(); 51 File destFile = FileUtils.createTempFile();
  52 + reconnectIfTimeout();
53 sftp.download(filePath, destFile); 53 sftp.download(filePath, destFile);
54 return FileUtil.readBytes(destFile); 54 return FileUtil.readBytes(destFile);
55 } 55 }
56 56
57 private String getFilePath(String path) { 57 private String getFilePath(String path) {
58 - return config.getBasePath() + path; 58 + return config.getBasePath() + File.separator + path;
  59 + }
  60 +
  61 + private synchronized void reconnectIfTimeout() {
  62 + sftp.reconnectIfTimeout();
59 } 63 }
60 64
61 } 65 }
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/utils/FileTypeUtils.java
@@ -6,7 +6,10 @@ import cn.iocoder.yudao.framework.common.util.http.HttpUtils; @@ -6,7 +6,10 @@ import cn.iocoder.yudao.framework.common.util.http.HttpUtils;
6 import com.alibaba.ttl.TransmittableThreadLocal; 6 import com.alibaba.ttl.TransmittableThreadLocal;
7 import jakarta.servlet.http.HttpServletResponse; 7 import jakarta.servlet.http.HttpServletResponse;
8 import lombok.SneakyThrows; 8 import lombok.SneakyThrows;
  9 +import lombok.extern.slf4j.Slf4j;
9 import org.apache.tika.Tika; 10 import org.apache.tika.Tika;
  11 +import org.apache.tika.mime.MimeTypeException;
  12 +import org.apache.tika.mime.MimeTypes;
10 13
11 import java.io.IOException; 14 import java.io.IOException;
12 15
@@ -15,12 +18,13 @@ import java.io.IOException; @@ -15,12 +18,13 @@ import java.io.IOException;
15 * 18 *
16 * @author 芋道源码 19 * @author 芋道源码
17 */ 20 */
  21 +@Slf4j
18 public class FileTypeUtils { 22 public class FileTypeUtils {
19 23
20 private static final ThreadLocal<Tika> TIKA = TransmittableThreadLocal.withInitial(Tika::new); 24 private static final ThreadLocal<Tika> TIKA = TransmittableThreadLocal.withInitial(Tika::new);
21 25
22 /** 26 /**
23 - * 获得文件的 mineType,对于doc,jar等文件会有误差 27 + * 获得文件的 mineType,对于 doc,jar 等文件会有误差
24 * 28 *
25 * @param data 文件内容 29 * @param data 文件内容
26 * @return mineType 无法识别时会返回“application/octet-stream” 30 * @return mineType 无法识别时会返回“application/octet-stream”
@@ -31,7 +35,7 @@ public class FileTypeUtils { @@ -31,7 +35,7 @@ public class FileTypeUtils {
31 } 35 }
32 36
33 /** 37 /**
34 - * 已知文件名,获取文件类型,在某些情况下比通过字节数组准确,例如使用jar文件时,通过名字更为准确 38 + * 已知文件名,获取文件类型,在某些情况下比通过字节数组准确,例如使用 jar 文件时,通过名字更为准确
35 * 39 *
36 * @param name 文件名 40 * @param name 文件名
37 * @return mineType 无法识别时会返回“application/octet-stream” 41 * @return mineType 无法识别时会返回“application/octet-stream”
@@ -52,6 +56,23 @@ public class FileTypeUtils { @@ -52,6 +56,23 @@ public class FileTypeUtils {
52 } 56 }
53 57
54 /** 58 /**
  59 + * 根据 mineType 获得文件后缀
  60 + *
  61 + * 注意:如果获取不到,或者发生异常,都返回 null
  62 + *
  63 + * @param mineType 类型
  64 + * @return 后缀,例如说 .pdf
  65 + */
  66 + public static String getExtension(String mineType) {
  67 + try {
  68 + return MimeTypes.getDefaultMimeTypes().forName(mineType).getExtension();
  69 + } catch (MimeTypeException e) {
  70 + log.warn("[getExtension][获取文件后缀({}) 失败]", mineType, e);
  71 + return null;
  72 + }
  73 + }
  74 +
  75 + /**
55 * 返回附件 76 * 返回附件
56 * 77 *
57 * @param response 响应 78 * @param response 响应
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngine.java
@@ -164,21 +164,21 @@ public class CodegenEngine { @@ -164,21 +164,21 @@ public class CodegenEngine {
164 .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_SCHEMA.getType(), vue3Vben5AntdSchemaTemplatePath("views/modules/list_sub_erp.vue"), // 特殊:主子表专属逻辑 164 .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_SCHEMA.getType(), vue3Vben5AntdSchemaTemplatePath("views/modules/list_sub_erp.vue"), // 特殊:主子表专属逻辑
165 vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/${subSimpleClassName_strikeCase}-list.vue")) 165 vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/${subSimpleClassName_strikeCase}-list.vue"))
166 // VUE3_VBEN5_ANTD 166 // VUE3_VBEN5_ANTD
167 - .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD.getType(), vue3Vben5AntdGeneralTemplatePath("views/index.vue"), 167 + .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_GENERAL.getType(), vue3Vben5AntdGeneralTemplatePath("views/index.vue"),
168 vue3FilePath("views/${table.moduleName}/${table.businessName}/index.vue")) 168 vue3FilePath("views/${table.moduleName}/${table.businessName}/index.vue"))
169 - .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD.getType(), vue3Vben5AntdGeneralTemplatePath("views/form.vue"), 169 + .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_GENERAL.getType(), vue3Vben5AntdGeneralTemplatePath("views/form.vue"),
170 vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/form.vue")) 170 vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/form.vue"))
171 - .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD.getType(), vue3Vben5AntdGeneralTemplatePath("api/api.ts"), 171 + .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_GENERAL.getType(), vue3Vben5AntdGeneralTemplatePath("api/api.ts"),
172 vue3FilePath("api/${table.moduleName}/${table.businessName}/index.ts")) 172 vue3FilePath("api/${table.moduleName}/${table.businessName}/index.ts"))
173 - .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD.getType(), vue3Vben5AntdGeneralTemplatePath("views/modules/form_sub_normal.vue"), // 特殊:主子表专属逻辑 173 + .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_GENERAL.getType(), vue3Vben5AntdGeneralTemplatePath("views/modules/form_sub_normal.vue"), // 特殊:主子表专属逻辑
174 vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/${subSimpleClassName_strikeCase}-form.vue")) 174 vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/${subSimpleClassName_strikeCase}-form.vue"))
175 - .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD.getType(), vue3Vben5AntdGeneralTemplatePath("views/modules/form_sub_inner.vue"), // 特殊:主子表专属逻辑 175 + .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_GENERAL.getType(), vue3Vben5AntdGeneralTemplatePath("views/modules/form_sub_inner.vue"), // 特殊:主子表专属逻辑
176 vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/${subSimpleClassName_strikeCase}-form.vue")) 176 vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/${subSimpleClassName_strikeCase}-form.vue"))
177 - .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD.getType(), vue3Vben5AntdGeneralTemplatePath("views/modules/form_sub_erp.vue"), // 特殊:主子表专属逻辑 177 + .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_GENERAL.getType(), vue3Vben5AntdGeneralTemplatePath("views/modules/form_sub_erp.vue"), // 特殊:主子表专属逻辑
178 vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/${subSimpleClassName_strikeCase}-form.vue")) 178 vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/${subSimpleClassName_strikeCase}-form.vue"))
179 - .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD.getType(), vue3Vben5AntdGeneralTemplatePath("views/modules/list_sub_inner.vue"), // 特殊:主子表专属逻辑 179 + .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_GENERAL.getType(), vue3Vben5AntdGeneralTemplatePath("views/modules/list_sub_inner.vue"), // 特殊:主子表专属逻辑
180 vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/${subSimpleClassName_strikeCase}-list.vue")) 180 vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/${subSimpleClassName_strikeCase}-list.vue"))
181 - .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD.getType(), vue3Vben5AntdGeneralTemplatePath("views/modules/list_sub_erp.vue"), // 特殊:主子表专属逻辑 181 + .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_GENERAL.getType(), vue3Vben5AntdGeneralTemplatePath("views/modules/list_sub_erp.vue"), // 特殊:主子表专属逻辑
182 vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/${subSimpleClassName_strikeCase}-list.vue")) 182 vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/${subSimpleClassName_strikeCase}-list.vue"))
183 .build(); 183 .build();
184 184
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileService.java
@@ -5,6 +5,7 @@ import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FileCreateReq @@ -5,6 +5,7 @@ import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FileCreateReq
5 import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO; 5 import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO;
6 import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePresignedUrlRespVO; 6 import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePresignedUrlRespVO;
7 import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO; 7 import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO;
  8 +import jakarta.validation.constraints.NotEmpty;
8 9
9 /** 10 /**
10 * 文件 Service 接口 11 * 文件 Service 接口
@@ -24,12 +25,24 @@ public interface FileService { @@ -24,12 +25,24 @@ public interface FileService {
24 /** 25 /**
25 * 保存文件,并返回文件的访问路径 26 * 保存文件,并返回文件的访问路径
26 * 27 *
27 - * @param name 文件名称  
28 - * @param path 文件路径  
29 * @param content 文件内容 28 * @param content 文件内容
  29 + * @param name 文件名称,允许空
  30 + * @param directory 目录,允许空
  31 + * @param type 文件的 MIME 类型,允许空
30 * @return 文件路径 32 * @return 文件路径
31 */ 33 */
32 - String createFile(String name, String path, byte[] content); 34 + String createFile(@NotEmpty(message = "文件内容不能为空") byte[] content,
  35 + String name, String directory, String type);
  36 +
  37 + /**
  38 + * 生成文件预签名地址信息
  39 + *
  40 + * @param name 文件名
  41 + * @param directory 目录
  42 + * @return 预签名地址信息
  43 + */
  44 + FilePresignedUrlRespVO getFilePresignedUrl(@NotEmpty(message = "文件名不能为空") String name,
  45 + String directory);
33 46
34 /** 47 /**
35 * 创建文件 48 * 创建文件
@@ -55,12 +68,4 @@ public interface FileService { @@ -55,12 +68,4 @@ public interface FileService {
55 */ 68 */
56 byte[] getFileContent(Long configId, String path) throws Exception; 69 byte[] getFileContent(Long configId, String path) throws Exception;
57 70
58 - /**  
59 - * 生成文件预签名地址信息  
60 - *  
61 - * @param path 文件路径  
62 - * @return 预签名地址信息  
63 - */  
64 - FilePresignedUrlRespVO getFilePresignedUrl(String path) throws Exception;  
65 -  
66 } 71 }
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImpl.java
1 package cn.iocoder.yudao.module.infra.service.file; 1 package cn.iocoder.yudao.module.infra.service.file;
2 2
  3 +import cn.hutool.core.date.LocalDateTimeUtil;
  4 +import cn.hutool.core.io.FileUtil;
3 import cn.hutool.core.lang.Assert; 5 import cn.hutool.core.lang.Assert;
4 import cn.hutool.core.util.StrUtil; 6 import cn.hutool.core.util.StrUtil;
  7 +import cn.hutool.crypto.digest.DigestUtil;
5 import cn.iocoder.yudao.framework.common.pojo.PageResult; 8 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 -import cn.iocoder.yudao.framework.common.util.io.FileUtils;  
7 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 9 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
8 -import cn.iocoder.yudao.module.infra.framework.file.core.client.FileClient;  
9 -import cn.iocoder.yudao.module.infra.framework.file.core.client.s3.FilePresignedUrlRespDTO;  
10 -import cn.iocoder.yudao.module.infra.framework.file.core.utils.FileTypeUtils;  
11 import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FileCreateReqVO; 10 import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FileCreateReqVO;
12 import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO; 11 import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO;
13 import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePresignedUrlRespVO; 12 import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePresignedUrlRespVO;
14 import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO; 13 import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO;
15 import cn.iocoder.yudao.module.infra.dal.mysql.file.FileMapper; 14 import cn.iocoder.yudao.module.infra.dal.mysql.file.FileMapper;
  15 +import cn.iocoder.yudao.module.infra.framework.file.core.client.FileClient;
  16 +import cn.iocoder.yudao.module.infra.framework.file.core.client.s3.FilePresignedUrlRespDTO;
  17 +import cn.iocoder.yudao.module.infra.framework.file.core.utils.FileTypeUtils;
  18 +import com.google.common.annotations.VisibleForTesting;
16 import jakarta.annotation.Resource; 19 import jakarta.annotation.Resource;
17 import lombok.SneakyThrows; 20 import lombok.SneakyThrows;
18 import org.springframework.stereotype.Service; 21 import org.springframework.stereotype.Service;
19 22
  23 +import static cn.hutool.core.date.DatePattern.PURE_DATE_PATTERN;
20 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 24 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
21 import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_NOT_EXISTS; 25 import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_NOT_EXISTS;
22 26
@@ -28,6 +32,20 @@ import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_NOT_EX @@ -28,6 +32,20 @@ import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_NOT_EX
28 @Service 32 @Service
29 public class FileServiceImpl implements FileService { 33 public class FileServiceImpl implements FileService {
30 34
  35 + /**
  36 + * 上传文件的前缀,是否包含日期(yyyyMMdd)
  37 + *
  38 + * 目的:按照日期,进行分目录
  39 + */
  40 + static boolean PATH_PREFIX_DATE_ENABLE = true;
  41 + /**
  42 + * 上传文件的后缀,是否包含时间戳
  43 + *
  44 + * 目的:保证文件的唯一性,避免覆盖
  45 + * 定制:可按需调整成 UUID、或者其他方式
  46 + */
  47 + static boolean PATH_SUFFIX_TIMESTAMP_ENABLE = true;
  48 +
31 @Resource 49 @Resource
32 private FileConfigService fileConfigService; 50 private FileConfigService fileConfigService;
33 51
@@ -41,34 +59,82 @@ public class FileServiceImpl implements FileService { @@ -41,34 +59,82 @@ public class FileServiceImpl implements FileService {
41 59
42 @Override 60 @Override
43 @SneakyThrows 61 @SneakyThrows
44 - public String createFile(String name, String path, byte[] content) {  
45 - // 计算默认的 path 名  
46 - String type = FileTypeUtils.getMineType(content, name);  
47 - if (StrUtil.isEmpty(path)) {  
48 - path = FileUtils.generatePath(content, name); 62 + public String createFile(byte[] content, String name, String directory, String type) {
  63 + // 1.1 处理 type 为空的情况
  64 + if (StrUtil.isEmpty(type)) {
  65 + type = FileTypeUtils.getMineType(content, name);
49 } 66 }
50 - // 如果 name 为空,则使用 path 填充 67 + // 1.2 处理 name 为空的情况
51 if (StrUtil.isEmpty(name)) { 68 if (StrUtil.isEmpty(name)) {
52 - name = path; 69 + name = DigestUtil.sha256Hex(content);
  70 + }
  71 + if (StrUtil.isEmpty(FileUtil.extName(name))) {
  72 + // 如果 name 没有后缀 type,则补充后缀
  73 + String extension = FileTypeUtils.getExtension(type);
  74 + if (StrUtil.isNotEmpty(extension)) {
  75 + name = name + extension;
  76 + }
53 } 77 }
54 78
55 - // 上传到文件存储器 79 + // 2.1 生成上传的 path,需要保证唯一
  80 + String path = generateUploadPath(name, directory);
  81 + // 2.2 上传到文件存储器
56 FileClient client = fileConfigService.getMasterFileClient(); 82 FileClient client = fileConfigService.getMasterFileClient();
57 Assert.notNull(client, "客户端(master) 不能为空"); 83 Assert.notNull(client, "客户端(master) 不能为空");
58 String url = client.upload(content, path, type); 84 String url = client.upload(content, path, type);
59 85
60 - // 保存到数据库  
61 - FileDO file = new FileDO();  
62 - file.setConfigId(client.getId());  
63 - file.setName(name);  
64 - file.setPath(path);  
65 - file.setUrl(url);  
66 - file.setType(type);  
67 - file.setSize(content.length);  
68 - fileMapper.insert(file); 86 + // 3. 保存到数据库
  87 + fileMapper.insert(new FileDO().setConfigId(client.getId())
  88 + .setName(name).setPath(path).setUrl(url)
  89 + .setType(type).setSize(content.length));
69 return url; 90 return url;
70 } 91 }
71 92
  93 + @VisibleForTesting
  94 + String generateUploadPath(String name, String directory) {
  95 + // 1. 生成前缀、后缀
  96 + String prefix = null;
  97 + if (PATH_PREFIX_DATE_ENABLE) {
  98 + prefix = LocalDateTimeUtil.format(LocalDateTimeUtil.now(), PURE_DATE_PATTERN);
  99 + }
  100 + String suffix = null;
  101 + if (PATH_SUFFIX_TIMESTAMP_ENABLE) {
  102 + suffix = String.valueOf(System.currentTimeMillis());
  103 + }
  104 +
  105 + // 2.1 先拼接 suffix 后缀
  106 + if (StrUtil.isNotEmpty(suffix)) {
  107 + String ext = FileUtil.extName(name);
  108 + if (StrUtil.isNotEmpty(ext)) {
  109 + name = FileUtil.mainName(name) + StrUtil.C_UNDERLINE + suffix + StrUtil.DOT + ext;
  110 + } else {
  111 + name = name + StrUtil.C_UNDERLINE + suffix;
  112 + }
  113 + }
  114 + // 2.2 再拼接 prefix 前缀
  115 + if (StrUtil.isNotEmpty(prefix)) {
  116 + name = prefix + StrUtil.SLASH + name;
  117 + }
  118 + // 2.3 最后拼接 directory 目录
  119 + if (StrUtil.isNotEmpty(directory)) {
  120 + name = directory + StrUtil.SLASH + name;
  121 + }
  122 + return name;
  123 + }
  124 +
  125 + @Override
  126 + @SneakyThrows
  127 + public FilePresignedUrlRespVO getFilePresignedUrl(String name, String directory) {
  128 + // 1. 生成上传的 path,需要保证唯一
  129 + String path = generateUploadPath(name, directory);
  130 +
  131 + // 2. 获取文件预签名地址
  132 + FileClient fileClient = fileConfigService.getMasterFileClient();
  133 + FilePresignedUrlRespDTO presignedObjectUrl = fileClient.getPresignedObjectUrl(path);
  134 + return BeanUtils.toBean(presignedObjectUrl, FilePresignedUrlRespVO.class,
  135 + object -> object.setConfigId(fileClient.getId()).setPath(path));
  136 + }
  137 +
72 @Override 138 @Override
73 public Long createFile(FileCreateReqVO createReqVO) { 139 public Long createFile(FileCreateReqVO createReqVO) {
74 FileDO file = BeanUtils.toBean(createReqVO, FileDO.class); 140 FileDO file = BeanUtils.toBean(createReqVO, FileDO.class);
@@ -105,12 +171,4 @@ public class FileServiceImpl implements FileService { @@ -105,12 +171,4 @@ public class FileServiceImpl implements FileService {
105 return client.getContent(path); 171 return client.getContent(path);
106 } 172 }
107 173
108 - @Override  
109 - public FilePresignedUrlRespVO getFilePresignedUrl(String path) throws Exception {  
110 - FileClient fileClient = fileConfigService.getMasterFileClient();  
111 - FilePresignedUrlRespDTO presignedObjectUrl = fileClient.getPresignedObjectUrl(path);  
112 - return BeanUtils.toBean(presignedObjectUrl, FilePresignedUrlRespVO.class,  
113 - object -> object.setConfigId(fileClient.getId()));  
114 - }  
115 -  
116 } 174 }
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/general/views/form.vue.vm
@@ -147,7 +147,7 @@ const [Modal, modalApi] = useVbenModal({ @@ -147,7 +147,7 @@ const [Modal, modalApi] = useVbenModal({
147 key: 'action_process_msg', 147 key: 'action_process_msg',
148 }); 148 });
149 } finally { 149 } finally {
150 - modalApi.lock(false); 150 + modalApi.unlock();
151 } 151 }
152 }, 152 },
153 async onOpenChange(isOpen: boolean) { 153 async onOpenChange(isOpen: boolean) {
@@ -165,7 +165,7 @@ const [Modal, modalApi] = useVbenModal({ @@ -165,7 +165,7 @@ const [Modal, modalApi] = useVbenModal({
165 try { 165 try {
166 data = await get${simpleClassName}(data.id); 166 data = await get${simpleClassName}(data.id);
167 } finally { 167 } finally {
168 - modalApi.lock(false); 168 + modalApi.unlock();
169 } 169 }
170 } 170 }
171 formData.value = data; 171 formData.value = data;
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/general/views/modules/form_sub_erp.vue.vm
@@ -24,6 +24,13 @@ @@ -24,6 +24,13 @@
24 }); 24 });
25 25
26 const [Form, formApi] = useVbenForm({ 26 const [Form, formApi] = useVbenForm({
  27 + commonConfig: {
  28 + componentProps: {
  29 + class: 'w-full',
  30 + },
  31 + formItemClass: 'col-span-2',
  32 + labelWidth: 80,
  33 + },
27 layout: 'horizontal', 34 layout: 'horizontal',
28 schema: use${subSimpleClassName}FormSchema(), 35 schema: use${subSimpleClassName}FormSchema(),
29 showDefaultActions: false 36 showDefaultActions: false
@@ -50,7 +57,7 @@ @@ -50,7 +57,7 @@
50 key: 'action_process_msg', 57 key: 'action_process_msg',
51 }); 58 });
52 } finally { 59 } finally {
53 - modalApi.lock(false); 60 + modalApi.unlock();
54 } 61 }
55 }, 62 },
56 async onOpenChange(isOpen: boolean) { 63 async onOpenChange(isOpen: boolean) {
@@ -69,7 +76,7 @@ @@ -69,7 +76,7 @@
69 try { 76 try {
70 data = await get${subSimpleClassName}(data.id); 77 data = await get${subSimpleClassName}(data.id);
71 } finally { 78 } finally {
72 - modalApi.lock(false); 79 + modalApi.unlock();
73 } 80 }
74 } 81 }
75 // 设置到 values 82 // 设置到 values
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/general/views/modules/form_sub_normal.vue.vm
@@ -96,9 +96,16 @@ watch( @@ -96,9 +96,16 @@ watch(
96 ); 96 );
97 #else 97 #else
98 const [Form, formApi] = useVbenForm({ 98 const [Form, formApi] = useVbenForm({
99 -layout: 'horizontal',  
100 -schema: use${subSimpleClassName}FormSchema(),  
101 -showDefaultActions: false 99 + commonConfig: {
  100 + componentProps: {
  101 + class: 'w-full',
  102 + },
  103 + formItemClass: 'col-span-2',
  104 + labelWidth: 80,
  105 + },
  106 + layout: 'horizontal',
  107 + schema: use${subSimpleClassName}FormSchema(),
  108 + showDefaultActions: false
102 }); 109 });
103 110
104 /** 暴露出表单校验方法和表单值获取方法 */ 111 /** 暴露出表单校验方法和表单值获取方法 */
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/schema/views/data.ts.vm
1 -import type { VxeTableGridOptions } from '@vben/plugins/vxe-table';  
2 import type { VbenFormSchema } from '#/adapter/form'; 1 import type { VbenFormSchema } from '#/adapter/form';
3 -import type { OnActionClickFn } from '#/adapter/vxe-table'; 2 +import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
4 import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}'; 3 import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
5 4
6 import { z } from '#/adapter/form'; 5 import { z } from '#/adapter/form';
@@ -40,7 +39,6 @@ export function useFormSchema(): VbenFormSchema[] { @@ -40,7 +39,6 @@ export function useFormSchema(): VbenFormSchema[] {
40 }); 39 });
41 return handleTree(data); 40 return handleTree(data);
42 }, 41 },
43 - class: 'w-full',  
44 labelField: '${treeNameColumn.javaField}', 42 labelField: '${treeNameColumn.javaField}',
45 valueField: 'id', 43 valueField: 'id',
46 childrenField: 'children', 44 childrenField: 'children',
@@ -90,7 +88,6 @@ export function useFormSchema(): VbenFormSchema[] { @@ -90,7 +88,6 @@ export function useFormSchema(): VbenFormSchema[] {
90 options: [], 88 options: [],
91 #end 89 #end
92 placeholder: '请选择${comment}', 90 placeholder: '请选择${comment}',
93 - class: 'w-full',  
94 }, 91 },
95 #elseif($column.htmlType == "checkbox")## 多选框 92 #elseif($column.htmlType == "checkbox")## 多选框
96 component: 'Checkbox', 93 component: 'Checkbox',
@@ -128,7 +125,6 @@ export function useFormSchema(): VbenFormSchema[] { @@ -128,7 +125,6 @@ export function useFormSchema(): VbenFormSchema[] {
128 component: 'InputNumber', 125 component: 'InputNumber',
129 componentProps: { 126 componentProps: {
130 min: 0, 127 min: 0,
131 - class: 'w-full',  
132 controlsPosition: 'right', 128 controlsPosition: 'right',
133 placeholder: '请输入${comment}', 129 placeholder: '请输入${comment}',
134 }, 130 },
@@ -326,7 +322,6 @@ export function use${subSimpleClassName}FormSchema(): VbenFormSchema[] { @@ -326,7 +322,6 @@ export function use${subSimpleClassName}FormSchema(): VbenFormSchema[] {
326 options: [], 322 options: [],
327 #end 323 #end
328 placeholder: '请选择${comment}', 324 placeholder: '请选择${comment}',
329 - class: 'w-full',  
330 }, 325 },
331 #elseif($column.htmlType == "checkbox")## 多选框 326 #elseif($column.htmlType == "checkbox")## 多选框
332 component: 'Checkbox', 327 component: 'Checkbox',
@@ -364,7 +359,6 @@ export function use${subSimpleClassName}FormSchema(): VbenFormSchema[] { @@ -364,7 +359,6 @@ export function use${subSimpleClassName}FormSchema(): VbenFormSchema[] {
364 component: 'InputNumber', 359 component: 'InputNumber',
365 componentProps: { 360 componentProps: {
366 min: 0, 361 min: 0,
367 - class: 'w-full',  
368 controlsPosition: 'right', 362 controlsPosition: 'right',
369 placeholder: '请输入${comment}', 363 placeholder: '请输入${comment}',
370 }, 364 },
@@ -601,7 +595,6 @@ export function use${subSimpleClassName}GridColumns( @@ -601,7 +595,6 @@ export function use${subSimpleClassName}GridColumns(
601 options: [], 595 options: [],
602 #end 596 #end
603 placeholder: '请选择${comment}', 597 placeholder: '请选择${comment}',
604 - class: 'w-full',  
605 }, 598 },
606 #elseif($column.htmlType == "checkbox")## 多选框 599 #elseif($column.htmlType == "checkbox")## 多选框
607 component: 'Checkbox', 600 component: 'Checkbox',
@@ -639,7 +632,6 @@ export function use${subSimpleClassName}GridColumns( @@ -639,7 +632,6 @@ export function use${subSimpleClassName}GridColumns(
639 component: 'InputNumber', 632 component: 'InputNumber',
640 componentProps: { 633 componentProps: {
641 min: 0, 634 min: 0,
642 - class: 'w-full',  
643 controlsPosition: 'right', 635 controlsPosition: 'right',
644 placeholder: '请输入${comment}', 636 placeholder: '请输入${comment}',
645 }, 637 },
@@ -682,4 +674,4 @@ export function use${subSimpleClassName}GridColumns( @@ -682,4 +674,4 @@ export function use${subSimpleClassName}GridColumns(
682 674
683 #end 675 #end
684 #end 676 #end
685 -#end  
686 \ No newline at end of file 677 \ No newline at end of file
  678 +#end
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/schema/views/form.vue.vm
@@ -42,6 +42,13 @@ const getTitle = computed(() =&gt; { @@ -42,6 +42,13 @@ const getTitle = computed(() =&gt; {
42 #end 42 #end
43 43
44 const [Form, formApi] = useVbenForm({ 44 const [Form, formApi] = useVbenForm({
  45 + commonConfig: {
  46 + componentProps: {
  47 + class: 'w-full',
  48 + },
  49 + formItemClass: 'col-span-2',
  50 + labelWidth: 80,
  51 + },
45 layout: 'horizontal', 52 layout: 'horizontal',
46 schema: useFormSchema(), 53 schema: useFormSchema(),
47 showDefaultActions: false 54 showDefaultActions: false
@@ -100,7 +107,7 @@ const [Modal, modalApi] = useVbenModal({ @@ -100,7 +107,7 @@ const [Modal, modalApi] = useVbenModal({
100 key: 'action_process_msg', 107 key: 'action_process_msg',
101 }); 108 });
102 } finally { 109 } finally {
103 - modalApi.lock(false); 110 + modalApi.unlock();
104 } 111 }
105 }, 112 },
106 async onOpenChange(isOpen: boolean) { 113 async onOpenChange(isOpen: boolean) {
@@ -118,7 +125,7 @@ const [Modal, modalApi] = useVbenModal({ @@ -118,7 +125,7 @@ const [Modal, modalApi] = useVbenModal({
118 try { 125 try {
119 data = await get${simpleClassName}(data.id); 126 data = await get${simpleClassName}(data.id);
120 } finally { 127 } finally {
121 - modalApi.lock(false); 128 + modalApi.unlock();
122 } 129 }
123 } 130 }
124 // 设置到 values 131 // 设置到 values
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/schema/views/modules/form_sub_erp.vue.vm
@@ -24,6 +24,13 @@ @@ -24,6 +24,13 @@
24 }); 24 });
25 25
26 const [Form, formApi] = useVbenForm({ 26 const [Form, formApi] = useVbenForm({
  27 + commonConfig: {
  28 + componentProps: {
  29 + class: 'w-full',
  30 + },
  31 + formItemClass: 'col-span-2',
  32 + labelWidth: 80,
  33 + },
27 layout: 'horizontal', 34 layout: 'horizontal',
28 schema: use${subSimpleClassName}FormSchema(), 35 schema: use${subSimpleClassName}FormSchema(),
29 showDefaultActions: false 36 showDefaultActions: false
@@ -50,7 +57,7 @@ @@ -50,7 +57,7 @@
50 key: 'action_process_msg', 57 key: 'action_process_msg',
51 }); 58 });
52 } finally { 59 } finally {
53 - modalApi.lock(false); 60 + modalApi.unlock();
54 } 61 }
55 }, 62 },
56 async onOpenChange(isOpen: boolean) { 63 async onOpenChange(isOpen: boolean) {
@@ -69,7 +76,7 @@ @@ -69,7 +76,7 @@
69 try { 76 try {
70 data = await get${subSimpleClassName}(data.id); 77 data = await get${subSimpleClassName}(data.id);
71 } finally { 78 } finally {
72 - modalApi.lock(false); 79 + modalApi.unlock();
73 } 80 }
74 } 81 }
75 // 设置到 values 82 // 设置到 values
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben5_antd/schema/views/modules/form_sub_normal.vue.vm
@@ -96,9 +96,16 @@ watch( @@ -96,9 +96,16 @@ watch(
96 ); 96 );
97 #else 97 #else
98 const [Form, formApi] = useVbenForm({ 98 const [Form, formApi] = useVbenForm({
99 -layout: 'horizontal',  
100 -schema: use${subSimpleClassName}FormSchema(),  
101 -showDefaultActions: false 99 + commonConfig: {
  100 + componentProps: {
  101 + class: 'w-full',
  102 + },
  103 + formItemClass: 'col-span-2',
  104 + labelWidth: 80,
  105 + },
  106 + layout: 'horizontal',
  107 + schema: use${subSimpleClassName}FormSchema(),
  108 + showDefaultActions: false
102 }); 109 });
103 110
104 /** 暴露出表单校验方法和表单值获取方法 */ 111 /** 暴露出表单校验方法和表单值获取方法 */
yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/framework/file/core/ftp/FtpFileClientTest.java
@@ -8,8 +8,23 @@ import cn.iocoder.yudao.module.infra.framework.file.core.client.ftp.FtpFileClien @@ -8,8 +8,23 @@ import cn.iocoder.yudao.module.infra.framework.file.core.client.ftp.FtpFileClien
8 import org.junit.jupiter.api.Disabled; 8 import org.junit.jupiter.api.Disabled;
9 import org.junit.jupiter.api.Test; 9 import org.junit.jupiter.api.Test;
10 10
  11 +/**
  12 + * {@link FtpFileClient} 集成测试
  13 + *
  14 + * @author 芋道源码
  15 + */
11 public class FtpFileClientTest { 16 public class FtpFileClientTest {
12 17
  18 +// docker run -d \
  19 +// -p 2121:21 -p 30000-30009:30000-30009 \
  20 +// -e FTP_USER=foo \
  21 +// -e FTP_PASS=pass \
  22 +// -e PASV_ADDRESS=127.0.0.1 \
  23 +// -e PASV_MIN_PORT=30000 \
  24 +// -e PASV_MAX_PORT=30009 \
  25 +// -v $(pwd)/ftp-data:/home/vsftpd \
  26 +// fauria/vsftpd
  27 +
13 @Test 28 @Test
14 @Disabled 29 @Disabled
15 public void test() { 30 public void test() {
@@ -17,10 +32,10 @@ public class FtpFileClientTest { @@ -17,10 +32,10 @@ public class FtpFileClientTest {
17 FtpFileClientConfig config = new FtpFileClientConfig(); 32 FtpFileClientConfig config = new FtpFileClientConfig();
18 config.setDomain("http://127.0.0.1:48080"); 33 config.setDomain("http://127.0.0.1:48080");
19 config.setBasePath("/home/ftp"); 34 config.setBasePath("/home/ftp");
20 - config.setHost("kanchai.club");  
21 - config.setPort(221);  
22 - config.setUsername("");  
23 - config.setPassword(""); 35 + config.setHost("127.0.0.1");
  36 + config.setPort(2121);
  37 + config.setUsername("foo");
  38 + config.setPassword("pass");
24 config.setMode(FtpMode.Passive.name()); 39 config.setMode(FtpMode.Passive.name());
25 FtpFileClient client = new FtpFileClient(0L, config); 40 FtpFileClient client = new FtpFileClient(0L, config);
26 client.init(); 41 client.init();
yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/framework/file/core/sftp/SftpFileClientTest.java
@@ -7,19 +7,29 @@ import cn.iocoder.yudao.module.infra.framework.file.core.client.sftp.SftpFileCli @@ -7,19 +7,29 @@ import cn.iocoder.yudao.module.infra.framework.file.core.client.sftp.SftpFileCli
7 import org.junit.jupiter.api.Disabled; 7 import org.junit.jupiter.api.Disabled;
8 import org.junit.jupiter.api.Test; 8 import org.junit.jupiter.api.Test;
9 9
  10 +/**
  11 + * {@link SftpFileClient} 集成测试
  12 + *
  13 + * @author 芋道源码
  14 + */
10 public class SftpFileClientTest { 15 public class SftpFileClientTest {
11 16
  17 +// docker run -p 2222:22 -d \
  18 +// -v $(pwd)/sftp-data:/home/foo/upload \
  19 +// atmoz/sftp \
  20 +// foo:pass:1001
  21 +
12 @Test 22 @Test
13 @Disabled 23 @Disabled
14 public void test() { 24 public void test() {
15 // 创建客户端 25 // 创建客户端
16 SftpFileClientConfig config = new SftpFileClientConfig(); 26 SftpFileClientConfig config = new SftpFileClientConfig();
17 config.setDomain("http://127.0.0.1:48080"); 27 config.setDomain("http://127.0.0.1:48080");
18 - config.setBasePath("/home/ftp");  
19 - config.setHost("kanchai.club");  
20 - config.setPort(222);  
21 - config.setUsername("");  
22 - config.setPassword(""); 28 + config.setBasePath("/upload"); // 注意,这个是相对路径,不是实际 linux 上的路径!!!
  29 + config.setHost("127.0.0.1");
  30 + config.setPort(2222);
  31 + config.setUsername("foo");
  32 + config.setPassword("pass");
23 SftpFileClient client = new SftpFileClient(0L, config); 33 SftpFileClient client = new SftpFileClient(0L, config);
24 client.init(); 34 client.init();
25 // 上传文件 35 // 上传文件
yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImplTest.java
@@ -3,19 +3,20 @@ package cn.iocoder.yudao.module.infra.service.file; @@ -3,19 +3,20 @@ package cn.iocoder.yudao.module.infra.service.file;
3 import cn.hutool.core.io.resource.ResourceUtil; 3 import cn.hutool.core.io.resource.ResourceUtil;
4 import cn.iocoder.yudao.framework.common.pojo.PageResult; 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; 5 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
6 -import cn.iocoder.yudao.module.infra.framework.file.core.client.FileClient;  
7 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; 6 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
8 import cn.iocoder.yudao.framework.test.core.util.AssertUtils; 7 import cn.iocoder.yudao.framework.test.core.util.AssertUtils;
9 import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO; 8 import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.FilePageReqVO;
10 import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO; 9 import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO;
11 import cn.iocoder.yudao.module.infra.dal.mysql.file.FileMapper; 10 import cn.iocoder.yudao.module.infra.dal.mysql.file.FileMapper;
  11 +import cn.iocoder.yudao.module.infra.framework.file.core.client.FileClient;
  12 +import jakarta.annotation.Resource;
  13 +import org.junit.jupiter.api.BeforeEach;
12 import org.junit.jupiter.api.Test; 14 import org.junit.jupiter.api.Test;
13 import org.springframework.boot.test.mock.mockito.MockBean; 15 import org.springframework.boot.test.mock.mockito.MockBean;
14 import org.springframework.context.annotation.Import; 16 import org.springframework.context.annotation.Import;
15 17
16 -import jakarta.annotation.Resource;  
17 -  
18 import java.time.LocalDateTime; 18 import java.time.LocalDateTime;
  19 +import java.util.concurrent.atomic.AtomicReference;
19 20
20 import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime; 21 import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime;
21 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; 22 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
@@ -29,7 +30,7 @@ import static org.mockito.Mockito.*; @@ -29,7 +30,7 @@ import static org.mockito.Mockito.*;
29 public class FileServiceImplTest extends BaseDbUnitTest { 30 public class FileServiceImplTest extends BaseDbUnitTest {
30 31
31 @Resource 32 @Resource
32 - private FileService fileService; 33 + private FileServiceImpl fileService;
33 34
34 @Resource 35 @Resource
35 private FileMapper fileMapper; 36 private FileMapper fileMapper;
@@ -37,6 +38,12 @@ public class FileServiceImplTest extends BaseDbUnitTest { @@ -37,6 +38,12 @@ public class FileServiceImplTest extends BaseDbUnitTest {
37 @MockBean 38 @MockBean
38 private FileConfigService fileConfigService; 39 private FileConfigService fileConfigService;
39 40
  41 + @BeforeEach
  42 + public void setUp() {
  43 + FileServiceImpl.PATH_PREFIX_DATE_ENABLE = true;
  44 + FileServiceImpl.PATH_SUFFIX_TIMESTAMP_ENABLE = true;
  45 + }
  46 +
40 @Test 47 @Test
41 public void testGetFilePage() { 48 public void testGetFilePage() {
42 // mock 数据 49 // mock 数据
@@ -70,28 +77,69 @@ public class FileServiceImplTest extends BaseDbUnitTest { @@ -70,28 +77,69 @@ public class FileServiceImplTest extends BaseDbUnitTest {
70 AssertUtils.assertPojoEquals(dbFile, pageResult.getList().get(0)); 77 AssertUtils.assertPojoEquals(dbFile, pageResult.getList().get(0));
71 } 78 }
72 79
  80 + /**
  81 + * content、name、directory、type 都非空
  82 + */
73 @Test 83 @Test
74 - public void testCreateFile_success() throws Exception { 84 + public void testCreateFile_success_01() throws Exception {
75 // 准备参数 85 // 准备参数
76 - String path = randomString();  
77 byte[] content = ResourceUtil.readBytes("file/erweima.jpg"); 86 byte[] content = ResourceUtil.readBytes("file/erweima.jpg");
  87 + String name = "单测文件名";
  88 + String directory = randomString();
  89 + String type = "image/jpeg";
78 // mock Master 文件客户端 90 // mock Master 文件客户端
79 FileClient client = mock(FileClient.class); 91 FileClient client = mock(FileClient.class);
80 when(fileConfigService.getMasterFileClient()).thenReturn(client); 92 when(fileConfigService.getMasterFileClient()).thenReturn(client);
81 String url = randomString(); 93 String url = randomString();
82 - when(client.upload(same(content), same(path), eq("image/jpeg"))).thenReturn(url); 94 + AtomicReference<String> pathRef = new AtomicReference<>();
  95 + when(client.upload(same(content), argThat(path -> {
  96 + assertTrue(path.matches(directory + "/\\d{8}/" + name + "_\\d+.jpg"));
  97 + pathRef.set(path);
  98 + return true;
  99 + }), eq(type))).thenReturn(url);
83 when(client.getId()).thenReturn(10L); 100 when(client.getId()).thenReturn(10L);
84 - String name = "单测文件名";  
85 // 调用 101 // 调用
86 - String result = fileService.createFile(name, path, content); 102 + String result = fileService.createFile(content, name, directory, type);
87 // 断言 103 // 断言
88 assertEquals(result, url); 104 assertEquals(result, url);
89 // 校验数据 105 // 校验数据
90 - FileDO file = fileMapper.selectOne(FileDO::getPath, path); 106 + FileDO file = fileMapper.selectOne(FileDO::getUrl, url);
91 assertEquals(10L, file.getConfigId()); 107 assertEquals(10L, file.getConfigId());
92 - assertEquals(path, file.getPath()); 108 + assertEquals(pathRef.get(), file.getPath());
93 assertEquals(url, file.getUrl()); 109 assertEquals(url, file.getUrl());
94 - assertEquals("image/jpeg", file.getType()); 110 + assertEquals(type, file.getType());
  111 + assertEquals(content.length, file.getSize());
  112 + }
  113 +
  114 + /**
  115 + * content 非空,其它都空
  116 + */
  117 + @Test
  118 + public void testCreateFile_success_02() throws Exception {
  119 + // 准备参数
  120 + byte[] content = ResourceUtil.readBytes("file/erweima.jpg");
  121 + // mock Master 文件客户端
  122 + String type = "image/jpeg";
  123 + FileClient client = mock(FileClient.class);
  124 + when(fileConfigService.getMasterFileClient()).thenReturn(client);
  125 + String url = randomString();
  126 + AtomicReference<String> pathRef = new AtomicReference<>();
  127 + when(client.upload(same(content), argThat(path -> {
  128 + assertTrue(path.matches("\\d{8}/6318848e882d8a7e7e82789d87608f684ee52d41966bfc8cad3ce15aad2b970e_\\d+\\.jpg"));
  129 + pathRef.set(path);
  130 + return true;
  131 + }), eq(type))).thenReturn(url);
  132 + when(client.getId()).thenReturn(10L);
  133 + // 调用
  134 + String result = fileService.createFile(content, null, null, null);
  135 + // 断言
  136 + assertEquals(result, url);
  137 + // 校验数据
  138 + FileDO file = fileMapper.selectOne(FileDO::getUrl, url);
  139 + assertEquals(10L, file.getConfigId());
  140 + assertEquals(pathRef.get(), file.getPath());
  141 + assertEquals(url, file.getUrl());
  142 + assertEquals(type, file.getType());
95 assertEquals(content.length, file.getSize()); 143 assertEquals(content.length, file.getSize());
96 } 144 }
97 145
@@ -140,4 +188,122 @@ public class FileServiceImplTest extends BaseDbUnitTest { @@ -140,4 +188,122 @@ public class FileServiceImplTest extends BaseDbUnitTest {
140 assertSame(result, content); 188 assertSame(result, content);
141 } 189 }
142 190
  191 + @Test
  192 + public void testGenerateUploadPath_AllEnabled() {
  193 + // 准备参数
  194 + String name = "test.jpg";
  195 + String directory = "avatar";
  196 + FileServiceImpl.PATH_PREFIX_DATE_ENABLE = true;
  197 + FileServiceImpl.PATH_SUFFIX_TIMESTAMP_ENABLE = true;
  198 +
  199 + // 调用
  200 + String path = fileService.generateUploadPath(name, directory);
  201 +
  202 + // 断言
  203 + // 格式为:avatar/yyyyMMdd/test_timestamp.jpg
  204 + assertTrue(path.startsWith(directory + "/"));
  205 + // 包含日期格式:8 位数字,如 20240517
  206 + assertTrue(path.matches(directory + "/\\d{8}/test_\\d+\\.jpg"));
  207 + }
  208 +
  209 + @Test
  210 + public void testGenerateUploadPath_PrefixEnabled_SuffixDisabled() {
  211 + // 准备参数
  212 + String name = "test.jpg";
  213 + String directory = "avatar";
  214 + FileServiceImpl.PATH_PREFIX_DATE_ENABLE = true;
  215 + FileServiceImpl.PATH_SUFFIX_TIMESTAMP_ENABLE = false;
  216 +
  217 + // 调用
  218 + String path = fileService.generateUploadPath(name, directory);
  219 +
  220 + // 断言
  221 + // 格式为:avatar/yyyyMMdd/test.jpg
  222 + assertTrue(path.startsWith(directory + "/"));
  223 + // 包含日期格式:8 位数字,如 20240517
  224 + assertTrue(path.matches(directory + "/\\d{8}/test\\.jpg"));
  225 + }
  226 +
  227 + @Test
  228 + public void testGenerateUploadPath_PrefixDisabled_SuffixEnabled() {
  229 + // 准备参数
  230 + String name = "test.jpg";
  231 + String directory = "avatar";
  232 + FileServiceImpl.PATH_PREFIX_DATE_ENABLE = false;
  233 + FileServiceImpl.PATH_SUFFIX_TIMESTAMP_ENABLE = true;
  234 +
  235 + // 调用
  236 + String path = fileService.generateUploadPath(name, directory);
  237 +
  238 + // 断言
  239 + // 格式为:avatar/test_timestamp.jpg
  240 + assertTrue(path.startsWith(directory + "/"));
  241 + assertTrue(path.matches(directory + "/test_\\d+\\.jpg"));
  242 + }
  243 +
  244 + @Test
  245 + public void testGenerateUploadPath_AllDisabled() {
  246 + // 准备参数
  247 + String name = "test.jpg";
  248 + String directory = "avatar";
  249 + FileServiceImpl.PATH_PREFIX_DATE_ENABLE = false;
  250 + FileServiceImpl.PATH_SUFFIX_TIMESTAMP_ENABLE = false;
  251 +
  252 + // 调用
  253 + String path = fileService.generateUploadPath(name, directory);
  254 +
  255 + // 断言
  256 + // 格式为:avatar/test.jpg
  257 + assertEquals(directory + "/" + name, path);
  258 + }
  259 +
  260 + @Test
  261 + public void testGenerateUploadPath_NoExtension() {
  262 + // 准备参数
  263 + String name = "test";
  264 + String directory = "avatar";
  265 + FileServiceImpl.PATH_PREFIX_DATE_ENABLE = true;
  266 + FileServiceImpl.PATH_SUFFIX_TIMESTAMP_ENABLE = true;
  267 +
  268 + // 调用
  269 + String path = fileService.generateUploadPath(name, directory);
  270 +
  271 + // 断言
  272 + // 格式为:avatar/yyyyMMdd/test_timestamp
  273 + assertTrue(path.startsWith(directory + "/"));
  274 + assertTrue(path.matches(directory + "/\\d{8}/test_\\d+"));
  275 + }
  276 +
  277 + @Test
  278 + public void testGenerateUploadPath_DirectoryNull() {
  279 + // 准备参数
  280 + String name = "test.jpg";
  281 + String directory = null;
  282 + FileServiceImpl.PATH_PREFIX_DATE_ENABLE = true;
  283 + FileServiceImpl.PATH_SUFFIX_TIMESTAMP_ENABLE = true;
  284 +
  285 + // 调用
  286 + String path = fileService.generateUploadPath(name, directory);
  287 +
  288 + // 断言
  289 + // 格式为:yyyyMMdd/test_timestamp.jpg
  290 + assertTrue(path.matches("\\d{8}/test_\\d+\\.jpg"));
  291 + }
  292 +
  293 + @Test
  294 + public void testGenerateUploadPath_DirectoryEmpty() {
  295 + // 准备参数
  296 + String name = "test.jpg";
  297 + String directory = "";
  298 + FileServiceImpl.PATH_PREFIX_DATE_ENABLE = true;
  299 + FileServiceImpl.PATH_SUFFIX_TIMESTAMP_ENABLE = true;
  300 +
  301 + // 调用
  302 + String path = fileService.generateUploadPath(name, directory);
  303 +
  304 + // 断言
  305 + // 格式为:yyyyMMdd/test_timestamp.jpg
  306 + assertTrue(path.matches("\\d{8}/test_\\d+\\.jpg"));
  307 + }
  308 +
143 } 309 }
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/firmware/IotOtaFirmwareCreateReqVO.java
@@ -5,24 +5,22 @@ import jakarta.validation.constraints.NotEmpty; @@ -5,24 +5,22 @@ import jakarta.validation.constraints.NotEmpty;
5 import jakarta.validation.constraints.NotNull; 5 import jakarta.validation.constraints.NotNull;
6 import lombok.Data; 6 import lombok.Data;
7 7
8 -import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;  
9 -  
10 @Schema(description = "管理后台 - IoT OTA 固件创建 Request VO") 8 @Schema(description = "管理后台 - IoT OTA 固件创建 Request VO")
11 @Data 9 @Data
12 public class IotOtaFirmwareCreateReqVO { 10 public class IotOtaFirmwareCreateReqVO {
13 11
14 - @Schema(description = "固件名称", requiredMode = REQUIRED, example = "智能开关固件") 12 + @Schema(description = "固件名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "智能开关固件")
15 @NotEmpty(message = "固件名称不能为空") 13 @NotEmpty(message = "固件名称不能为空")
16 private String name; 14 private String name;
17 15
18 @Schema(description = "固件描述", example = "某品牌型号固件,测试用") 16 @Schema(description = "固件描述", example = "某品牌型号固件,测试用")
19 private String description; 17 private String description;
20 18
21 - @Schema(description = "版本号", requiredMode = REQUIRED, example = "1.0.0") 19 + @Schema(description = "版本号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1.0.0")
22 @NotEmpty(message = "版本号不能为空") 20 @NotEmpty(message = "版本号不能为空")
23 private String version; 21 private String version;
24 22
25 - @Schema(description = "产品编号", requiredMode = REQUIRED, example = "1024") 23 + @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
26 @NotNull(message = "产品编号不能为空") 24 @NotNull(message = "产品编号不能为空")
27 private String productId; 25 private String productId;
28 26
@@ -30,7 +28,7 @@ public class IotOtaFirmwareCreateReqVO { @@ -30,7 +28,7 @@ public class IotOtaFirmwareCreateReqVO {
30 // TODO @li:是不是必传哈 28 // TODO @li:是不是必传哈
31 private String signMethod; 29 private String signMethod;
32 30
33 - @Schema(description = "固件文件 URL", requiredMode = REQUIRED, example = "https://www.iocoder.cn/yudao-firmware.zip") 31 + @Schema(description = "固件文件 URL", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/yudao-firmware.zip")
34 @NotEmpty(message = "固件文件 URL 不能为空") 32 @NotEmpty(message = "固件文件 URL 不能为空")
35 private String fileUrl; 33 private String fileUrl;
36 34
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/firmware/IotOtaFirmwareRespVO.java
@@ -7,8 +7,6 @@ import com.fhs.core.trans.vo.VO; @@ -7,8 +7,6 @@ import com.fhs.core.trans.vo.VO;
7 import io.swagger.v3.oas.annotations.media.Schema; 7 import io.swagger.v3.oas.annotations.media.Schema;
8 import lombok.Data; 8 import lombok.Data;
9 9
10 -import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;  
11 -  
12 @Data 10 @Data
13 @Schema(description = "管理后台 - IoT OTA 固件 Response VO") 11 @Schema(description = "管理后台 - IoT OTA 固件 Response VO")
14 public class IotOtaFirmwareRespVO implements VO { 12 public class IotOtaFirmwareRespVO implements VO {
@@ -16,12 +14,12 @@ public class IotOtaFirmwareRespVO implements VO { @@ -16,12 +14,12 @@ public class IotOtaFirmwareRespVO implements VO {
16 /** 14 /**
17 * 固件编号 15 * 固件编号
18 */ 16 */
19 - @Schema(description = "固件编号", requiredMode = REQUIRED, example = "1024") 17 + @Schema(description = "固件编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
20 private Long id; 18 private Long id;
21 /** 19 /**
22 * 固件名称 20 * 固件名称
23 */ 21 */
24 - @Schema(description = "固件名称", requiredMode = REQUIRED, example = "OTA固件") 22 + @Schema(description = "固件名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "OTA固件")
25 private String name; 23 private String name;
26 /** 24 /**
27 * 固件描述 25 * 固件描述
@@ -31,7 +29,7 @@ public class IotOtaFirmwareRespVO implements VO { @@ -31,7 +29,7 @@ public class IotOtaFirmwareRespVO implements VO {
31 /** 29 /**
32 * 版本号 30 * 版本号
33 */ 31 */
34 - @Schema(description = "版本号", requiredMode = REQUIRED, example = "1.0.0") 32 + @Schema(description = "版本号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1.0.0")
35 private String version; 33 private String version;
36 34
37 /** 35 /**
@@ -39,7 +37,7 @@ public class IotOtaFirmwareRespVO implements VO { @@ -39,7 +37,7 @@ public class IotOtaFirmwareRespVO implements VO {
39 * <p> 37 * <p>
40 * 关联 {@link cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO#getId()} 38 * 关联 {@link cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO#getId()}
41 */ 39 */
42 - @Schema(description = "产品编号", requiredMode = REQUIRED, example = "1024") 40 + @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
43 @Trans(type = TransType.SIMPLE, target = IotProductDO.class, fields = {"name"}, refs = {"productName"}) 41 @Trans(type = TransType.SIMPLE, target = IotProductDO.class, fields = {"name"}, refs = {"productName"})
44 private String productId; 42 private String productId;
45 /** 43 /**
@@ -47,12 +45,12 @@ public class IotOtaFirmwareRespVO implements VO { @@ -47,12 +45,12 @@ public class IotOtaFirmwareRespVO implements VO {
47 * <p> 45 * <p>
48 * 冗余 {@link cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO#getProductKey()} 46 * 冗余 {@link cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO#getProductKey()}
49 */ 47 */
50 - @Schema(description = "产品标识", requiredMode = REQUIRED, example = "iot-product-key") 48 + @Schema(description = "产品标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "iot-product-key")
51 private String productKey; 49 private String productKey;
52 /** 50 /**
53 * 产品名称 51 * 产品名称
54 */ 52 */
55 - @Schema(description = "产品名称", requiredMode = REQUIRED, example = "OTA产品") 53 + @Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "OTA产品")
56 private String productName; 54 private String productName;
57 /** 55 /**
58 * 签名方式 56 * 签名方式
@@ -69,12 +67,12 @@ public class IotOtaFirmwareRespVO implements VO { @@ -69,12 +67,12 @@ public class IotOtaFirmwareRespVO implements VO {
69 /** 67 /**
70 * 固件文件大小 68 * 固件文件大小
71 */ 69 */
72 - @Schema(description = "固件文件大小", requiredMode = REQUIRED, example = "1024") 70 + @Schema(description = "固件文件大小", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
73 private Long fileSize; 71 private Long fileSize;
74 /** 72 /**
75 * 固件文件 URL 73 * 固件文件 URL
76 */ 74 */
77 - @Schema(description = "固件文件 URL", requiredMode = REQUIRED, example = "https://www.iocoder.cn") 75 + @Schema(description = "固件文件 URL", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn")
78 private String fileUrl; 76 private String fileUrl;
79 /** 77 /**
80 * 自定义信息,建议使用 JSON 格式 78 * 自定义信息,建议使用 JSON 格式
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/firmware/IotOtaFirmwareUpdateReqVO.java
@@ -5,18 +5,16 @@ import jakarta.validation.constraints.NotEmpty; @@ -5,18 +5,16 @@ import jakarta.validation.constraints.NotEmpty;
5 import jakarta.validation.constraints.NotNull; 5 import jakarta.validation.constraints.NotNull;
6 import lombok.Data; 6 import lombok.Data;
7 7
8 -import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;  
9 -  
10 @Schema(description = "管理后台 - IoT OTA 固件更新 Request VO") 8 @Schema(description = "管理后台 - IoT OTA 固件更新 Request VO")
11 @Data 9 @Data
12 public class IotOtaFirmwareUpdateReqVO { 10 public class IotOtaFirmwareUpdateReqVO {
13 11
14 - @Schema(description = "固件编号", requiredMode = REQUIRED, example = "1024") 12 + @Schema(description = "固件编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
15 @NotNull(message = "固件编号不能为空") 13 @NotNull(message = "固件编号不能为空")
16 private Long id; 14 private Long id;
17 15
18 // TODO @li:name 是不是可以飞必传哈 16 // TODO @li:name 是不是可以飞必传哈
19 - @Schema(description = "固件名称", requiredMode = REQUIRED, example = "智能开关固件") 17 + @Schema(description = "固件名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "智能开关固件")
20 @NotEmpty(message = "固件名称不能为空") 18 @NotEmpty(message = "固件名称不能为空")
21 private String name; 19 private String name;
22 20
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/upgrade/record/IotOtaUpgradeRecordPageReqVO.java
@@ -5,8 +5,6 @@ import io.swagger.v3.oas.annotations.media.Schema; @@ -5,8 +5,6 @@ import io.swagger.v3.oas.annotations.media.Schema;
5 import jakarta.validation.constraints.NotNull; 5 import jakarta.validation.constraints.NotNull;
6 import lombok.Data; 6 import lombok.Data;
7 7
8 -import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;  
9 -  
10 @Data 8 @Data
11 @Schema(description = "管理后台 - IoT OTA 升级记录分页 Request VO") 9 @Schema(description = "管理后台 - IoT OTA 升级记录分页 Request VO")
12 public class IotOtaUpgradeRecordPageReqVO extends PageParam { 10 public class IotOtaUpgradeRecordPageReqVO extends PageParam {
@@ -17,7 +15,7 @@ public class IotOtaUpgradeRecordPageReqVO extends PageParam { @@ -17,7 +15,7 @@ public class IotOtaUpgradeRecordPageReqVO extends PageParam {
17 * <p> 15 * <p>
18 * 该字段用于标识升级任务的唯一编号,不能为空。 16 * 该字段用于标识升级任务的唯一编号,不能为空。
19 */ 17 */
20 - @Schema(description = "升级任务编号", requiredMode = REQUIRED, example = "1024") 18 + @Schema(description = "升级任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
21 @NotNull(message = "升级任务编号不能为空") 19 @NotNull(message = "升级任务编号不能为空")
22 private Long taskId; 20 private Long taskId;
23 21
@@ -26,7 +24,7 @@ public class IotOtaUpgradeRecordPageReqVO extends PageParam { @@ -26,7 +24,7 @@ public class IotOtaUpgradeRecordPageReqVO extends PageParam {
26 * <p> 24 * <p>
27 * 该字段用于标识设备的名称,通常用于区分不同的设备。 25 * 该字段用于标识设备的名称,通常用于区分不同的设备。
28 */ 26 */
29 - @Schema(description = "设备标识", requiredMode = REQUIRED, example = "摄像头A1-1") 27 + @Schema(description = "设备标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "摄像头A1-1")
30 private String deviceName; 28 private String deviceName;
31 29
32 } 30 }
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/upgrade/record/IotOtaUpgradeRecordRespVO.java
@@ -10,8 +10,6 @@ import lombok.Data; @@ -10,8 +10,6 @@ import lombok.Data;
10 10
11 import java.time.LocalDateTime; 11 import java.time.LocalDateTime;
12 12
13 -import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;  
14 -  
15 @Data 13 @Data
16 @Schema(description = "管理后台 - IoT OTA 升级记录 Response VO") 14 @Schema(description = "管理后台 - IoT OTA 升级记录 Response VO")
17 public class IotOtaUpgradeRecordRespVO { 15 public class IotOtaUpgradeRecordRespVO {
@@ -19,73 +17,73 @@ public class IotOtaUpgradeRecordRespVO { @@ -19,73 +17,73 @@ public class IotOtaUpgradeRecordRespVO {
19 /** 17 /**
20 * 升级记录编号 18 * 升级记录编号
21 */ 19 */
22 - @Schema(description = "升级记录编号", requiredMode = REQUIRED, example = "1024") 20 + @Schema(description = "升级记录编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
23 private Long id; 21 private Long id;
24 /** 22 /**
25 * 固件编号 23 * 固件编号
26 * <p> 24 * <p>
27 * 关联 {@link IotOtaFirmwareDO#getId()} 25 * 关联 {@link IotOtaFirmwareDO#getId()}
28 */ 26 */
29 - @Schema(description = "固件编号", requiredMode = REQUIRED, example = "1024") 27 + @Schema(description = "固件编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
30 @Trans(type = TransType.SIMPLE, target = IotOtaFirmwareDO.class, fields = {"version"}, refs = {"firmwareVersion"}) 28 @Trans(type = TransType.SIMPLE, target = IotOtaFirmwareDO.class, fields = {"version"}, refs = {"firmwareVersion"})
31 private Long firmwareId; 29 private Long firmwareId;
32 /** 30 /**
33 * 固件版本 31 * 固件版本
34 */ 32 */
35 - @Schema(description = "固件版本", requiredMode = REQUIRED, example = "v1.0.0") 33 + @Schema(description = "固件版本", requiredMode = Schema.RequiredMode.REQUIRED, example = "v1.0.0")
36 private String firmwareVersion; 34 private String firmwareVersion;
37 /** 35 /**
38 * 任务编号 36 * 任务编号
39 * <p> 37 * <p>
40 * 关联 {@link IotOtaUpgradeTaskDO#getId()} 38 * 关联 {@link IotOtaUpgradeTaskDO#getId()}
41 */ 39 */
42 - @Schema(description = "任务编号", requiredMode = REQUIRED, example = "1024") 40 + @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
43 private Long taskId; 41 private Long taskId;
44 /** 42 /**
45 * 产品标识 43 * 产品标识
46 * <p> 44 * <p>
47 * 关联 {@link cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO#getId()} 45 * 关联 {@link cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO#getId()}
48 */ 46 */
49 - @Schema(description = "产品标识", requiredMode = REQUIRED, example = "iot") 47 + @Schema(description = "产品标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "iot")
50 private String productKey; 48 private String productKey;
51 /** 49 /**
52 * 设备名称 50 * 设备名称
53 * <p> 51 * <p>
54 * 关联 {@link cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO#getId()} 52 * 关联 {@link cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO#getId()}
55 */ 53 */
56 - @Schema(description = "设备名称", requiredMode = REQUIRED, example = "iot") 54 + @Schema(description = "设备名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "iot")
57 private String deviceName; 55 private String deviceName;
58 /** 56 /**
59 * 设备编号 57 * 设备编号
60 * <p> 58 * <p>
61 * 关联 {@link cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO#getId()} 59 * 关联 {@link cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO#getId()}
62 */ 60 */
63 - @Schema(description = "设备编号", requiredMode = REQUIRED, example = "1024") 61 + @Schema(description = "设备编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
64 private String deviceId; 62 private String deviceId;
65 /** 63 /**
66 * 来源的固件编号 64 * 来源的固件编号
67 * <p> 65 * <p>
68 * 关联 {@link IotDeviceDO#getFirmwareId()} 66 * 关联 {@link IotDeviceDO#getFirmwareId()}
69 */ 67 */
70 - @Schema(description = "来源的固件编号", requiredMode = REQUIRED, example = "1024") 68 + @Schema(description = "来源的固件编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
71 @Trans(type = TransType.SIMPLE, target = IotOtaFirmwareDO.class, fields = {"version"}, refs = {"fromFirmwareVersion"}) 69 @Trans(type = TransType.SIMPLE, target = IotOtaFirmwareDO.class, fields = {"version"}, refs = {"fromFirmwareVersion"})
72 private Long fromFirmwareId; 70 private Long fromFirmwareId;
73 /** 71 /**
74 * 来源的固件版本 72 * 来源的固件版本
75 */ 73 */
76 - @Schema(description = "来源的固件版本", requiredMode = REQUIRED, example = "v1.0.0") 74 + @Schema(description = "来源的固件版本", requiredMode = Schema.RequiredMode.REQUIRED, example = "v1.0.0")
77 private String fromFirmwareVersion; 75 private String fromFirmwareVersion;
78 /** 76 /**
79 * 升级状态 77 * 升级状态
80 * <p> 78 * <p>
81 * 关联 {@link cn.iocoder.yudao.module.iot.enums.ota.IotOtaUpgradeRecordStatusEnum} 79 * 关联 {@link cn.iocoder.yudao.module.iot.enums.ota.IotOtaUpgradeRecordStatusEnum}
82 */ 80 */
83 - @Schema(description = "升级状态", requiredMode = REQUIRED, allowableValues = {"0", "10", "20", "30", "40", "50"}) 81 + @Schema(description = "升级状态", requiredMode = Schema.RequiredMode.REQUIRED, allowableValues = {"0", "10", "20", "30", "40", "50"})
84 private Integer status; 82 private Integer status;
85 /** 83 /**
86 * 升级进度,百分比 84 * 升级进度,百分比
87 */ 85 */
88 - @Schema(description = "升级进度,百分比", requiredMode = REQUIRED, example = "10") 86 + @Schema(description = "升级进度,百分比", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
89 private Integer progress; 87 private Integer progress;
90 /** 88 /**
91 * 升级进度描述 89 * 升级进度描述
@@ -93,17 +91,17 @@ public class IotOtaUpgradeRecordRespVO { @@ -93,17 +91,17 @@ public class IotOtaUpgradeRecordRespVO {
93 * 注意,只记录设备最后一次的升级进度描述 91 * 注意,只记录设备最后一次的升级进度描述
94 * 如果想看历史记录,可以查看 {@link cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceLogDO} 设备日志 92 * 如果想看历史记录,可以查看 {@link cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceLogDO} 设备日志
95 */ 93 */
96 - @Schema(description = "升级进度描述", requiredMode = REQUIRED, example = "10") 94 + @Schema(description = "升级进度描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
97 private String description; 95 private String description;
98 /** 96 /**
99 * 升级开始时间 97 * 升级开始时间
100 */ 98 */
101 - @Schema(description = "升级开始时间", requiredMode = REQUIRED, example = "2022-07-08 07:30:00") 99 + @Schema(description = "升级开始时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "2022-07-08 07:30:00")
102 private LocalDateTime startTime; 100 private LocalDateTime startTime;
103 /** 101 /**
104 * 升级结束时间 102 * 升级结束时间
105 */ 103 */
106 - @Schema(description = "升级结束时间", requiredMode = REQUIRED, example = "2022-07-08 07:30:00") 104 + @Schema(description = "升级结束时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "2022-07-08 07:30:00")
107 private LocalDateTime endTime; 105 private LocalDateTime endTime;
108 106
109 } 107 }
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/upgrade/task/IotOtaUpgradeTaskPageReqVO.java
@@ -5,8 +5,6 @@ import io.swagger.v3.oas.annotations.media.Schema; @@ -5,8 +5,6 @@ import io.swagger.v3.oas.annotations.media.Schema;
5 import jakarta.validation.constraints.NotNull; 5 import jakarta.validation.constraints.NotNull;
6 import lombok.Data; 6 import lombok.Data;
7 7
8 -import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;  
9 -  
10 @Data 8 @Data
11 @Schema(description = "管理后台 - IoT OTA 升级任务分页 Request VO") 9 @Schema(description = "管理后台 - IoT OTA 升级任务分页 Request VO")
12 public class IotOtaUpgradeTaskPageReqVO extends PageParam { 10 public class IotOtaUpgradeTaskPageReqVO extends PageParam {
@@ -21,7 +19,7 @@ public class IotOtaUpgradeTaskPageReqVO extends PageParam { @@ -21,7 +19,7 @@ public class IotOtaUpgradeTaskPageReqVO extends PageParam {
21 * 固件编号字段,用于唯一标识固件,不能为空 19 * 固件编号字段,用于唯一标识固件,不能为空
22 */ 20 */
23 @NotNull(message = "固件编号不能为空") 21 @NotNull(message = "固件编号不能为空")
24 - @Schema(description = "固件编号", requiredMode = REQUIRED, example = "1024") 22 + @Schema(description = "固件编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
25 private Long firmwareId; 23 private Long firmwareId;
26 24
27 } 25 }
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/upgrade/task/IotOtaUpgradeTaskRespVO.java
@@ -9,8 +9,6 @@ import lombok.Data; @@ -9,8 +9,6 @@ import lombok.Data;
9 import java.time.LocalDateTime; 9 import java.time.LocalDateTime;
10 import java.util.List; 10 import java.util.List;
11 11
12 -import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;  
13 -  
14 @Data 12 @Data
15 @Schema(description = "管理后台 - IoT OTA 升级任务 Response VO") 13 @Schema(description = "管理后台 - IoT OTA 升级任务 Response VO")
16 public class IotOtaUpgradeTaskRespVO implements VO { 14 public class IotOtaUpgradeTaskRespVO implements VO {
@@ -18,12 +16,12 @@ public class IotOtaUpgradeTaskRespVO implements VO { @@ -18,12 +16,12 @@ public class IotOtaUpgradeTaskRespVO implements VO {
18 /** 16 /**
19 * 任务编号 17 * 任务编号
20 */ 18 */
21 - @Schema(description = "任务编号", requiredMode = REQUIRED, example = "1024") 19 + @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
22 private Long id; 20 private Long id;
23 /** 21 /**
24 * 任务名称 22 * 任务名称
25 */ 23 */
26 - @Schema(description = "任务名称", requiredMode = REQUIRED, example = "升级任务") 24 + @Schema(description = "任务名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "升级任务")
27 private String name; 25 private String name;
28 /** 26 /**
29 * 任务描述 27 * 任务描述
@@ -35,31 +33,31 @@ public class IotOtaUpgradeTaskRespVO implements VO { @@ -35,31 +33,31 @@ public class IotOtaUpgradeTaskRespVO implements VO {
35 * <p> 33 * <p>
36 * 关联 {@link IotOtaFirmwareDO#getId()} 34 * 关联 {@link IotOtaFirmwareDO#getId()}
37 */ 35 */
38 - @Schema(description = "固件编号", requiredMode = REQUIRED, example = "1024") 36 + @Schema(description = "固件编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
39 private Long firmwareId; 37 private Long firmwareId;
40 /** 38 /**
41 * 任务状态 39 * 任务状态
42 * <p> 40 * <p>
43 * 关联 {@link cn.iocoder.yudao.module.iot.enums.ota.IotOtaUpgradeTaskStatusEnum} 41 * 关联 {@link cn.iocoder.yudao.module.iot.enums.ota.IotOtaUpgradeTaskStatusEnum}
44 */ 42 */
45 - @Schema(description = "任务状态", requiredMode = REQUIRED, allowableValues = {"10", "20", "21", "30"}) 43 + @Schema(description = "任务状态", requiredMode = Schema.RequiredMode.REQUIRED, allowableValues = {"10", "20", "21", "30"})
46 private Integer status; 44 private Integer status;
47 /** 45 /**
48 * 任务状态名称 46 * 任务状态名称
49 */ 47 */
50 - @Schema(description = "任务状态名称", requiredMode = REQUIRED, example = "进行中") 48 + @Schema(description = "任务状态名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "进行中")
51 private String statusName; 49 private String statusName;
52 /** 50 /**
53 * 升级范围 51 * 升级范围
54 * <p> 52 * <p>
55 * 关联 {@link cn.iocoder.yudao.module.iot.enums.ota.IotOtaUpgradeTaskScopeEnum} 53 * 关联 {@link cn.iocoder.yudao.module.iot.enums.ota.IotOtaUpgradeTaskScopeEnum}
56 */ 54 */
57 - @Schema(description = "升级范围", requiredMode = REQUIRED, allowableValues = {"1", "2"}) 55 + @Schema(description = "升级范围", requiredMode = Schema.RequiredMode.REQUIRED, allowableValues = {"1", "2"})
58 private Integer scope; 56 private Integer scope;
59 /** 57 /**
60 * 设备数量 58 * 设备数量
61 */ 59 */
62 - @Schema(description = "设备数量", requiredMode = REQUIRED, example = "1024") 60 + @Schema(description = "设备数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
63 private Long deviceCount; 61 private Long deviceCount;
64 /** 62 /**
65 * 选中的设备编号数组 63 * 选中的设备编号数组
@@ -78,7 +76,7 @@ public class IotOtaUpgradeTaskRespVO implements VO { @@ -78,7 +76,7 @@ public class IotOtaUpgradeTaskRespVO implements VO {
78 /** 76 /**
79 * 创建时间 77 * 创建时间
80 */ 78 */
81 - @Schema(description = "创建时间", requiredMode = REQUIRED, example = "2022-07-08 07:30:00") 79 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "2022-07-08 07:30:00")
82 private LocalDateTime createTime; 80 private LocalDateTime createTime;
83 81
84 } 82 }
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/upgrade/task/IotOtaUpgradeTaskSaveReqVO.java
@@ -11,8 +11,6 @@ import lombok.Data; @@ -11,8 +11,6 @@ import lombok.Data;
11 11
12 import java.util.List; 12 import java.util.List;
13 13
14 -import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;  
15 -  
16 @Data 14 @Data
17 @Schema(description = "管理后台 - IoT OTA 升级任务创建/修改 Request VO") 15 @Schema(description = "管理后台 - IoT OTA 升级任务创建/修改 Request VO")
18 public class IotOtaUpgradeTaskSaveReqVO { 16 public class IotOtaUpgradeTaskSaveReqVO {
@@ -24,7 +22,7 @@ public class IotOtaUpgradeTaskSaveReqVO { @@ -24,7 +22,7 @@ public class IotOtaUpgradeTaskSaveReqVO {
24 * 任务名称 22 * 任务名称
25 */ 23 */
26 @NotEmpty(message = "任务名称不能为空") 24 @NotEmpty(message = "任务名称不能为空")
27 - @Schema(description = "任务名称", requiredMode = REQUIRED, example = "升级任务") 25 + @Schema(description = "任务名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "升级任务")
28 private String name; 26 private String name;
29 27
30 /** 28 /**
@@ -39,7 +37,7 @@ public class IotOtaUpgradeTaskSaveReqVO { @@ -39,7 +37,7 @@ public class IotOtaUpgradeTaskSaveReqVO {
39 * 关联 {@link IotOtaFirmwareDO#getId()} 37 * 关联 {@link IotOtaFirmwareDO#getId()}
40 */ 38 */
41 @NotNull(message = "固件编号不能为空") 39 @NotNull(message = "固件编号不能为空")
42 - @Schema(description = "固件编号", requiredMode = REQUIRED, example = "1024") 40 + @Schema(description = "固件编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
43 private Long firmwareId; 41 private Long firmwareId;
44 42
45 /** 43 /**
@@ -49,7 +47,7 @@ public class IotOtaUpgradeTaskSaveReqVO { @@ -49,7 +47,7 @@ public class IotOtaUpgradeTaskSaveReqVO {
49 */ 47 */
50 @NotNull(message = "升级范围不能为空") 48 @NotNull(message = "升级范围不能为空")
51 @InEnum(value = IotOtaUpgradeTaskScopeEnum.class) 49 @InEnum(value = IotOtaUpgradeTaskScopeEnum.class)
52 - @Schema(description = "升级范围", requiredMode = REQUIRED, example = "1") 50 + @Schema(description = "升级范围", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
53 private Integer scope; 51 private Integer scope;
54 52
55 /** 53 /**
@@ -57,7 +55,7 @@ public class IotOtaUpgradeTaskSaveReqVO { @@ -57,7 +55,7 @@ public class IotOtaUpgradeTaskSaveReqVO {
57 * <p> 55 * <p>
58 * 关联 {@link IotDeviceDO#getId()} 56 * 关联 {@link IotDeviceDO#getId()}
59 */ 57 */
60 - @Schema(description = "选中的设备编号数组", requiredMode = REQUIRED, example = "[1,2,3,4]") 58 + @Schema(description = "选中的设备编号数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1,2,3,4]")
61 private List<Long> deviceIds; 59 private List<Long> deviceIds;
62 60
63 } 61 }
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/ProductSkuApi.java
@@ -5,6 +5,9 @@ import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO; @@ -5,6 +5,9 @@ import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO;
5 5
6 import java.util.Collection; 6 import java.util.Collection;
7 import java.util.List; 7 import java.util.List;
  8 +import java.util.Map;
  9 +
  10 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
8 11
9 /** 12 /**
10 * 商品 SKU API 接口 13 * 商品 SKU API 接口
@@ -31,6 +34,16 @@ public interface ProductSkuApi { @@ -31,6 +34,16 @@ public interface ProductSkuApi {
31 List<ProductSkuRespDTO> getSkuList(Collection<Long> ids); 34 List<ProductSkuRespDTO> getSkuList(Collection<Long> ids);
32 35
33 /** 36 /**
  37 + * 批量查询 SKU MAP
  38 + *
  39 + * @param ids SKU 编号列表
  40 + * @return SKU MAP
  41 + */
  42 + default Map<Long, ProductSkuRespDTO> getSkuMap(Collection<Long> ids) {
  43 + return convertMap(getSkuList(ids), ProductSkuRespDTO::getId);
  44 + }
  45 +
  46 + /**
34 * 批量查询 SKU 数组 47 * 批量查询 SKU 数组
35 * 48 *
36 * @param spuIds SPU 编号列表 49 * @param spuIds SPU 编号列表
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/spu/ProductSpuApi.java
@@ -30,7 +30,7 @@ public interface ProductSpuApi { @@ -30,7 +30,7 @@ public interface ProductSpuApi {
30 * @param ids SPU 编号列表 30 * @param ids SPU 编号列表
31 * @return SPU MAP 31 * @return SPU MAP
32 */ 32 */
33 - default Map<Long, ProductSpuRespDTO> getSpusMap(Collection<Long> ids) { 33 + default Map<Long, ProductSpuRespDTO> getSpuMap(Collection<Long> ids) {
34 return convertMap(getSpuList(ids), ProductSpuRespDTO::getId); 34 return convertMap(getSpuList(ids), ProductSpuRespDTO::getId);
35 } 35 }
36 36
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/history/vo/ProductBrowseHistoryRespVO.java
@@ -4,17 +4,15 @@ import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; @@ -4,17 +4,15 @@ import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
4 import io.swagger.v3.oas.annotations.media.Schema; 4 import io.swagger.v3.oas.annotations.media.Schema;
5 import lombok.Data; 5 import lombok.Data;
6 6
7 -import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;  
8 -  
9 @Schema(description = "管理后台 - 商品浏览记录 Response VO") 7 @Schema(description = "管理后台 - 商品浏览记录 Response VO")
10 @Data 8 @Data
11 @ExcelIgnoreUnannotated 9 @ExcelIgnoreUnannotated
12 public class ProductBrowseHistoryRespVO { 10 public class ProductBrowseHistoryRespVO {
13 11
14 - @Schema(description = "编号", requiredMode = REQUIRED, example = "1") 12 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
15 private Long id; 13 private Long id;
16 14
17 - @Schema(description = "商品 SPU 编号", requiredMode = REQUIRED, example = "29502") 15 + @Schema(description = "商品 SPU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "29502")
18 private Long spuId; 16 private Long spuId;
19 17
20 // ========== 商品相关字段 ========== 18 // ========== 商品相关字段 ==========
@@ -34,4 +32,4 @@ public class ProductBrowseHistoryRespVO { @@ -34,4 +32,4 @@ public class ProductBrowseHistoryRespVO {
34 @Schema(description = "库存", example = "100") 32 @Schema(description = "库存", example = "100")
35 private Integer stock; 33 private Integer stock;
36 34
37 -}  
38 \ No newline at end of file 35 \ No newline at end of file
  36 +}
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/favorite/vo/AppFavoriteBatchReqVO.java
@@ -6,13 +6,11 @@ import lombok.Data; @@ -6,13 +6,11 @@ import lombok.Data;
6 import jakarta.validation.constraints.NotEmpty; 6 import jakarta.validation.constraints.NotEmpty;
7 import java.util.List; 7 import java.util.List;
8 8
9 -import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;  
10 -  
11 @Schema(description = "用户 APP - 商品收藏的批量 Request VO") // 用于收藏、取消收藏、获取收藏 9 @Schema(description = "用户 APP - 商品收藏的批量 Request VO") // 用于收藏、取消收藏、获取收藏
12 @Data 10 @Data
13 public class AppFavoriteBatchReqVO { 11 public class AppFavoriteBatchReqVO {
14 12
15 - @Schema(description = "商品 SPU 编号数组", requiredMode = REQUIRED, example = "29502") 13 + @Schema(description = "商品 SPU 编号数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "29502")
16 @NotEmpty(message = "商品 SPU 编号数组不能为空") 14 @NotEmpty(message = "商品 SPU 编号数组不能为空")
17 private List<Long> spuIds; 15 private List<Long> spuIds;
18 16
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/favorite/vo/AppFavoriteReqVO.java
@@ -5,13 +5,11 @@ import lombok.Data; @@ -5,13 +5,11 @@ import lombok.Data;
5 5
6 import jakarta.validation.constraints.NotNull; 6 import jakarta.validation.constraints.NotNull;
7 7
8 -import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;  
9 -  
10 @Schema(description = "用户 APP - 商品收藏的单个 Request VO") // 用于收藏、取消收藏、获取收藏 8 @Schema(description = "用户 APP - 商品收藏的单个 Request VO") // 用于收藏、取消收藏、获取收藏
11 @Data 9 @Data
12 public class AppFavoriteReqVO { 10 public class AppFavoriteReqVO {
13 11
14 - @Schema(description = "商品 SPU 编号", requiredMode = REQUIRED, example = "29502") 12 + @Schema(description = "商品 SPU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "29502")
15 @NotNull(message = "商品 SPU 编号不能为空") 13 @NotNull(message = "商品 SPU 编号不能为空")
16 private Long spuId; 14 private Long spuId;
17 15
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/favorite/vo/AppFavoriteRespVO.java
@@ -2,16 +2,15 @@ package cn.iocoder.yudao.module.product.controller.app.favorite.vo; @@ -2,16 +2,15 @@ package cn.iocoder.yudao.module.product.controller.app.favorite.vo;
2 2
3 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
4 import lombok.Data; 4 import lombok.Data;
5 -import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;  
6 5
7 @Schema(description = "用户 App - 商品收藏 Response VO") 6 @Schema(description = "用户 App - 商品收藏 Response VO")
8 @Data 7 @Data
9 public class AppFavoriteRespVO { 8 public class AppFavoriteRespVO {
10 9
11 - @Schema(description = "编号", requiredMode = REQUIRED, example = "1") 10 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
12 private Long id; 11 private Long id;
13 12
14 - @Schema(description = "商品 SPU 编号", requiredMode = REQUIRED, example = "29502") 13 + @Schema(description = "商品 SPU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "29502")
15 private Long spuId; 14 private Long spuId;
16 15
17 // ========== 商品相关字段 ========== 16 // ========== 商品相关字段 ==========
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/history/vo/AppProductBrowseHistoryDeleteReqVO.java
@@ -6,13 +6,11 @@ import lombok.Data; @@ -6,13 +6,11 @@ import lombok.Data;
6 6
7 import java.util.List; 7 import java.util.List;
8 8
9 -import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;  
10 -  
11 @Schema(description = "用户 APP - 删除商品浏览记录的 Request VO") 9 @Schema(description = "用户 APP - 删除商品浏览记录的 Request VO")
12 @Data 10 @Data
13 public class AppProductBrowseHistoryDeleteReqVO { 11 public class AppProductBrowseHistoryDeleteReqVO {
14 12
15 - @Schema(description = "商品 SPU 编号数组", requiredMode = REQUIRED, example = "29502") 13 + @Schema(description = "商品 SPU 编号数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "29502")
16 @NotEmpty(message = "商品 SPU 编号数组不能为空") 14 @NotEmpty(message = "商品 SPU 编号数组不能为空")
17 private List<Long> spuIds; 15 private List<Long> spuIds;
18 16
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/history/vo/AppProductBrowseHistoryRespVO.java
@@ -3,33 +3,31 @@ package cn.iocoder.yudao.module.product.controller.app.history.vo; @@ -3,33 +3,31 @@ package cn.iocoder.yudao.module.product.controller.app.history.vo;
3 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
4 import lombok.Data; 4 import lombok.Data;
5 5
6 -import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;  
7 -  
8 @Schema(description = "用户 App - 商品浏览记录 Response VO") 6 @Schema(description = "用户 App - 商品浏览记录 Response VO")
9 @Data 7 @Data
10 public class AppProductBrowseHistoryRespVO { 8 public class AppProductBrowseHistoryRespVO {
11 9
12 - @Schema(description = "编号", requiredMode = REQUIRED, example = "1") 10 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
13 private Long id; 11 private Long id;
14 12
15 - @Schema(description = "商品 SPU 编号", requiredMode = REQUIRED, example = "29502") 13 + @Schema(description = "商品 SPU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "29502")
16 private Long spuId; 14 private Long spuId;
17 15
18 // ========== 商品相关字段 ========== 16 // ========== 商品相关字段 ==========
19 17
20 - @Schema(description = "商品 SPU 名称", requiredMode = REQUIRED, example = "赵六") 18 + @Schema(description = "商品 SPU 名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六")
21 private String spuName; 19 private String spuName;
22 20
23 - @Schema(description = "商品封面图", requiredMode = REQUIRED, example = "https://www.iocoder.cn/pic.png") 21 + @Schema(description = "商品封面图", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/pic.png")
24 private String picUrl; 22 private String picUrl;
25 23
26 - @Schema(description = "商品单价", requiredMode = REQUIRED, example = "50") 24 + @Schema(description = "商品单价", requiredMode = Schema.RequiredMode.REQUIRED, example = "50")
27 private Integer price; 25 private Integer price;
28 26
29 - @Schema(description = "商品销量", requiredMode = REQUIRED, example = "60") 27 + @Schema(description = "商品销量", requiredMode = Schema.RequiredMode.REQUIRED, example = "60")
30 private Integer salesCount; 28 private Integer salesCount;
31 29
32 - @Schema(description = "库存", requiredMode = REQUIRED, example = "80") 30 + @Schema(description = "库存", requiredMode = Schema.RequiredMode.REQUIRED, example = "80")
33 private Integer stock; 31 private Integer stock;
34 32
35 } 33 }
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/sku/ProductSkuMapper.java
@@ -6,6 +6,8 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; @@ -6,6 +6,8 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
6 import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO; 6 import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
7 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; 7 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
8 import org.apache.ibatis.annotations.Mapper; 8 import org.apache.ibatis.annotations.Mapper;
  9 +import org.apache.ibatis.annotations.Param;
  10 +import org.apache.ibatis.annotations.Select;
9 11
10 import java.util.Collection; 12 import java.util.Collection;
11 import java.util.List; 13 import java.util.List;
@@ -13,6 +15,9 @@ import java.util.List; @@ -13,6 +15,9 @@ import java.util.List;
13 @Mapper 15 @Mapper
14 public interface ProductSkuMapper extends BaseMapperX<ProductSkuDO> { 16 public interface ProductSkuMapper extends BaseMapperX<ProductSkuDO> {
15 17
  18 + @Select("SELECT * FROM product_sku WHERE id = #{id}")
  19 + ProductSkuDO selectByIdIncludeDeleted(@Param("id") Long id);
  20 +
16 default List<ProductSkuDO> selectListBySpuId(Long spuId) { 21 default List<ProductSkuDO> selectListBySpuId(Long spuId) {
17 return selectList(ProductSkuDO::getSpuId, spuId); 22 return selectList(ProductSkuDO::getSpuId, spuId);
18 } 23 }
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/spu/ProductSpuMapper.java
@@ -11,6 +11,8 @@ import cn.iocoder.yudao.module.product.enums.ProductConstants; @@ -11,6 +11,8 @@ import cn.iocoder.yudao.module.product.enums.ProductConstants;
11 import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum; 11 import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum;
12 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; 12 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
13 import org.apache.ibatis.annotations.Mapper; 13 import org.apache.ibatis.annotations.Mapper;
  14 +import org.apache.ibatis.annotations.Param;
  15 +import org.apache.ibatis.annotations.Select;
14 16
15 import java.util.Objects; 17 import java.util.Objects;
16 import java.util.Set; 18 import java.util.Set;
@@ -18,6 +20,9 @@ import java.util.Set; @@ -18,6 +20,9 @@ import java.util.Set;
18 @Mapper 20 @Mapper
19 public interface ProductSpuMapper extends BaseMapperX<ProductSpuDO> { 21 public interface ProductSpuMapper extends BaseMapperX<ProductSpuDO> {
20 22
  23 + @Select("SELECT * FROM product_spu WHERE id = #{id}")
  24 + ProductSpuDO selectByIdIncludeDeleted(@Param("id") Long id);
  25 +
21 /** 26 /**
22 * 获取商品 SPU 分页列表数据 27 * 获取商品 SPU 分页列表数据
23 * 28 *
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/comment/ProductCommentServiceImpl.java
@@ -91,7 +91,7 @@ public class ProductCommentServiceImpl implements ProductCommentService { @@ -91,7 +91,7 @@ public class ProductCommentServiceImpl implements ProductCommentService {
91 } 91 }
92 92
93 private ProductSkuDO validateSku(Long skuId) { 93 private ProductSkuDO validateSku(Long skuId) {
94 - ProductSkuDO sku = productSkuService.getSku(skuId); 94 + ProductSkuDO sku = productSkuService.getSku(skuId, true);
95 if (sku == null) { 95 if (sku == null) {
96 throw exception(SKU_NOT_EXISTS); 96 throw exception(SKU_NOT_EXISTS);
97 } 97 }
@@ -99,7 +99,7 @@ public class ProductCommentServiceImpl implements ProductCommentService { @@ -99,7 +99,7 @@ public class ProductCommentServiceImpl implements ProductCommentService {
99 } 99 }
100 100
101 private ProductSpuDO validateSpu(Long spuId) { 101 private ProductSpuDO validateSpu(Long spuId) {
102 - ProductSpuDO spu = productSpuService.getSpu(spuId); 102 + ProductSpuDO spu = productSpuService.getSpu(spuId, true);
103 if (null == spu) { 103 if (null == spu) {
104 throw exception(SPU_NOT_EXISTS); 104 throw exception(SPU_NOT_EXISTS);
105 } 105 }
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuService.java
@@ -30,6 +30,15 @@ public interface ProductSkuService { @@ -30,6 +30,15 @@ public interface ProductSkuService {
30 ProductSkuDO getSku(Long id); 30 ProductSkuDO getSku(Long id);
31 31
32 /** 32 /**
  33 + * 获得商品 SKU 信息
  34 + *
  35 + * @param id 编号
  36 + * @param includeDeleted 是否包含已删除的
  37 + * @return 商品 SKU 信息
  38 + */
  39 + ProductSkuDO getSku(Long id, boolean includeDeleted);
  40 +
  41 + /**
33 * 获得商品 SKU 列表 42 * 获得商品 SKU 列表
34 * 43 *
35 * @param ids 编号 44 * @param ids 编号
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuServiceImpl.java
@@ -69,6 +69,14 @@ public class ProductSkuServiceImpl implements ProductSkuService { @@ -69,6 +69,14 @@ public class ProductSkuServiceImpl implements ProductSkuService {
69 } 69 }
70 70
71 @Override 71 @Override
  72 + public ProductSkuDO getSku(Long id, boolean includeDeleted) {
  73 + if (includeDeleted) {
  74 + return productSkuMapper.selectByIdIncludeDeleted(id);
  75 + }
  76 + return getSku(id);
  77 + }
  78 +
  79 + @Override
72 public List<ProductSkuDO> getSkuList(Collection<Long> ids) { 80 public List<ProductSkuDO> getSkuList(Collection<Long> ids) {
73 if (CollUtil.isEmpty(ids)) { 81 if (CollUtil.isEmpty(ids)) {
74 return ListUtil.empty(); 82 return ListUtil.empty();
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuService.java
@@ -52,6 +52,15 @@ public interface ProductSpuService { @@ -52,6 +52,15 @@ public interface ProductSpuService {
52 ProductSpuDO getSpu(Long id); 52 ProductSpuDO getSpu(Long id);
53 53
54 /** 54 /**
  55 + * 获得商品 SPU
  56 + *
  57 + * @param id 编号
  58 + * @param includeDeleted 是否包含已删除的
  59 + * @return 商品 SPU
  60 + */
  61 + ProductSpuDO getSpu(Long id, boolean includeDeleted);
  62 +
  63 + /**
55 * 获得商品 SPU 列表 64 * 获得商品 SPU 列表
56 * 65 *
57 * @param ids 编号数组 66 * @param ids 编号数组
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuServiceImpl.java
@@ -190,6 +190,14 @@ public class ProductSpuServiceImpl implements ProductSpuService { @@ -190,6 +190,14 @@ public class ProductSpuServiceImpl implements ProductSpuService {
190 } 190 }
191 191
192 @Override 192 @Override
  193 + public ProductSpuDO getSpu(Long id, boolean includeDeleted) {
  194 + if (includeDeleted) {
  195 + return productSpuMapper.selectByIdIncludeDeleted(id);
  196 + }
  197 + return getSpu(id);
  198 + }
  199 +
  200 + @Override
193 public List<ProductSpuDO> getSpuList(Collection<Long> ids) { 201 public List<ProductSpuDO> getSpuList(Collection<Long> ids) {
194 if (CollUtil.isEmpty(ids)) { 202 if (CollUtil.isEmpty(ids)) {
195 return Collections.emptyList(); 203 return Collections.emptyList();
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/point/PointActivityController.java
@@ -124,7 +124,7 @@ public class PointActivityController { @@ -124,7 +124,7 @@ public class PointActivityController {
124 List<PointProductDO> products = pointActivityService.getPointProductListByActivityIds( 124 List<PointProductDO> products = pointActivityService.getPointProductListByActivityIds(
125 convertSet(activityList, PointActivityDO::getId)); 125 convertSet(activityList, PointActivityDO::getId));
126 Map<Long, List<PointProductDO>> productsMap = convertMultiMap(products, PointProductDO::getActivityId); 126 Map<Long, List<PointProductDO>> productsMap = convertMultiMap(products, PointProductDO::getActivityId);
127 - Map<Long, ProductSpuRespDTO> spuMap = productSpuApi.getSpusMap( 127 + Map<Long, ProductSpuRespDTO> spuMap = productSpuApi.getSpuMap(
128 convertSet(activityList, PointActivityDO::getSpuId)); 128 convertSet(activityList, PointActivityDO::getSpuId));
129 List<PointActivityRespVO> result = BeanUtils.toBean(activityList, PointActivityRespVO.class); 129 List<PointActivityRespVO> result = BeanUtils.toBean(activityList, PointActivityRespVO.class);
130 result.forEach(activity -> { 130 result.forEach(activity -> {
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/vo/template/AppCouponTemplateRespVO.java
1 package cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.template; 1 package cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.template;
2 2
3 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.Min;
4 import lombok.Data; 5 import lombok.Data;
5 6
6 -import jakarta.validation.constraints.Min;  
7 import java.time.LocalDateTime; 7 import java.time.LocalDateTime;
8 import java.util.List; 8 import java.util.List;
9 9
@@ -20,6 +20,9 @@ public class AppCouponTemplateRespVO { @@ -20,6 +20,9 @@ public class AppCouponTemplateRespVO {
20 @Schema(description = "优惠券说明", example = "优惠券使用说明") 20 @Schema(description = "优惠券说明", example = "优惠券使用说明")
21 private String description; 21 private String description;
22 22
  23 + @Schema(description = "发行总量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") // -1 - 则表示不限制发放数量
  24 + private Integer totalCount;
  25 +
23 @Schema(description = "每人限领个数", requiredMode = Schema.RequiredMode.REQUIRED, example = "66") // -1 - 则表示不限制 26 @Schema(description = "每人限领个数", requiredMode = Schema.RequiredMode.REQUIRED, example = "66") // -1 - 则表示不限制
24 private Integer takeLimitCount; 27 private Integer takeLimitCount;
25 28
@@ -62,6 +65,9 @@ public class AppCouponTemplateRespVO { @@ -62,6 +65,9 @@ public class AppCouponTemplateRespVO {
62 @Schema(description = "折扣上限", example = "100") // 单位:分,仅在 discountType 为 PERCENT 使用 65 @Schema(description = "折扣上限", example = "100") // 单位:分,仅在 discountType 为 PERCENT 使用
63 private Integer discountLimitPrice; 66 private Integer discountLimitPrice;
64 67
  68 + @Schema(description = "领取优惠券的数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  69 + private Integer takeCount;
  70 +
65 // ========== 用户相关字段 ========== 71 // ========== 用户相关字段 ==========
66 72
67 @Schema(description = "是否可以领取", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") 73 @Schema(description = "是否可以领取", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/point/AppPointActivityController.java
@@ -104,7 +104,7 @@ public class AppPointActivityController { @@ -104,7 +104,7 @@ public class AppPointActivityController {
104 List<PointProductDO> products = pointActivityService.getPointProductListByActivityIds( 104 List<PointProductDO> products = pointActivityService.getPointProductListByActivityIds(
105 convertSet(activityList, PointActivityDO::getId)); 105 convertSet(activityList, PointActivityDO::getId));
106 Map<Long, List<PointProductDO>> productsMap = convertMultiMap(products, PointProductDO::getActivityId); 106 Map<Long, List<PointProductDO>> productsMap = convertMultiMap(products, PointProductDO::getActivityId);
107 - Map<Long, ProductSpuRespDTO> spuMap = productSpuApi.getSpusMap( 107 + Map<Long, ProductSpuRespDTO> spuMap = productSpuApi.getSpuMap(
108 convertSet(activityList, PointActivityDO::getSpuId)); 108 convertSet(activityList, PointActivityDO::getSpuId));
109 List<AppPointActivityRespVO> result = BeanUtils.toBean(activityList, AppPointActivityRespVO.class); 109 List<AppPointActivityRespVO> result = BeanUtils.toBean(activityList, AppPointActivityRespVO.class);
110 result.forEach(activity -> { 110 result.forEach(activity -> {
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java
@@ -22,6 +22,7 @@ import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTemplateValidityType @@ -22,6 +22,7 @@ import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTemplateValidityType
22 import jakarta.annotation.Resource; 22 import jakarta.annotation.Resource;
23 import lombok.extern.slf4j.Slf4j; 23 import lombok.extern.slf4j.Slf4j;
24 import org.springframework.stereotype.Service; 24 import org.springframework.stereotype.Service;
  25 +import org.springframework.transaction.annotation.Propagation;
25 import org.springframework.transaction.annotation.Transactional; 26 import org.springframework.transaction.annotation.Transactional;
26 import org.springframework.validation.annotation.Validated; 27 import org.springframework.validation.annotation.Validated;
27 28
@@ -180,7 +181,7 @@ public class CouponServiceImpl implements CouponService { @@ -180,7 +181,7 @@ public class CouponServiceImpl implements CouponService {
180 * @param couponId 模版编号 181 * @param couponId 模版编号
181 * @param userId 用户编号 182 * @param userId 用户编号
182 */ 183 */
183 - @Transactional(rollbackFor = Exception.class) 184 + @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW) // 每次调用开启一个新的事务,避免在一个大的事务里面
184 public void invalidateCoupon(Long couponId, Long userId) { 185 public void invalidateCoupon(Long couponId, Long userId) {
185 if (couponId == null || couponId <= 0) { 186 if (couponId == null || couponId <= 0) {
186 return; 187 return;
@@ -270,13 +271,17 @@ public class CouponServiceImpl implements CouponService { @@ -270,13 +271,17 @@ public class CouponServiceImpl implements CouponService {
270 if (CollUtil.isEmpty(userIds)) { 271 if (CollUtil.isEmpty(userIds)) {
271 throw exception(COUPON_TEMPLATE_USER_ALREADY_TAKE); 272 throw exception(COUPON_TEMPLATE_USER_ALREADY_TAKE);
272 } 273 }
273 -  
274 // 校验模板 274 // 校验模板
275 if (couponTemplate == null) { 275 if (couponTemplate == null) {
276 throw exception(COUPON_TEMPLATE_NOT_EXISTS); 276 throw exception(COUPON_TEMPLATE_NOT_EXISTS);
277 } 277 }
278 - // 校验剩余数量  
279 - if (ObjUtil.notEqual(couponTemplate.getTakeLimitCount(), CouponTemplateDO.TIME_LIMIT_COUNT_MAX) // 非不限制 278 + // 校验领取方式
  279 + if (ObjUtil.notEqual(couponTemplate.getTakeType(), takeType.getType())) {
  280 + throw exception(COUPON_TEMPLATE_CANNOT_TAKE);
  281 + }
  282 + // 校验发放数量不能过小(仅在 CouponTakeTypeEnum.USER 用户领取时)
  283 + if (CouponTakeTypeEnum.isUser(couponTemplate.getTakeType())
  284 + && ObjUtil.notEqual(couponTemplate.getTakeLimitCount(), CouponTemplateDO.TIME_LIMIT_COUNT_MAX) // 非不限制
280 && couponTemplate.getTakeCount() + userIds.size() > couponTemplate.getTotalCount()) { 285 && couponTemplate.getTakeCount() + userIds.size() > couponTemplate.getTotalCount()) {
281 throw exception(COUPON_TEMPLATE_NOT_ENOUGH); 286 throw exception(COUPON_TEMPLATE_NOT_ENOUGH);
282 } 287 }
@@ -286,10 +291,6 @@ public class CouponServiceImpl implements CouponService { @@ -286,10 +291,6 @@ public class CouponServiceImpl implements CouponService {
286 throw exception(COUPON_TEMPLATE_EXPIRED); 291 throw exception(COUPON_TEMPLATE_EXPIRED);
287 } 292 }
288 } 293 }
289 - // 校验领取方式  
290 - if (ObjectUtil.notEqual(couponTemplate.getTakeType(), takeType.getType())) {  
291 - throw exception(COUPON_TEMPLATE_CANNOT_TAKE);  
292 - }  
293 } 294 }
294 295
295 /** 296 /**
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/aftersale/AppAfterSaleController.java
1 package cn.iocoder.yudao.module.trade.controller.app.aftersale; 1 package cn.iocoder.yudao.module.trade.controller.app.aftersale;
2 2
3 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 3 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 -import cn.iocoder.yudao.framework.common.pojo.PageParam;  
5 import cn.iocoder.yudao.framework.common.pojo.PageResult; 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  5 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
6 import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSaleCreateReqVO; 6 import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSaleCreateReqVO;
7 import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSaleDeliveryReqVO; 7 import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSaleDeliveryReqVO;
  8 +import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSalePageReqVO;
8 import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSaleRespVO; 9 import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSaleRespVO;
9 -import cn.iocoder.yudao.module.trade.convert.aftersale.AfterSaleConvert; 10 +import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.AfterSaleDO;
10 import cn.iocoder.yudao.module.trade.service.aftersale.AfterSaleService; 11 import cn.iocoder.yudao.module.trade.service.aftersale.AfterSaleService;
11 import io.swagger.v3.oas.annotations.Operation; 12 import io.swagger.v3.oas.annotations.Operation;
12 import io.swagger.v3.oas.annotations.Parameter; 13 import io.swagger.v3.oas.annotations.Parameter;
@@ -31,16 +32,17 @@ public class AppAfterSaleController { @@ -31,16 +32,17 @@ public class AppAfterSaleController {
31 32
32 @GetMapping(value = "/page") 33 @GetMapping(value = "/page")
33 @Operation(summary = "获得售后分页") 34 @Operation(summary = "获得售后分页")
34 - public CommonResult<PageResult<AppAfterSaleRespVO>> getAfterSalePage(PageParam pageParam) {  
35 - return success(AfterSaleConvert.INSTANCE.convertPage02(  
36 - afterSaleService.getAfterSalePage(getLoginUserId(), pageParam))); 35 + public CommonResult<PageResult<AppAfterSaleRespVO>> getAfterSalePage(AppAfterSalePageReqVO pageReqVO) {
  36 + PageResult<AfterSaleDO> pageResult = afterSaleService.getAfterSalePage(getLoginUserId(), pageReqVO);
  37 + return success(BeanUtils.toBean(pageResult, AppAfterSaleRespVO.class));
37 } 38 }
38 39
39 @GetMapping(value = "/get") 40 @GetMapping(value = "/get")
40 @Operation(summary = "获得售后订单") 41 @Operation(summary = "获得售后订单")
41 @Parameter(name = "id", description = "售后编号", required = true, example = "1") 42 @Parameter(name = "id", description = "售后编号", required = true, example = "1")
42 public CommonResult<AppAfterSaleRespVO> getAfterSale(@RequestParam("id") Long id) { 43 public CommonResult<AppAfterSaleRespVO> getAfterSale(@RequestParam("id") Long id) {
43 - return success(AfterSaleConvert.INSTANCE.convert(afterSaleService.getAfterSale(getLoginUserId(), id))); 44 + AfterSaleDO afterSale = afterSaleService.getAfterSale(getLoginUserId(), id);
  45 + return success(BeanUtils.toBean(afterSale, AppAfterSaleRespVO.class));
44 } 46 }
45 47
46 @PostMapping(value = "/create") 48 @PostMapping(value = "/create")
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/aftersale/vo/AppAfterSalePageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.controller.app.aftersale.vo;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +import lombok.EqualsAndHashCode;
  7 +import lombok.ToString;
  8 +
  9 +import java.util.Set;
  10 +
  11 +@Schema(description = "用户 App - 交易售后分页 Request VO")
  12 +@Data
  13 +@EqualsAndHashCode(callSuper = true)
  14 +@ToString(callSuper = true)
  15 +public class AppAfterSalePageReqVO extends PageParam {
  16 +
  17 + @Schema(description = "售后状态", example = "10, 20")
  18 + private Set<Integer> statuses;
  19 +
  20 +}
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/base/spu/AppProductSpuBaseRespVO.java
@@ -3,8 +3,6 @@ package cn.iocoder.yudao.module.trade.controller.app.base.spu; @@ -3,8 +3,6 @@ package cn.iocoder.yudao.module.trade.controller.app.base.spu;
3 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
4 import lombok.Data; 4 import lombok.Data;
5 5
6 -import java.util.List;  
7 -  
8 /** 6 /**
9 * 商品 SPU 基础 Response VO 7 * 商品 SPU 基础 Response VO
10 * 8 *
@@ -25,4 +23,10 @@ public class AppProductSpuBaseRespVO { @@ -25,4 +23,10 @@ public class AppProductSpuBaseRespVO {
25 @Schema(description = "商品分类编号", example = "1") 23 @Schema(description = "商品分类编号", example = "1")
26 private Long categoryId; 24 private Long categoryId;
27 25
  26 + @Schema(description = "商品库存", requiredMode = Schema.RequiredMode.REQUIRED, example = "10000")
  27 + private Integer stock;
  28 +
  29 + @Schema(description = "商品状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  30 + private Integer status;
  31 +
28 } 32 }
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/delivery/vo/pickup/AppDeliveryPickUpStoreRespVO.java
1 package cn.iocoder.yudao.module.trade.controller.app.delivery.vo.pickup; 1 package cn.iocoder.yudao.module.trade.controller.app.delivery.vo.pickup;
2 2
  3 +import com.fasterxml.jackson.annotation.JsonFormat;
3 import io.swagger.v3.oas.annotations.media.Schema; 4 import io.swagger.v3.oas.annotations.media.Schema;
  5 +import jakarta.validation.constraints.NotNull;
4 import lombok.Data; 6 import lombok.Data;
5 7
  8 +import java.time.LocalTime;
  9 +
6 @Schema(description = "用户 App - 自提门店 Response VO") 10 @Schema(description = "用户 App - 自提门店 Response VO")
7 @Data 11 @Data
8 public class AppDeliveryPickUpStoreRespVO { 12 public class AppDeliveryPickUpStoreRespVO {
@@ -28,6 +32,16 @@ public class AppDeliveryPickUpStoreRespVO { @@ -28,6 +32,16 @@ public class AppDeliveryPickUpStoreRespVO {
28 @Schema(description = "门店详细地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "复旦大学路 188 号") 32 @Schema(description = "门店详细地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "复旦大学路 188 号")
29 private String detailAddress; 33 private String detailAddress;
30 34
  35 + @Schema(description = "营业开始时间", requiredMode = Schema.RequiredMode.REQUIRED)
  36 + @NotNull(message = "营业开始时间不能为空")
  37 + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "HH:mm")
  38 + private LocalTime openingTime;
  39 +
  40 + @Schema(description = "营业结束时间", requiredMode = Schema.RequiredMode.REQUIRED)
  41 + @NotNull(message = "营业结束时间不能为空")
  42 + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "HH:mm")
  43 + private LocalTime closingTime;
  44 +
31 @Schema(description = "纬度", requiredMode = Schema.RequiredMode.REQUIRED, example = "5.88") 45 @Schema(description = "纬度", requiredMode = Schema.RequiredMode.REQUIRED, example = "5.88")
32 private Double latitude; 46 private Double latitude;
33 47
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/aftersale/AfterSaleConvert.java
@@ -11,7 +11,6 @@ import cn.iocoder.yudao.module.trade.controller.admin.base.member.user.MemberUse @@ -11,7 +11,6 @@ import cn.iocoder.yudao.module.trade.controller.admin.base.member.user.MemberUse
11 import cn.iocoder.yudao.module.trade.controller.admin.base.product.property.ProductPropertyValueDetailRespVO; 11 import cn.iocoder.yudao.module.trade.controller.admin.base.product.property.ProductPropertyValueDetailRespVO;
12 import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderBaseVO; 12 import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderBaseVO;
13 import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSaleCreateReqVO; 13 import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSaleCreateReqVO;
14 -import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSaleRespVO;  
15 import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.AfterSaleDO; 14 import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.AfterSaleDO;
16 import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.AfterSaleLogDO; 15 import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.AfterSaleLogDO;
17 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO; 16 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
@@ -63,10 +62,6 @@ public interface AfterSaleConvert { @@ -63,10 +62,6 @@ public interface AfterSaleConvert {
63 62
64 ProductPropertyValueDetailRespVO convert(ProductPropertyValueDetailRespDTO bean); 63 ProductPropertyValueDetailRespVO convert(ProductPropertyValueDetailRespDTO bean);
65 64
66 - AppAfterSaleRespVO convert(AfterSaleDO bean);  
67 -  
68 - PageResult<AppAfterSaleRespVO> convertPage02(PageResult<AfterSaleDO> page);  
69 -  
70 default AfterSaleDetailRespVO convert(AfterSaleDO afterSale, TradeOrderDO order, TradeOrderItemDO orderItem, 65 default AfterSaleDetailRespVO convert(AfterSaleDO afterSale, TradeOrderDO order, TradeOrderItemDO orderItem,
71 MemberUserRespDTO user, List<AfterSaleLogDO> logs) { 66 MemberUserRespDTO user, List<AfterSaleLogDO> logs) {
72 AfterSaleDetailRespVO respVO = convert02(afterSale); 67 AfterSaleDetailRespVO respVO = convert02(afterSale);
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/cart/TradeCartConvert.java
1 package cn.iocoder.yudao.module.trade.convert.cart; 1 package cn.iocoder.yudao.module.trade.convert.cart;
2 2
  3 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
3 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO; 4 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
4 import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO; 5 import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
5 import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum; 6 import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum;
@@ -33,21 +34,18 @@ public interface TradeCartConvert { @@ -33,21 +34,18 @@ public interface TradeCartConvert {
33 cartVO.setId(cart.getId()).setCount(cart.getCount()).setSelected(cart.getSelected()); 34 cartVO.setId(cart.getId()).setCount(cart.getCount()).setSelected(cart.getSelected());
34 ProductSpuRespDTO spu = spuMap.get(cart.getSpuId()); 35 ProductSpuRespDTO spu = spuMap.get(cart.getSpuId());
35 ProductSkuRespDTO sku = skuMap.get(cart.getSkuId()); 36 ProductSkuRespDTO sku = skuMap.get(cart.getSkuId());
36 - cartVO.setSpu(convert(spu)).setSku(convert(sku)); 37 + cartVO.setSpu(BeanUtils.toBean(spu, AppProductSpuBaseRespVO.class))
  38 + .setSku(BeanUtils.toBean(sku, AppProductSkuBaseRespVO.class));
37 // 如果 SPU 不存在,或者下架,或者库存不足,说明是无效的 39 // 如果 SPU 不存在,或者下架,或者库存不足,说明是无效的
38 if (spu == null 40 if (spu == null
39 || !ProductSpuStatusEnum.isEnable(spu.getStatus()) 41 || !ProductSpuStatusEnum.isEnable(spu.getStatus())
40 || spu.getStock() <= 0) { 42 || spu.getStock() <= 0) {
41 - cartVO.setSelected(false); // 强制设置成不可选中  
42 invalidList.add(cartVO); 43 invalidList.add(cartVO);
43 } else { 44 } else {
44 - // 虽然 SKU 可能也会不存在,但是可以通过购物车重新选择  
45 validList.add(cartVO); 45 validList.add(cartVO);
46 } 46 }
47 }); 47 });
48 return new AppCartListRespVO().setValidList(validList).setInvalidList(invalidList); 48 return new AppCartListRespVO().setValidList(validList).setInvalidList(invalidList);
49 } 49 }
50 - AppProductSpuBaseRespVO convert(ProductSpuRespDTO spu);  
51 - AppProductSkuBaseRespVO convert(ProductSkuRespDTO sku);  
52 50
53 } 51 }
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/aftersale/AfterSaleMapper.java
1 package cn.iocoder.yudao.module.trade.dal.mysql.aftersale; 1 package cn.iocoder.yudao.module.trade.dal.mysql.aftersale;
2 2
3 -import cn.iocoder.yudao.framework.common.pojo.PageParam;  
4 import cn.iocoder.yudao.framework.common.pojo.PageResult; 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; 4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
6 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; 5 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
7 import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.AfterSalePageReqVO; 6 import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.AfterSalePageReqVO;
  7 +import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSalePageReqVO;
8 import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.AfterSaleDO; 8 import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.AfterSaleDO;
9 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; 9 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
10 import org.apache.ibatis.annotations.Mapper; 10 import org.apache.ibatis.annotations.Mapper;
@@ -27,9 +27,10 @@ public interface AfterSaleMapper extends BaseMapperX&lt;AfterSaleDO&gt; { @@ -27,9 +27,10 @@ public interface AfterSaleMapper extends BaseMapperX&lt;AfterSaleDO&gt; {
27 .orderByDesc(AfterSaleDO::getId)); 27 .orderByDesc(AfterSaleDO::getId));
28 } 28 }
29 29
30 - default PageResult<AfterSaleDO> selectPage(Long userId, PageParam pageParam) {  
31 - return selectPage(pageParam, new LambdaQueryWrapperX<AfterSaleDO>()  
32 - .eqIfPresent(AfterSaleDO::getUserId, userId) 30 + default PageResult<AfterSaleDO> selectPage(Long userId, AppAfterSalePageReqVO pageReqVO) {
  31 + return selectPage(pageReqVO, new LambdaQueryWrapperX<AfterSaleDO>()
  32 + .eq(AfterSaleDO::getUserId, userId)
  33 + .inIfPresent(AfterSaleDO::getStatus, pageReqVO.getStatuses())
33 .orderByDesc(AfterSaleDO::getId)); 34 .orderByDesc(AfterSaleDO::getId));
34 } 35 }
35 36
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleService.java
1 package cn.iocoder.yudao.module.trade.service.aftersale; 1 package cn.iocoder.yudao.module.trade.service.aftersale;
2 2
3 -import cn.iocoder.yudao.framework.common.pojo.PageParam;  
4 import cn.iocoder.yudao.framework.common.pojo.PageResult; 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.AfterSaleDisagreeReqVO; 4 import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.AfterSaleDisagreeReqVO;
6 import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.AfterSalePageReqVO; 5 import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.AfterSalePageReqVO;
7 import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.AfterSaleRefuseReqVO; 6 import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.AfterSaleRefuseReqVO;
8 import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSaleCreateReqVO; 7 import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSaleCreateReqVO;
9 import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSaleDeliveryReqVO; 8 import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSaleDeliveryReqVO;
  9 +import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSalePageReqVO;
10 import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.AfterSaleDO; 10 import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.AfterSaleDO;
11 11
12 /** 12 /**
@@ -28,10 +28,10 @@ public interface AfterSaleService { @@ -28,10 +28,10 @@ public interface AfterSaleService {
28 * 【会员】获得售后订单分页 28 * 【会员】获得售后订单分页
29 * 29 *
30 * @param userId 用户编号 30 * @param userId 用户编号
31 - * @param pageParam 分页参数 31 + * @param pageReqVO 分页参数
32 * @return 售后订单分页 32 * @return 售后订单分页
33 */ 33 */
34 - PageResult<AfterSaleDO> getAfterSalePage(Long userId, PageParam pageParam); 34 + PageResult<AfterSaleDO> getAfterSalePage(Long userId, AppAfterSalePageReqVO pageReqVO);
35 35
36 /** 36 /**
37 * 【会员】获得售后单 37 * 【会员】获得售后单
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleServiceImpl.java
@@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.trade.service.aftersale; @@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.trade.service.aftersale;
3 import cn.hutool.core.map.MapUtil; 3 import cn.hutool.core.map.MapUtil;
4 import cn.hutool.core.util.ObjectUtil; 4 import cn.hutool.core.util.ObjectUtil;
5 import cn.hutool.core.util.StrUtil; 5 import cn.hutool.core.util.StrUtil;
6 -import cn.iocoder.yudao.framework.common.pojo.PageParam;  
7 import cn.iocoder.yudao.framework.common.pojo.PageResult; 6 import cn.iocoder.yudao.framework.common.pojo.PageResult;
8 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; 7 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
9 import cn.iocoder.yudao.module.pay.api.refund.PayRefundApi; 8 import cn.iocoder.yudao.module.pay.api.refund.PayRefundApi;
@@ -16,6 +15,7 @@ import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.AfterSalePage @@ -16,6 +15,7 @@ import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.AfterSalePage
16 import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.AfterSaleRefuseReqVO; 15 import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.AfterSaleRefuseReqVO;
17 import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSaleCreateReqVO; 16 import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSaleCreateReqVO;
18 import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSaleDeliveryReqVO; 17 import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSaleDeliveryReqVO;
  18 +import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSalePageReqVO;
19 import cn.iocoder.yudao.module.trade.convert.aftersale.AfterSaleConvert; 19 import cn.iocoder.yudao.module.trade.convert.aftersale.AfterSaleConvert;
20 import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.AfterSaleDO; 20 import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.AfterSaleDO;
21 import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressDO; 21 import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressDO;
@@ -36,6 +36,7 @@ import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties @@ -36,6 +36,7 @@ import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties
36 import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressService; 36 import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressService;
37 import cn.iocoder.yudao.module.trade.service.order.TradeOrderQueryService; 37 import cn.iocoder.yudao.module.trade.service.order.TradeOrderQueryService;
38 import cn.iocoder.yudao.module.trade.service.order.TradeOrderUpdateService; 38 import cn.iocoder.yudao.module.trade.service.order.TradeOrderUpdateService;
  39 +import jakarta.annotation.Resource;
39 import lombok.extern.slf4j.Slf4j; 40 import lombok.extern.slf4j.Slf4j;
40 import org.springframework.context.annotation.Lazy; 41 import org.springframework.context.annotation.Lazy;
41 import org.springframework.stereotype.Service; 42 import org.springframework.stereotype.Service;
@@ -44,7 +45,6 @@ import org.springframework.transaction.support.TransactionSynchronization; @@ -44,7 +45,6 @@ import org.springframework.transaction.support.TransactionSynchronization;
44 import org.springframework.transaction.support.TransactionSynchronizationManager; 45 import org.springframework.transaction.support.TransactionSynchronizationManager;
45 import org.springframework.validation.annotation.Validated; 46 import org.springframework.validation.annotation.Validated;
46 47
47 -import jakarta.annotation.Resource;  
48 import java.time.LocalDateTime; 48 import java.time.LocalDateTime;
49 49
50 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 50 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -87,8 +87,8 @@ public class AfterSaleServiceImpl implements AfterSaleService { @@ -87,8 +87,8 @@ public class AfterSaleServiceImpl implements AfterSaleService {
87 } 87 }
88 88
89 @Override 89 @Override
90 - public PageResult<AfterSaleDO> getAfterSalePage(Long userId, PageParam pageParam) {  
91 - return tradeAfterSaleMapper.selectPage(userId, pageParam); 90 + public PageResult<AfterSaleDO> getAfterSalePage(Long userId, AppAfterSalePageReqVO pageReqVO) {
  91 + return tradeAfterSaleMapper.selectPage(userId, pageReqVO);
92 } 92 }
93 93
94 @Override 94 @Override
@@ -386,7 +386,7 @@ public class AfterSaleServiceImpl implements AfterSaleService { @@ -386,7 +386,7 @@ public class AfterSaleServiceImpl implements AfterSaleService {
386 public void afterCommit() { 386 public void afterCommit() {
387 // 创建退款单 387 // 创建退款单
388 PayRefundCreateReqDTO createReqDTO = AfterSaleConvert.INSTANCE.convert(userIp, afterSale, tradeOrderProperties) 388 PayRefundCreateReqDTO createReqDTO = AfterSaleConvert.INSTANCE.convert(userIp, afterSale, tradeOrderProperties)
389 - .setReason(StrUtil.format("退款【{}】", afterSale.getSpuName()));; 389 + .setReason(StrUtil.format("退款【{}】", afterSale.getSpuName()));
390 Long payRefundId = payRefundApi.createRefund(createReqDTO); 390 Long payRefundId = payRefundApi.createRefund(createReqDTO);
391 // 更新售后单的退款单号 391 // 更新售后单的退款单号
392 tradeAfterSaleMapper.updateById(new AfterSaleDO().setId(afterSale.getId()).setPayRefundId(payRefundId)); 392 tradeAfterSaleMapper.updateById(new AfterSaleDO().setId(afterSale.getId()).setPayRefundId(payRefundId));
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawServiceImpl.java
@@ -135,7 +135,7 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService { @@ -135,7 +135,7 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService {
135 private Long createPayTransfer(BrokerageWithdrawDO withdraw) { 135 private Long createPayTransfer(BrokerageWithdrawDO withdraw) {
136 // 1.1 获取微信 openid 136 // 1.1 获取微信 openid
137 SocialUserRespDTO socialUser = socialUserApi.getSocialUserByUserId( 137 SocialUserRespDTO socialUser = socialUserApi.getSocialUserByUserId(
138 - UserTypeEnum.MEMBER.getValue(), withdraw.getUserId(), SocialTypeEnum.WECHAT_MINI_APP.getType()); 138 + UserTypeEnum.MEMBER.getValue(), withdraw.getUserId(), SocialTypeEnum.WECHAT_MINI_PROGRAM.getType());
139 // TODO @luchi:这里,需要校验非空。如果空的话,要有业务异常哈; 139 // TODO @luchi:这里,需要校验非空。如果空的话,要有业务异常哈;
140 // 1.2 构建请求 140 // 1.2 构建请求
141 PayTransferCreateReqDTO payTransferCreateReqDTO = new PayTransferCreateReqDTO() 141 PayTransferCreateReqDTO payTransferCreateReqDTO = new PayTransferCreateReqDTO()
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java
@@ -545,6 +545,14 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { @@ -545,6 +545,14 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
545 if (ObjectUtil.notEqual(order.getStatus(), TradeOrderStatusEnum.UNPAID.getStatus())) { 545 if (ObjectUtil.notEqual(order.getStatus(), TradeOrderStatusEnum.UNPAID.getStatus())) {
546 throw exception(ORDER_CANCEL_FAIL_STATUS_NOT_UNPAID); 546 throw exception(ORDER_CANCEL_FAIL_STATUS_NOT_UNPAID);
547 } 547 }
  548 + // 1.3 校验是否支持延迟(不允许取消)
  549 + if (TradeOrderStatusEnum.isUnpaid(order.getStatus())) {
  550 + PayOrderRespDTO payOrder = payOrderApi.getOrder(order.getPayOrderId());
  551 + if (payOrder != null && PayOrderStatusEnum.isSuccess(payOrder.getStatus())) {
  552 + log.warn("[cancelOrderByMember][order({}) 支付单已支付(支付回调延迟),不支持取消]", order.getId());
  553 + throw exception(ORDER_CANCEL_FAIL_STATUS_NOT_UNPAID);
  554 + }
  555 + }
548 556
549 // 2. 取消订单 557 // 2. 取消订单
550 cancelOrder0(order, TradeOrderCancelTypeEnum.MEMBER_CANCEL); 558 cancelOrder0(order, TradeOrderCancelTypeEnum.MEMBER_CANCEL);
@@ -581,6 +589,15 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { @@ -581,6 +589,15 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
581 @Transactional(rollbackFor = Exception.class) 589 @Transactional(rollbackFor = Exception.class)
582 @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.SYSTEM_CANCEL) 590 @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.SYSTEM_CANCEL)
583 public void cancelOrderBySystem(TradeOrderDO order) { 591 public void cancelOrderBySystem(TradeOrderDO order) {
  592 + // 校验是否支持延迟(不允许取消)
  593 + if (TradeOrderStatusEnum.isUnpaid(order.getStatus())) {
  594 + PayOrderRespDTO payOrder = payOrderApi.getOrder(order.getPayOrderId());
  595 + if (payOrder != null && PayOrderStatusEnum.isSuccess(payOrder.getStatus())) {
  596 + log.warn("[cancelOrderBySystem][order({}) 支付单已支付(支付回调延迟),不支持取消]", order.getId());
  597 + return;
  598 + }
  599 + }
  600 +
584 cancelOrder0(order, TradeOrderCancelTypeEnum.PAY_TIMEOUT); 601 cancelOrder0(order, TradeOrderCancelTypeEnum.PAY_TIMEOUT);
585 } 602 }
586 603
@@ -895,12 +912,11 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService { @@ -895,12 +912,11 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
895 if (order == null) { 912 if (order == null) {
896 throw exception(ORDER_NOT_FOUND); 913 throw exception(ORDER_NOT_FOUND);
897 } 914 }
898 -  
899 // 1.3 校验订单是否支付 915 // 1.3 校验订单是否支付
900 if (!order.getPayStatus()) { 916 if (!order.getPayStatus()) {
901 throw exception(ORDER_CANCEL_PAID_FAIL, "已支付"); 917 throw exception(ORDER_CANCEL_PAID_FAIL, "已支付");
902 } 918 }
903 - // 1.3 校验订单是否未退款 919 + // 1.4 校验订单是否未退款
904 if (ObjUtil.notEqual(TradeOrderRefundStatusEnum.NONE.getStatus(), order.getRefundStatus())) { 920 if (ObjUtil.notEqual(TradeOrderRefundStatusEnum.NONE.getStatus(), order.getRefundStatus())) {
905 throw exception(ORDER_CANCEL_PAID_FAIL, "未退款"); 921 throw exception(ORDER_CANCEL_PAID_FAIL, "未退款");
906 } 922 }
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeBrokerageOrderHandler.java
@@ -20,6 +20,7 @@ import org.springframework.stereotype.Component; @@ -20,6 +20,7 @@ import org.springframework.stereotype.Component;
20 20
21 import jakarta.annotation.Resource; 21 import jakarta.annotation.Resource;
22 import java.util.List; 22 import java.util.List;
  23 +import java.util.Map;
23 24
24 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; 25 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
25 26
@@ -101,13 +102,17 @@ public class TradeBrokerageOrderHandler implements TradeOrderHandler { @@ -101,13 +102,17 @@ public class TradeBrokerageOrderHandler implements TradeOrderHandler {
101 protected void addBrokerage(Long userId, List<TradeOrderItemDO> orderItems) { 102 protected void addBrokerage(Long userId, List<TradeOrderItemDO> orderItems) {
102 MemberUserRespDTO user = memberUserApi.getUser(userId); 103 MemberUserRespDTO user = memberUserApi.getUser(userId);
103 Assert.notNull(user); 104 Assert.notNull(user);
104 - ProductSpuRespDTO spu = productSpuApi.getSpu(orderItems.get(0).getSpuId());  
105 - Assert.notNull(spu);  
106 - ProductSkuRespDTO sku = productSkuApi.getSku(orderItems.get(0).getSkuId()); 105 + Map<Long, ProductSpuRespDTO> spusMap = productSpuApi.getSpuMap(convertList(orderItems, TradeOrderItemDO::getSpuId));
  106 + Map<Long, ProductSkuRespDTO> skusMap = productSkuApi.getSkuMap(convertList(orderItems, TradeOrderItemDO::getSkuId));
107 107
108 // 每一个订单项,都会去生成分销记录 108 // 每一个订单项,都会去生成分销记录
109 - List<BrokerageAddReqBO> addList = convertList(orderItems,  
110 - item -> TradeOrderConvert.INSTANCE.convert(user, item, spu, sku)); 109 + List<BrokerageAddReqBO> addList = convertList(orderItems, item -> {
  110 + ProductSpuRespDTO spu = spusMap.get(item.getSpuId());
  111 + Assert.notNull(spu);
  112 + ProductSkuRespDTO sku = skusMap.get(item.getSkuId());
  113 + Assert.notNull(sku);
  114 + return TradeOrderConvert.INSTANCE.convert(user, item, spu, sku);
  115 + });
111 brokerageRecordService.addBrokerage(userId, BrokerageRecordBizTypeEnum.ORDER, addList); 116 brokerageRecordService.addBrokerage(userId, BrokerageRecordBizTypeEnum.ORDER, addList);
112 } 117 }
113 118
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/auth/MemberAuthServiceImpl.java
1 package cn.iocoder.yudao.module.member.service.auth; 1 package cn.iocoder.yudao.module.member.service.auth;
2 2
3 import cn.hutool.core.lang.Assert; 3 import cn.hutool.core.lang.Assert;
4 -import cn.hutool.core.util.ObjectUtil;  
5 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; 4 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
6 import cn.iocoder.yudao.framework.common.enums.TerminalEnum; 5 import cn.iocoder.yudao.framework.common.enums.TerminalEnum;
7 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; 6 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
@@ -27,11 +26,11 @@ import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum; @@ -27,11 +26,11 @@ import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum;
27 import cn.iocoder.yudao.module.system.enums.oauth2.OAuth2ClientConstants; 26 import cn.iocoder.yudao.module.system.enums.oauth2.OAuth2ClientConstants;
28 import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum; 27 import cn.iocoder.yudao.module.system.enums.sms.SmsSceneEnum;
29 import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum; 28 import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
  29 +import jakarta.annotation.Resource;
30 import lombok.extern.slf4j.Slf4j; 30 import lombok.extern.slf4j.Slf4j;
31 import org.springframework.stereotype.Service; 31 import org.springframework.stereotype.Service;
32 import org.springframework.transaction.annotation.Transactional; 32 import org.springframework.transaction.annotation.Transactional;
33 33
34 -import jakarta.annotation.Resource;  
35 import java.util.Objects; 34 import java.util.Objects;
36 35
37 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 36 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -147,7 +146,7 @@ public class MemberAuthServiceImpl implements MemberAuthService { @@ -147,7 +146,7 @@ public class MemberAuthServiceImpl implements MemberAuthService {
147 146
148 // 绑定社交用户 147 // 绑定社交用户
149 String openid = socialUserApi.bindSocialUser(new SocialUserBindReqDTO(user.getId(), getUserType().getValue(), 148 String openid = socialUserApi.bindSocialUser(new SocialUserBindReqDTO(user.getId(), getUserType().getValue(),
150 - SocialTypeEnum.WECHAT_MINI_APP.getType(), reqVO.getLoginCode(), reqVO.getState())); 149 + SocialTypeEnum.WECHAT_MINI_PROGRAM.getType(), reqVO.getLoginCode(), reqVO.getState()));
151 150
152 // 创建 Token 令牌,记录登录日志 151 // 创建 Token 令牌,记录登录日志
153 return createTokenAfterLoginSuccess(user, user.getMobile(), LoginLogTypeEnum.LOGIN_SOCIAL, openid); 152 return createTokenAfterLoginSuccess(user, user.getMobile(), LoginLogTypeEnum.LOGIN_SOCIAL, openid);
yudao-module-member/yudao-module-member-biz/src/test/java/cn/iocoder/yudao/module/member/service/user/MemberUserServiceImplTest.java
@@ -5,12 +5,12 @@ import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; @@ -5,12 +5,12 @@ import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
5 import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils; 5 import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils;
6 import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration; 6 import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration;
7 import cn.iocoder.yudao.framework.test.core.ut.BaseDbAndRedisUnitTest; 7 import cn.iocoder.yudao.framework.test.core.ut.BaseDbAndRedisUnitTest;
8 -import cn.iocoder.yudao.module.infra.api.file.FileApi;  
9 import cn.iocoder.yudao.module.member.controller.app.user.vo.AppMemberUserUpdateMobileReqVO; 8 import cn.iocoder.yudao.module.member.controller.app.user.vo.AppMemberUserUpdateMobileReqVO;
10 import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO; 9 import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO;
11 import cn.iocoder.yudao.module.member.dal.mysql.user.MemberUserMapper; 10 import cn.iocoder.yudao.module.member.dal.mysql.user.MemberUserMapper;
12 import cn.iocoder.yudao.module.member.service.auth.MemberAuthServiceImpl; 11 import cn.iocoder.yudao.module.member.service.auth.MemberAuthServiceImpl;
13 import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi; 12 import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi;
  13 +import jakarta.annotation.Resource;
14 import org.junit.jupiter.api.Disabled; 14 import org.junit.jupiter.api.Disabled;
15 import org.junit.jupiter.api.Test; 15 import org.junit.jupiter.api.Test;
16 import org.springframework.boot.test.mock.mockito.MockBean; 16 import org.springframework.boot.test.mock.mockito.MockBean;
@@ -18,7 +18,6 @@ import org.springframework.context.annotation.Import; @@ -18,7 +18,6 @@ import org.springframework.context.annotation.Import;
18 import org.springframework.data.redis.core.StringRedisTemplate; 18 import org.springframework.data.redis.core.StringRedisTemplate;
19 import org.springframework.security.crypto.password.PasswordEncoder; 19 import org.springframework.security.crypto.password.PasswordEncoder;
20 20
21 -import jakarta.annotation.Resource;  
22 import java.util.function.Consumer; 21 import java.util.function.Consumer;
23 22
24 import static cn.hutool.core.util.RandomUtil.randomEle; 23 import static cn.hutool.core.util.RandomUtil.randomEle;
@@ -53,8 +52,6 @@ public class MemberUserServiceImplTest extends BaseDbAndRedisUnitTest { @@ -53,8 +52,6 @@ public class MemberUserServiceImplTest extends BaseDbAndRedisUnitTest {
53 52
54 @MockBean 53 @MockBean
55 private SmsCodeApi smsCodeApi; 54 private SmsCodeApi smsCodeApi;
56 - @MockBean  
57 - private FileApi fileApi;  
58 55
59 // TODO 芋艿:后续重构这个单测 56 // TODO 芋艿:后续重构这个单测
60 // @Test 57 // @Test
@@ -73,25 +70,6 @@ public class MemberUserServiceImplTest extends BaseDbAndRedisUnitTest { @@ -73,25 +70,6 @@ public class MemberUserServiceImplTest extends BaseDbAndRedisUnitTest {
73 // // 断言 70 // // 断言
74 // assertEquals(newNickName,nickname); 71 // assertEquals(newNickName,nickname);
75 // } 72 // }
76 -//  
77 -// @Test  
78 -// public void testUpdateAvatar_success() throws Exception {  
79 -// // mock 数据  
80 -// MemberUserDO dbUser = randomUserDO();  
81 -// userMapper.insert(dbUser);  
82 -//  
83 -// // 准备参数  
84 -// Long userId = dbUser.getId();  
85 -// byte[] avatarFileBytes = randomBytes(10);  
86 -// ByteArrayInputStream avatarFile = new ByteArrayInputStream(avatarFileBytes);  
87 -// // mock 方法  
88 -// String avatar = randomString();  
89 -// when(fileApi.createFile(eq(avatarFileBytes))).thenReturn(avatar);  
90 -// // 调用  
91 -// String str = memberUserService.updateUserAvatar(userId, avatarFile);  
92 -// // 断言  
93 -// assertEquals(avatar, str);  
94 -// }  
95 73
96 @Test 74 @Test
97 @Disabled // TODO 芋艿:后续再修复 75 @Disabled // TODO 芋艿:后续再修复
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/material/MpMaterialServiceImpl.java
@@ -218,7 +218,7 @@ public class MpMaterialServiceImpl implements MpMaterialService { @@ -218,7 +218,7 @@ public class MpMaterialServiceImpl implements MpMaterialService {
218 218
219 private String uploadFile(String mediaId, File file) { 219 private String uploadFile(String mediaId, File file) {
220 String path = mediaId + "." + FileTypeUtil.getType(file); 220 String path = mediaId + "." + FileTypeUtil.getType(file);
221 - return fileApi.createFile(path, FileUtil.readBytes(file)); 221 + return fileApi.createFile(FileUtil.readBytes(file), path);
222 } 222 }
223 223
224 } 224 }
yudao-module-report/yudao-module-report-biz/pom.xml
@@ -64,6 +64,10 @@ @@ -64,6 +64,10 @@
64 <groupId>org.jeecgframework.jimureport</groupId> 64 <groupId>org.jeecgframework.jimureport</groupId>
65 <artifactId>jimureport-spring-boot3-starter-fastjson2</artifactId> 65 <artifactId>jimureport-spring-boot3-starter-fastjson2</artifactId>
66 </dependency> 66 </dependency>
  67 + <dependency>
  68 + <groupId>org.jeecgframework.jimureport</groupId>
  69 + <artifactId>jimubi-spring-boot3-starter</artifactId>
  70 + </dependency>
67 71
68 <dependency> 72 <dependency>
69 <groupId>cn.iocoder.boot</groupId> 73 <groupId>cn.iocoder.boot</groupId>
yudao-module-report/yudao-module-report-biz/src/main/java/cn/iocoder/yudao/module/report/framework/jmreport/config/JmReportConfiguration.java
1 package cn.iocoder.yudao.module.report.framework.jmreport.config; 1 package cn.iocoder.yudao.module.report.framework.jmreport.config;
2 2
3 import cn.iocoder.yudao.framework.security.config.SecurityProperties; 3 import cn.iocoder.yudao.framework.security.config.SecurityProperties;
4 -import cn.iocoder.yudao.module.system.api.oauth2.OAuth2TokenApi; 4 +import cn.iocoder.yudao.module.report.framework.jmreport.core.service.JmOnlDragExternalServiceImpl;
5 import cn.iocoder.yudao.module.report.framework.jmreport.core.service.JmReportTokenServiceImpl; 5 import cn.iocoder.yudao.module.report.framework.jmreport.core.service.JmReportTokenServiceImpl;
  6 +import cn.iocoder.yudao.module.system.api.oauth2.OAuth2TokenApi;
6 import cn.iocoder.yudao.module.system.api.permission.PermissionApi; 7 import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
7 import org.jeecg.modules.jmreport.api.JmReportTokenServiceI; 8 import org.jeecg.modules.jmreport.api.JmReportTokenServiceI;
8 import org.springframework.context.annotation.Bean; 9 import org.springframework.context.annotation.Bean;
9 import org.springframework.context.annotation.ComponentScan; 10 import org.springframework.context.annotation.ComponentScan;
10 import org.springframework.context.annotation.Configuration; 11 import org.springframework.context.annotation.Configuration;
  12 +import org.springframework.context.annotation.Primary;
11 13
12 /** 14 /**
13 * 积木报表的配置类 15 * 积木报表的配置类
@@ -19,11 +21,16 @@ import org.springframework.context.annotation.Configuration; @@ -19,11 +21,16 @@ import org.springframework.context.annotation.Configuration;
19 public class JmReportConfiguration { 21 public class JmReportConfiguration {
20 22
21 @Bean 23 @Bean
22 - @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")  
23 public JmReportTokenServiceI jmReportTokenService(OAuth2TokenApi oAuth2TokenApi, 24 public JmReportTokenServiceI jmReportTokenService(OAuth2TokenApi oAuth2TokenApi,
24 PermissionApi permissionApi, 25 PermissionApi permissionApi,
25 SecurityProperties securityProperties) { 26 SecurityProperties securityProperties) {
26 return new JmReportTokenServiceImpl(oAuth2TokenApi, permissionApi, securityProperties); 27 return new JmReportTokenServiceImpl(oAuth2TokenApi, permissionApi, securityProperties);
27 } 28 }
28 29
  30 + @Bean // 暂时注释:可以按需实现后打开
  31 + @Primary
  32 + public JmOnlDragExternalServiceImpl jmOnlDragExternalService2() {
  33 + return new JmOnlDragExternalServiceImpl();
  34 + }
  35 +
29 } 36 }
yudao-module-report/yudao-module-report-biz/src/main/java/cn/iocoder/yudao/module/report/framework/jmreport/core/service/JmOnlDragExternalServiceImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.report.framework.jmreport.core.service;
  2 +
  3 +import com.alibaba.fastjson.JSONObject;
  4 +import lombok.RequiredArgsConstructor;
  5 +import org.jeecg.modules.drag.service.IOnlDragExternalService;
  6 +import org.jeecg.modules.drag.vo.DragDictModel;
  7 +import org.jeecg.modules.drag.vo.DragLogDTO;
  8 +
  9 +import java.util.List;
  10 +import java.util.Map;
  11 +
  12 +/**
  13 + * {@link IOnlDragExternalService} 实现类,提供积木仪表盘的查询等功能
  14 + *
  15 + * 实现可参考:
  16 + * 1. <a href="https://github.com/jeecgboot/jimureport/blob/master/jimureport-example/src/main/java/com/jeecg/modules/jmreport/extend/JimuDragExternalServiceImpl.java">jimureport-example</a>
  17 + * 2. <a href="https://gitee.com/jeecg/JeecgBoot/blob/master/jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/config/jimureport/JimuDragExternalServiceImpl.java">JeecgBoot 集成</a>
  18 + *
  19 + * @author 芋道源码
  20 + */
  21 +@RequiredArgsConstructor
  22 +public class JmOnlDragExternalServiceImpl implements IOnlDragExternalService {
  23 +
  24 + // ========== DictItem 相关 ==========
  25 +
  26 + @Override
  27 + public Map<String, List<DragDictModel>> getManyDictItems(List<String> codeList, List<JSONObject> tableDictList) {
  28 + return IOnlDragExternalService.super.getManyDictItems(codeList, tableDictList);
  29 + }
  30 +
  31 + @Override
  32 + public List<DragDictModel> getDictItems(String dictCode) {
  33 + return IOnlDragExternalService.super.getDictItems(dictCode);
  34 + }
  35 +
  36 + @Override
  37 + public List<DragDictModel> getTableDictItems(String dictTable, String dictText, String dictCode) {
  38 + return IOnlDragExternalService.super.getTableDictItems(dictTable, dictText, dictCode);
  39 + }
  40 +
  41 + @Override
  42 + public List<DragDictModel> getCategoryTreeDictItems(List<String> ids) {
  43 + return IOnlDragExternalService.super.getCategoryTreeDictItems(ids);
  44 + }
  45 +
  46 + @Override
  47 + public List<DragDictModel> getUserDictItems(List<String> ids) {
  48 + return IOnlDragExternalService.super.getUserDictItems(ids);
  49 + }
  50 +
  51 + @Override
  52 + public List<DragDictModel> getDeptsDictItems(List<String> ids) {
  53 + return IOnlDragExternalService.super.getDeptsDictItems(ids);
  54 + }
  55 +
  56 + // ========== Log 相关 ==========
  57 +
  58 + @Override
  59 + public void addLog(DragLogDTO dto) {
  60 + IOnlDragExternalService.super.addLog(dto);
  61 + }
  62 +
  63 + @Override
  64 + public void addLog(String logMsg, int logType, int operateType) {
  65 + IOnlDragExternalService.super.addLog(logMsg, logType, operateType);
  66 + }
  67 +
  68 +}
yudao-module-report/yudao-module-report-biz/src/main/java/cn/iocoder/yudao/module/report/framework/security/config/SecurityConfiguration.java
@@ -18,7 +18,11 @@ public class SecurityConfiguration { @@ -18,7 +18,11 @@ public class SecurityConfiguration {
18 18
19 @Override 19 @Override
20 public void customize(AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry registry) { 20 public void customize(AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry registry) {
21 - registry.requestMatchers("/jmreport/**").permitAll(); // 积木报表 21 + // 积木报表
  22 + registry.requestMatchers("/jmreport/**").permitAll();
  23 + // 积木仪表盘
  24 + registry.requestMatchers("/drag/**").permitAll();
  25 + registry.requestMatchers("/jimubi/**").permitAll();
22 } 26 }
23 27
24 }; 28 };
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java
@@ -45,6 +45,7 @@ public interface ErrorCodeConstants { @@ -45,6 +45,7 @@ public interface ErrorCodeConstants {
45 ErrorCode USER_COUNT_MAX = new ErrorCode(1_002_003_008, "创建用户失败,原因:超过租户最大租户配额({})!"); 45 ErrorCode USER_COUNT_MAX = new ErrorCode(1_002_003_008, "创建用户失败,原因:超过租户最大租户配额({})!");
46 ErrorCode USER_IMPORT_INIT_PASSWORD = new ErrorCode(1_002_003_009, "初始密码不能为空"); 46 ErrorCode USER_IMPORT_INIT_PASSWORD = new ErrorCode(1_002_003_009, "初始密码不能为空");
47 ErrorCode USER_MOBILE_NOT_EXISTS = new ErrorCode(1_002_003_010, "该手机号尚未注册"); 47 ErrorCode USER_MOBILE_NOT_EXISTS = new ErrorCode(1_002_003_010, "该手机号尚未注册");
  48 + ErrorCode USER_REGISTER_DISABLED = new ErrorCode(1_002_003_011, "注册功能已关闭");
48 49
49 // ========== 部门模块 1-002-004-000 ========== 50 // ========== 部门模块 1-002-004-000 ==========
50 ErrorCode DEPT_NAME_DUPLICATE = new ErrorCode(1_002_004_000, "已经存在该名字的部门"); 51 ErrorCode DEPT_NAME_DUPLICATE = new ErrorCode(1_002_004_000, "已经存在该名字的部门");
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/social/SocialTypeEnum.java
@@ -52,7 +52,7 @@ public enum SocialTypeEnum implements ArrayValuable&lt;Integer&gt; { @@ -52,7 +52,7 @@ public enum SocialTypeEnum implements ArrayValuable&lt;Integer&gt; {
52 * 52 *
53 * @see <a href="https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html">接入文档</a> 53 * @see <a href="https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html">接入文档</a>
54 */ 54 */
55 - WECHAT_MINI_APP(34, "WECHAT_MINI_APP"), 55 + WECHAT_MINI_PROGRAM(34, "WECHAT_MINI_PROGRAM"),
56 ; 56 ;
57 57
58 public static final Integer[] ARRAYS = Arrays.stream(values()).map(SocialTypeEnum::getType).toArray(Integer[]::new); 58 public static final Integer[] ARRAYS = Arrays.stream(values()).map(SocialTypeEnum::getType).toArray(Integer[]::new);
yudao-module-system/yudao-module-system-biz/pom.xml
@@ -97,8 +97,12 @@ @@ -97,8 +97,12 @@
97 97
98 <!-- 三方云服务相关 --> 98 <!-- 三方云服务相关 -->
99 <dependency> 99 <dependency>
100 - <groupId>com.xingyuv</groupId>  
101 - <artifactId>spring-boot-starter-justauth</artifactId> <!-- 社交登陆(例如说,个人微信、企业微信等等) --> 100 + <groupId>me.zhyd.oauth</groupId>
  101 + <artifactId>JustAuth</artifactId> <!-- 社交登陆(例如说,个人微信、企业微信等等) -->
  102 + </dependency>
  103 + <dependency>
  104 + <groupId>com.xkcoding.justauth</groupId>
  105 + <artifactId>justauth-spring-boot-starter</artifactId>
102 </dependency> 106 </dependency>
103 107
104 <dependency> 108 <dependency>
@@ -111,8 +115,8 @@ @@ -111,8 +115,8 @@
111 </dependency> 115 </dependency>
112 116
113 <dependency> 117 <dependency>
114 - <groupId>com.xingyuv</groupId>  
115 - <artifactId>spring-boot-starter-captcha-plus</artifactId> <!-- 验证码,一般用于登录使用 --> 118 + <groupId>com.anji-plus</groupId>
  119 + <artifactId>captcha-spring-boot-starter</artifactId> <!-- 验证码,一般用于登录使用 -->
116 </dependency> 120 </dependency>
117 121
118 <dependency> 122 <dependency>
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialClientApiImpl.java
@@ -84,7 +84,7 @@ public class SocialClientApiImpl implements SocialClientApi { @@ -84,7 +84,7 @@ public class SocialClientApiImpl implements SocialClientApi {
84 84
85 // 2. 获得社交用户 85 // 2. 获得社交用户
86 SocialUserRespDTO socialUser = socialUserService.getSocialUserByUserId(reqDTO.getUserType(), reqDTO.getUserId(), 86 SocialUserRespDTO socialUser = socialUserService.getSocialUserByUserId(reqDTO.getUserType(), reqDTO.getUserId(),
87 - SocialTypeEnum.WECHAT_MINI_APP.getType()); 87 + SocialTypeEnum.WECHAT_MINI_PROGRAM.getType());
88 if (StrUtil.isBlankIfStr(socialUser.getOpenid())) { 88 if (StrUtil.isBlankIfStr(socialUser.getOpenid())) {
89 log.warn("[sendWxaSubscribeMessage][reqDTO({}) 发送订阅消息失败,原因:会员 openid 缺失]", reqDTO); 89 log.warn("[sendWxaSubscribeMessage][reqDTO({}) 发送订阅消息失败,原因:会员 openid 缺失]", reqDTO);
90 return; 90 return;
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/captcha/CaptchaController.java
@@ -2,20 +2,19 @@ package cn.iocoder.yudao.module.system.controller.admin.captcha; @@ -2,20 +2,19 @@ package cn.iocoder.yudao.module.system.controller.admin.captcha;
2 2
3 import cn.hutool.core.util.StrUtil; 3 import cn.hutool.core.util.StrUtil;
4 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; 4 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
5 -import com.xingyuv.captcha.model.common.ResponseModel;  
6 -import com.xingyuv.captcha.model.vo.CaptchaVO;  
7 -import com.xingyuv.captcha.service.CaptchaService; 5 +import com.anji.captcha.model.common.ResponseModel;
  6 +import com.anji.captcha.model.vo.CaptchaVO;
  7 +import com.anji.captcha.service.CaptchaService;
8 import io.swagger.v3.oas.annotations.Operation; 8 import io.swagger.v3.oas.annotations.Operation;
9 import io.swagger.v3.oas.annotations.tags.Tag; 9 import io.swagger.v3.oas.annotations.tags.Tag;
  10 +import jakarta.annotation.Resource;
  11 +import jakarta.annotation.security.PermitAll;
  12 +import jakarta.servlet.http.HttpServletRequest;
10 import org.springframework.web.bind.annotation.PostMapping; 13 import org.springframework.web.bind.annotation.PostMapping;
11 import org.springframework.web.bind.annotation.RequestBody; 14 import org.springframework.web.bind.annotation.RequestBody;
12 import org.springframework.web.bind.annotation.RequestMapping; 15 import org.springframework.web.bind.annotation.RequestMapping;
13 import org.springframework.web.bind.annotation.RestController; 16 import org.springframework.web.bind.annotation.RestController;
14 17
15 -import jakarta.annotation.Resource;  
16 -import jakarta.annotation.security.PermitAll;  
17 -import jakarta.servlet.http.HttpServletRequest;  
18 -  
19 @Tag(name = "管理后台 - 验证码") 18 @Tag(name = "管理后台 - 验证码")
20 @RestController("adminCaptchaController") 19 @RestController("adminCaptchaController")
21 @RequestMapping("/system/captcha") 20 @RequestMapping("/system/captcha")
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/UserProfileController.java
@@ -23,14 +23,11 @@ import jakarta.validation.Valid; @@ -23,14 +23,11 @@ import jakarta.validation.Valid;
23 import lombok.extern.slf4j.Slf4j; 23 import lombok.extern.slf4j.Slf4j;
24 import org.springframework.validation.annotation.Validated; 24 import org.springframework.validation.annotation.Validated;
25 import org.springframework.web.bind.annotation.*; 25 import org.springframework.web.bind.annotation.*;
26 -import org.springframework.web.multipart.MultipartFile;  
27 26
28 import java.util.List; 27 import java.util.List;
29 28
30 -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;  
31 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 29 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
32 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; 30 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
33 -import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_IS_EMPTY;  
34 31
35 @Tag(name = "管理后台 - 用户个人中心") 32 @Tag(name = "管理后台 - 用户个人中心")
36 @RestController 33 @RestController
@@ -79,16 +76,4 @@ public class UserProfileController { @@ -79,16 +76,4 @@ public class UserProfileController {
79 return success(true); 76 return success(true);
80 } 77 }
81 78
82 - @Deprecated // TODO @芋艿:逐步替换到 updateUserProfile 接口  
83 - @RequestMapping(value = "/update-avatar",  
84 - method = {RequestMethod.POST, RequestMethod.PUT}) // 解决 uni-app 不支持 Put 上传文件的问题  
85 - @Operation(summary = "上传用户个人头像")  
86 - public CommonResult<String> updateUserAvatar(@RequestParam("avatarFile") MultipartFile file) throws Exception {  
87 - if (file.isEmpty()) {  
88 - throw exception(FILE_IS_EMPTY);  
89 - }  
90 - String avatar = userService.updateUserAvatar(getLoginUserId(), file.getInputStream());  
91 - return success(avatar);  
92 - }  
93 -  
94 } 79 }
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/social/SocialClientDO.java
@@ -7,8 +7,8 @@ import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum; @@ -7,8 +7,8 @@ import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
7 import com.baomidou.mybatisplus.annotation.KeySequence; 7 import com.baomidou.mybatisplus.annotation.KeySequence;
8 import com.baomidou.mybatisplus.annotation.TableId; 8 import com.baomidou.mybatisplus.annotation.TableId;
9 import com.baomidou.mybatisplus.annotation.TableName; 9 import com.baomidou.mybatisplus.annotation.TableName;
10 -import com.xingyuv.jushauth.config.AuthConfig;  
11 import lombok.*; 10 import lombok.*;
  11 +import me.zhyd.oauth.config.AuthConfig;
12 12
13 /** 13 /**
14 * 社交客户端 DO 14 * 社交客户端 DO
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/mail/MailAccountMapper.java
@@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.system.dal.mysql.mail; @@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.system.dal.mysql.mail;
3 import cn.iocoder.yudao.framework.common.pojo.PageResult; 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; 4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
5 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; 5 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
6 -import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX;  
7 import cn.iocoder.yudao.module.system.controller.admin.mail.vo.account.MailAccountPageReqVO; 6 import cn.iocoder.yudao.module.system.controller.admin.mail.vo.account.MailAccountPageReqVO;
8 import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailAccountDO; 7 import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailAccountDO;
9 import org.apache.ibatis.annotations.Mapper; 8 import org.apache.ibatis.annotations.Mapper;
@@ -14,7 +13,8 @@ public interface MailAccountMapper extends BaseMapperX&lt;MailAccountDO&gt; { @@ -14,7 +13,8 @@ public interface MailAccountMapper extends BaseMapperX&lt;MailAccountDO&gt; {
14 default PageResult<MailAccountDO> selectPage(MailAccountPageReqVO pageReqVO) { 13 default PageResult<MailAccountDO> selectPage(MailAccountPageReqVO pageReqVO) {
15 return selectPage(pageReqVO, new LambdaQueryWrapperX<MailAccountDO>() 14 return selectPage(pageReqVO, new LambdaQueryWrapperX<MailAccountDO>()
16 .likeIfPresent(MailAccountDO::getMail, pageReqVO.getMail()) 15 .likeIfPresent(MailAccountDO::getMail, pageReqVO.getMail())
17 - .likeIfPresent(MailAccountDO::getUsername , pageReqVO.getUsername())); 16 + .likeIfPresent(MailAccountDO::getUsername , pageReqVO.getUsername())
  17 + .orderByDesc(MailAccountDO::getId));
18 } 18 }
19 19
20 } 20 }
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/mail/MailTemplateMapper.java
@@ -3,14 +3,9 @@ package cn.iocoder.yudao.module.system.dal.mysql.mail; @@ -3,14 +3,9 @@ package cn.iocoder.yudao.module.system.dal.mysql.mail;
3 import cn.iocoder.yudao.framework.common.pojo.PageResult; 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; 4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
5 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; 5 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
6 -import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX;  
7 import cn.iocoder.yudao.module.system.controller.admin.mail.vo.template.MailTemplatePageReqVO; 6 import cn.iocoder.yudao.module.system.controller.admin.mail.vo.template.MailTemplatePageReqVO;
8 import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailTemplateDO; 7 import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailTemplateDO;
9 -import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsTemplateDO;  
10 import org.apache.ibatis.annotations.Mapper; 8 import org.apache.ibatis.annotations.Mapper;
11 -import org.apache.ibatis.annotations.Select;  
12 -  
13 -import java.util.Date;  
14 9
15 @Mapper 10 @Mapper
16 public interface MailTemplateMapper extends BaseMapperX<MailTemplateDO> { 11 public interface MailTemplateMapper extends BaseMapperX<MailTemplateDO> {
@@ -21,7 +16,8 @@ public interface MailTemplateMapper extends BaseMapperX&lt;MailTemplateDO&gt; { @@ -21,7 +16,8 @@ public interface MailTemplateMapper extends BaseMapperX&lt;MailTemplateDO&gt; {
21 .likeIfPresent(MailTemplateDO::getCode, pageReqVO.getCode()) 16 .likeIfPresent(MailTemplateDO::getCode, pageReqVO.getCode())
22 .likeIfPresent(MailTemplateDO::getName, pageReqVO.getName()) 17 .likeIfPresent(MailTemplateDO::getName, pageReqVO.getName())
23 .eqIfPresent(MailTemplateDO::getAccountId, pageReqVO.getAccountId()) 18 .eqIfPresent(MailTemplateDO::getAccountId, pageReqVO.getAccountId())
24 - .betweenIfPresent(MailTemplateDO::getCreateTime, pageReqVO.getCreateTime())); 19 + .betweenIfPresent(MailTemplateDO::getCreateTime, pageReqVO.getCreateTime())
  20 + .orderByDesc(MailTemplateDO::getId));
25 } 21 }
26 22
27 default Long selectCountByAccountId(Long accountId) { 23 default Long selectCountByAccountId(Long accountId) {
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/captcha/config/YudaoCaptchaConfiguration.java
1 package cn.iocoder.yudao.module.system.framework.captcha.config; 1 package cn.iocoder.yudao.module.system.framework.captcha.config;
2 2
3 import cn.iocoder.yudao.module.system.framework.captcha.core.RedisCaptchaServiceImpl; 3 import cn.iocoder.yudao.module.system.framework.captcha.core.RedisCaptchaServiceImpl;
4 -import com.xingyuv.captcha.properties.AjCaptchaProperties;  
5 -import com.xingyuv.captcha.service.CaptchaCacheService;  
6 -import com.xingyuv.captcha.service.impl.CaptchaServiceFactory; 4 +import com.anji.captcha.config.AjCaptchaAutoConfiguration;
  5 +import com.anji.captcha.properties.AjCaptchaProperties;
  6 +import com.anji.captcha.service.CaptchaCacheService;
  7 +import com.anji.captcha.service.impl.CaptchaServiceFactory;
  8 +import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
7 import org.springframework.context.annotation.Bean; 9 import org.springframework.context.annotation.Bean;
8 import org.springframework.context.annotation.Configuration; 10 import org.springframework.context.annotation.Configuration;
  11 +import org.springframework.context.annotation.Primary;
9 import org.springframework.data.redis.core.StringRedisTemplate; 12 import org.springframework.data.redis.core.StringRedisTemplate;
10 13
11 /** 14 /**
@@ -14,9 +17,11 @@ import org.springframework.data.redis.core.StringRedisTemplate; @@ -14,9 +17,11 @@ import org.springframework.data.redis.core.StringRedisTemplate;
14 * @author 芋道源码 17 * @author 芋道源码
15 */ 18 */
16 @Configuration(proxyBeanMethods = false) 19 @Configuration(proxyBeanMethods = false)
  20 +@ImportAutoConfiguration(AjCaptchaAutoConfiguration.class) // 目的:解决 aj-captcha 针对 SpringBoot 3.X 自动配置不生效的问题
17 public class YudaoCaptchaConfiguration { 21 public class YudaoCaptchaConfiguration {
18 22
19 - @Bean 23 + @Bean(name = "AjCaptchaCacheService")
  24 + @Primary
20 public CaptchaCacheService captchaCacheService(AjCaptchaProperties config, 25 public CaptchaCacheService captchaCacheService(AjCaptchaProperties config,
21 StringRedisTemplate stringRedisTemplate) { 26 StringRedisTemplate stringRedisTemplate) {
22 CaptchaCacheService captchaCacheService = CaptchaServiceFactory.getCache(config.getCacheType().name()); 27 CaptchaCacheService captchaCacheService = CaptchaServiceFactory.getCache(config.getCacheType().name());
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/captcha/core/RedisCaptchaServiceImpl.java
1 package cn.iocoder.yudao.module.system.framework.captcha.core; 1 package cn.iocoder.yudao.module.system.framework.captcha.core;
2 2
3 -import com.xingyuv.captcha.service.CaptchaCacheService; 3 +import com.anji.captcha.service.CaptchaCacheService;
4 import lombok.Setter; 4 import lombok.Setter;
5 import org.springframework.data.redis.core.StringRedisTemplate; 5 import org.springframework.data.redis.core.StringRedisTemplate;
6 6
@@ -28,7 +28,7 @@ public class RedisCaptchaServiceImpl implements CaptchaCacheService { @@ -28,7 +28,7 @@ public class RedisCaptchaServiceImpl implements CaptchaCacheService {
28 28
29 @Override 29 @Override
30 public boolean exists(String key) { 30 public boolean exists(String key) {
31 - return Boolean.TRUE.equals(stringRedisTemplate.hasKey(key)); 31 + return stringRedisTemplate.hasKey(key);
32 } 32 }
33 33
34 @Override 34 @Override