Commit 28c818e9bcbb62eec212b417e059bdb480ade30e
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) | |
pom.xml
| @@ -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<CodegenTableDO> { | @@ -23,8 +23,7 @@ public interface CodegenTableMapper extends BaseMapperX<CodegenTableDO> { | ||
| 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<ConfigDO> { | @@ -19,7 +19,8 @@ public interface ConfigMapper extends BaseMapperX<ConfigDO> { | ||
| 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<JobDO> { | @@ -24,7 +24,7 @@ public interface JobMapper extends BaseMapperX<JobDO> { | ||
| 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<FtpFileClientConfig> { | @@ -26,12 +26,6 @@ public class FtpFileClient extends AbstractFileClient<FtpFileClientConfig> { | ||
| 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<FtpFileClientConfig> { | @@ -43,8 +37,8 @@ public class FtpFileClient extends AbstractFileClient<FtpFileClientConfig> { | ||
| 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<FtpFileClientConfig> { | @@ -55,7 +49,7 @@ public class FtpFileClient extends AbstractFileClient<FtpFileClientConfig> { | ||
| 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<FtpFileClientConfig> { | @@ -65,13 +59,17 @@ public class FtpFileClient extends AbstractFileClient<FtpFileClientConfig> { | ||
| 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<LocalFileClientConfig> { | @@ -18,10 +18,6 @@ public class LocalFileClient extends AbstractFileClient<LocalFileClientConfig> { | ||
| 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<LocalFileClientConfig> { | @@ -46,7 +42,7 @@ public class LocalFileClient extends AbstractFileClient<LocalFileClientConfig> { | ||
| 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<S3FileClientConfig> { | @@ -38,31 +40,80 @@ public class S3FileClient extends AbstractFileClient<S3FileClientConfig> { | ||
| 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<S3FileClientConfig> { | @@ -79,40 +130,17 @@ public class S3FileClient extends AbstractFileClient<S3FileClientConfig> { | ||
| 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<SftpFileClientConfig> { | @@ -22,10 +22,6 @@ public class SftpFileClient extends AbstractFileClient<SftpFileClientConfig> { | ||
| 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<SftpFileClientConfig> { | @@ -35,6 +31,8 @@ public class SftpFileClient extends AbstractFileClient<SftpFileClientConfig> { | ||
| 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<SftpFileClientConfig> { | @@ -43,6 +41,7 @@ public class SftpFileClient extends AbstractFileClient<SftpFileClientConfig> { | ||
| 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<SftpFileClientConfig> { | @@ -50,12 +49,17 @@ public class SftpFileClient extends AbstractFileClient<SftpFileClientConfig> { | ||
| 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(() => { | @@ -42,6 +42,13 @@ const getTitle = computed(() => { | ||
| 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<AfterSaleDO> { | @@ -27,9 +27,10 @@ public interface AfterSaleMapper extends BaseMapperX<AfterSaleDO> { | ||
| 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<Integer> { | @@ -52,7 +52,7 @@ public enum SocialTypeEnum implements ArrayValuable<Integer> { | ||
| 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<MailAccountDO> { | @@ -14,7 +13,8 @@ public interface MailAccountMapper extends BaseMapperX<MailAccountDO> { | ||
| 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<MailTemplateDO> { | @@ -21,7 +16,8 @@ public interface MailTemplateMapper extends BaseMapperX<MailTemplateDO> { | ||
| 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 |