Commit 787de1be94f21d945d55c2de28ad4c0db6692fdb

Authored by YunaiV
2 parents f9e64a9f 497d826a

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

# Conflicts:
#	sql/mysql/mall.sql
#	sql/mysql/ruoyi-vue-pro.sql
#	yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/wallet/vo/recharge/AppPayWalletRechargeCreateReqVO.java
#	yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargeServiceImpl.java
#	yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserService.java
Showing 189 changed files with 1533 additions and 2012 deletions

Too many changes.

To preserve performance only 100 of 189 files are displayed.

README.md
@@ -256,7 +256,7 @@ _鍓嶇鍩轰簬 crmeb uniapp 缁忚繃鎺堟潈閲嶆瀯锛屼紭鍖栦唬鐮佸疄鐜帮紝鎺ュ叆鑺嬮 @@ -256,7 +256,7 @@ _鍓嶇鍩轰簬 crmeb uniapp 缁忚繃鎺堟潈閲嶆瀯锛屼紭鍖栦唬鐮佸疄鐜帮紝鎺ュ叆鑺嬮
256 256
257 | 妗嗘灦 | 璇存槑 | 鐗堟湰 | 瀛︿範鎸囧崡 | 257 | 妗嗘灦 | 璇存槑 | 鐗堟湰 | 瀛︿範鎸囧崡 |
258 |---------------------------------------------------------------------------------------------|------------------|----------------|----------------------------------------------------------------| 258 |---------------------------------------------------------------------------------------------|------------------|----------------|----------------------------------------------------------------|
259 -| [Spring Boot](https://spring.io/projects/spring-boot) | 搴旂敤寮鍙戞鏋 | 2.7.16 | [鏂囨。](https://github.com/YunaiV/SpringBoot-Labs) | 259 +| [Spring Boot](https://spring.io/projects/spring-boot) | 搴旂敤寮鍙戞鏋 | 2.7.17 | [鏂囨。](https://github.com/YunaiV/SpringBoot-Labs) |
260 | [MySQL](https://www.mysql.com/cn/) | 鏁版嵁搴撴湇鍔″櫒 | 5.7 / 8.0+ | | 260 | [MySQL](https://www.mysql.com/cn/) | 鏁版嵁搴撴湇鍔″櫒 | 5.7 / 8.0+ | |
261 | [Druid](https://github.com/alibaba/druid) | JDBC 杩炴帴姹犮佺洃鎺х粍浠 | 1.2.19 | [鏂囨。](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) | 261 | [Druid](https://github.com/alibaba/druid) | JDBC 杩炴帴姹犮佺洃鎺х粍浠 | 1.2.19 | [鏂囨。](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) |
262 | [MyBatis Plus](https://mp.baomidou.com/) | MyBatis 澧炲己宸ュ叿鍖 | 3.5.3.2 | [鏂囨。](http://www.iocoder.cn/Spring-Boot/MyBatis/?yudao) | 262 | [MyBatis Plus](https://mp.baomidou.com/) | MyBatis 澧炲己宸ュ叿鍖 | 3.5.3.2 | [鏂囨。](http://www.iocoder.cn/Spring-Boot/MyBatis/?yudao) |
@@ -13,16 +13,16 @@ @@ -13,16 +13,16 @@
13 <!-- Server 主项目 --> 13 <!-- Server 主项目 -->
14 <module>yudao-server</module> 14 <module>yudao-server</module>
15 <!-- 各种 module 拓展 --> 15 <!-- 各种 module 拓展 -->
16 - <module>yudao-module-member</module>  
17 <module>yudao-module-system</module> 16 <module>yudao-module-system</module>
18 <module>yudao-module-infra</module> 17 <module>yudao-module-infra</module>
19 - <!-- <module>yudao-module-bpm</module>--> 18 +<!-- <module>yudao-module-member</module>-->
  19 +<!-- <module>yudao-module-bpm</module>-->
20 <!-- <module>yudao-module-report</module>--> 20 <!-- <module>yudao-module-report</module>-->
21 <!-- <module>yudao-module-mp</module>--> 21 <!-- <module>yudao-module-mp</module>-->
22 - <module>yudao-module-pay</module>  
23 - <module>yudao-module-mall</module> 22 +<!-- <module>yudao-module-pay</module>-->
  23 +<!-- <module>yudao-module-mall</module>-->
24 <!-- 示例项目 --> 24 <!-- 示例项目 -->
25 - <module>yudao-example</module> 25 +<!-- <module>yudao-example</module>-->
26 </modules> 26 </modules>
27 27
28 <name>${project.artifactId}</name> 28 <name>${project.artifactId}</name>
@@ -30,7 +30,7 @@ @@ -30,7 +30,7 @@
30 <url>https://github.com/YunaiV/ruoyi-vue-pro</url> 30 <url>https://github.com/YunaiV/ruoyi-vue-pro</url>
31 31
32 <properties> 32 <properties>
33 - <revision>1.8.2-snapshot</revision> 33 + <revision>1.8.3-snapshot</revision>
34 <!-- Maven 相关 --> 34 <!-- Maven 相关 -->
35 <java.version>1.8</java.version> 35 <java.version>1.8</java.version>
36 <maven.compiler.source>${java.version}</maven.compiler.source> 36 <maven.compiler.source>${java.version}</maven.compiler.source>
@@ -40,7 +40,7 @@ @@ -40,7 +40,7 @@
40 <flatten-maven-plugin.version>1.5.0</flatten-maven-plugin.version> 40 <flatten-maven-plugin.version>1.5.0</flatten-maven-plugin.version>
41 <!-- 看看咋放到 bom 里 --> 41 <!-- 看看咋放到 bom 里 -->
42 <lombok.version>1.18.30</lombok.version> 42 <lombok.version>1.18.30</lombok.version>
43 - <spring.boot.version>2.7.16</spring.boot.version> 43 + <spring.boot.version>2.7.17</spring.boot.version>
44 <mapstruct.version>1.5.5.Final</mapstruct.version> 44 <mapstruct.version>1.5.5.Final</mapstruct.version>
45 <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> 45 <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
46 </properties> 46 </properties>
yudao-dependencies/pom.xml
@@ -14,28 +14,30 @@ @@ -14,28 +14,30 @@
14 <url>https://github.com/YunaiV/ruoyi-vue-pro</url> 14 <url>https://github.com/YunaiV/ruoyi-vue-pro</url>
15 15
16 <properties> 16 <properties>
17 - <revision>1.8.2-snapshot</revision> 17 + <revision>1.8.3-snapshot</revision>
18 <flatten-maven-plugin.version>1.5.0</flatten-maven-plugin.version> 18 <flatten-maven-plugin.version>1.5.0</flatten-maven-plugin.version>
19 <!-- 统一依赖管理 --> 19 <!-- 统一依赖管理 -->
20 - <spring.boot.version>2.7.16</spring.boot.version> 20 + <spring.boot.version>2.7.17</spring.boot.version>
21 <!-- Web 相关 --> 21 <!-- Web 相关 -->
22 <springdoc.version>1.6.15</springdoc.version> 22 <springdoc.version>1.6.15</springdoc.version>
23 <knife4j.version>4.3.0</knife4j.version> 23 <knife4j.version>4.3.0</knife4j.version>
24 <servlet.versoin>2.5</servlet.versoin> 24 <servlet.versoin>2.5</servlet.versoin>
25 <!-- DB 相关 --> 25 <!-- DB 相关 -->
26 - <druid.version>1.2.19</druid.version>  
27 - <mybatis-plus.version>3.5.3.2</mybatis-plus.version>  
28 - <mybatis-plus-generator.version>3.5.3.2</mybatis-plus-generator.version> 26 + <druid.version>1.2.20</druid.version>
  27 + <mybatis-plus.version>3.5.4</mybatis-plus.version>
  28 + <mybatis-plus-generator.version>3.5.4</mybatis-plus-generator.version>
29 <dynamic-datasource.version>3.6.1</dynamic-datasource.version> 29 <dynamic-datasource.version>3.6.1</dynamic-datasource.version>
30 - <mybatis-plus-join.version>1.4.6</mybatis-plus-join.version> 30 + <mybatis-plus-join.version>1.4.7</mybatis-plus-join.version>
31 <redisson.version>3.18.0</redisson.version> 31 <redisson.version>3.18.0</redisson.version>
32 - <dm8.jdbc.version>8.1.2.141</dm8.jdbc.version> 32 + <dm8.jdbc.version>8.1.3.62</dm8.jdbc.version>
  33 + <!-- 消息队列 -->
  34 + <rocketmq-spring.version>2.2.3</rocketmq-spring.version>
33 <!-- 服务保障相关 --> 35 <!-- 服务保障相关 -->
34 - <lock4j.version>2.2.3</lock4j.version> 36 + <lock4j.version>2.2.5</lock4j.version>
35 <resilience4j.version>1.7.1</resilience4j.version> 37 <resilience4j.version>1.7.1</resilience4j.version>
36 <!-- 监控相关 --> 38 <!-- 监控相关 -->
37 <skywalking.version>8.12.0</skywalking.version> 39 <skywalking.version>8.12.0</skywalking.version>
38 - <spring-boot-admin.version>2.7.10</spring-boot-admin.version> 40 + <spring-boot-admin.version>2.7.11</spring-boot-admin.version>
39 <opentracing.version>0.33.0</opentracing.version> 41 <opentracing.version>0.33.0</opentracing.version>
40 <!-- Test 测试相关 --> 42 <!-- Test 测试相关 -->
41 <podam.version>7.2.11.RELEASE</podam.version> 43 <podam.version>7.2.11.RELEASE</podam.version>
@@ -44,8 +46,8 @@ @@ -44,8 +46,8 @@
44 <!-- Bpm 工作流相关 --> 46 <!-- Bpm 工作流相关 -->
45 <flowable.version>6.8.0</flowable.version> 47 <flowable.version>6.8.0</flowable.version>
46 <!-- 工具类相关 --> 48 <!-- 工具类相关 -->
47 - <captcha-plus.version>1.0.8</captcha-plus.version>  
48 - <jsoup.version>1.16.1</jsoup.version> 49 + <captcha-plus.version>1.0.10</captcha-plus.version>
  50 + <jsoup.version>1.16.2</jsoup.version>
49 <lombok.version>1.18.30</lombok.version> 51 <lombok.version>1.18.30</lombok.version>
50 <mapstruct.version>1.5.5.Final</mapstruct.version> 52 <mapstruct.version>1.5.5.Final</mapstruct.version>
51 <hutool.version>5.8.22</hutool.version> 53 <hutool.version>5.8.22</hutool.version>
@@ -53,10 +55,10 @@ @@ -53,10 +55,10 @@
53 <velocity.version>2.3</velocity.version> 55 <velocity.version>2.3</velocity.version>
54 <screw.version>1.0.5</screw.version> 56 <screw.version>1.0.5</screw.version>
55 <fastjson.version>1.2.83</fastjson.version> 57 <fastjson.version>1.2.83</fastjson.version>
56 - <guava.version>32.1.2-jre</guava.version> 58 + <guava.version>32.1.3-jre</guava.version>
57 <guice.version>5.1.0</guice.version> 59 <guice.version>5.1.0</guice.version>
58 <transmittable-thread-local.version>2.14.2</transmittable-thread-local.version> 60 <transmittable-thread-local.version>2.14.2</transmittable-thread-local.version>
59 - <commons-net.version>3.9.0</commons-net.version> 61 + <commons-net.version>3.10.0</commons-net.version>
60 <jsch.version>0.1.55</jsch.version> 62 <jsch.version>0.1.55</jsch.version>
61 <tika-core.version>2.7.0</tika-core.version> 63 <tika-core.version>2.7.0</tika-core.version>
62 <ip2region.version>2.7.0</ip2region.version> 64 <ip2region.version>2.7.0</ip2region.version>
@@ -67,8 +69,8 @@ @@ -67,8 +69,8 @@
67 <minio.version>8.5.6</minio.version> 69 <minio.version>8.5.6</minio.version>
68 <aliyun-java-sdk-core.version>4.6.4</aliyun-java-sdk-core.version> 70 <aliyun-java-sdk-core.version>4.6.4</aliyun-java-sdk-core.version>
69 <aliyun-java-sdk-dysmsapi.version>2.2.1</aliyun-java-sdk-dysmsapi.version> 71 <aliyun-java-sdk-dysmsapi.version>2.2.1</aliyun-java-sdk-dysmsapi.version>
70 - <tencentcloud-sdk-java.version>3.1.853</tencentcloud-sdk-java.version>  
71 - <justauth.version>1.0.5</justauth.version> 72 + <tencentcloud-sdk-java.version>3.1.880</tencentcloud-sdk-java.version>
  73 + <justauth.version>1.0.7</justauth.version>
72 <jimureport.version>1.6.1</jimureport.version> 74 <jimureport.version>1.6.1</jimureport.version>
73 <xercesImpl.version>2.12.2</xercesImpl.version> 75 <xercesImpl.version>2.12.2</xercesImpl.version>
74 <weixin-java.version>4.5.0</weixin-java.version> 76 <weixin-java.version>4.5.0</weixin-java.version>
@@ -98,11 +100,6 @@ @@ -98,11 +100,6 @@
98 </dependency> 100 </dependency>
99 <dependency> 101 <dependency>
100 <groupId>cn.iocoder.boot</groupId> 102 <groupId>cn.iocoder.boot</groupId>
101 - <artifactId>yudao-spring-boot-starter-biz-trade</artifactId>  
102 - <version>${revision}</version>  
103 - </dependency>  
104 - <dependency>  
105 - <groupId>cn.iocoder.boot</groupId>  
106 <artifactId>yudao-spring-boot-starter-biz-dict</artifactId> 103 <artifactId>yudao-spring-boot-starter-biz-dict</artifactId>
107 <version>${revision}</version> 104 <version>${revision}</version>
108 </dependency> 105 </dependency>
@@ -260,6 +257,12 @@ @@ -260,6 +257,12 @@
260 <version>${revision}</version> 257 <version>${revision}</version>
261 </dependency> 258 </dependency>
262 259
  260 + <dependency>
  261 + <groupId>org.apache.rocketmq</groupId>
  262 + <artifactId>rocketmq-spring-boot-starter</artifactId>
  263 + <version>${rocketmq-spring.version}</version>
  264 + </dependency>
  265 +
263 <!-- 服务保障相关 --> 266 <!-- 服务保障相关 -->
264 <dependency> 267 <dependency>
265 <groupId>cn.iocoder.boot</groupId> 268 <groupId>cn.iocoder.boot</groupId>
yudao-example/yudao-sso-demo-by-code/pom.xml
@@ -21,7 +21,7 @@ @@ -21,7 +21,7 @@
21 <maven.compiler.target>8</maven.compiler.target> 21 <maven.compiler.target>8</maven.compiler.target>
22 <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> 22 <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
23 <!-- 统一依赖管理 --> 23 <!-- 统一依赖管理 -->
24 - <spring.boot.version>2.7.16</spring.boot.version> 24 + <spring.boot.version>2.7.17</spring.boot.version>
25 </properties> 25 </properties>
26 26
27 <dependencyManagement> 27 <dependencyManagement>
yudao-example/yudao-sso-demo-by-password/pom.xml
@@ -21,7 +21,7 @@ @@ -21,7 +21,7 @@
21 <maven.compiler.target>8</maven.compiler.target> 21 <maven.compiler.target>8</maven.compiler.target>
22 <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> 22 <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
23 <!-- 统一依赖管理 --> 23 <!-- 统一依赖管理 -->
24 - <spring.boot.version>2.7.16</spring.boot.version> 24 + <spring.boot.version>2.7.17</spring.boot.version>
25 </properties> 25 </properties>
26 26
27 <dependencyManagement> 27 <dependencyManagement>
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/core/KeyValue.java
@@ -4,6 +4,8 @@ import lombok.AllArgsConstructor; @@ -4,6 +4,8 @@ import lombok.AllArgsConstructor;
4 import lombok.Data; 4 import lombok.Data;
5 import lombok.NoArgsConstructor; 5 import lombok.NoArgsConstructor;
6 6
  7 +import java.io.Serializable;
  8 +
7 /** 9 /**
8 * Key Value 的键值对 10 * Key Value 的键值对
9 * 11 *
@@ -12,7 +14,7 @@ import lombok.NoArgsConstructor; @@ -12,7 +14,7 @@ import lombok.NoArgsConstructor;
12 @Data 14 @Data
13 @NoArgsConstructor 15 @NoArgsConstructor
14 @AllArgsConstructor 16 @AllArgsConstructor
15 -public class KeyValue<K, V> { 17 +public class KeyValue<K, V> implements Serializable {
16 18
17 private K key; 19 private K key;
18 private V value; 20 private V value;
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java
@@ -8,6 +8,7 @@ import com.google.common.collect.ImmutableMap; @@ -8,6 +8,7 @@ import com.google.common.collect.ImmutableMap;
8 import java.util.*; 8 import java.util.*;
9 import java.util.function.*; 9 import java.util.function.*;
10 import java.util.stream.Collectors; 10 import java.util.stream.Collectors;
  11 +import java.util.stream.Stream;
11 12
12 import static java.util.Arrays.asList; 13 import static java.util.Arrays.asList;
13 14
@@ -271,4 +272,20 @@ public class CollectionUtils { @@ -271,4 +272,20 @@ public class CollectionUtils {
271 return deptId == null ? Collections.emptyList() : Collections.singleton(deptId); 272 return deptId == null ? Collections.emptyList() : Collections.singleton(deptId);
272 } 273 }
273 274
  275 + public static <T, U> List<U> convertListByFlatMap(Collection<T> from,
  276 + Function<T, ? extends Stream<? extends U>> func) {
  277 + if (CollUtil.isEmpty(from)) {
  278 + return new ArrayList<>();
  279 + }
  280 + return from.stream().flatMap(func).filter(Objects::nonNull).collect(Collectors.toList());
  281 + }
  282 +
  283 + public static <T, U> Set<U> convertSetByFlatMap(Collection<T> from,
  284 + Function<T, ? extends Stream<? extends U>> func) {
  285 + if (CollUtil.isEmpty(from)) {
  286 + return new HashSet<>();
  287 + }
  288 + return from.stream().flatMap(func).filter(Objects::nonNull).collect(Collectors.toSet());
  289 + }
  290 +
274 } 291 }
yudao-framework/yudao-spring-boot-starter-biz-error-code/src/main/java/cn/iocoder/yudao/framework/errorcode/config/YudaoErrorCodeConfiguration.java renamed to yudao-framework/yudao-spring-boot-starter-biz-error-code/src/main/java/cn/iocoder/yudao/framework/errorcode/config/YudaoErrorCodeAutoConfiguration.java
@@ -21,7 +21,7 @@ import org.springframework.scheduling.annotation.EnableScheduling; @@ -21,7 +21,7 @@ import org.springframework.scheduling.annotation.EnableScheduling;
21 @ConditionalOnProperty(prefix = "yudao.error-code", value = "enable", matchIfMissing = true) // 允许使用 yudao.error-code.enable=false 禁用访问日志 21 @ConditionalOnProperty(prefix = "yudao.error-code", value = "enable", matchIfMissing = true) // 允许使用 yudao.error-code.enable=false 禁用访问日志
22 @EnableConfigurationProperties(ErrorCodeProperties.class) 22 @EnableConfigurationProperties(ErrorCodeProperties.class)
23 @EnableScheduling // 开启调度任务的功能,因为 ErrorCodeRemoteLoader 通过定时刷新错误码 23 @EnableScheduling // 开启调度任务的功能,因为 ErrorCodeRemoteLoader 通过定时刷新错误码
24 -public class YudaoErrorCodeConfiguration { 24 +public class YudaoErrorCodeAutoConfiguration {
25 25
26 @Bean 26 @Bean
27 public ErrorCodeAutoGenerator errorCodeAutoGenerator(@Value("${spring.application.name}") String applicationName, 27 public ErrorCodeAutoGenerator errorCodeAutoGenerator(@Value("${spring.application.name}") String applicationName,
yudao-framework/yudao-spring-boot-starter-biz-error-code/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
1 -cn.iocoder.yudao.framework.errorcode.config.YudaoErrorCodeConfiguration  
2 \ No newline at end of file 1 \ No newline at end of file
  2 +cn.iocoder.yudao.framework.errorcode.config.YudaoErrorCodeAutoConfiguration
yudao-framework/yudao-spring-boot-starter-biz-tenant/pom.xml
@@ -48,6 +48,22 @@ @@ -48,6 +48,22 @@
48 <dependency> 48 <dependency>
49 <groupId>cn.iocoder.boot</groupId> 49 <groupId>cn.iocoder.boot</groupId>
50 <artifactId>yudao-spring-boot-starter-mq</artifactId> 50 <artifactId>yudao-spring-boot-starter-mq</artifactId>
  51 + <optional>true</optional>
  52 + </dependency>
  53 + <dependency>
  54 + <groupId>org.springframework.kafka</groupId>
  55 + <artifactId>spring-kafka</artifactId>
  56 + <optional>true</optional>
  57 + </dependency>
  58 + <dependency>
  59 + <groupId>org.springframework.amqp</groupId>
  60 + <artifactId>spring-rabbit</artifactId>
  61 + <optional>true</optional>
  62 + </dependency>
  63 + <dependency>
  64 + <groupId>org.apache.rocketmq</groupId>
  65 + <artifactId>rocketmq-spring-boot-starter</artifactId>
  66 + <optional>true</optional>
51 </dependency> 67 </dependency>
52 68
53 <!-- Test 测试相关 --> 69 <!-- Test 测试相关 -->
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/config/YudaoTenantAutoConfiguration.java
@@ -6,7 +6,9 @@ import cn.iocoder.yudao.framework.redis.config.YudaoCacheProperties; @@ -6,7 +6,9 @@ import cn.iocoder.yudao.framework.redis.config.YudaoCacheProperties;
6 import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnoreAspect; 6 import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnoreAspect;
7 import cn.iocoder.yudao.framework.tenant.core.db.TenantDatabaseInterceptor; 7 import cn.iocoder.yudao.framework.tenant.core.db.TenantDatabaseInterceptor;
8 import cn.iocoder.yudao.framework.tenant.core.job.TenantJobAspect; 8 import cn.iocoder.yudao.framework.tenant.core.job.TenantJobAspect;
9 -import cn.iocoder.yudao.framework.tenant.core.mq.TenantRedisMessageInterceptor; 9 +import cn.iocoder.yudao.framework.tenant.core.mq.rabbitmq.TenantRabbitMQInitializer;
  10 +import cn.iocoder.yudao.framework.tenant.core.mq.redis.TenantRedisMessageInterceptor;
  11 +import cn.iocoder.yudao.framework.tenant.core.mq.rocketmq.TenantRocketMQInitializer;
10 import cn.iocoder.yudao.framework.tenant.core.redis.TenantRedisCacheManager; 12 import cn.iocoder.yudao.framework.tenant.core.redis.TenantRedisCacheManager;
11 import cn.iocoder.yudao.framework.tenant.core.security.TenantSecurityWebFilter; 13 import cn.iocoder.yudao.framework.tenant.core.security.TenantSecurityWebFilter;
12 import cn.iocoder.yudao.framework.tenant.core.service.TenantFrameworkService; 14 import cn.iocoder.yudao.framework.tenant.core.service.TenantFrameworkService;
@@ -18,6 +20,7 @@ import cn.iocoder.yudao.module.system.api.tenant.TenantApi; @@ -18,6 +20,7 @@ import cn.iocoder.yudao.module.system.api.tenant.TenantApi;
18 import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; 20 import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
19 import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor; 21 import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
20 import org.springframework.boot.autoconfigure.AutoConfiguration; 22 import org.springframework.boot.autoconfigure.AutoConfiguration;
  23 +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
21 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 24 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
22 import org.springframework.boot.context.properties.EnableConfigurationProperties; 25 import org.springframework.boot.context.properties.EnableConfigurationProperties;
23 import org.springframework.boot.web.servlet.FilterRegistrationBean; 26 import org.springframework.boot.web.servlet.FilterRegistrationBean;
@@ -92,6 +95,18 @@ public class YudaoTenantAutoConfiguration { @@ -92,6 +95,18 @@ public class YudaoTenantAutoConfiguration {
92 return new TenantRedisMessageInterceptor(); 95 return new TenantRedisMessageInterceptor();
93 } 96 }
94 97
  98 + @Bean
  99 + @ConditionalOnClass(name = "org.springframework.amqp.rabbit.core.RabbitTemplate")
  100 + public TenantRabbitMQInitializer tenantRabbitMQInitializer() {
  101 + return new TenantRabbitMQInitializer();
  102 + }
  103 +
  104 + @Bean
  105 + @ConditionalOnClass(name = "org.apache.rocketmq.spring.core.RocketMQTemplate")
  106 + public TenantRocketMQInitializer tenantRocketMQInitializer() {
  107 + return new TenantRocketMQInitializer();
  108 + }
  109 +
95 // ========== Job ========== 110 // ========== Job ==========
96 111
97 @Bean 112 @Bean
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/mq/kafka/TenantKafkaEnvironmentPostProcessor.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.tenant.core.mq.kafka;
  2 +
  3 +import cn.hutool.core.util.StrUtil;
  4 +import lombok.extern.slf4j.Slf4j;
  5 +import org.springframework.boot.SpringApplication;
  6 +import org.springframework.boot.env.EnvironmentPostProcessor;
  7 +import org.springframework.core.env.ConfigurableEnvironment;
  8 +
  9 +/**
  10 + * 多租户的 Kafka 的 {@link EnvironmentPostProcessor} 实现类
  11 + *
  12 + * Kafka Producer 发送消息时,增加 {@link TenantKafkaProducerInterceptor} 拦截器
  13 + *
  14 + * @author 芋道源码
  15 + */
  16 +@Slf4j
  17 +public class TenantKafkaEnvironmentPostProcessor implements EnvironmentPostProcessor {
  18 +
  19 + private static final String PROPERTY_KEY_INTERCEPTOR_CLASSES = "spring.kafka.producer.properties.interceptor.classes";
  20 +
  21 + @Override
  22 + public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
  23 + // 添加 TenantKafkaProducerInterceptor 拦截器
  24 + try {
  25 + String value = environment.getProperty(PROPERTY_KEY_INTERCEPTOR_CLASSES);
  26 + if (StrUtil.isEmpty(value)) {
  27 + value = TenantKafkaProducerInterceptor.class.getName();
  28 + } else {
  29 + value += "," + TenantKafkaProducerInterceptor.class.getName();
  30 + }
  31 + environment.getSystemProperties().put(PROPERTY_KEY_INTERCEPTOR_CLASSES, value);
  32 + } catch (NoClassDefFoundError ignore) {
  33 + // 如果触发 NoClassDefFoundError 异常,说明 TenantKafkaProducerInterceptor 类不存在,即没引入 kafka-spring 依赖
  34 + }
  35 + }
  36 +
  37 +}
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/mq/kafka/TenantKafkaProducerInterceptor.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.tenant.core.mq.kafka;
  2 +
  3 +import cn.hutool.core.util.ReflectUtil;
  4 +import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
  5 +import org.apache.kafka.clients.producer.ProducerInterceptor;
  6 +import org.apache.kafka.clients.producer.ProducerRecord;
  7 +import org.apache.kafka.clients.producer.RecordMetadata;
  8 +import org.apache.kafka.common.header.Headers;
  9 +import org.springframework.messaging.handler.invocation.InvocableHandlerMethod;
  10 +
  11 +import java.util.Map;
  12 +
  13 +import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.HEADER_TENANT_ID;
  14 +
  15 +/**
  16 + * Kafka 消息队列的多租户 {@link ProducerInterceptor} 实现类
  17 + *
  18 + * 1. Producer 发送消息时,将 {@link TenantContextHolder} 租户编号,添加到消息的 Header 中
  19 + * 2. Consumer 消费消息时,将消息的 Header 的租户编号,添加到 {@link TenantContextHolder} 中,通过 {@link InvocableHandlerMethod} 实现
  20 + *
  21 + * @author 芋道源码
  22 + */
  23 +public class TenantKafkaProducerInterceptor implements ProducerInterceptor<Object, Object> {
  24 +
  25 + @Override
  26 + public ProducerRecord<Object, Object> onSend(ProducerRecord<Object, Object> record) {
  27 + Long tenantId = TenantContextHolder.getTenantId();
  28 + if (tenantId != null) {
  29 + Headers headers = (Headers) ReflectUtil.getFieldValue(record, "headers"); // private 属性,没有 get 方法,智能反射
  30 + headers.add(HEADER_TENANT_ID, tenantId.toString().getBytes());
  31 + }
  32 + return record;
  33 + }
  34 +
  35 + @Override
  36 + public void onAcknowledgement(RecordMetadata metadata, Exception exception) {
  37 + }
  38 +
  39 + @Override
  40 + public void close() {
  41 + }
  42 +
  43 + @Override
  44 + public void configure(Map<String, ?> configs) {
  45 + }
  46 +
  47 +}
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/mq/rabbitmq/TenantRabbitMQInitializer.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.tenant.core.mq.rabbitmq;
  2 +
  3 +import org.springframework.amqp.rabbit.core.RabbitTemplate;
  4 +import org.springframework.beans.BeansException;
  5 +import org.springframework.beans.factory.config.BeanPostProcessor;
  6 +
  7 +/**
  8 + * 多租户的 RabbitMQ 初始化器
  9 + *
  10 + * @author 芋道源码
  11 + */
  12 +public class TenantRabbitMQInitializer implements BeanPostProcessor {
  13 +
  14 + @Override
  15 + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
  16 + if (bean instanceof RabbitTemplate) {
  17 + RabbitTemplate rabbitTemplate = (RabbitTemplate) bean;
  18 + rabbitTemplate.addBeforePublishPostProcessors(new TenantRabbitMQMessagePostProcessor());
  19 + }
  20 + return bean;
  21 + }
  22 +
  23 +}
0 \ No newline at end of file 24 \ No newline at end of file
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/mq/rabbitmq/TenantRabbitMQMessagePostProcessor.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.tenant.core.mq.rabbitmq;
  2 +
  3 +import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
  4 +import org.apache.kafka.clients.producer.ProducerInterceptor;
  5 +import org.springframework.amqp.AmqpException;
  6 +import org.springframework.amqp.core.Message;
  7 +import org.springframework.amqp.core.MessagePostProcessor;
  8 +import org.springframework.messaging.handler.invocation.InvocableHandlerMethod;
  9 +
  10 +import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.HEADER_TENANT_ID;
  11 +
  12 +/**
  13 + * RabbitMQ 消息队列的多租户 {@link ProducerInterceptor} 实现类
  14 + *
  15 + * 1. Producer 发送消息时,将 {@link TenantContextHolder} 租户编号,添加到消息的 Header 中
  16 + * 2. Consumer 消费消息时,将消息的 Header 的租户编号,添加到 {@link TenantContextHolder} 中,通过 {@link InvocableHandlerMethod} 实现
  17 + *
  18 + * @author 芋道源码
  19 + */
  20 +public class TenantRabbitMQMessagePostProcessor implements MessagePostProcessor {
  21 +
  22 + @Override
  23 + public Message postProcessMessage(Message message) throws AmqpException {
  24 + Long tenantId = TenantContextHolder.getTenantId();
  25 + if (tenantId != null) {
  26 + message.getMessageProperties().getHeaders().put(HEADER_TENANT_ID, tenantId);
  27 + }
  28 + return message;
  29 + }
  30 +
  31 +}
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/mq/TenantRedisMessageInterceptor.java renamed to yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/mq/redis/TenantRedisMessageInterceptor.java
1 -package cn.iocoder.yudao.framework.tenant.core.mq; 1 +package cn.iocoder.yudao.framework.tenant.core.mq.redis;
2 2
3 import cn.hutool.core.util.StrUtil; 3 import cn.hutool.core.util.StrUtil;
4 -import cn.iocoder.yudao.framework.mq.core.interceptor.RedisMessageInterceptor;  
5 -import cn.iocoder.yudao.framework.mq.core.message.AbstractRedisMessage; 4 +import cn.iocoder.yudao.framework.mq.redis.core.interceptor.RedisMessageInterceptor;
  5 +import cn.iocoder.yudao.framework.mq.redis.core.message.AbstractRedisMessage;
6 import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; 6 import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
7 7
8 import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.HEADER_TENANT_ID; 8 import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.HEADER_TENANT_ID;
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/mq/rocketmq/TenantRocketMQConsumeMessageHook.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.tenant.core.mq.rocketmq;
  2 +
  3 +import cn.hutool.core.lang.Assert;
  4 +import cn.hutool.core.util.StrUtil;
  5 +import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
  6 +import org.apache.rocketmq.client.hook.ConsumeMessageContext;
  7 +import org.apache.rocketmq.client.hook.ConsumeMessageHook;
  8 +import org.apache.rocketmq.common.message.MessageExt;
  9 +import org.springframework.messaging.handler.invocation.InvocableHandlerMethod;
  10 +
  11 +import java.util.List;
  12 +
  13 +import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.HEADER_TENANT_ID;
  14 +
  15 +/**
  16 + * RocketMQ 消息队列的多租户 {@link ConsumeMessageHook} 实现类
  17 + *
  18 + * Consumer 消费消息时,将消息的 Header 的租户编号,添加到 {@link TenantContextHolder} 中,通过 {@link InvocableHandlerMethod} 实现
  19 + *
  20 + * @author 芋道源码
  21 + */
  22 +public class TenantRocketMQConsumeMessageHook implements ConsumeMessageHook {
  23 +
  24 + @Override
  25 + public String hookName() {
  26 + return getClass().getSimpleName();
  27 + }
  28 +
  29 + @Override
  30 + public void consumeMessageBefore(ConsumeMessageContext context) {
  31 + // 校验,消息必须是单条,不然设置租户可能不正确
  32 + List<MessageExt> messages = context.getMsgList();
  33 + Assert.isTrue(messages.size() == 1, "消息条数({})不正确", messages.size());
  34 + // 设置租户编号
  35 + String tenantId = messages.get(0).getUserProperty(HEADER_TENANT_ID);
  36 + if (StrUtil.isNotEmpty(tenantId)) {
  37 + TenantContextHolder.setTenantId(Long.parseLong(tenantId));
  38 + }
  39 + }
  40 +
  41 + @Override
  42 + public void consumeMessageAfter(ConsumeMessageContext context) {
  43 + TenantContextHolder.clear();
  44 + }
  45 +
  46 +}
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/mq/rocketmq/TenantRocketMQInitializer.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.tenant.core.mq.rocketmq;
  2 +
  3 +import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
  4 +import org.apache.rocketmq.client.impl.consumer.DefaultMQPushConsumerImpl;
  5 +import org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl;
  6 +import org.apache.rocketmq.client.producer.DefaultMQProducer;
  7 +import org.apache.rocketmq.spring.core.RocketMQTemplate;
  8 +import org.apache.rocketmq.spring.support.DefaultRocketMQListenerContainer;
  9 +import org.springframework.beans.BeansException;
  10 +import org.springframework.beans.factory.config.BeanPostProcessor;
  11 +
  12 +/**
  13 + * 多租户的 RocketMQ 初始化器
  14 + *
  15 + * @author 芋道源码
  16 + */
  17 +public class TenantRocketMQInitializer implements BeanPostProcessor {
  18 +
  19 + @Override
  20 + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
  21 + if (bean instanceof DefaultRocketMQListenerContainer) {
  22 + DefaultRocketMQListenerContainer container = (DefaultRocketMQListenerContainer) bean;
  23 + initTenantConsumer(container.getConsumer());
  24 + } else if (bean instanceof RocketMQTemplate) {
  25 + RocketMQTemplate template = (RocketMQTemplate) bean;
  26 + initTenantProducer(template.getProducer());
  27 + }
  28 + return bean;
  29 + }
  30 +
  31 + private void initTenantProducer(DefaultMQProducer producer) {
  32 + if (producer == null) {
  33 + return;
  34 + }
  35 + DefaultMQProducerImpl producerImpl = producer.getDefaultMQProducerImpl();
  36 + if (producerImpl == null) {
  37 + return;
  38 + }
  39 + producerImpl.registerSendMessageHook(new TenantRocketMQSendMessageHook());
  40 + }
  41 +
  42 + private void initTenantConsumer(DefaultMQPushConsumer consumer) {
  43 + if (consumer == null) {
  44 + return;
  45 + }
  46 + DefaultMQPushConsumerImpl consumerImpl = consumer.getDefaultMQPushConsumerImpl();
  47 + if (consumerImpl == null) {
  48 + return;
  49 + }
  50 + consumerImpl.registerConsumeMessageHook(new TenantRocketMQConsumeMessageHook());
  51 + }
  52 +
  53 +}
0 \ No newline at end of file 54 \ No newline at end of file
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/mq/rocketmq/TenantRocketMQSendMessageHook.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.tenant.core.mq.rocketmq;
  2 +
  3 +import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
  4 +import org.apache.rocketmq.client.hook.SendMessageContext;
  5 +import org.apache.rocketmq.client.hook.SendMessageHook;
  6 +
  7 +import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.HEADER_TENANT_ID;
  8 +
  9 +/**
  10 + * RocketMQ 消息队列的多租户 {@link SendMessageHook} 实现类
  11 + *
  12 + * Producer 发送消息时,将 {@link TenantContextHolder} 租户编号,添加到消息的 Header 中
  13 + *
  14 + * @author 芋道源码
  15 + */
  16 +public class TenantRocketMQSendMessageHook implements SendMessageHook {
  17 +
  18 + @Override
  19 + public String hookName() {
  20 + return getClass().getSimpleName();
  21 + }
  22 +
  23 + @Override
  24 + public void sendMessageBefore(SendMessageContext sendMessageContext) {
  25 + Long tenantId = TenantContextHolder.getTenantId();
  26 + if (tenantId == null) {
  27 + return;
  28 + }
  29 + sendMessageContext.getMessage().putUserProperty(HEADER_TENANT_ID, tenantId.toString());
  30 + }
  31 +
  32 + @Override
  33 + public void sendMessageAfter(SendMessageContext sendMessageContext) {
  34 + }
  35 +
  36 +}
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/org/springframework/messaging/handler/invocation/InvocableHandlerMethod.java 0 → 100644
  1 +/*
  2 + * Copyright 2002-2021 the original author or authors.
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * https://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package org.springframework.messaging.handler.invocation;
  18 +
  19 +import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
  20 +import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
  21 +import org.springframework.core.DefaultParameterNameDiscoverer;
  22 +import org.springframework.core.MethodParameter;
  23 +import org.springframework.core.ParameterNameDiscoverer;
  24 +import org.springframework.core.ResolvableType;
  25 +import org.springframework.lang.Nullable;
  26 +import org.springframework.messaging.Message;
  27 +import org.springframework.messaging.handler.HandlerMethod;
  28 +import org.springframework.util.ObjectUtils;
  29 +
  30 +import java.lang.reflect.InvocationTargetException;
  31 +import java.lang.reflect.Method;
  32 +import java.lang.reflect.Type;
  33 +import java.util.Arrays;
  34 +
  35 +import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.HEADER_TENANT_ID;
  36 +
  37 +/**
  38 + * Extension of {@link HandlerMethod} that invokes the underlying method with
  39 + * argument values resolved from the current HTTP request through a list of
  40 + * {@link HandlerMethodArgumentResolver}.
  41 + *
  42 + * 针对 rabbitmq-spring 和 kafka-spring,不存在合适的拓展点,可以实现 Consumer 消费前,读取 Header 中的 tenant-id 设置到 {@link TenantContextHolder} 中
  43 + * TODO 芋艿:持续跟进,看看有没新的拓展点
  44 + *
  45 + * @author Rossen Stoyanchev
  46 + * @author Juergen Hoeller
  47 + * @since 4.0
  48 + */
  49 +public class InvocableHandlerMethod extends HandlerMethod {
  50 +
  51 + private static final Object[] EMPTY_ARGS = new Object[0];
  52 +
  53 + private HandlerMethodArgumentResolverComposite resolvers = new HandlerMethodArgumentResolverComposite();
  54 +
  55 + private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
  56 +
  57 + /**
  58 + * Create an instance from a {@code HandlerMethod}.
  59 + */
  60 + public InvocableHandlerMethod(HandlerMethod handlerMethod) {
  61 + super(handlerMethod);
  62 + }
  63 +
  64 + /**
  65 + * Create an instance from a bean instance and a method.
  66 + */
  67 + public InvocableHandlerMethod(Object bean, Method method) {
  68 + super(bean, method);
  69 + }
  70 +
  71 + /**
  72 + * Construct a new handler method with the given bean instance, method name and parameters.
  73 + * @param bean the object bean
  74 + * @param methodName the method name
  75 + * @param parameterTypes the method parameter types
  76 + * @throws NoSuchMethodException when the method cannot be found
  77 + */
  78 + public InvocableHandlerMethod(Object bean, String methodName, Class<?>... parameterTypes)
  79 + throws NoSuchMethodException {
  80 +
  81 + super(bean, methodName, parameterTypes);
  82 + }
  83 +
  84 + /**
  85 + * Set {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers} to use for resolving method argument values.
  86 + */
  87 + public void setMessageMethodArgumentResolvers(HandlerMethodArgumentResolverComposite argumentResolvers) {
  88 + this.resolvers = argumentResolvers;
  89 + }
  90 +
  91 + /**
  92 + * Set the ParameterNameDiscoverer for resolving parameter names when needed
  93 + * (e.g. default request attribute name).
  94 + * <p>Default is a {@link DefaultParameterNameDiscoverer}.
  95 + */
  96 + public void setParameterNameDiscoverer(ParameterNameDiscoverer parameterNameDiscoverer) {
  97 + this.parameterNameDiscoverer = parameterNameDiscoverer;
  98 + }
  99 +
  100 + /**
  101 + * Invoke the method after resolving its argument values in the context of the given message.
  102 + * <p>Argument values are commonly resolved through
  103 + * {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers}.
  104 + * The {@code providedArgs} parameter however may supply argument values to be used directly,
  105 + * i.e. without argument resolution.
  106 + * <p>Delegates to {@link #getMethodArgumentValues} and calls {@link #doInvoke} with the
  107 + * resolved arguments.
  108 + * @param message the current message being processed
  109 + * @param providedArgs "given" arguments matched by type, not resolved
  110 + * @return the raw value returned by the invoked method
  111 + * @throws Exception raised if no suitable argument resolver can be found,
  112 + * or if the method raised an exception
  113 + * @see #getMethodArgumentValues
  114 + * @see #doInvoke
  115 + */
  116 + @Nullable
  117 + public Object invoke(Message<?> message, Object... providedArgs) throws Exception {
  118 + Object[] args = getMethodArgumentValues(message, providedArgs);
  119 + if (logger.isTraceEnabled()) {
  120 + logger.trace("Arguments: " + Arrays.toString(args));
  121 + }
  122 + // 注意:如下是本类的改动点!!!
  123 + // 情况一:无租户编号的情况
  124 + Long tenantId= parseTenantId(message);
  125 + if (tenantId == null) {
  126 + return doInvoke(args);
  127 + }
  128 + // 情况二:有租户的情况下
  129 + return TenantUtils.execute(tenantId, () -> doInvoke(args));
  130 + }
  131 +
  132 + private Long parseTenantId(Message<?> message) {
  133 + Object tenantId = message.getHeaders().get(HEADER_TENANT_ID);
  134 + if (tenantId == null) {
  135 + return null;
  136 + }
  137 + if (tenantId instanceof Long) {
  138 + return (Long) tenantId;
  139 + }
  140 + if (tenantId instanceof Number) {
  141 + return ((Number) tenantId).longValue();
  142 + }
  143 + if (tenantId instanceof String) {
  144 + return Long.parseLong((String) tenantId);
  145 + }
  146 + if (tenantId instanceof byte[]) {
  147 + return Long.parseLong(new String((byte[]) tenantId));
  148 + }
  149 + throw new IllegalArgumentException("未知的数据类型:" + tenantId);
  150 + }
  151 +
  152 + /**
  153 + * Get the method argument values for the current message, checking the provided
  154 + * argument values and falling back to the configured argument resolvers.
  155 + * <p>The resulting array will be passed into {@link #doInvoke}.
  156 + * @since 5.1.2
  157 + */
  158 + protected Object[] getMethodArgumentValues(Message<?> message, Object... providedArgs) throws Exception {
  159 + MethodParameter[] parameters = getMethodParameters();
  160 + if (ObjectUtils.isEmpty(parameters)) {
  161 + return EMPTY_ARGS;
  162 + }
  163 +
  164 + Object[] args = new Object[parameters.length];
  165 + for (int i = 0; i < parameters.length; i++) {
  166 + MethodParameter parameter = parameters[i];
  167 + parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
  168 + args[i] = findProvidedArgument(parameter, providedArgs);
  169 + if (args[i] != null) {
  170 + continue;
  171 + }
  172 + if (!this.resolvers.supportsParameter(parameter)) {
  173 + throw new MethodArgumentResolutionException(
  174 + message, parameter, formatArgumentError(parameter, "No suitable resolver"));
  175 + }
  176 + try {
  177 + args[i] = this.resolvers.resolveArgument(parameter, message);
  178 + }
  179 + catch (Exception ex) {
  180 + // Leave stack trace for later, exception may actually be resolved and handled...
  181 + if (logger.isDebugEnabled()) {
  182 + String exMsg = ex.getMessage();
  183 + if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
  184 + logger.debug(formatArgumentError(parameter, exMsg));
  185 + }
  186 + }
  187 + throw ex;
  188 + }
  189 + }
  190 + return args;
  191 + }
  192 +
  193 + /**
  194 + * Invoke the handler method with the given argument values.
  195 + */
  196 + @Nullable
  197 + protected Object doInvoke(Object... args) throws Exception {
  198 + try {
  199 + return getBridgedMethod().invoke(getBean(), args);
  200 + }
  201 + catch (IllegalArgumentException ex) {
  202 + assertTargetBean(getBridgedMethod(), getBean(), args);
  203 + String text = (ex.getMessage() != null ? ex.getMessage() : "Illegal argument");
  204 + throw new IllegalStateException(formatInvokeError(text, args), ex);
  205 + }
  206 + catch (InvocationTargetException ex) {
  207 + // Unwrap for HandlerExceptionResolvers ...
  208 + Throwable targetException = ex.getTargetException();
  209 + if (targetException instanceof RuntimeException) {
  210 + throw (RuntimeException) targetException;
  211 + }
  212 + else if (targetException instanceof Error) {
  213 + throw (Error) targetException;
  214 + }
  215 + else if (targetException instanceof Exception) {
  216 + throw (Exception) targetException;
  217 + }
  218 + else {
  219 + throw new IllegalStateException(formatInvokeError("Invocation failure", args), targetException);
  220 + }
  221 + }
  222 + }
  223 +
  224 + MethodParameter getAsyncReturnValueType(@Nullable Object returnValue) {
  225 + return new AsyncResultMethodParameter(returnValue);
  226 + }
  227 +
  228 + private class AsyncResultMethodParameter extends HandlerMethodParameter {
  229 +
  230 + @Nullable
  231 + private final Object returnValue;
  232 +
  233 + private final ResolvableType returnType;
  234 +
  235 + public AsyncResultMethodParameter(@Nullable Object returnValue) {
  236 + super(-1);
  237 + this.returnValue = returnValue;
  238 + this.returnType = ResolvableType.forType(super.getGenericParameterType()).getGeneric();
  239 + }
  240 +
  241 + protected AsyncResultMethodParameter(AsyncResultMethodParameter original) {
  242 + super(original);
  243 + this.returnValue = original.returnValue;
  244 + this.returnType = original.returnType;
  245 + }
  246 +
  247 + @Override
  248 + public Class<?> getParameterType() {
  249 + if (this.returnValue != null) {
  250 + return this.returnValue.getClass();
  251 + }
  252 + if (!ResolvableType.NONE.equals(this.returnType)) {
  253 + return this.returnType.toClass();
  254 + }
  255 + return super.getParameterType();
  256 + }
  257 +
  258 + @Override
  259 + public Type getGenericParameterType() {
  260 + return this.returnType.getType();
  261 + }
  262 +
  263 + @Override
  264 + public AsyncResultMethodParameter clone() {
  265 + return new AsyncResultMethodParameter(this);
  266 + }
  267 + }
  268 +
  269 +}
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/resources/META-INF/spring.factories 0 → 100644
  1 +org.springframework.boot.env.EnvironmentPostProcessor=\
  2 + cn.iocoder.yudao.framework.tenant.core.mq.kafka.TenantKafkaEnvironmentPostProcessor
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/test/java/cn/iocoder/yudao/framework/tenant/core/job/TestJob.java deleted
1 -package cn.iocoder.yudao.framework.tenant.core.job;  
2 -  
3 -import cn.hutool.core.collection.CollUtil;  
4 -import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;  
5 -import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;  
6 -import org.springframework.stereotype.Component;  
7 -  
8 -import java.util.List;  
9 -import java.util.concurrent.CopyOnWriteArrayList;  
10 -  
11 -@Component  
12 -public class TestJob implements JobHandler {  
13 -  
14 - private final List<Long> tenantIds = new CopyOnWriteArrayList<>();  
15 -  
16 - @Override  
17 - @TenantJob // 标记多租户  
18 - public String execute(String param) throws Exception {  
19 - tenantIds.add(TenantContextHolder.getTenantId());  
20 - return "success";  
21 - }  
22 -  
23 - public List<Long> getTenantIds() {  
24 - CollUtil.sort(tenantIds, Long::compareTo);  
25 - return tenantIds;  
26 - }  
27 -  
28 -}  
yudao-framework/yudao-spring-boot-starter-mq/pom.xml
@@ -12,7 +12,7 @@ @@ -12,7 +12,7 @@
12 <packaging>jar</packaging> 12 <packaging>jar</packaging>
13 13
14 <name>${project.artifactId}</name> 14 <name>${project.artifactId}</name>
15 - <description>消息队列,基于 Redis Pub/Sub 实现广播消费,基于 Stream 实现集群消费</description> 15 + <description>消息队列,支持 Redis、RocketMQ、RabbitMQ、Kafka 四种</description>
16 <url>https://github.com/YunaiV/ruoyi-vue-pro</url> 16 <url>https://github.com/YunaiV/ruoyi-vue-pro</url>
17 17
18 <dependencies> 18 <dependencies>
@@ -21,6 +21,23 @@ @@ -21,6 +21,23 @@
21 <groupId>cn.iocoder.boot</groupId> 21 <groupId>cn.iocoder.boot</groupId>
22 <artifactId>yudao-spring-boot-starter-redis</artifactId> 22 <artifactId>yudao-spring-boot-starter-redis</artifactId>
23 </dependency> 23 </dependency>
  24 +
  25 + <!-- 消息队列相关 -->
  26 + <dependency>
  27 + <groupId>org.springframework.kafka</groupId>
  28 + <artifactId>spring-kafka</artifactId>
  29 + <optional>true</optional>
  30 + </dependency>
  31 + <dependency>
  32 + <groupId>org.springframework.amqp</groupId>
  33 + <artifactId>spring-rabbit</artifactId>
  34 + <optional>true</optional>
  35 + </dependency>
  36 + <dependency>
  37 + <groupId>org.apache.rocketmq</groupId>
  38 + <artifactId>rocketmq-spring-boot-starter</artifactId>
  39 + <optional>true</optional>
  40 + </dependency>
24 </dependencies> 41 </dependencies>
25 42
26 -</project> 43 -</project>
  44 +</project>
27 \ No newline at end of file 45 \ No newline at end of file
yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/package-info.java
1 /** 1 /**
2 - * 消息队列,基于 Redis 提供:  
3 - * 1. 基于 Pub/Sub 实现广播消费  
4 - * 2. 基于 Stream 实现集群消费 2 + * 消息队列,支持 Redis、RocketMQ、RabbitMQ、Kafka 四种
5 */ 3 */
6 package cn.iocoder.yudao.framework.mq; 4 package cn.iocoder.yudao.framework.mq;
yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/rabbitmq/config/YudaoRabbitMQAutoConfiguration.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.mq.rabbitmq.config;
  2 +
  3 +import cn.hutool.core.util.ReflectUtil;
  4 +import lombok.extern.slf4j.Slf4j;
  5 +import org.springframework.amqp.utils.SerializationUtils;
  6 +import org.springframework.boot.autoconfigure.AutoConfiguration;
  7 +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
  8 +
  9 +import java.lang.reflect.Field;
  10 +
  11 +/**
  12 + * RabbitMQ 消息队列配置类
  13 + *
  14 + * @author 芋道源码
  15 + */
  16 +@AutoConfiguration
  17 +@Slf4j
  18 +@ConditionalOnClass(name = "org.springframework.amqp.rabbit.core.RabbitTemplate")
  19 +public class YudaoRabbitMQAutoConfiguration {
  20 +
  21 + static {
  22 + // 强制设置 SerializationUtils 的 TRUST_ALL 为 true,避免 RabbitMQ Consumer 反序列化消息报错
  23 + // 为什么不通过设置 spring.amqp.deserialization.trust.all 呢?因为可能在 SerializationUtils static 初始化后
  24 + Field trustAllField = ReflectUtil.getField(SerializationUtils.class, "TRUST_ALL");
  25 + ReflectUtil.removeFinalModify(trustAllField);
  26 + ReflectUtil.setFieldValue(SerializationUtils.class, trustAllField, true);
  27 + }
  28 +
  29 +}
yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/rabbitmq/core/package-info.java 0 → 100644
  1 +/**
  2 + * 占位符,无特殊逻辑
  3 + */
  4 +package cn.iocoder.yudao.framework.mq.rabbitmq.core;
0 \ No newline at end of file 5 \ No newline at end of file
yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/rabbitmq/package-info.java 0 → 100644
  1 +/**
  2 + * 消息队列,基于 RabbitMQ 提供
  3 + */
  4 +package cn.iocoder.yudao.framework.mq.rabbitmq;
yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/config/YudaoMQAutoConfiguration.java renamed to yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/config/YudaoRedisMQAutoConfiguration.java
1 -package cn.iocoder.yudao.framework.mq.config; 1 +package cn.iocoder.yudao.framework.mq.redis.config;
2 2
3 import cn.hutool.core.map.MapUtil; 3 import cn.hutool.core.map.MapUtil;
4 import cn.hutool.core.util.StrUtil; 4 import cn.hutool.core.util.StrUtil;
5 import cn.hutool.system.SystemUtil; 5 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.core.RedisMQTemplate;  
8 -import cn.iocoder.yudao.framework.mq.core.interceptor.RedisMessageInterceptor;  
9 -import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener;  
10 -import cn.iocoder.yudao.framework.mq.core.stream.AbstractStreamMessageListener;  
11 -import cn.iocoder.yudao.framework.mq.job.RedisPendingMessageResendJob; 7 +import cn.iocoder.yudao.framework.mq.redis.core.RedisMQTemplate;
  8 +import cn.iocoder.yudao.framework.mq.redis.core.interceptor.RedisMessageInterceptor;
  9 +import cn.iocoder.yudao.framework.mq.redis.core.job.RedisPendingMessageResendJob;
  10 +import cn.iocoder.yudao.framework.mq.redis.core.pubsub.AbstractRedisChannelMessageListener;
  11 +import cn.iocoder.yudao.framework.mq.redis.core.stream.AbstractRedisStreamMessageListener;
12 import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration; 12 import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration;
13 import lombok.extern.slf4j.Slf4j; 13 import lombok.extern.slf4j.Slf4j;
14 import org.redisson.api.RedissonClient; 14 import org.redisson.api.RedissonClient;
15 import org.springframework.beans.factory.annotation.Value; 15 import org.springframework.beans.factory.annotation.Value;
16 import org.springframework.boot.autoconfigure.AutoConfiguration; 16 import org.springframework.boot.autoconfigure.AutoConfiguration;
17 import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; 17 import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
18 -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;  
19 import org.springframework.context.annotation.Bean; 18 import org.springframework.context.annotation.Bean;
20 import org.springframework.data.redis.connection.RedisServerCommands; 19 import org.springframework.data.redis.connection.RedisServerCommands;
21 import org.springframework.data.redis.connection.stream.Consumer; 20 import org.springframework.data.redis.connection.stream.Consumer;
@@ -27,7 +26,6 @@ import org.springframework.data.redis.core.RedisTemplate; @@ -27,7 +26,6 @@ import org.springframework.data.redis.core.RedisTemplate;
27 import org.springframework.data.redis.core.StringRedisTemplate; 26 import org.springframework.data.redis.core.StringRedisTemplate;
28 import org.springframework.data.redis.listener.ChannelTopic; 27 import org.springframework.data.redis.listener.ChannelTopic;
29 import org.springframework.data.redis.listener.RedisMessageListenerContainer; 28 import org.springframework.data.redis.listener.RedisMessageListenerContainer;
30 -import org.springframework.data.redis.stream.DefaultStreamMessageListenerContainerX;  
31 import org.springframework.data.redis.stream.StreamMessageListenerContainer; 29 import org.springframework.data.redis.stream.StreamMessageListenerContainer;
32 import org.springframework.scheduling.annotation.EnableScheduling; 30 import org.springframework.scheduling.annotation.EnableScheduling;
33 31
@@ -42,7 +40,7 @@ import java.util.Properties; @@ -42,7 +40,7 @@ import java.util.Properties;
42 @Slf4j 40 @Slf4j
43 @EnableScheduling // 启用定时任务,用于 RedisPendingMessageResendJob 重发消息 41 @EnableScheduling // 启用定时任务,用于 RedisPendingMessageResendJob 重发消息
44 @AutoConfiguration(after = YudaoRedisAutoConfiguration.class) 42 @AutoConfiguration(after = YudaoRedisAutoConfiguration.class)
45 -public class YudaoMQAutoConfiguration { 43 +public class YudaoRedisMQAutoConfiguration {
46 44
47 @Bean 45 @Bean
48 public RedisMQTemplate redisMQTemplate(StringRedisTemplate redisTemplate, 46 public RedisMQTemplate redisMQTemplate(StringRedisTemplate redisTemplate,
@@ -59,10 +57,9 @@ public class YudaoMQAutoConfiguration { @@ -59,10 +57,9 @@ public class YudaoMQAutoConfiguration {
59 * 创建 Redis Pub/Sub 广播消费的容器 57 * 创建 Redis Pub/Sub 广播消费的容器
60 */ 58 */
61 @Bean(initMethod = "start", destroyMethod = "stop") 59 @Bean(initMethod = "start", destroyMethod = "stop")
62 - @ConditionalOnBean(AbstractChannelMessageListener.class) // 只有 AbstractChannelMessageListener 存在的时候,才需要注册 Redis pubsub 监听  
63 - @ConditionalOnProperty(prefix = "yudao.mq.redis.pubsub", value = "enable", matchIfMissing = true) // 允许使用 yudao.mq.redis.pubsub.enable=false 禁用多租户 60 + @ConditionalOnBean(AbstractRedisChannelMessageListener.class) // 只有 AbstractChannelMessageListener 存在的时候,才需要注册 Redis pubsub 监听
64 public RedisMessageListenerContainer redisMessageListenerContainer( 61 public RedisMessageListenerContainer redisMessageListenerContainer(
65 - RedisMQTemplate redisMQTemplate, List<AbstractChannelMessageListener<?>> listeners) { 62 + RedisMQTemplate redisMQTemplate, List<AbstractRedisChannelMessageListener<?>> listeners) {
66 // 创建 RedisMessageListenerContainer 对象 63 // 创建 RedisMessageListenerContainer 对象
67 RedisMessageListenerContainer container = new RedisMessageListenerContainer(); 64 RedisMessageListenerContainer container = new RedisMessageListenerContainer();
68 // 设置 RedisConnection 工厂。 65 // 设置 RedisConnection 工厂。
@@ -81,9 +78,8 @@ public class YudaoMQAutoConfiguration { @@ -81,9 +78,8 @@ public class YudaoMQAutoConfiguration {
81 * 创建 Redis Stream 重新消费的任务 78 * 创建 Redis Stream 重新消费的任务
82 */ 79 */
83 @Bean 80 @Bean
84 - @ConditionalOnBean(AbstractStreamMessageListener.class) // 只有 AbstractStreamMessageListener 存在的时候,才需要注册 Redis pubsub 监听  
85 - @ConditionalOnProperty(prefix = "yudao.mq.redis.stream", value = "enable", matchIfMissing = true) // 允许使用 yudao.mq.redis.stream.enable=false 禁用多租户  
86 - public RedisPendingMessageResendJob redisPendingMessageResendJob(List<AbstractStreamMessageListener<?>> listeners, 81 + @ConditionalOnBean(AbstractRedisStreamMessageListener.class) // 只有 AbstractStreamMessageListener 存在的时候,才需要注册 Redis pubsub 监听
  82 + public RedisPendingMessageResendJob redisPendingMessageResendJob(List<AbstractRedisStreamMessageListener<?>> listeners,
87 RedisMQTemplate redisTemplate, 83 RedisMQTemplate redisTemplate,
88 @Value("${spring.application.name}") String groupName, 84 @Value("${spring.application.name}") String groupName,
89 RedissonClient redissonClient) { 85 RedissonClient redissonClient) {
@@ -92,14 +88,13 @@ public class YudaoMQAutoConfiguration { @@ -92,14 +88,13 @@ public class YudaoMQAutoConfiguration {
92 88
93 /** 89 /**
94 * 创建 Redis Stream 集群消费的容器 90 * 创建 Redis Stream 集群消费的容器
95 - * <p>  
96 - * Redis Stream 的 xreadgroup 命令:https://www.geek-book.com/src/docs/redis/redis/redis.io/commands/xreadgroup.html 91 + *
  92 + * 基础知识:<a href="https://www.geek-book.com/src/docs/redis/redis/redis.io/commands/xreadgroup.html">Redis Stream 的 xreadgroup 命令</a>
97 */ 93 */
98 @Bean(initMethod = "start", destroyMethod = "stop") 94 @Bean(initMethod = "start", destroyMethod = "stop")
99 - @ConditionalOnBean(AbstractStreamMessageListener.class) // 只有 AbstractStreamMessageListener 存在的时候,才需要注册 Redis pubsub 监听  
100 - @ConditionalOnProperty(prefix = "yudao.mq.redis.stream", value = "enable", matchIfMissing = true) // 允许使用 yudao.mq.redis.stream.enable=false 禁用多租户 95 + @ConditionalOnBean(AbstractRedisStreamMessageListener.class) // 只有 AbstractStreamMessageListener 存在的时候,才需要注册 Redis pubsub 监听
101 public StreamMessageListenerContainer<String, ObjectRecord<String, String>> redisStreamMessageListenerContainer( 96 public StreamMessageListenerContainer<String, ObjectRecord<String, String>> redisStreamMessageListenerContainer(
102 - RedisMQTemplate redisMQTemplate, List<AbstractStreamMessageListener<?>> listeners) { 97 + RedisMQTemplate redisMQTemplate, List<AbstractRedisStreamMessageListener<?>> listeners) {
103 RedisTemplate<String, ?> redisTemplate = redisMQTemplate.getRedisTemplate(); 98 RedisTemplate<String, ?> redisTemplate = redisMQTemplate.getRedisTemplate();
104 checkRedisVersion(redisTemplate); 99 checkRedisVersion(redisTemplate);
105 // 第一步,创建 StreamMessageListenerContainer 容器 100 // 第一步,创建 StreamMessageListenerContainer 容器
@@ -111,8 +106,7 @@ public class YudaoMQAutoConfiguration { @@ -111,8 +106,7 @@ public class YudaoMQAutoConfiguration {
111 .build(); 106 .build();
112 // 创建 container 对象 107 // 创建 container 对象
113 StreamMessageListenerContainer<String, ObjectRecord<String, String>> container = 108 StreamMessageListenerContainer<String, ObjectRecord<String, String>> container =
114 -// StreamMessageListenerContainer.create(redisTemplate.getRequiredConnectionFactory(), containerOptions);  
115 - DefaultStreamMessageListenerContainerX.create(redisMQTemplate.getRedisTemplate().getRequiredConnectionFactory(), containerOptions); 109 + StreamMessageListenerContainer.create(redisMQTemplate.getRedisTemplate().getRequiredConnectionFactory(), containerOptions);
116 110
117 // 第二步,注册监听器,消费对应的 Stream 主题 111 // 第二步,注册监听器,消费对应的 Stream 主题
118 String consumerName = buildConsumerName(); 112 String consumerName = buildConsumerName();
yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/core/RedisMQTemplate.java renamed to yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/core/RedisMQTemplate.java
1 -package cn.iocoder.yudao.framework.mq.core; 1 +package cn.iocoder.yudao.framework.mq.redis.core;
2 2
3 import cn.iocoder.yudao.framework.common.util.json.JsonUtils; 3 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
4 -import cn.iocoder.yudao.framework.mq.core.interceptor.RedisMessageInterceptor;  
5 -import cn.iocoder.yudao.framework.mq.core.message.AbstractRedisMessage;  
6 -import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessage;  
7 -import cn.iocoder.yudao.framework.mq.core.stream.AbstractStreamMessage; 4 +import cn.iocoder.yudao.framework.mq.redis.core.interceptor.RedisMessageInterceptor;
  5 +import cn.iocoder.yudao.framework.mq.redis.core.message.AbstractRedisMessage;
  6 +import cn.iocoder.yudao.framework.mq.redis.core.pubsub.AbstractRedisChannelMessage;
  7 +import cn.iocoder.yudao.framework.mq.redis.core.stream.AbstractRedisStreamMessage;
8 import lombok.AllArgsConstructor; 8 import lombok.AllArgsConstructor;
9 import lombok.Getter; 9 import lombok.Getter;
10 import org.springframework.data.redis.connection.stream.RecordId; 10 import org.springframework.data.redis.connection.stream.RecordId;
@@ -35,7 +35,7 @@ public class RedisMQTemplate { @@ -35,7 +35,7 @@ public class RedisMQTemplate {
35 * 35 *
36 * @param message 消息 36 * @param message 消息
37 */ 37 */
38 - public <T extends AbstractChannelMessage> void send(T message) { 38 + public <T extends AbstractRedisChannelMessage> void send(T message) {
39 try { 39 try {
40 sendMessageBefore(message); 40 sendMessageBefore(message);
41 // 发送消息 41 // 发送消息
@@ -51,7 +51,7 @@ public class RedisMQTemplate { @@ -51,7 +51,7 @@ public class RedisMQTemplate {
51 * @param message 消息 51 * @param message 消息
52 * @return 消息记录的编号对象 52 * @return 消息记录的编号对象
53 */ 53 */
54 - public <T extends AbstractStreamMessage> RecordId send(T message) { 54 + public <T extends AbstractRedisStreamMessage> RecordId send(T message) {
55 try { 55 try {
56 sendMessageBefore(message); 56 sendMessageBefore(message);
57 // 发送消息 57 // 发送消息
yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/core/interceptor/RedisMessageInterceptor.java renamed to yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/core/interceptor/RedisMessageInterceptor.java
1 -package cn.iocoder.yudao.framework.mq.core.interceptor; 1 +package cn.iocoder.yudao.framework.mq.redis.core.interceptor;
2 2
3 -import cn.iocoder.yudao.framework.mq.core.message.AbstractRedisMessage; 3 +import cn.iocoder.yudao.framework.mq.redis.core.message.AbstractRedisMessage;
4 4
5 /** 5 /**
6 * {@link AbstractRedisMessage} 消息拦截器 6 * {@link AbstractRedisMessage} 消息拦截器
yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/job/RedisPendingMessageResendJob.java renamed to yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/core/job/RedisPendingMessageResendJob.java
1 -package cn.iocoder.yudao.framework.mq.job; 1 +package cn.iocoder.yudao.framework.mq.redis.core.job;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 -import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;  
5 -import cn.iocoder.yudao.framework.mq.core.stream.AbstractStreamMessageListener; 4 +import cn.iocoder.yudao.framework.mq.redis.core.RedisMQTemplate;
  5 +import cn.iocoder.yudao.framework.mq.redis.core.stream.AbstractRedisStreamMessageListener;
6 import lombok.AllArgsConstructor; 6 import lombok.AllArgsConstructor;
7 import lombok.extern.slf4j.Slf4j; 7 import lombok.extern.slf4j.Slf4j;
8 import org.redisson.api.RLock; 8 import org.redisson.api.RLock;
@@ -33,7 +33,7 @@ public class RedisPendingMessageResendJob { @@ -33,7 +33,7 @@ public class RedisPendingMessageResendJob {
33 */ 33 */
34 private static final int EXPIRE_TIME = 5 * 60; 34 private static final int EXPIRE_TIME = 5 * 60;
35 35
36 - private final List<AbstractStreamMessageListener<?>> listeners; 36 + private final List<AbstractRedisStreamMessageListener<?>> listeners;
37 private final RedisMQTemplate redisTemplate; 37 private final RedisMQTemplate redisTemplate;
38 private final String groupName; 38 private final String groupName;
39 private final RedissonClient redissonClient; 39 private final RedissonClient redissonClient;
yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/core/message/AbstractRedisMessage.java renamed to yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/core/message/AbstractRedisMessage.java
1 -package cn.iocoder.yudao.framework.mq.core.message; 1 +package cn.iocoder.yudao.framework.mq.redis.core.message;
2 2
3 import lombok.Data; 3 import lombok.Data;
4 4
yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/core/pubsub/AbstractChannelMessage.java renamed to yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/core/pubsub/AbstractRedisChannelMessage.java
1 -package cn.iocoder.yudao.framework.mq.core.pubsub; 1 +package cn.iocoder.yudao.framework.mq.redis.core.pubsub;
2 2
3 -import cn.iocoder.yudao.framework.mq.core.message.AbstractRedisMessage; 3 +import cn.iocoder.yudao.framework.mq.redis.core.message.AbstractRedisMessage;
4 import com.fasterxml.jackson.annotation.JsonIgnore; 4 import com.fasterxml.jackson.annotation.JsonIgnore;
5 5
6 /** 6 /**
@@ -8,14 +8,16 @@ import com.fasterxml.jackson.annotation.JsonIgnore; @@ -8,14 +8,16 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
8 * 8 *
9 * @author 芋道源码 9 * @author 芋道源码
10 */ 10 */
11 -public abstract class AbstractChannelMessage extends AbstractRedisMessage { 11 +public abstract class AbstractRedisChannelMessage extends AbstractRedisMessage {
12 12
13 /** 13 /**
14 - * 获得 Redis Channel 14 + * 获得 Redis Channel,默认使用类名
15 * 15 *
16 * @return Channel 16 * @return Channel
17 */ 17 */
18 @JsonIgnore // 避免序列化。原因是,Redis 发布 Channel 消息的时候,已经会指定。 18 @JsonIgnore // 避免序列化。原因是,Redis 发布 Channel 消息的时候,已经会指定。
19 - public abstract String getChannel(); 19 + public String getChannel() {
  20 + return getClass().getSimpleName();
  21 + }
20 22
21 } 23 }
yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/core/pubsub/AbstractChannelMessageListener.java renamed to yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/core/pubsub/AbstractRedisChannelMessageListener.java
1 -package cn.iocoder.yudao.framework.mq.core.pubsub; 1 +package cn.iocoder.yudao.framework.mq.redis.core.pubsub;
2 2
3 import cn.hutool.core.util.TypeUtil; 3 import cn.hutool.core.util.TypeUtil;
4 import cn.iocoder.yudao.framework.common.util.json.JsonUtils; 4 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
5 -import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;  
6 -import cn.iocoder.yudao.framework.mq.core.interceptor.RedisMessageInterceptor;  
7 -import cn.iocoder.yudao.framework.mq.core.message.AbstractRedisMessage; 5 +import cn.iocoder.yudao.framework.mq.redis.core.RedisMQTemplate;
  6 +import cn.iocoder.yudao.framework.mq.redis.core.interceptor.RedisMessageInterceptor;
  7 +import cn.iocoder.yudao.framework.mq.redis.core.message.AbstractRedisMessage;
8 import lombok.Setter; 8 import lombok.Setter;
9 import lombok.SneakyThrows; 9 import lombok.SneakyThrows;
10 import org.springframework.data.redis.connection.Message; 10 import org.springframework.data.redis.connection.Message;
@@ -20,7 +20,7 @@ import java.util.List; @@ -20,7 +20,7 @@ import java.util.List;
20 * 20 *
21 * @author 芋道源码 21 * @author 芋道源码
22 */ 22 */
23 -public abstract class AbstractChannelMessageListener<T extends AbstractChannelMessage> implements MessageListener { 23 +public abstract class AbstractRedisChannelMessageListener<T extends AbstractRedisChannelMessage> implements MessageListener {
24 24
25 /** 25 /**
26 * 消息类型 26 * 消息类型
@@ -37,7 +37,7 @@ public abstract class AbstractChannelMessageListener&lt;T extends AbstractChannelMe @@ -37,7 +37,7 @@ public abstract class AbstractChannelMessageListener&lt;T extends AbstractChannelMe
37 private RedisMQTemplate redisMQTemplate; 37 private RedisMQTemplate redisMQTemplate;
38 38
39 @SneakyThrows 39 @SneakyThrows
40 - protected AbstractChannelMessageListener() { 40 + protected AbstractRedisChannelMessageListener() {
41 this.messageType = getMessageClass(); 41 this.messageType = getMessageClass();
42 this.channel = messageType.getDeclaredConstructor().newInstance().getChannel(); 42 this.channel = messageType.getDeclaredConstructor().newInstance().getChannel();
43 } 43 }
yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/core/stream/AbstractStreamMessage.java renamed to yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/core/stream/AbstractRedisStreamMessage.java
1 -package cn.iocoder.yudao.framework.mq.core.stream; 1 +package cn.iocoder.yudao.framework.mq.redis.core.stream;
2 2
3 -import cn.iocoder.yudao.framework.mq.core.message.AbstractRedisMessage; 3 +import cn.iocoder.yudao.framework.mq.redis.core.message.AbstractRedisMessage;
4 import com.fasterxml.jackson.annotation.JsonIgnore; 4 import com.fasterxml.jackson.annotation.JsonIgnore;
5 5
6 /** 6 /**
@@ -8,14 +8,16 @@ import com.fasterxml.jackson.annotation.JsonIgnore; @@ -8,14 +8,16 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
8 * 8 *
9 * @author 芋道源码 9 * @author 芋道源码
10 */ 10 */
11 -public abstract class AbstractStreamMessage extends AbstractRedisMessage { 11 +public abstract class AbstractRedisStreamMessage extends AbstractRedisMessage {
12 12
13 /** 13 /**
14 - * 获得 Redis Stream Key 14 + * 获得 Redis Stream Key,默认使用类名
15 * 15 *
16 * @return Channel 16 * @return Channel
17 */ 17 */
18 @JsonIgnore // 避免序列化 18 @JsonIgnore // 避免序列化
19 - public abstract String getStreamKey(); 19 + public String getStreamKey() {
  20 + return getClass().getSimpleName();
  21 + }
20 22
21 } 23 }
yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/core/stream/AbstractStreamMessageListener.java renamed to yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/core/stream/AbstractRedisStreamMessageListener.java
1 -package cn.iocoder.yudao.framework.mq.core.stream; 1 +package cn.iocoder.yudao.framework.mq.redis.core.stream;
2 2
3 import cn.hutool.core.util.TypeUtil; 3 import cn.hutool.core.util.TypeUtil;
4 import cn.iocoder.yudao.framework.common.util.json.JsonUtils; 4 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
5 -import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;  
6 -import cn.iocoder.yudao.framework.mq.core.interceptor.RedisMessageInterceptor;  
7 -import cn.iocoder.yudao.framework.mq.core.message.AbstractRedisMessage; 5 +import cn.iocoder.yudao.framework.mq.redis.core.RedisMQTemplate;
  6 +import cn.iocoder.yudao.framework.mq.redis.core.interceptor.RedisMessageInterceptor;
  7 +import cn.iocoder.yudao.framework.mq.redis.core.message.AbstractRedisMessage;
8 import lombok.Getter; 8 import lombok.Getter;
9 import lombok.Setter; 9 import lombok.Setter;
10 import lombok.SneakyThrows; 10 import lombok.SneakyThrows;
@@ -22,7 +22,7 @@ import java.util.List; @@ -22,7 +22,7 @@ import java.util.List;
22 * 22 *
23 * @author 芋道源码 23 * @author 芋道源码
24 */ 24 */
25 -public abstract class AbstractStreamMessageListener<T extends AbstractStreamMessage> 25 +public abstract class AbstractRedisStreamMessageListener<T extends AbstractRedisStreamMessage>
26 implements StreamListener<String, ObjectRecord<String, String>> { 26 implements StreamListener<String, ObjectRecord<String, String>> {
27 27
28 /** 28 /**
@@ -48,7 +48,7 @@ public abstract class AbstractStreamMessageListener&lt;T extends AbstractStreamMess @@ -48,7 +48,7 @@ public abstract class AbstractStreamMessageListener&lt;T extends AbstractStreamMess
48 private RedisMQTemplate redisMQTemplate; 48 private RedisMQTemplate redisMQTemplate;
49 49
50 @SneakyThrows 50 @SneakyThrows
51 - protected AbstractStreamMessageListener() { 51 + protected AbstractRedisStreamMessageListener() {
52 this.messageType = getMessageClass(); 52 this.messageType = getMessageClass();
53 this.streamKey = messageType.getDeclaredConstructor().newInstance().getStreamKey(); 53 this.streamKey = messageType.getDeclaredConstructor().newInstance().getStreamKey();
54 } 54 }
yudao-framework/yudao-spring-boot-starter-mq/src/main/java/cn/iocoder/yudao/framework/mq/redis/package-info.java 0 → 100644
  1 +/**
  2 + * 消息队列,基于 Redis 提供:
  3 + * 1. 基于 Pub/Sub 实现广播消费
  4 + * 2. 基于 Stream 实现集群消费
  5 + */
  6 +package cn.iocoder.yudao.framework.mq.redis;
yudao-framework/yudao-spring-boot-starter-mq/src/main/java/org/springframework/data/redis/stream/DefaultStreamMessageListenerContainerX.java deleted
1 -package org.springframework.data.redis.stream;  
2 -  
3 -import cn.hutool.core.util.ReflectUtil;  
4 -import org.springframework.data.redis.connection.RedisConnectionFactory;  
5 -import org.springframework.data.redis.connection.stream.ByteRecord;  
6 -import org.springframework.data.redis.connection.stream.ReadOffset;  
7 -import org.springframework.data.redis.connection.stream.Record;  
8 -import org.springframework.util.Assert;  
9 -  
10 -import java.util.Collections;  
11 -import java.util.List;  
12 -import java.util.function.Function;  
13 -  
14 -/**  
15 - * 拓展 DefaultStreamMessageListenerContainer 实现,解决 Spring Data Redis + Redisson 结合使用时,Redisson 在 Stream 获得不到数据时,返回 null 而不是空 List,导致 NPE 异常。  
16 - * 对应 issue:https://github.com/spring-projects/spring-data-redis/issues/2147 和 https://github.com/redisson/redisson/issues/4006  
17 - * 目前看下来 Spring Data Redis 不肯加 null 判断,Redisson 暂时也没改返回 null 到空 List 的打算,所以暂时只能自己改,哽咽!  
18 - *  
19 - * @author 芋道源码  
20 - */  
21 -public class DefaultStreamMessageListenerContainerX<K, V extends Record<K, ?>> extends DefaultStreamMessageListenerContainer<K, V> {  
22 -  
23 - /**  
24 - * 参考 {@link StreamMessageListenerContainer#create(RedisConnectionFactory, StreamMessageListenerContainerOptions)} 的实现  
25 - */  
26 - public static <K, V extends Record<K, ?>> StreamMessageListenerContainer<K, V> create(RedisConnectionFactory connectionFactory, StreamMessageListenerContainer.StreamMessageListenerContainerOptions<K, V> options) {  
27 - Assert.notNull(connectionFactory, "RedisConnectionFactory must not be null!");  
28 - Assert.notNull(options, "StreamMessageListenerContainerOptions must not be null!");  
29 - return new DefaultStreamMessageListenerContainerX<>(connectionFactory, options);  
30 - }  
31 -  
32 - public DefaultStreamMessageListenerContainerX(RedisConnectionFactory connectionFactory, StreamMessageListenerContainerOptions<K, V> containerOptions) {  
33 - super(connectionFactory, containerOptions);  
34 - }  
35 -  
36 - /**  
37 - * 参考 {@link DefaultStreamMessageListenerContainer#register(StreamReadRequest, StreamListener)} 的实现  
38 - */  
39 - @Override  
40 - public Subscription register(StreamReadRequest<K> streamRequest, StreamListener<K, V> listener) {  
41 - return this.doRegisterX(getReadTaskX(streamRequest, listener));  
42 - }  
43 -  
44 - @SuppressWarnings("unchecked")  
45 - private StreamPollTask<K, V> getReadTaskX(StreamReadRequest<K> streamRequest, StreamListener<K, V> listener) {  
46 - StreamPollTask<K, V> task = ReflectUtil.invoke(this, "getReadTask", streamRequest, listener);  
47 - // 修改 readFunction 方法  
48 - Function<ReadOffset, List<ByteRecord>> readFunction = (Function<ReadOffset, List<ByteRecord>>) ReflectUtil.getFieldValue(task, "readFunction");  
49 - ReflectUtil.setFieldValue(task, "readFunction", (Function<ReadOffset, List<ByteRecord>>) readOffset -> {  
50 - List<ByteRecord> records = readFunction.apply(readOffset);  
51 - //【重点】保证 records 不是空,避免 NPE 的问题!!!  
52 - return records != null ? records : Collections.emptyList();  
53 - });  
54 - return task;  
55 - }  
56 -  
57 - private Subscription doRegisterX(Task task) {  
58 - return ReflectUtil.invoke(this, "doRegister", task);  
59 - }  
60 -  
61 -}  
62 -  
yudao-framework/yudao-spring-boot-starter-mq/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
1 -cn.iocoder.yudao.framework.mq.config.YudaoMQAutoConfiguration  
2 \ No newline at end of file 1 \ No newline at end of file
  2 +cn.iocoder.yudao.framework.mq.redis.config.YudaoRedisMQAutoConfiguration
  3 +cn.iocoder.yudao.framework.mq.rabbitmq.config.YudaoRabbitMQAutoConfiguration
3 \ No newline at end of file 4 \ No newline at end of file
yudao-framework/yudao-spring-boot-starter-mq/《芋道 Spring Boot 事件机制 Event 入门》.md 0 → 100644
  1 +<http://www.iocoder.cn/Spring-Boot/RocketMQ/?yudao>
yudao-framework/yudao-spring-boot-starter-mq/《芋道 Spring Boot 消息队列 Kafka 入门》.md 0 → 100644
  1 +<http://www.iocoder.cn/Spring-Boot/Kafka/?yudao>
yudao-framework/yudao-spring-boot-starter-mq/《芋道 Spring Boot 消息队列 RabbitMQ 入门》.md 0 → 100644
  1 +<http://www.iocoder.cn/Spring-Boot/RabbitMQ/?yudao>
yudao-framework/yudao-spring-boot-starter-mq/《芋道 Spring Boot 消息队列 RocketMQ 入门》.md 0 → 100644
  1 +<http://www.iocoder.cn/Spring-Boot/RocketMQ/?yudao>
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/core/service/ApiAccessLog.java
@@ -5,7 +5,6 @@ import lombok.Data; @@ -5,7 +5,6 @@ import lombok.Data;
5 import javax.validation.constraints.NotNull; 5 import javax.validation.constraints.NotNull;
6 import java.time.LocalDateTime; 6 import java.time.LocalDateTime;
7 7
8 -// TODO @小吉祥:搞个 job,清理 14 天外的访问日志;  
9 /** 8 /**
10 * API 访问日志 9 * API 访问日志
11 * 10 *
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/core/service/ApiErrorLog.java
@@ -5,7 +5,6 @@ import lombok.Data; @@ -5,7 +5,6 @@ import lombok.Data;
5 import javax.validation.constraints.NotNull; 5 import javax.validation.constraints.NotNull;
6 import java.time.LocalDateTime; 6 import java.time.LocalDateTime;
7 7
8 -// TODO @小吉祥:搞个 job,清理 14 天外的异常日志;  
9 /** 8 /**
10 * API 错误日志 9 * API 错误日志
11 * 10 *
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmCommentTypeEnum.java
@@ -20,16 +20,16 @@ public enum BpmCommentTypeEnum { @@ -20,16 +20,16 @@ public enum BpmCommentTypeEnum {
20 ; 20 ;
21 21
22 /** 22 /**
23 - * 结果 23 + * 操作类型
24 */ 24 */
25 private final Integer type; 25 private final Integer type;
26 /** 26 /**
27 - * 描述 27 + * 操作名字
28 */ 28 */
29 - private final String desc; 29 + private final String name;
30 /** 30 /**
31 - * 模板信息 31 + * 操作描述
32 */ 32 */
33 - private final String templateComment; 33 + private final String comment;
34 34
35 } 35 }
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmProcessInstanceResultEnum.java
@@ -30,13 +30,13 @@ public enum BpmProcessInstanceResultEnum { @@ -30,13 +30,13 @@ public enum BpmProcessInstanceResultEnum {
30 * 相当于是 通过 APPROVE 的特殊状态 30 * 相当于是 通过 APPROVE 的特殊状态
31 * 例如:A审批, A 后加签了 B,并且审批通过了任务,但是 B 还未审批,则当前任务状态为“待后加签任务完成” 31 * 例如:A审批, A 后加签了 B,并且审批通过了任务,但是 B 还未审批,则当前任务状态为“待后加签任务完成”
32 */ 32 */
33 - ADD_SIGN_AFTER(7, "待后加签任务完成"), 33 + SIGN_AFTER(7, "待后加签任务完成"),
34 /** 34 /**
35 * 【加签】源任务未审批,但是向前加签了,所以源任务状态变为“待前加签任务完成” 35 * 【加签】源任务未审批,但是向前加签了,所以源任务状态变为“待前加签任务完成”
36 * 相当于是 处理中 PROCESS 的特殊状态 36 * 相当于是 处理中 PROCESS 的特殊状态
37 * 例如:A 审批, A 前加签了 B,B 还未审核 37 * 例如:A 审批, A 前加签了 B,B 还未审核
38 */ 38 */
39 - ADD_SIGN_BEFORE(8, "待前加签任务完成"), 39 + SIGN_BEFORE(8, "待前加签任务完成"),
40 /** 40 /**
41 * 【加签】后加签任务被创建时的初始状态 41 * 【加签】后加签任务被创建时的初始状态
42 * 相当于是 处理中 PROCESS 的特殊状态 42 * 相当于是 处理中 PROCESS 的特殊状态
@@ -71,7 +71,7 @@ public enum BpmProcessInstanceResultEnum { @@ -71,7 +71,7 @@ public enum BpmProcessInstanceResultEnum {
71 public static boolean isEndResult(Integer result) { 71 public static boolean isEndResult(Integer result) {
72 return ObjectUtils.equalsAny(result, APPROVE.getResult(), REJECT.getResult(), 72 return ObjectUtils.equalsAny(result, APPROVE.getResult(), REJECT.getResult(),
73 CANCEL.getResult(), BACK.getResult(), 73 CANCEL.getResult(), BACK.getResult(),
74 - ADD_SIGN_AFTER.getResult()); 74 + SIGN_AFTER.getResult());
75 } 75 }
76 76
77 } 77 }
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/task/BpmTaskAddSignTypeEnum.java
@@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.bpm.enums.task; @@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.bpm.enums.task;
3 import lombok.AllArgsConstructor; 3 import lombok.AllArgsConstructor;
4 import lombok.Getter; 4 import lombok.Getter;
5 5
6 -  
7 /** 6 /**
8 * 流程任务 -- 加签类型枚举类型 7 * 流程任务 -- 加签类型枚举类型
9 */ 8 */
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmTaskController.java
@@ -74,7 +74,7 @@ public class BpmTaskController { @@ -74,7 +74,7 @@ public class BpmTaskController {
74 return success(true); 74 return success(true);
75 } 75 }
76 76
77 - @GetMapping("/get-return-list") 77 + @GetMapping("/return-list")
78 @Operation(summary = "获取所有可回退的节点", description = "用于【流程详情】的【回退】按钮") 78 @Operation(summary = "获取所有可回退的节点", description = "用于【流程详情】的【回退】按钮")
79 @Parameter(name = "taskId", description = "当前任务ID", required = true) 79 @Parameter(name = "taskId", description = "当前任务ID", required = true)
80 @PreAuthorize("@ss.hasPermission('bpm:task:update')") 80 @PreAuthorize("@ss.hasPermission('bpm:task:update')")
@@ -98,27 +98,28 @@ public class BpmTaskController { @@ -98,27 +98,28 @@ public class BpmTaskController {
98 return success(true); 98 return success(true);
99 } 99 }
100 100
101 - @PutMapping("/add-sign") 101 + @PutMapping("/create-sign")
102 @Operation(summary = "加签", description = "before 前加签,after 后加签") 102 @Operation(summary = "加签", description = "before 前加签,after 后加签")
103 @PreAuthorize("@ss.hasPermission('bpm:task:update')") 103 @PreAuthorize("@ss.hasPermission('bpm:task:update')")
104 - public CommonResult<Boolean> addSignTask(@Valid @RequestBody BpmTaskAddSignReqVO reqVO) {  
105 - taskService.addSignTask(getLoginUserId(), reqVO); 104 + public CommonResult<Boolean> createSignTask(@Valid @RequestBody BpmTaskAddSignReqVO reqVO) {
  105 + taskService.createSignTask(getLoginUserId(), reqVO);
106 return success(true); 106 return success(true);
107 } 107 }
108 108
109 - @PutMapping("/sub-sign") 109 + @DeleteMapping("/delete-sign")
110 @Operation(summary = "减签") 110 @Operation(summary = "减签")
111 @PreAuthorize("@ss.hasPermission('bpm:task:update')") 111 @PreAuthorize("@ss.hasPermission('bpm:task:update')")
112 - public CommonResult<Boolean> subSignTask(@Valid @RequestBody BpmTaskSubSignReqVO reqVO) {  
113 - taskService.subSignTask(getLoginUserId(), reqVO); 112 + public CommonResult<Boolean> deleteSignTask(@Valid @RequestBody BpmTaskSubSignReqVO reqVO) {
  113 + taskService.deleteSignTask(getLoginUserId(), reqVO);
114 return success(true); 114 return success(true);
115 } 115 }
116 116
117 - @GetMapping("/get-children-task-list") 117 + @GetMapping("children-list")
118 @Operation(summary = "获取能被减签的任务") 118 @Operation(summary = "获取能被减签的任务")
  119 + @Parameter(name = "parentId", description = "父级任务 ID", required = true)
119 @PreAuthorize("@ss.hasPermission('bpm:task:update')") 120 @PreAuthorize("@ss.hasPermission('bpm:task:update')")
120 - public CommonResult<List<BpmTaskSubSignRespVO>> getChildrenTaskList(@RequestParam("taskId") String taskId) {  
121 - return success(taskService.getChildrenTaskList(taskId)); 121 + public CommonResult<List<BpmTaskSubSignRespVO>> getChildrenTaskList(@RequestParam("parentId") String parentId) {
  122 + return success(taskService.getChildrenTaskList(parentId));
122 } 123 }
123 124
124 } 125 }
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskAddSignReqVO.java
@@ -6,6 +6,7 @@ import lombok.Data; @@ -6,6 +6,7 @@ import lombok.Data;
6 import javax.validation.constraints.NotEmpty; 6 import javax.validation.constraints.NotEmpty;
7 import java.util.Set; 7 import java.util.Set;
8 8
  9 +// TODO @海洋:类名,应该是 create 哈
9 @Schema(description = "管理后台 - 加签流程任务的 Request VO") 10 @Schema(description = "管理后台 - 加签流程任务的 Request VO")
10 @Data 11 @Data
11 public class BpmTaskAddSignReqVO { 12 public class BpmTaskAddSignReqVO {
@@ -26,4 +27,4 @@ public class BpmTaskAddSignReqVO { @@ -26,4 +27,4 @@ public class BpmTaskAddSignReqVO {
26 @NotEmpty(message = "加签原因不能为空") 27 @NotEmpty(message = "加签原因不能为空")
27 private String reason; 28 private String reason;
28 29
29 -}  
30 \ No newline at end of file 30 \ No newline at end of file
  31 +}
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskSubSignReqVO.java
@@ -5,6 +5,7 @@ import lombok.Data; @@ -5,6 +5,7 @@ import lombok.Data;
5 5
6 import javax.validation.constraints.NotEmpty; 6 import javax.validation.constraints.NotEmpty;
7 7
  8 +// TODO @海洋:类名,应该是 delete 哈
8 @Schema(description = "管理后台 - 减签流程任务的 Request VO") 9 @Schema(description = "管理后台 - 减签流程任务的 Request VO")
9 @Data 10 @Data
10 public class BpmTaskSubSignReqVO { 11 public class BpmTaskSubSignReqVO {
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java
1 package cn.iocoder.yudao.module.bpm.convert.task; 1 package cn.iocoder.yudao.module.bpm.convert.task;
2 2
3 -import cn.hutool.core.collection.CollUtil;  
4 import cn.hutool.core.date.LocalDateTimeUtil; 3 import cn.hutool.core.date.LocalDateTimeUtil;
  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.util.collection.CollectionUtils; 6 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
  7 +import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
7 import cn.iocoder.yudao.framework.common.util.date.DateUtils; 8 import cn.iocoder.yudao.framework.common.util.date.DateUtils;
8 import cn.iocoder.yudao.framework.common.util.number.NumberUtils; 9 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
9 import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*; 10 import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*;
@@ -25,6 +26,9 @@ import java.util.Date; @@ -25,6 +26,9 @@ import java.util.Date;
25 import java.util.List; 26 import java.util.List;
26 import java.util.Map; 27 import java.util.Map;
27 28
  29 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMultiMap;
  30 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList;
  31 +
28 /** 32 /**
29 * Bpm 任务 Convert 33 * Bpm 任务 Convert
30 * 34 *
@@ -167,32 +171,31 @@ public interface BpmTaskConvert { @@ -167,32 +171,31 @@ public interface BpmTaskConvert {
167 default List<BpmTaskSubSignRespVO> convertList(List<BpmTaskExtDO> bpmTaskExtDOList, 171 default List<BpmTaskSubSignRespVO> convertList(List<BpmTaskExtDO> bpmTaskExtDOList,
168 Map<Long, AdminUserRespDTO> userMap, 172 Map<Long, AdminUserRespDTO> userMap,
169 Map<String, Task> idTaskMap){ 173 Map<String, Task> idTaskMap){
170 - return CollectionUtils.convertList(bpmTaskExtDOList, task->{  
171 - BpmTaskSubSignRespVO bpmTaskSubSignRespVO = new BpmTaskSubSignRespVO();  
172 - bpmTaskSubSignRespVO.setName(task.getName());  
173 - bpmTaskSubSignRespVO.setId(task.getTaskId());  
174 - Task sourceTask = idTaskMap.get(task.getTaskId()); 174 + return CollectionUtils.convertList(bpmTaskExtDOList, task -> {
  175 + BpmTaskSubSignRespVO bpmTaskSubSignRespVO = new BpmTaskSubSignRespVO()
  176 + .setId(task.getTaskId()).setName(task.getName());
175 // 后加签任务不会直接设置 assignee ,所以不存在 assignee 的情况,则去取 owner 177 // 后加签任务不会直接设置 assignee ,所以不存在 assignee 的情况,则去取 owner
176 - String assignee = StrUtil.isNotEmpty(sourceTask.getAssignee()) ? sourceTask.getAssignee() : sourceTask.getOwner();  
177 - AdminUserRespDTO assignUser = userMap.get(NumberUtils.parseLong(assignee));  
178 - if (assignUser != null) {  
179 - bpmTaskSubSignRespVO.setAssigneeUser(convert3(assignUser));  
180 - } 178 + Task sourceTask = idTaskMap.get(task.getTaskId());
  179 + String assignee = ObjectUtil.defaultIfBlank(sourceTask.getOwner(),sourceTask.getAssignee());
  180 + MapUtils.findAndThen(userMap,NumberUtils.parseLong(assignee),
  181 + assignUser-> bpmTaskSubSignRespVO.setAssigneeUser(convert3(assignUser)));
181 return bpmTaskSubSignRespVO; 182 return bpmTaskSubSignRespVO;
182 }); 183 });
183 } 184 }
184 185
185 /** 186 /**
186 * 转换任务为父子级 187 * 转换任务为父子级
  188 + *
187 * @param sourceList 原始数据 189 * @param sourceList 原始数据
188 * @return 转换后的父子级数组 190 * @return 转换后的父子级数组
189 */ 191 */
190 - default List<BpmTaskRespVO> convertChildrenList(List<BpmTaskRespVO> sourceList){  
191 - List<BpmTaskRespVO> childrenTaskList = CollectionUtils.filterList(sourceList, r -> StrUtil.isNotEmpty(r.getParentTaskId()));  
192 - Map<String, List<BpmTaskRespVO>> parentChildrenTaskListMap = CollectionUtils.convertMultiMap(childrenTaskList, BpmTaskRespVO::getParentTaskId); 192 + default List<BpmTaskRespVO> convertChildrenList(List<BpmTaskRespVO> sourceList) {
  193 + List<BpmTaskRespVO> childrenTaskList = filterList(sourceList, r -> StrUtil.isNotEmpty(r.getParentTaskId()));
  194 + Map<String, List<BpmTaskRespVO>> parentChildrenTaskListMap = convertMultiMap(childrenTaskList, BpmTaskRespVO::getParentTaskId);
193 for (BpmTaskRespVO bpmTaskRespVO : sourceList) { 195 for (BpmTaskRespVO bpmTaskRespVO : sourceList) {
194 bpmTaskRespVO.setChildren(parentChildrenTaskListMap.get(bpmTaskRespVO.getId())); 196 bpmTaskRespVO.setChildren(parentChildrenTaskListMap.get(bpmTaskRespVO.getId()));
195 } 197 }
196 - return CollectionUtils.filterList(sourceList, r -> StrUtil.isEmpty(r.getParentTaskId())); 198 + return filterList(sourceList, r -> StrUtil.isEmpty(r.getParentTaskId()));
197 } 199 }
  200 +
198 } 201 }
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/task/BpmTaskExtMapper.java
@@ -21,13 +21,13 @@ public interface BpmTaskExtMapper extends BaseMapperX&lt;BpmTaskExtDO&gt; { @@ -21,13 +21,13 @@ public interface BpmTaskExtMapper extends BaseMapperX&lt;BpmTaskExtDO&gt; {
21 return selectList(BpmTaskExtDO::getTaskId, taskIds); 21 return selectList(BpmTaskExtDO::getTaskId, taskIds);
22 } 22 }
23 23
  24 + // TODO @海:BpmProcessInstanceResultEnum.CAN_SUB_SIGN_STATUS_LIST) 应该作为条件,mapper 不要有业务
24 default List<BpmTaskExtDO> selectProcessListByTaskIds(Collection<String> taskIds) { 25 default List<BpmTaskExtDO> selectProcessListByTaskIds(Collection<String> taskIds) {
25 return selectList(new LambdaQueryWrapperX<BpmTaskExtDO>() 26 return selectList(new LambdaQueryWrapperX<BpmTaskExtDO>()
26 .in(BpmTaskExtDO::getTaskId, taskIds) 27 .in(BpmTaskExtDO::getTaskId, taskIds)
27 .in(BpmTaskExtDO::getResult, BpmProcessInstanceResultEnum.CAN_SUB_SIGN_STATUS_LIST)); 28 .in(BpmTaskExtDO::getResult, BpmProcessInstanceResultEnum.CAN_SUB_SIGN_STATUS_LIST));
28 } 29 }
29 30
30 -  
31 default BpmTaskExtDO selectByTaskId(String taskId) { 31 default BpmTaskExtDO selectByTaskId(String taskId) {
32 return selectOne(BpmTaskExtDO::getTaskId, taskId); 32 return selectOne(BpmTaskExtDO::getTaskId, taskId);
33 } 33 }
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/bpm/config/BpmSecurityConfiguration.java deleted
1 -package cn.iocoder.yudao.module.bpm.framework.bpm.config;  
2 -  
3 -import cn.iocoder.yudao.framework.security.config.AuthorizeRequestsCustomizer;  
4 -import org.springframework.context.annotation.Bean;  
5 -import org.springframework.context.annotation.Configuration;  
6 -import org.springframework.security.config.annotation.web.builders.HttpSecurity;  
7 -import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;  
8 -  
9 -/**  
10 - * @author kemengkai  
11 - * @create 2022-05-07 08:15  
12 - */  
13 -@Configuration("bpmSecurityConfiguration")  
14 -public class BpmSecurityConfiguration {  
15 -  
16 - @Bean("bpmAuthorizeRequestsCustomizer")  
17 - public AuthorizeRequestsCustomizer authorizeRequestsCustomizer() {  
18 - return new AuthorizeRequestsCustomizer() {  
19 -  
20 - @Override  
21 - public void customize(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry) {  
22 - // 任务回退接口  
23 - registry.antMatchers(buildAdminApi("/bpm/task/back")).permitAll();  
24 - }  
25 -  
26 - };  
27 - }  
28 -}  
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java
@@ -12,6 +12,7 @@ import javax.validation.Valid; @@ -12,6 +12,7 @@ import javax.validation.Valid;
12 * @author yunlongn 12 * @author yunlongn
13 */ 13 */
14 public interface BpmModelService { 14 public interface BpmModelService {
  15 +
15 /** 16 /**
16 * 获得流程模型分页 17 * 获得流程模型分页
17 * 18 *
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java
@@ -288,5 +288,4 @@ public class BpmModelServiceImpl implements BpmModelService { @@ -288,5 +288,4 @@ public class BpmModelServiceImpl implements BpmModelService {
288 processDefinitionService.updateProcessDefinitionState(oldDefinition.getId(), SuspensionState.SUSPENDED.getStateCode()); 288 processDefinitionService.updateProcessDefinitionState(oldDefinition.getId(), SuspensionState.SUSPENDED.getStateCode());
289 } 289 }
290 290
291 -  
292 } 291 }
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskService.java
@@ -163,7 +163,7 @@ public interface BpmTaskService { @@ -163,7 +163,7 @@ public interface BpmTaskService {
163 * @param userId 被加签的用户和任务 ID,加签类型 163 * @param userId 被加签的用户和任务 ID,加签类型
164 * @param reqVO 当前用户 ID 164 * @param reqVO 当前用户 ID
165 */ 165 */
166 - void addSignTask(Long userId, BpmTaskAddSignReqVO reqVO); 166 + void createSignTask(Long userId, BpmTaskAddSignReqVO reqVO);
167 167
168 /** 168 /**
169 * 任务减签名 169 * 任务减签名
@@ -171,14 +171,14 @@ public interface BpmTaskService { @@ -171,14 +171,14 @@ public interface BpmTaskService {
171 * @param userId 当前用户ID 171 * @param userId 当前用户ID
172 * @param reqVO 被减签的任务 ID,理由 172 * @param reqVO 被减签的任务 ID,理由
173 */ 173 */
174 - void subSignTask(Long userId, BpmTaskSubSignReqVO reqVO); 174 + void deleteSignTask(Long userId, BpmTaskSubSignReqVO reqVO);
175 175
176 /** 176 /**
177 * 获取指定任务的子任务和审批人信息 177 * 获取指定任务的子任务和审批人信息
178 * 178 *
179 - * @param taskId 指定任务ID 179 + * @param parentId 指定任务ID
180 * @return 子任务列表 180 * @return 子任务列表
181 */ 181 */
182 - List<BpmTaskSubSignRespVO> getChildrenTaskList(String taskId); 182 + List<BpmTaskSubSignRespVO> getChildrenTaskList(String parentId);
183 183
184 } 184 }
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java
@@ -53,7 +53,6 @@ import javax.annotation.Resource; @@ -53,7 +53,6 @@ import javax.annotation.Resource;
53 import javax.validation.Valid; 53 import javax.validation.Valid;
54 import java.time.LocalDateTime; 54 import java.time.LocalDateTime;
55 import java.util.*; 55 import java.util.*;
56 -import java.util.stream.Collectors;  
57 import java.util.stream.Stream; 56 import java.util.stream.Stream;
58 57
59 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 58 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -240,10 +239,11 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -240,10 +239,11 @@ public class BpmTaskServiceImpl implements BpmTaskService {
240 handleParentTask(task); 239 handleParentTask(task);
241 } 240 }
242 241
  242 +
243 /** 243 /**
244 * 审批通过存在“后加签”的任务。 244 * 审批通过存在“后加签”的任务。
245 * <p> 245 * <p>
246 - * 注意:该任务不能马上完成,需要一个中间状态(ADD_SIGN_AFTER),并激活剩余所有子任务(PROCESS)为可审批处理 246 + * 注意:该任务不能马上完成,需要一个中间状态(SIGN_AFTER),并激活剩余所有子任务(PROCESS)为可审批处理
247 * 247 *
248 * @param task 当前任务 248 * @param task 当前任务
249 * @param reqVO 前端请求参数 249 * @param reqVO 前端请求参数
@@ -251,7 +251,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -251,7 +251,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
251 private void approveAfterSignTask(Task task, BpmTaskApproveReqVO reqVO) { 251 private void approveAfterSignTask(Task task, BpmTaskApproveReqVO reqVO) {
252 // 1. 有向后加签,则该任务状态临时设置为 ADD_SIGN_AFTER 状态 252 // 1. 有向后加签,则该任务状态临时设置为 ADD_SIGN_AFTER 状态
253 taskExtMapper.updateByTaskId( 253 taskExtMapper.updateByTaskId(
254 - new BpmTaskExtDO().setTaskId(task.getId()).setResult(BpmProcessInstanceResultEnum.ADD_SIGN_AFTER.getResult()) 254 + new BpmTaskExtDO().setTaskId(task.getId()).setResult(BpmProcessInstanceResultEnum.SIGN_AFTER.getResult())
255 .setReason(reqVO.getReason()).setEndTime(LocalDateTime.now())); 255 .setReason(reqVO.getReason()).setEndTime(LocalDateTime.now()));
256 256
257 // 2. 激活子任务 257 // 2. 激活子任务
@@ -265,7 +265,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -265,7 +265,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
265 } 265 }
266 266
267 /** 267 /**
268 - * 处理当前任务的父任务 268 + * 处理当前任务的父任务,主要处理“加签”的情况
269 * 269 *
270 * @param task 当前任务 270 * @param task 当前任务
271 */ 271 */
@@ -274,76 +274,79 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -274,76 +274,79 @@ public class BpmTaskServiceImpl implements BpmTaskService {
274 if (StrUtil.isBlank(parentTaskId)) { 274 if (StrUtil.isBlank(parentTaskId)) {
275 return; 275 return;
276 } 276 }
277 - if (StrUtil.isNotBlank(parentTaskId)) {  
278 - // 1. 判断当前任务的父任务是否还有子任务  
279 - Long childrenTaskCount = getChildrenTaskCount(parentTaskId);  
280 - if (childrenTaskCount > 0) {  
281 - return;  
282 - }  
283 - // 2. 获取父任务  
284 - Task parentTask = validateTaskExist(parentTaskId);  
285 -  
286 - // 3. 情况一:处理向前加签  
287 - String scopeType = parentTask.getScopeType();  
288 - if (BpmTaskAddSignTypeEnum.BEFORE.getType().equals(scopeType)) {  
289 - // 3.1 如果是向前加签的任务,则调用 resolveTask 指派父任务,将 owner 重新赋值给父任务的 assignee  
290 - taskService.resolveTask(parentTaskId);  
291 - // 3.2 更新任务拓展表为处理中  
292 - taskExtMapper.updateByTaskId(  
293 - new BpmTaskExtDO().setTaskId(parentTask.getId()).setResult(BpmProcessInstanceResultEnum.PROCESS.getResult()));  
294 - } else if (BpmTaskAddSignTypeEnum.AFTER.getType().equals(scopeType)) {  
295 - // 3. 情况二:处理向后加签  
296 - handleAfterSign(parentTask);  
297 - } 277 + // 1. 判断当前任务的父任务是否还有子任务
  278 + Long childrenTaskCount = getChildrenTaskCount(parentTaskId);
  279 + if (childrenTaskCount > 0) {
  280 + return;
  281 + }
  282 + // 2. 获取父任务
  283 + Task parentTask = validateTaskExist(parentTaskId);
298 284
299 - // 4. 子任务已处理完成,清空 scopeType 字段,修改 parentTask 信息,方便后续可以继续向前后向后加签  
300 - // 再查询一次的原因是避免报错:Task was updated by another transaction concurrently  
301 - // 因为前面处理后可能会导致 parentTask rev 字段被修改,需要重新获取最新的  
302 - parentTask = getTask(parentTaskId);  
303 - if (parentTask == null) {  
304 - // 为空的情况是:已经通过 handleAfterSign 方法将任务完成了,所以 ru_task 表会查不到数据  
305 - return;  
306 - }  
307 - clearTaskScopeTypeAndSave(parentTask); 285 + // 3. 处理加签情况
  286 + String scopeType = parentTask.getScopeType();
  287 + if(!validateSignType(scopeType)){
  288 + return;
308 } 289 }
  290 + // 3.1 情况一:处理向前加签
  291 + if (BpmTaskAddSignTypeEnum.BEFORE.getType().equals(scopeType)) {
  292 + // 3.1.1 如果是向前加签的任务,则调用 resolveTask 指派父任务,将 owner 重新赋值给父任务的 assignee,这样它就可以被审批
  293 + taskService.resolveTask(parentTaskId);
  294 + // 3.1.2 更新任务拓展表为处理中
  295 + taskExtMapper.updateByTaskId(
  296 + new BpmTaskExtDO().setTaskId(parentTask.getId()).setResult(BpmProcessInstanceResultEnum.PROCESS.getResult()));
  297 + } else if (BpmTaskAddSignTypeEnum.AFTER.getType().equals(scopeType)) {
  298 + // 3.2 情况二:处理向后加签
  299 + handleParentTaskForAfterSign(parentTask);
  300 + }
  301 +
  302 + // 4. 子任务已处理完成,清空 scopeType 字段,修改 parentTask 信息,方便后续可以继续向前后向后加签
  303 + // 再查询一次的原因是避免报错:Task was updated by another transaction concurrently
  304 + // 因为前面处理后可能会导致 parentTask rev 字段被修改,需要重新获取最新的
  305 + parentTask = getTask(parentTaskId);
  306 + if (parentTask == null) {
  307 + // 为空的情况是:已经通过 handleAfterSign 方法将任务完成了,所以 ru_task 表会查不到数据
  308 + return;
  309 + }
  310 + clearTaskScopeTypeAndSave(parentTask);
309 } 311 }
310 312
  313 +
311 /** 314 /**
312 * 处理后加签任务 315 * 处理后加签任务
313 * 316 *
314 * @param parentTask 当前审批任务的父任务 317 * @param parentTask 当前审批任务的父任务
315 */ 318 */
316 - private void handleAfterSign(Task parentTask) { 319 + // TODO @海:这个逻辑,怎么感觉可以是 parentTask 的 parent,再去调用 handleParentTask 方法;可以微信聊下;
  320 + private void handleParentTaskForAfterSign(Task parentTask) {
317 String parentTaskId = parentTask.getId(); 321 String parentTaskId = parentTask.getId();
318 - //1. 更新 parentTask 的任务拓展表为通过,并调用 complete 完成自己 322 + // 1. 更新 parentTask 的任务拓展表为通过,并调用 complete 完成自己
319 BpmTaskExtDO currentTaskExt = taskExtMapper.selectByTaskId(parentTask.getId()); 323 BpmTaskExtDO currentTaskExt = taskExtMapper.selectByTaskId(parentTask.getId());
320 - BpmTaskExtDO currentTaskUpdateEntity = new BpmTaskExtDO().setTaskId(parentTask.getId()) 324 + BpmTaskExtDO currentTaskExtUpdateObj = new BpmTaskExtDO().setTaskId(parentTask.getId())
321 .setResult(BpmProcessInstanceResultEnum.APPROVE.getResult()); 325 .setResult(BpmProcessInstanceResultEnum.APPROVE.getResult());
322 if (currentTaskExt.getEndTime() == null) { 326 if (currentTaskExt.getEndTime() == null) {
323 // 1.1 有这个判断是因为,以前没设置过结束时间,才去设置 327 // 1.1 有这个判断是因为,以前没设置过结束时间,才去设置
324 - currentTaskUpdateEntity.setEndTime(LocalDateTime.now()); 328 + currentTaskExtUpdateObj.setEndTime(LocalDateTime.now());
325 } 329 }
326 - // 1.2 完成自己  
327 - taskExtMapper.updateByTaskId(currentTaskUpdateEntity); 330 + taskExtMapper.updateByTaskId(currentTaskExtUpdateObj);
  331 + // 1.2 完成自己(因为它已经没有子任务,所以也可以完成)
328 taskService.complete(parentTaskId); 332 taskService.complete(parentTaskId);
329 333
330 // 2. 如果有父级,递归查询上级任务是否都已经完成 334 // 2. 如果有父级,递归查询上级任务是否都已经完成
331 if (StrUtil.isEmpty(parentTask.getParentTaskId())) { 335 if (StrUtil.isEmpty(parentTask.getParentTaskId())) {
332 return; 336 return;
333 } 337 }
334 - // TODO @海:这块待讨论,脑子略乱;感觉 handleAfterSign 的后半段,和 handleParentTask 有点重叠???  
335 // 2.1 判断整条链路的任务是否完成 338 // 2.1 判断整条链路的任务是否完成
336 // 例如从 A 任务加签了一个 B 任务,B 任务又加签了一个 C 任务,C 任务加签了 D 任务 339 // 例如从 A 任务加签了一个 B 任务,B 任务又加签了一个 C 任务,C 任务加签了 D 任务
337 // 此时,D 任务完成,要一直往上找到祖先任务 A调用 complete 方法完成 A 任务 340 // 此时,D 任务完成,要一直往上找到祖先任务 A调用 complete 方法完成 A 任务
338 boolean allChildrenTaskFinish = true; 341 boolean allChildrenTaskFinish = true;
339 while (StrUtil.isNotBlank(parentTask.getParentTaskId())) { 342 while (StrUtil.isNotBlank(parentTask.getParentTaskId())) {
340 parentTask = validateTaskExist(parentTask.getParentTaskId()); 343 parentTask = validateTaskExist(parentTask.getParentTaskId());
341 - BpmTaskExtDO bpmTaskExtDO = taskExtMapper.selectByTaskId(parentTask.getId());  
342 - if (bpmTaskExtDO == null) { 344 + BpmTaskExtDO parentTaskExt = taskExtMapper.selectByTaskId(parentTask.getId());
  345 + if (parentTaskExt == null) {
343 break; 346 break;
344 } 347 }
345 - boolean currentTaskFinish = BpmProcessInstanceResultEnum.isEndResult(bpmTaskExtDO.getResult());  
346 - // 2.2 如果 allChildrenTaskFinish 已经被赋值为 false ,则不会再赋值为 true,因为整个链路没有完成 348 + boolean currentTaskFinish = BpmProcessInstanceResultEnum.isEndResult(parentTaskExt.getResult());
  349 + // 2.2 如果 allChildrenTaskFinish 已经被赋值为 false,则不会再赋值为 true,因为整个链路没有完成
347 if (allChildrenTaskFinish) { 350 if (allChildrenTaskFinish) {
348 allChildrenTaskFinish = currentTaskFinish; 351 allChildrenTaskFinish = currentTaskFinish;
349 } 352 }
@@ -354,19 +357,19 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -354,19 +357,19 @@ public class BpmTaskServiceImpl implements BpmTaskService {
354 357
355 // 3 处理非完成状态的任务 358 // 3 处理非完成状态的任务
356 // 3.1 判断当前任务的父任务是否还有子任务 359 // 3.1 判断当前任务的父任务是否还有子任务
357 - Long childrenTaskCount = getChildrenTaskCount(bpmTaskExtDO.getTaskId()); 360 + Long childrenTaskCount = getChildrenTaskCount(parentTaskExt.getTaskId());
358 if (childrenTaskCount > 0) { 361 if (childrenTaskCount > 0) {
359 continue; 362 continue;
360 } 363 }
361 // 3.2 没有子任务,判断当前任务状态是否为 ADD_SIGN_BEFORE 待前加签任务完成 364 // 3.2 没有子任务,判断当前任务状态是否为 ADD_SIGN_BEFORE 待前加签任务完成
362 - if (BpmProcessInstanceResultEnum.ADD_SIGN_BEFORE.getResult().equals(bpmTaskExtDO.getResult())) { 365 + if (BpmProcessInstanceResultEnum.SIGN_BEFORE.getResult().equals(parentTaskExt.getResult())) {
363 // 3.3 需要修改该任务状态为处理中 366 // 3.3 需要修改该任务状态为处理中
364 - taskService.resolveTask(bpmTaskExtDO.getTaskId());  
365 - bpmTaskExtDO.setResult(BpmProcessInstanceResultEnum.PROCESS.getResult());  
366 - taskExtMapper.updateByTaskId(bpmTaskExtDO); 367 + taskService.resolveTask(parentTaskExt.getTaskId());
  368 + parentTaskExt.setResult(BpmProcessInstanceResultEnum.PROCESS.getResult());
  369 + taskExtMapper.updateByTaskId(parentTaskExt);
367 } 370 }
368 // 3.4 清空 scopeType 字段,用于任务没有子任务时使用该方法,方便任务可以再次被不同的方式加签 371 // 3.4 清空 scopeType 字段,用于任务没有子任务时使用该方法,方便任务可以再次被不同的方式加签
369 - parentTask = validateTaskExist(bpmTaskExtDO.getTaskId()); 372 + parentTask = validateTaskExist(parentTaskExt.getTaskId());
370 clearTaskScopeTypeAndSave(parentTask); 373 clearTaskScopeTypeAndSave(parentTask);
371 } 374 }
372 375
@@ -387,7 +390,6 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -387,7 +390,6 @@ public class BpmTaskServiceImpl implements BpmTaskService {
387 taskService.saveTask(task); 390 taskService.saveTask(task);
388 } 391 }
389 392
390 -  
391 /** 393 /**
392 * 获取子任务个数 394 * 获取子任务个数
393 * 395 *
@@ -709,7 +711,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -709,7 +711,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
709 711
710 @Override 712 @Override
711 @Transactional(rollbackFor = Exception.class) 713 @Transactional(rollbackFor = Exception.class)
712 - public void addSignTask(Long userId, BpmTaskAddSignReqVO reqVO) { 714 + public void createSignTask(Long userId, BpmTaskAddSignReqVO reqVO) {
713 // 1. 获取和校验任务 715 // 1. 获取和校验任务
714 TaskEntityImpl taskEntity = validateAddSign(userId, reqVO); 716 TaskEntityImpl taskEntity = validateAddSign(userId, reqVO);
715 List<AdminUserRespDTO> userList = adminUserApi.getUserList(reqVO.getUserIdList()); 717 List<AdminUserRespDTO> userList = adminUserApi.getUserList(reqVO.getUserIdList());
@@ -728,7 +730,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -728,7 +730,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
728 // 2.3 更新扩展表状态 730 // 2.3 更新扩展表状态
729 taskExtMapper.updateByTaskId( 731 taskExtMapper.updateByTaskId(
730 new BpmTaskExtDO().setTaskId(taskEntity.getId()) 732 new BpmTaskExtDO().setTaskId(taskEntity.getId())
731 - .setResult(BpmProcessInstanceResultEnum.ADD_SIGN_BEFORE.getResult()) 733 + .setResult(BpmProcessInstanceResultEnum.SIGN_BEFORE.getResult())
732 .setReason(reqVO.getReason())); 734 .setReason(reqVO.getReason()));
733 } 735 }
734 // 2.4 记录加签方式,完成任务时需要用到判断 736 // 2.4 记录加签方式,完成任务时需要用到判断
@@ -737,12 +739,12 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -737,12 +739,12 @@ public class BpmTaskServiceImpl implements BpmTaskService {
737 taskService.saveTask(taskEntity); 739 taskService.saveTask(taskEntity);
738 740
739 // 3. 创建加签任务 741 // 3. 创建加签任务
740 - createAddSignChildrenTasks(convertList(reqVO.getUserIdList(), String::valueOf), taskEntity); 742 + createSignTask(convertList(reqVO.getUserIdList(), String::valueOf), taskEntity);
741 743
742 // 4. 记录加签 comment,拼接结果为: [当前用户]向前加签/向后加签给了[多个用户],理由为:reason 744 // 4. 记录加签 comment,拼接结果为: [当前用户]向前加签/向后加签给了[多个用户],理由为:reason
743 AdminUserRespDTO currentUser = adminUserApi.getUser(userId); 745 AdminUserRespDTO currentUser = adminUserApi.getUser(userId);
744 - String comment = StrUtil.format(BpmCommentTypeEnum.ADD_SIGN.getTemplateComment(), currentUser.getNickname(), BpmTaskAddSignTypeEnum.formatDesc(reqVO.getType()),  
745 - String.join(",", convertList(userList, AdminUserRespDTO::getNickname)), reqVO.getReason()); 746 + String comment = StrUtil.format(BpmCommentTypeEnum.ADD_SIGN.getComment(), currentUser.getNickname(),
  747 + BpmTaskAddSignTypeEnum.formatDesc(reqVO.getType()), String.join(",", convertList(userList, AdminUserRespDTO::getNickname)), reqVO.getReason());
746 taskService.addComment(reqVO.getId(), taskEntity.getProcessInstanceId(), 748 taskService.addComment(reqVO.getId(), taskEntity.getProcessInstanceId(),
747 BpmCommentTypeEnum.ADD_SIGN.getType().toString(), comment); 749 BpmCommentTypeEnum.ADD_SIGN.getType().toString(), comment);
748 } 750 }
@@ -786,7 +788,7 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -786,7 +788,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
786 * @param addSingUserIdList 被加签的用户 ID 788 * @param addSingUserIdList 被加签的用户 ID
787 * @param taskEntity 被加签的任务 789 * @param taskEntity 被加签的任务
788 */ 790 */
789 - private void createAddSignChildrenTasks(List<String> addSingUserIdList, TaskEntityImpl taskEntity) { 791 + private void createSignTask(List<String> addSingUserIdList, TaskEntityImpl taskEntity) {
790 if (CollUtil.isEmpty(addSingUserIdList)) { 792 if (CollUtil.isEmpty(addSingUserIdList)) {
791 return; 793 return;
792 } 794 }
@@ -795,23 +797,22 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -795,23 +797,22 @@ public class BpmTaskServiceImpl implements BpmTaskService {
795 if (StrUtil.isBlank(addSignId)) { 797 if (StrUtil.isBlank(addSignId)) {
796 continue; 798 continue;
797 } 799 }
798 - createChildrenTask(taskEntity, addSignId); 800 + createSignTask(taskEntity, addSignId);
799 } 801 }
800 } 802 }
801 803
802 /** 804 /**
803 - * 创建子任务 805 + * 创建加签子任务
804 * 806 *
805 * @param parentTask 父任务 807 * @param parentTask 父任务
806 * @param assignee 子任务的执行人 808 * @param assignee 子任务的执行人
807 - * @return  
808 */ 809 */
809 - private void createChildrenTask(TaskEntityImpl parentTask, String assignee) { 810 + private void createSignTask(TaskEntityImpl parentTask, String assignee) {
810 // 1. 生成子任务 811 // 1. 生成子任务
811 TaskEntityImpl task = (TaskEntityImpl) taskService.newTask(IdUtil.fastSimpleUUID()); 812 TaskEntityImpl task = (TaskEntityImpl) taskService.newTask(IdUtil.fastSimpleUUID());
812 task = BpmTaskConvert.INSTANCE.convert(task, parentTask); 813 task = BpmTaskConvert.INSTANCE.convert(task, parentTask);
813 if (BpmTaskAddSignTypeEnum.BEFORE.getType().equals(parentTask.getScopeType())) { 814 if (BpmTaskAddSignTypeEnum.BEFORE.getType().equals(parentTask.getScopeType())) {
814 - // 2.1 前加签,才设置审批人,否则设置 owner 815 + // 2.1 前加签,设置审批人
815 task.setAssignee(assignee); 816 task.setAssignee(assignee);
816 } else { 817 } else {
817 // 2.2.1 设置 owner 不设置 assignee 是因为不能同时审批,需要等父任务完成 818 // 2.2.1 设置 owner 不设置 assignee 是因为不能同时审批,需要等父任务完成
@@ -825,9 +826,10 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -825,9 +826,10 @@ public class BpmTaskServiceImpl implements BpmTaskService {
825 826
826 @Override 827 @Override
827 @Transactional(rollbackFor = Exception.class) 828 @Transactional(rollbackFor = Exception.class)
828 - public void subSignTask(Long userId, BpmTaskSubSignReqVO reqVO) { 829 + public void deleteSignTask(Long userId, BpmTaskSubSignReqVO reqVO) {
  830 + // 1.1 校验 task 可以被减签
829 Task task = validateSubSign(reqVO.getId()); 831 Task task = validateSubSign(reqVO.getId());
830 - AdminUserRespDTO user = adminUserApi.getUser(userId); 832 + // 1.2 校验取消人存在
831 AdminUserRespDTO cancelUser = null; 833 AdminUserRespDTO cancelUser = null;
832 if (StrUtil.isNotBlank(task.getAssignee())) { 834 if (StrUtil.isNotBlank(task.getAssignee())) {
833 cancelUser = adminUserApi.getUser(NumberUtils.parseLong(task.getAssignee())); 835 cancelUser = adminUserApi.getUser(NumberUtils.parseLong(task.getAssignee()));
@@ -836,20 +838,24 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -836,20 +838,24 @@ public class BpmTaskServiceImpl implements BpmTaskService {
836 cancelUser = adminUserApi.getUser(NumberUtils.parseLong(task.getOwner())); 838 cancelUser = adminUserApi.getUser(NumberUtils.parseLong(task.getOwner()));
837 } 839 }
838 Assert.notNull(cancelUser, "任务中没有所有者和审批人,数据错误"); 840 Assert.notNull(cancelUser, "任务中没有所有者和审批人,数据错误");
839 - //1. 获取所有需要删除的任务 ID ,包含当前任务和所有子任务 841 +
  842 + // 2. 删除任务和对应子任务
  843 + // 2.1 获取所有需要删除的任务 ID ,包含当前任务和所有子任务
840 List<String> allTaskIdList = getAllChildTaskIds(task.getId()); 844 List<String> allTaskIdList = getAllChildTaskIds(task.getId());
841 - //2. 删除任务和所有子任务 845 + // 2.2 删除任务和所有子任务
842 taskService.deleteTasks(allTaskIdList); 846 taskService.deleteTasks(allTaskIdList);
843 - //3. 修改扩展表状态为取消 847 + // 2.3 修改扩展表状态为取消
  848 + AdminUserRespDTO user = adminUserApi.getUser(userId);
844 taskExtMapper.updateBatchByTaskIdList(allTaskIdList, new BpmTaskExtDO().setResult(BpmProcessInstanceResultEnum.CANCEL.getResult()) 849 taskExtMapper.updateBatchByTaskIdList(allTaskIdList, new BpmTaskExtDO().setResult(BpmProcessInstanceResultEnum.CANCEL.getResult())
845 .setReason(StrUtil.format("由于{}操作[减签],任务被取消", user.getNickname()))); 850 .setReason(StrUtil.format("由于{}操作[减签],任务被取消", user.getNickname())));
846 - //4.记录日志到父任务中 先记录日志是因为,通过 handleParentTask 方法之后,任务可能被完成了,并且不存在了,会报异常,所以先记录  
847 - String comment = StrUtil.format(BpmCommentTypeEnum.SUB_SIGN.getTemplateComment(), user.getNickname(), cancelUser.getNickname()); 851 +
  852 + // 3. 记录日志到父任务中。先记录日志是因为,通过 handleParentTask 方法之后,任务可能被完成了,并且不存在了,会报异常,所以先记录
  853 + String comment = StrUtil.format(BpmCommentTypeEnum.SUB_SIGN.getComment(), user.getNickname(), cancelUser.getNickname());
848 taskService.addComment(task.getParentTaskId(), task.getProcessInstanceId(), 854 taskService.addComment(task.getParentTaskId(), task.getProcessInstanceId(),
849 BpmCommentTypeEnum.SUB_SIGN.getType().toString(), comment); 855 BpmCommentTypeEnum.SUB_SIGN.getType().toString(), comment);
850 - //5. 处理当前任务的父任务  
851 - this.handleParentTask(task);  
852 856
  857 + // 4. 处理当前任务的父任务
  858 + handleParentTask(task);
853 } 859 }
854 860
855 /** 861 /**
@@ -860,14 +866,29 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -860,14 +866,29 @@ public class BpmTaskServiceImpl implements BpmTaskService {
860 */ 866 */
861 private Task validateSubSign(String id) { 867 private Task validateSubSign(String id) {
862 Task task = validateTaskExist(id); 868 Task task = validateTaskExist(id);
863 - //必须有parentId  
864 - if (StrUtil.isEmpty(task.getParentTaskId())) { 869 +
  870 + // 必须有 scopeType
  871 + String scopeType = task.getScopeType();
  872 + if (StrUtil.isEmpty(scopeType)) {
  873 + throw exception(TASK_SUB_SIGN_NO_PARENT);
  874 + }
  875 + // 并且值为 向前和向后加签
  876 + if (!validateSignType(scopeType)) {
865 throw exception(TASK_SUB_SIGN_NO_PARENT); 877 throw exception(TASK_SUB_SIGN_NO_PARENT);
866 } 878 }
867 return task; 879 return task;
868 } 880 }
869 881
870 /** 882 /**
  883 + * 判断当前类型是否为加签
  884 + * @param scopeType 任务的 scopeType
  885 + * @return 当前 scopeType 为加签则返回 true
  886 + */
  887 + private boolean validateSignType(String scopeType){
  888 + return StrUtil.equalsAny(scopeType,BpmTaskAddSignTypeEnum.BEFORE.getType(),scopeType, BpmTaskAddSignTypeEnum.AFTER.getType());
  889 + }
  890 +
  891 + /**
871 * 获取所有要被取消的删除的任务 ID 集合 892 * 获取所有要被取消的删除的任务 ID 集合
872 * 893 *
873 * @param parentTaskId 父级任务ID 894 * @param parentTaskId 父级任务ID
@@ -875,25 +896,40 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -875,25 +896,40 @@ public class BpmTaskServiceImpl implements BpmTaskService {
875 */ 896 */
876 public List<String> getAllChildTaskIds(String parentTaskId) { 897 public List<String> getAllChildTaskIds(String parentTaskId) {
877 List<String> allChildTaskIds = new ArrayList<>(); 898 List<String> allChildTaskIds = new ArrayList<>();
878 - //1. 先将自己放入  
879 - allChildTaskIds.add(parentTaskId);  
880 - //2. 递归获取子级  
881 - recursiveGetChildTaskIds(parentTaskId, allChildTaskIds); 899 + // 1. 递归获取子级
  900 + Stack<String> stack = new Stack<>();
  901 + // 1.1 将根任务ID入栈
  902 + stack.push(parentTaskId);
  903 + //控制遍历的次数不超过 Byte.MAX_VALUE,避免脏数据造成死循环
  904 + int count = 0;
  905 + // TODO @海:< 的前后空格,要注意哈;
  906 + while (!stack.isEmpty() && count<Byte.MAX_VALUE) {
  907 + // 1.2 弹出栈顶任务ID
  908 + String taskId = stack.pop();
  909 + // 1.3 将任务ID添加到结果集合中
  910 + allChildTaskIds.add(taskId);
  911 + // 1.4 获取该任务的子任务列表
  912 + // TODO @海:有个更高效的写法;一次性去 in 一层;不然每个节点,都去查询一次 db, 太浪费了;每次 in,最终就是 O(h) 查询,而不是 O(n) 查询;
  913 + List<String> childrenTaskIdList = getChildrenTaskIdList(taskId);
  914 + if (CollUtil.isNotEmpty(childrenTaskIdList)) {
  915 + for (String childTaskId : childrenTaskIdList) {
  916 + // 1.5 将子任务ID入栈,以便后续处理
  917 + stack.push(childTaskId);
  918 + }
  919 + }
  920 + count++;
  921 + }
882 return allChildTaskIds; 922 return allChildTaskIds;
883 } 923 }
884 924
885 /** 925 /**
886 - * 递归处理子级任务 926 + * 获取指定父级任务的所有子任务 ID 集合
887 * 927 *
888 - * @param taskId 当前任务ID  
889 - * @param taskIds 结果 928 + * @param parentTaskId 父任务 ID
  929 + * @return 所有子任务的 ID 集合
890 */ 930 */
891 - private void recursiveGetChildTaskIds(String taskId, List<String> taskIds) {  
892 - List<String> childrenTaskIdList = getChildrenTaskIdList(taskId);  
893 - for (String childTaskId : childrenTaskIdList) {  
894 - taskIds.add(childTaskId); // 将子任务的ID添加到集合中  
895 - recursiveGetChildTaskIds(childTaskId, taskIds); // 递归获取子任务的子任务  
896 - } 931 + private List<String> getChildrenTaskIdList(String parentTaskId) {
  932 + return convertList(getChildrenTaskList0(parentTaskId), Task::getId);
897 } 933 }
898 934
899 /** 935 /**
@@ -902,33 +938,32 @@ public class BpmTaskServiceImpl implements BpmTaskService { @@ -902,33 +938,32 @@ public class BpmTaskServiceImpl implements BpmTaskService {
902 * @param parentTaskId 父任务 ID 938 * @param parentTaskId 父任务 ID
903 * @return 所有子任务的 ID 集合 939 * @return 所有子任务的 ID 集合
904 */ 940 */
905 - private List<String> getChildrenTaskIdList(String parentTaskId) { 941 + private List<Task> getChildrenTaskList0(String parentTaskId) {
906 String tableName = managementService.getTableName(TaskEntity.class); 942 String tableName = managementService.getTableName(TaskEntity.class);
907 - String sql = "select ID_ from " + tableName + " where PARENT_TASK_ID_=#{parentTaskId}";  
908 - List<Task> childrenTaskList = taskService.createNativeTaskQuery().sql(sql).parameter("parentTaskId", parentTaskId).list();  
909 - return convertList(childrenTaskList, Task::getId); 943 + // taskService.createTaskQuery() 没有 parentId 参数,所以写 sql 查询
  944 + String sql = "select ID_,OWNER_,ASSIGNEE_ from " + tableName + " where PARENT_TASK_ID_=#{parentTaskId}";
  945 + return taskService.createNativeTaskQuery().sql(sql).parameter("parentTaskId", parentTaskId).list();
910 } 946 }
911 947
  948 +
912 @Override 949 @Override
913 - public List<BpmTaskSubSignRespVO> getChildrenTaskList(String taskId) {  
914 - List<String> childrenTaskIdList = getChildrenTaskIdList(taskId);  
915 - if (CollUtil.isEmpty(childrenTaskIdList)) { 950 + public List<BpmTaskSubSignRespVO> getChildrenTaskList(String parentId) {
  951 + // 1. 只查询进行中的任务 后加签的任务,可能不存在 assignee,所以还需要查询 owner
  952 + List<Task> taskList = getChildrenTaskList0(parentId);
  953 + if (CollUtil.isEmpty(taskList)) {
916 return Collections.emptyList(); 954 return Collections.emptyList();
917 } 955 }
918 - //1. 只查询进行中的任务  
919 - List<BpmTaskExtDO> bpmTaskExtDOList = taskExtMapper.selectProcessListByTaskIds(childrenTaskIdList);  
920 - //2. 后加签的任务,可能不存在 assignee,所以还需要查询 owner  
921 - List<Task> taskList = taskService.createTaskQuery().taskIds(childrenTaskIdList).list();  
922 - Map<String, Task> idTaskMap = convertMap(taskList, TaskInfo::getId);  
923 - //3. 将 owner 和 assignee 统一到一个集合中  
924 - List<Long> userIds = taskList.stream()  
925 - .flatMap(control ->  
926 - Stream.of(control.getAssignee(), control.getOwner())  
927 - .filter(Objects::nonNull))  
928 - .distinct()  
929 - .map(NumberUtils::parseLong)  
930 - .collect(Collectors.toList()); 956 + List<String> childrenTaskIdList = convertList(taskList, Task::getId);
  957 +
  958 + // 2.1 将 owner 和 assignee 统一到一个集合中
  959 + List<Long> userIds = convertListByFlatMap(taskList, control ->
  960 + Stream.of(NumberUtils.parseLong(control.getAssignee()), NumberUtils.parseLong(control.getOwner()))
  961 + .filter(Objects::nonNull));
  962 + // 2.2 组装数据
931 Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds); 963 Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);
932 - return BpmTaskConvert.INSTANCE.convertList(bpmTaskExtDOList, userMap, idTaskMap); 964 + List<BpmTaskExtDO> taskExtList = taskExtMapper.selectProcessListByTaskIds(childrenTaskIdList);
  965 + Map<String, Task> idTaskMap = convertMap(taskList, TaskInfo::getId);
  966 + return BpmTaskConvert.INSTANCE.convertList(taskExtList, userMap, idTaskMap);
933 } 967 }
  968 +
934 } 969 }
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/db/DatabaseDocController.java
@@ -147,7 +147,7 @@ public class DatabaseDocController { @@ -147,7 +147,7 @@ public class DatabaseDocController {
147 */ 147 */
148 private static ProcessConfig buildProcessConfig() { 148 private static ProcessConfig buildProcessConfig() {
149 return ProcessConfig.builder() 149 return ProcessConfig.builder()
150 - .ignoreTablePrefix(Arrays.asList("QRTZ_", "ACT_")) // 忽略表前缀 150 + .ignoreTablePrefix(Arrays.asList("QRTZ_", "ACT_", "FLW_")) // 忽略表前缀
151 .build(); 151 .build();
152 } 152 }
153 153
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/framework/security/config/SecurityConfiguration.java
@@ -29,8 +29,6 @@ public class SecurityConfiguration { @@ -29,8 +29,6 @@ public class SecurityConfiguration {
29 .antMatchers("/swagger-resources/**").anonymous() 29 .antMatchers("/swagger-resources/**").anonymous()
30 .antMatchers("/webjars/**").anonymous() 30 .antMatchers("/webjars/**").anonymous()
31 .antMatchers("/*/api-docs").anonymous(); 31 .antMatchers("/*/api-docs").anonymous();
32 - // 积木报表  
33 - registry.antMatchers("/jmreport/**").permitAll();  
34 // Spring Boot Actuator 的安全配置 32 // Spring Boot Actuator 的安全配置
35 registry.antMatchers("/actuator").anonymous() 33 registry.antMatchers("/actuator").anonymous()
36 .antMatchers("/actuator/**").anonymous(); 34 .antMatchers("/actuator/**").anonymous();
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/job/logger/JobLogCleanJob.java renamed to yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/job/job/JobLogCleanJob.java
1 -package cn.iocoder.yudao.module.infra.job.logger; 1 +package cn.iocoder.yudao.module.infra.job.job;
2 2
3 import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler; 3 import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
4 import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore; 4 import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
@@ -33,7 +33,7 @@ public class JobLogCleanJob implements JobHandler { @@ -33,7 +33,7 @@ public class JobLogCleanJob implements JobHandler {
33 @TenantIgnore 33 @TenantIgnore
34 public String execute(String param) { 34 public String execute(String param) {
35 Integer count = jobLogService.cleanJobLog(JOB_CLEAN_RETAIN_DAY, DELETE_LIMIT); 35 Integer count = jobLogService.cleanJobLog(JOB_CLEAN_RETAIN_DAY, DELETE_LIMIT);
36 - log.info("[count][定时执行清理定时任务日志数量 ({}) 个]", count); 36 + log.info("[execute][定时执行清理定时任务日志数量 ({}) 个]", count);
37 return String.format("定时执行清理定时任务日志数量 %s 个", count); 37 return String.format("定时执行清理定时任务日志数量 %s 个", count);
38 } 38 }
39 39
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/job/job/AccessLogCleanJob.java renamed to yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/job/logger/AccessLogCleanJob.java
1 -package cn.iocoder.yudao.module.infra.job.job; 1 +package cn.iocoder.yudao.module.infra.job.logger;
2 2
3 import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler; 3 import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
4 import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore; 4 import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
@@ -34,7 +34,7 @@ public class AccessLogCleanJob implements JobHandler { @@ -34,7 +34,7 @@ public class AccessLogCleanJob implements JobHandler {
34 @TenantIgnore 34 @TenantIgnore
35 public String execute(String param) { 35 public String execute(String param) {
36 Integer count = apiAccessLogService.cleanAccessLog(JOB_CLEAN_RETAIN_DAY, DELETE_LIMIT); 36 Integer count = apiAccessLogService.cleanAccessLog(JOB_CLEAN_RETAIN_DAY, DELETE_LIMIT);
37 - log.info("[count][定时执行清理访问日志数量 ({}) 个]", count); 37 + log.info("[execute][定时执行清理访问日志数量 ({}) 个]", count);
38 return String.format("定时执行清理错误日志数量 %s 个", count); 38 return String.format("定时执行清理错误日志数量 %s 个", count);
39 } 39 }
40 40
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/job/job/ErrorLogCleanJob.java renamed to yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/job/logger/ErrorLogCleanJob.java
1 -package cn.iocoder.yudao.module.infra.job.job; 1 +package cn.iocoder.yudao.module.infra.job.logger;
2 2
3 import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler; 3 import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
4 import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore; 4 import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
@@ -34,7 +34,7 @@ public class ErrorLogCleanJob implements JobHandler { @@ -34,7 +34,7 @@ public class ErrorLogCleanJob implements JobHandler {
34 @TenantIgnore 34 @TenantIgnore
35 public String execute(String param) { 35 public String execute(String param) {
36 Integer count = apiErrorLogService.cleanErrorLog(JOB_CLEAN_RETAIN_DAY,DELETE_LIMIT); 36 Integer count = apiErrorLogService.cleanErrorLog(JOB_CLEAN_RETAIN_DAY,DELETE_LIMIT);
37 - log.info("[count][定时执行清理错误日志数量 ({}) 个]", count); 37 + log.info("[execute][定时执行清理错误日志数量 ({}) 个]", count);
38 return String.format("定时执行清理错误日志数量 %s 个", count); 38 return String.format("定时执行清理错误日志数量 %s 个", count);
39 } 39 }
40 40
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImpl.java
@@ -58,9 +58,9 @@ public class FileConfigServiceImpl implements FileConfigService { @@ -58,9 +58,9 @@ public class FileConfigServiceImpl implements FileConfigService {
58 FileConfigDO config = Objects.equals(CACHE_MASTER_ID, id) ? 58 FileConfigDO config = Objects.equals(CACHE_MASTER_ID, id) ?
59 fileConfigMapper.selectByMaster() : fileConfigMapper.selectById(id); 59 fileConfigMapper.selectByMaster() : fileConfigMapper.selectById(id);
60 if (config != null) { 60 if (config != null) {
61 - fileClientFactory.createOrUpdateFileClient(id, config.getStorage(), config.getConfig()); 61 + fileClientFactory.createOrUpdateFileClient(config.getId(), config.getStorage(), config.getConfig());
62 } 62 }
63 - return fileClientFactory.getFileClient(id); 63 + return fileClientFactory.getFileClient(null == config ? id : config.getId());
64 } 64 }
65 65
66 }); 66 });
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben/api/api.ts.vm
@@ -28,5 +28,5 @@ export function delete${simpleClassName}(id: number) { @@ -28,5 +28,5 @@ export function delete${simpleClassName}(id: number) {
28 28
29 // 导出${table.classComment} Excel 29 // 导出${table.classComment} Excel
30 export function export${simpleClassName}(params) { 30 export function export${simpleClassName}(params) {
31 - return defHttp.download({ url: '${baseURL}/export-excel', params }, '${table.classComment}.xls') 31 + return defHttp.download({ url: '${baseURL}/export-excel', params }, '${table.classComment}.xls')
32 } 32 }
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben/views/data.ts.vm
1 -import type { BasicColumn, FormSchema } from '@/components/Table'  
2 -import { useRender } from '@/components/Table'  
3 -import { DICT_TYPE, getDictOptions } from '@/utils/dict' 1 +import type {BasicColumn, FormSchema} from '@/components/Table'
  2 +import {useRender} from '@/components/Table'
  3 +import {DICT_TYPE, getDictOptions} from '@/utils/dict'
4 4
5 export const columns: BasicColumn[] = [ 5 export const columns: BasicColumn[] = [
6 #foreach($column in $columns) 6 #foreach($column in $columns)
@@ -50,7 +50,7 @@ export const searchFormSchema: FormSchema[] = [ @@ -50,7 +50,7 @@ export const searchFormSchema: FormSchema[] = [
50 field: '${javaField}', 50 field: '${javaField}',
51 #if ($column.htmlType == "input") 51 #if ($column.htmlType == "input")
52 component: 'Input', 52 component: 'Input',
53 - #elseif ($column.htmlType == "select" || $column.htmlType == "radio") 53 + #elseif ($column.htmlType == "select")
54 component: 'Select', 54 component: 'Select',
55 componentProps: { 55 componentProps: {
56 #if ("" != $dictType)## 设置了 dictType 数据字典的情况 56 #if ("" != $dictType)## 设置了 dictType 数据字典的情况
@@ -59,6 +59,15 @@ export const searchFormSchema: FormSchema[] = [ @@ -59,6 +59,15 @@ export const searchFormSchema: FormSchema[] = [
59 options: [], 59 options: [],
60 #end 60 #end
61 }, 61 },
  62 + #elseif ($column.htmlType == "radio")
  63 + component: 'Radio',
  64 + componentProps: {
  65 + #if ("" != $dictType)## 设置了 dictType 数据字典的情况
  66 + options: getDictOptions(DICT_TYPE.$dictType.toUpperCase()),
  67 + #else## 未设置 dictType 数据字典的情况
  68 + options: [],
  69 + #end
  70 + },
62 #elseif($column.htmlType == "datetime") 71 #elseif($column.htmlType == "datetime")
63 component: 'RangePicker', 72 component: 'RangePicker',
64 #end 73 #end
@@ -181,7 +190,8 @@ export const updateFormSchema: FormSchema[] = [ @@ -181,7 +190,8 @@ export const updateFormSchema: FormSchema[] = [
181 fileType: 'file', 190 fileType: 'file',
182 maxCount: 1, 191 maxCount: 1,
183 }, 192 },
184 - #elseif($column.htmlType == "editor")## 文本编辑器component: 'Editor', 193 + #elseif($column.htmlType == "editor")## 文本编辑器
  194 + component: 'Editor',
185 #elseif($column.htmlType == "select")## 下拉框 195 #elseif($column.htmlType == "select")## 下拉框
186 component: 'Select', 196 component: 'Select',
187 componentProps: { 197 componentProps: {
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben/views/index.vue.vm
1 <script lang="ts" setup> 1 <script lang="ts" setup>
2 -import ${ simpleClassName }Modal from './${simpleClassName}Modal.vue' 2 +import ${simpleClassName}Modal from './${simpleClassName}Modal.vue'
3 import { columns, searchFormSchema } from './${classNameVar}.data' 3 import { columns, searchFormSchema } from './${classNameVar}.data'
4 import { useI18n } from '@/hooks/web/useI18n' 4 import { useI18n } from '@/hooks/web/useI18n'
5 import { useMessage } from '@/hooks/web/useMessage' 5 import { useMessage } from '@/hooks/web/useMessage'
6 import { useModal } from '@/components/Modal' 6 import { useModal } from '@/components/Modal'
7 -import { IconEnum } from '@/enums/appEnum'  
8 -import { BasicTable, useTable, TableAction } from '@/components/Table'  
9 -import { delete${ simpleClassName }, export${ simpleClassName }, get${ simpleClassName } Page } from '@/api/${table.moduleName}/${classNameVar}' 7 +import { useTable } from '@/components/Table'
  8 +import { delete${simpleClassName}, export${simpleClassName}, get${simpleClassName}Page } from '@/api/${table.moduleName}/${classNameVar}'
10 9
11 defineOptions({ name: '${table.className}' }) 10 defineOptions({ name: '${table.className}' })
12 11
@@ -16,7 +15,7 @@ const [registerModal, { openModal }] = useModal() @@ -16,7 +15,7 @@ const [registerModal, { openModal }] = useModal()
16 15
17 const [registerTable, { getForm, reload }] = useTable({ 16 const [registerTable, { getForm, reload }] = useTable({
18 title: '${table.classComment}列表', 17 title: '${table.classComment}列表',
19 - api: get${ simpleClassName }Page, 18 + api: get${simpleClassName}Page,
20 columns, 19 columns,
21 formConfig: { labelWidth: 120, schemas: searchFormSchema }, 20 formConfig: { labelWidth: 120, schemas: searchFormSchema },
22 useSearchForm: true, 21 useSearchForm: true,
@@ -43,14 +42,14 @@ async function handleExport() { @@ -43,14 +42,14 @@ async function handleExport() {
43 iconType: 'warning', 42 iconType: 'warning',
44 content: t('common.exportMessage'), 43 content: t('common.exportMessage'),
45 async onOk() { 44 async onOk() {
46 - await export${ simpleClassName } (getForm().getFieldsValue()) 45 + await export${simpleClassName}(getForm().getFieldsValue())
47 createMessage.success(t('common.exportSuccessText')) 46 createMessage.success(t('common.exportSuccessText'))
48 }, 47 },
49 }) 48 })
50 } 49 }
51 50
52 async function handleDelete(record: Recordable) { 51 async function handleDelete(record: Recordable) {
53 - await delete${ simpleClassName } (record.id) 52 + await delete${simpleClassName}(record.id)
54 createMessage.success(t('common.delSuccessText')) 53 createMessage.success(t('common.delSuccessText'))
55 reload() 54 reload()
56 } 55 }
@@ -62,7 +61,7 @@ async function handleDelete(record: Recordable) { @@ -62,7 +61,7 @@ async function handleDelete(record: Recordable) {
62 <a-button type="primary" v-auth="['${permissionPrefix}:create']" :preIcon="IconEnum.ADD" @click="handleCreate"> 61 <a-button type="primary" v-auth="['${permissionPrefix}:create']" :preIcon="IconEnum.ADD" @click="handleCreate">
63 {{ t('action.create') }} 62 {{ t('action.create') }}
64 </a-button> 63 </a-button>
65 - <a-button type="warning" v-auth="['${permissionPrefix}:export']" :preIcon="IconEnum.EXPORT" @click="handleExport"> 64 + <a-button v-auth="['${permissionPrefix}:export']" :preIcon="IconEnum.EXPORT" @click="handleExport">
66 {{ t('action.export') }} 65 {{ t('action.export') }}
67 </a-button> 66 </a-button>
68 </template> 67 </template>
yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/file/FileConfigServiceImplTest.java
@@ -258,12 +258,12 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest { @@ -258,12 +258,12 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest {
258 Long id = fileConfig.getId(); 258 Long id = fileConfig.getId();
259 // mock 获得 Client 259 // mock 获得 Client
260 FileClient fileClient = new LocalFileClient(id, new LocalFileClientConfig()); 260 FileClient fileClient = new LocalFileClient(id, new LocalFileClientConfig());
261 - when(fileClientFactory.getFileClient(eq(0L))).thenReturn(fileClient); 261 + when(fileClientFactory.getFileClient(eq(fileConfig.getId()))).thenReturn(fileClient);
262 262
263 // 调用,并断言 263 // 调用,并断言
264 assertSame(fileClient, fileConfigService.getMasterFileClient()); 264 assertSame(fileClient, fileConfigService.getMasterFileClient());
265 // 断言缓存 265 // 断言缓存
266 - verify(fileClientFactory).createOrUpdateFileClient(eq(0L), eq(fileConfig.getStorage()), 266 + verify(fileClientFactory).createOrUpdateFileClient(eq(fileConfig.getId()), eq(fileConfig.getStorage()),
267 eq(fileConfig.getConfig())); 267 eq(fileConfig.getConfig()));
268 } 268 }
269 269
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/comment/dto/ProductCommentCreateReqDTO.java
@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.product.api.comment.dto; @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.product.api.comment.dto;
2 2
3 import lombok.Data; 3 import lombok.Data;
4 4
  5 +import javax.validation.constraints.NotNull;
5 import java.util.List; 6 import java.util.List;
6 7
7 /** 8 /**
@@ -15,6 +16,7 @@ public class ProductCommentCreateReqDTO { @@ -15,6 +16,7 @@ public class ProductCommentCreateReqDTO {
15 /** 16 /**
16 * 商品 SKU 编号 17 * 商品 SKU 编号
17 */ 18 */
  19 + @NotNull(message = "商品 SKU 编号不能为空")
18 private Long skuId; 20 private Long skuId;
19 /** 21 /**
20 * 订单编号 22 * 订单编号
@@ -26,20 +28,19 @@ public class ProductCommentCreateReqDTO { @@ -26,20 +28,19 @@ public class ProductCommentCreateReqDTO {
26 private Long orderItemId; 28 private Long orderItemId;
27 29
28 /** 30 /**
29 - * 评分星级 1-5 分  
30 - */  
31 - private Integer scores;  
32 - /**  
33 * 描述星级 1-5 分 31 * 描述星级 1-5 分
34 */ 32 */
  33 + @NotNull(message = "描述星级不能为空")
35 private Integer descriptionScores; 34 private Integer descriptionScores;
36 /** 35 /**
37 * 服务星级 1-5 分 36 * 服务星级 1-5 分
38 */ 37 */
  38 + @NotNull(message = "服务星级不能为空")
39 private Integer benefitScores; 39 private Integer benefitScores;
40 /** 40 /**
41 * 评论内容 41 * 评论内容
42 */ 42 */
  43 + @NotNull(message = "评论内容不能为空")
43 private String content; 44 private String content;
44 /** 45 /**
45 * 评论图片地址数组,以逗号分隔最多上传 9 张 46 * 评论图片地址数组,以逗号分隔最多上传 9 张
@@ -49,11 +50,12 @@ public class ProductCommentCreateReqDTO { @@ -49,11 +50,12 @@ public class ProductCommentCreateReqDTO {
49 /** 50 /**
50 * 是否匿名 51 * 是否匿名
51 */ 52 */
  53 + @NotNull(message = "是否匿名不能为空")
52 private Boolean anonymous; 54 private Boolean anonymous;
53 /** 55 /**
54 * 评价人 56 * 评价人
55 */ 57 */
  58 + @NotNull(message = "评价人不能为空")
56 private Long userId; 59 private Long userId;
57 60
58 -  
59 } 61 }
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/property/ProductPropertyValueApi.java deleted
1 -package cn.iocoder.yudao.module.product.api.property;  
2 -  
3 -import cn.iocoder.yudao.module.product.api.property.dto.ProductPropertyValueDetailRespDTO;  
4 -  
5 -import java.util.Collection;  
6 -import java.util.List;  
7 -  
8 -/**  
9 - * 商品属性值 API 接口  
10 - *  
11 - * @author 芋道源码  
12 - */  
13 -public interface ProductPropertyValueApi {  
14 -  
15 - /**  
16 - * 根据编号数组,获得属性值列表  
17 - *  
18 - * @param ids 编号数组  
19 - * @return 属性值明细列表  
20 - */  
21 - List<ProductPropertyValueDetailRespDTO> getPropertyValueDetailList(Collection<Long> ids);  
22 -  
23 -}  
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/spu/dto/ProductSpuRespDTO.java
@@ -3,8 +3,6 @@ package cn.iocoder.yudao.module.product.api.spu.dto; @@ -3,8 +3,6 @@ package cn.iocoder.yudao.module.product.api.spu.dto;
3 import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum; 3 import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum;
4 import lombok.Data; 4 import lombok.Data;
5 5
6 -import java.util.List;  
7 -  
8 // TODO @LeeYan9: ProductSpuRespDTO 6 // TODO @LeeYan9: ProductSpuRespDTO
9 /** 7 /**
10 * 商品 SPU 信息 Response DTO 8 * 商品 SPU 信息 Response DTO
@@ -27,55 +25,22 @@ public class ProductSpuRespDTO { @@ -27,55 +25,22 @@ public class ProductSpuRespDTO {
27 */ 25 */
28 private String name; 26 private String name;
29 /** 27 /**
30 - * 关键字  
31 - */  
32 - private String keyword;  
33 - /**  
34 * 单位 28 * 单位
35 * 29 *
36 * 对应 product_unit 数据字典 30 * 对应 product_unit 数据字典
37 */ 31 */
38 private Integer unit; 32 private Integer unit;
39 - /**  
40 - * 商品简介  
41 - */  
42 - private String introduction;  
43 - /**  
44 - * 商品详情  
45 - */  
46 - private String description;  
47 - // TODO @芋艿:是不是要删除  
48 - /**  
49 - * 商品条码(一维码)  
50 - */  
51 - private String barCode;  
52 33
53 /** 34 /**
54 * 商品分类编号 35 * 商品分类编号
55 */ 36 */
56 private Long categoryId; 37 private Long categoryId;
57 /** 38 /**
58 - * 商品品牌编号  
59 - */  
60 - private Long brandId;  
61 - /**  
62 * 商品封面图 39 * 商品封面图
63 */ 40 */
64 private String picUrl; 41 private String picUrl;
65 - /**  
66 - * 商品轮播图  
67 - */  
68 - private List<String> sliderPicUrls;  
69 - /**  
70 - * 商品视频  
71 - */  
72 - private String videoUrl;  
73 42
74 /** 43 /**
75 - * 排序字段  
76 - */  
77 - private Integer sort;  
78 - /**  
79 * 商品状态 44 * 商品状态
80 * <p> 45 * <p>
81 * 枚举 {@link ProductSpuStatusEnum} 46 * 枚举 {@link ProductSpuStatusEnum}
@@ -124,22 +89,6 @@ public class ProductSpuRespDTO { @@ -124,22 +89,6 @@ public class ProductSpuRespDTO {
124 */ 89 */
125 private Integer giveIntegral; 90 private Integer giveIntegral;
126 91
127 - // ========== 统计相关字段 =========  
128 -  
129 - /**  
130 - * 商品销量  
131 - */  
132 - private Integer salesCount;  
133 - /**  
134 - * 虚拟销量  
135 - */  
136 - private Integer virtualSalesCount;  
137 - /**  
138 - * 商品点击量  
139 - */  
140 - private Integer clickCount;  
141 -  
142 -  
143 // ========== 分销相关字段 ========= 92 // ========== 分销相关字段 =========
144 93
145 /** 94 /**
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/enums/group/ProductGroupStyleEnum.java deleted
1 -package cn.iocoder.yudao.module.product.enums.group;  
2 -  
3 -import cn.iocoder.yudao.framework.common.core.IntArrayValuable;  
4 -import lombok.AllArgsConstructor;  
5 -import lombok.Getter;  
6 -  
7 -import java.util.Arrays;  
8 -  
9 -/**  
10 - * 商品分组的样式枚举  
11 - *  
12 - * @author 芋道源码  
13 - */  
14 -@Getter  
15 -@AllArgsConstructor  
16 -public enum ProductGroupStyleEnum implements IntArrayValuable {  
17 -  
18 - ONE(1, "每列一个"),  
19 - TWO(2, "每列两个"),  
20 - THREE(2, "每列三个"),;  
21 -  
22 - public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(ProductGroupStyleEnum::getStyle).toArray();  
23 -  
24 - /**  
25 - * 列表样式  
26 - */  
27 - private final Integer style;  
28 - /**  
29 - * 状态名  
30 - */  
31 - private final String name;  
32 -  
33 - @Override  
34 - public int[] array() {  
35 - return ARRAYS;  
36 - }  
37 -  
38 -}  
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/property/ProductPropertyValueApiImpl.java deleted
1 -package cn.iocoder.yudao.module.product.api.property;  
2 -  
3 -import cn.iocoder.yudao.module.product.api.property.dto.ProductPropertyValueDetailRespDTO;  
4 -import cn.iocoder.yudao.module.product.convert.property.ProductPropertyValueConvert;  
5 -import cn.iocoder.yudao.module.product.service.property.ProductPropertyValueService;  
6 -import org.springframework.stereotype.Service;  
7 -import org.springframework.validation.annotation.Validated;  
8 -  
9 -import javax.annotation.Resource;  
10 -import java.util.Collection;  
11 -import java.util.List;  
12 -  
13 -/**  
14 - * 商品属性值 API 实现类  
15 - *  
16 - * @author 芋道源码  
17 - */  
18 -@Service  
19 -@Validated  
20 -public class ProductPropertyValueApiImpl implements ProductPropertyValueApi {  
21 -  
22 - @Resource  
23 - private ProductPropertyValueService productPropertyValueService;  
24 -  
25 - @Override  
26 - public List<ProductPropertyValueDetailRespDTO> getPropertyValueDetailList(Collection<Long> ids) {  
27 - return ProductPropertyValueConvert.INSTANCE.convertList02(  
28 - productPropertyValueService.getPropertyValueDetailList(ids));  
29 - }  
30 -  
31 -}  
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/sku/ProductSkuApiImpl.java
1 package cn.iocoder.yudao.module.product.api.sku; 1 package cn.iocoder.yudao.module.product.api.sku;
2 2
3 -import cn.hutool.core.collection.CollUtil;  
4 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO; 3 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
5 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO; 4 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO;
6 import cn.iocoder.yudao.module.product.convert.sku.ProductSkuConvert; 5 import cn.iocoder.yudao.module.product.convert.sku.ProductSkuConvert;
@@ -11,7 +10,6 @@ import org.springframework.validation.annotation.Validated; @@ -11,7 +10,6 @@ import org.springframework.validation.annotation.Validated;
11 10
12 import javax.annotation.Resource; 11 import javax.annotation.Resource;
13 import java.util.Collection; 12 import java.util.Collection;
14 -import java.util.Collections;  
15 import java.util.List; 13 import java.util.List;
16 14
17 /** 15 /**
@@ -35,18 +33,12 @@ public class ProductSkuApiImpl implements ProductSkuApi { @@ -35,18 +33,12 @@ public class ProductSkuApiImpl implements ProductSkuApi {
35 33
36 @Override 34 @Override
37 public List<ProductSkuRespDTO> getSkuList(Collection<Long> ids) { 35 public List<ProductSkuRespDTO> getSkuList(Collection<Long> ids) {
38 - if (CollUtil.isEmpty(ids)) {  
39 - return Collections.emptyList();  
40 - }  
41 List<ProductSkuDO> skus = productSkuService.getSkuList(ids); 36 List<ProductSkuDO> skus = productSkuService.getSkuList(ids);
42 return ProductSkuConvert.INSTANCE.convertList04(skus); 37 return ProductSkuConvert.INSTANCE.convertList04(skus);
43 } 38 }
44 39
45 @Override 40 @Override
46 public List<ProductSkuRespDTO> getSkuListBySpuId(Collection<Long> spuIds) { 41 public List<ProductSkuRespDTO> getSkuListBySpuId(Collection<Long> spuIds) {
47 - if (CollUtil.isEmpty(spuIds)) {  
48 - return Collections.emptyList();  
49 - }  
50 List<ProductSkuDO> skus = productSkuService.getSkuListBySpuId(spuIds); 42 List<ProductSkuDO> skus = productSkuService.getSkuListBySpuId(spuIds);
51 return ProductSkuConvert.INSTANCE.convertList04(skus); 43 return ProductSkuConvert.INSTANCE.convertList04(skus);
52 } 44 }
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/api/spu/ProductSpuApiImpl.java
@@ -27,9 +27,6 @@ public class ProductSpuApiImpl implements ProductSpuApi { @@ -27,9 +27,6 @@ public class ProductSpuApiImpl implements ProductSpuApi {
27 27
28 @Override 28 @Override
29 public List<ProductSpuRespDTO> getSpuList(Collection<Long> ids) { 29 public List<ProductSpuRespDTO> getSpuList(Collection<Long> ids) {
30 - if (CollectionUtil.isEmpty(ids)) {  
31 - return Collections.emptyList();  
32 - }  
33 return ProductSpuConvert.INSTANCE.convertList2(spuService.getSpuList(ids)); 30 return ProductSpuConvert.INSTANCE.convertList2(spuService.getSpuList(ids));
34 } 31 }
35 32
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/convert/property/ProductPropertyValueConvert.java
1 package cn.iocoder.yudao.module.product.convert.property; 1 package cn.iocoder.yudao.module.product.convert.property;
2 2
3 import cn.iocoder.yudao.framework.common.pojo.PageResult; 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;  
5 -import cn.iocoder.yudao.framework.common.util.collection.MapUtils;  
6 -import cn.iocoder.yudao.module.product.api.property.dto.ProductPropertyValueDetailRespDTO;  
7 import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValueCreateReqVO; 4 import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValueCreateReqVO;
8 import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValueRespVO; 5 import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValueRespVO;
9 import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValueUpdateReqVO; 6 import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValueUpdateReqVO;
10 -import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyDO;  
11 import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyValueDO; 7 import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyValueDO;
12 -import cn.iocoder.yudao.module.product.service.property.bo.ProductPropertyValueDetailRespBO;  
13 import org.mapstruct.Mapper; 8 import org.mapstruct.Mapper;
14 import org.mapstruct.factory.Mappers; 9 import org.mapstruct.factory.Mappers;
15 10
16 import java.util.List; 11 import java.util.List;
17 -import java.util.Map;  
18 -  
19 -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;  
20 12
21 /** 13 /**
22 * 属性值 Convert 14 * 属性值 Convert
@@ -38,18 +30,4 @@ public interface ProductPropertyValueConvert { @@ -38,18 +30,4 @@ public interface ProductPropertyValueConvert {
38 30
39 PageResult<ProductPropertyValueRespVO> convertPage(PageResult<ProductPropertyValueDO> page); 31 PageResult<ProductPropertyValueRespVO> convertPage(PageResult<ProductPropertyValueDO> page);
40 32
41 - default List<ProductPropertyValueDetailRespBO> convertList(List<ProductPropertyValueDO> values, List<ProductPropertyDO> keys) {  
42 - Map<Long, ProductPropertyDO> keyMap = convertMap(keys, ProductPropertyDO::getId);  
43 - return CollectionUtils.convertList(values, value -> {  
44 - ProductPropertyValueDetailRespBO valueDetail = new ProductPropertyValueDetailRespBO()  
45 - .setValueId(value.getId()).setValueName(value.getName());  
46 - // 设置属性项  
47 - MapUtils.findAndThen(keyMap, value.getPropertyId(),  
48 - key -> valueDetail.setPropertyId(key.getId()).setPropertyName(key.getName()));  
49 - return valueDetail;  
50 - });  
51 - }  
52 -  
53 - List<ProductPropertyValueDetailRespDTO> convertList02(List<ProductPropertyValueDetailRespBO> list);  
54 -  
55 } 33 }
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/property/ProductPropertyValueService.java
@@ -5,7 +5,6 @@ import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.Produc @@ -5,7 +5,6 @@ import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.Produc
5 import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValuePageReqVO; 5 import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValuePageReqVO;
6 import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValueUpdateReqVO; 6 import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValueUpdateReqVO;
7 import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyValueDO; 7 import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyValueDO;
8 -import cn.iocoder.yudao.module.product.service.property.bo.ProductPropertyValueDetailRespBO;  
9 8
10 import java.util.Collection; 9 import java.util.Collection;
11 import java.util.List; 10 import java.util.List;
@@ -57,14 +56,6 @@ public interface ProductPropertyValueService { @@ -57,14 +56,6 @@ public interface ProductPropertyValueService {
57 List<ProductPropertyValueDO> getPropertyValueListByPropertyId(Collection<Long> propertyIds); 56 List<ProductPropertyValueDO> getPropertyValueListByPropertyId(Collection<Long> propertyIds);
58 57
59 /** 58 /**
60 - * 根据编号数组,获得属性值列表  
61 - *  
62 - * @param ids 编号数组  
63 - * @return 属性值明细列表  
64 - */  
65 - List<ProductPropertyValueDetailRespBO> getPropertyValueDetailList(Collection<Long> ids);  
66 -  
67 - /**  
68 * 根据属性项编号,活的属性值数量 59 * 根据属性项编号,活的属性值数量
69 * 60 *
70 * @param propertyId 属性项编号数 61 * @param propertyId 属性项编号数
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/property/ProductPropertyValueServiceImpl.java
1 package cn.iocoder.yudao.module.product.service.property; 1 package cn.iocoder.yudao.module.product.service.property;
2 2
3 -import cn.hutool.core.collection.CollUtil;  
4 import cn.iocoder.yudao.framework.common.pojo.PageResult; 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValueCreateReqVO; 4 import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValueCreateReqVO;
6 import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValuePageReqVO; 5 import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValuePageReqVO;
7 import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValueUpdateReqVO; 6 import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValueUpdateReqVO;
8 import cn.iocoder.yudao.module.product.convert.property.ProductPropertyValueConvert; 7 import cn.iocoder.yudao.module.product.convert.property.ProductPropertyValueConvert;
9 -import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyDO;  
10 import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyValueDO; 8 import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyValueDO;
11 import cn.iocoder.yudao.module.product.dal.mysql.property.ProductPropertyValueMapper; 9 import cn.iocoder.yudao.module.product.dal.mysql.property.ProductPropertyValueMapper;
12 -import cn.iocoder.yudao.module.product.service.property.bo.ProductPropertyValueDetailRespBO;  
13 import cn.iocoder.yudao.module.product.service.sku.ProductSkuService; 10 import cn.iocoder.yudao.module.product.service.sku.ProductSkuService;
14 import org.springframework.context.annotation.Lazy; 11 import org.springframework.context.annotation.Lazy;
15 import org.springframework.stereotype.Service; 12 import org.springframework.stereotype.Service;
@@ -17,11 +14,9 @@ import org.springframework.validation.annotation.Validated; @@ -17,11 +14,9 @@ import org.springframework.validation.annotation.Validated;
17 14
18 import javax.annotation.Resource; 15 import javax.annotation.Resource;
19 import java.util.Collection; 16 import java.util.Collection;
20 -import java.util.Collections;  
21 import java.util.List; 17 import java.util.List;
22 18
23 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 19 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
24 -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;  
25 import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.PROPERTY_VALUE_EXISTS; 20 import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.PROPERTY_VALUE_EXISTS;
26 import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.PROPERTY_VALUE_NOT_EXISTS; 21 import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.PROPERTY_VALUE_NOT_EXISTS;
27 22
@@ -100,23 +95,6 @@ public class ProductPropertyValueServiceImpl implements ProductPropertyValueServ @@ -100,23 +95,6 @@ public class ProductPropertyValueServiceImpl implements ProductPropertyValueServ
100 } 95 }
101 96
102 @Override 97 @Override
103 - public List<ProductPropertyValueDetailRespBO> getPropertyValueDetailList(Collection<Long> ids) {  
104 - // 获得属性值列表  
105 - if (CollUtil.isEmpty(ids)) {  
106 - return Collections.emptyList();  
107 - }  
108 - List<ProductPropertyValueDO> values = productPropertyValueMapper.selectBatchIds(ids);  
109 - if (CollUtil.isEmpty(values)) {  
110 - return Collections.emptyList();  
111 - }  
112 - // 获得属性项列表  
113 - List<ProductPropertyDO> keys = productPropertyService.getPropertyList(  
114 - convertSet(values, ProductPropertyValueDO::getPropertyId));  
115 - // 组装明细  
116 - return ProductPropertyValueConvert.INSTANCE.convertList(values, keys);  
117 - }  
118 -  
119 - @Override  
120 public Integer getPropertyValueCountByPropertyId(Long propertyId) { 98 public Integer getPropertyValueCountByPropertyId(Long propertyId) {
121 return productPropertyValueMapper.selectCountByPropertyId(propertyId); 99 return productPropertyValueMapper.selectCountByPropertyId(propertyId);
122 } 100 }
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/property/bo/ProductPropertyValueDetailRespBO.java deleted
1 -package cn.iocoder.yudao.module.product.service.property.bo;  
2 -  
3 -import lombok.Data;  
4 -  
5 -/**  
6 - * 商品属性项的明细 Response BO  
7 - *  
8 - * @author 芋道源码  
9 - */  
10 -@Data  
11 -public class ProductPropertyValueDetailRespBO {  
12 -  
13 - /**  
14 - * 属性的编号  
15 - */  
16 - private Long propertyId;  
17 -  
18 - /**  
19 - * 属性的名称  
20 - */  
21 - private String propertyName;  
22 -  
23 - /**  
24 - * 属性值的编号  
25 - */  
26 - private Long valueId;  
27 -  
28 - /**  
29 - * 属性值的名称  
30 - */  
31 - private String valueName;  
32 -  
33 -}  
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/sku/ProductSkuServiceImpl.java
@@ -153,6 +153,9 @@ public class ProductSkuServiceImpl implements ProductSkuService { @@ -153,6 +153,9 @@ public class ProductSkuServiceImpl implements ProductSkuService {
153 153
154 @Override 154 @Override
155 public List<ProductSkuDO> getSkuListBySpuId(Collection<Long> spuIds) { 155 public List<ProductSkuDO> getSkuListBySpuId(Collection<Long> spuIds) {
  156 + if (CollUtil.isEmpty(spuIds)) {
  157 + return Collections.emptyList();
  158 + }
156 return productSkuMapper.selectListBySpuId(spuIds); 159 return productSkuMapper.selectListBySpuId(spuIds);
157 } 160 }
158 161
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuServiceImpl.java
@@ -188,6 +188,9 @@ public class ProductSpuServiceImpl implements ProductSpuService { @@ -188,6 +188,9 @@ public class ProductSpuServiceImpl implements ProductSpuService {
188 188
189 @Override 189 @Override
190 public List<ProductSpuDO> getSpuList(Collection<Long> ids) { 190 public List<ProductSpuDO> getSpuList(Collection<Long> ids) {
  191 + if (CollUtil.isEmpty(ids)) {
  192 + return Collections.emptyList();
  193 + }
191 return productSpuMapper.selectBatchIds(ids); 194 return productSpuMapper.selectBatchIds(ids);
192 } 195 }
193 196
yudao-module-mall/yudao-module-promotion-api/pom.xml
@@ -13,7 +13,7 @@ @@ -13,7 +13,7 @@
13 13
14 <name>${project.artifactId}</name> 14 <name>${project.artifactId}</name>
15 <description> 15 <description>
16 - market 模块 API,暴露给其它模块调用 16 + promotion 模块 API,暴露给其它模块调用
17 </description> 17 </description>
18 18
19 <dependencies> 19 <dependencies>
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/CombinationActivityApi.java deleted
1 -package cn.iocoder.yudao.module.promotion.api.combination;  
2 -  
3 -/**  
4 - * 拼团活动 Api 接口  
5 - *  
6 - * @author HUIHUI  
7 - */  
8 -public interface CombinationActivityApi {  
9 -  
10 -}  
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/CombinationRecordApi.java
@@ -42,14 +42,6 @@ public interface CombinationRecordApi { @@ -42,14 +42,6 @@ public interface CombinationRecordApi {
42 boolean isCombinationRecordSuccess(Long userId, Long orderId); 42 boolean isCombinationRecordSuccess(Long userId, Long orderId);
43 43
44 /** 44 /**
45 - * 更新拼团状态为【失败】  
46 - *  
47 - * @param userId 用户编号  
48 - * @param orderId 订单编号  
49 - */  
50 - void updateRecordStatusToFailed(Long userId, Long orderId);  
51 -  
52 - /**  
53 * 【下单前】校验是否满足拼团活动条件 45 * 【下单前】校验是否满足拼团活动条件
54 * 46 *
55 * 如果校验失败,则抛出业务异常 47 * 如果校验失败,则抛出业务异常
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/price/PriceApi.java deleted
1 -package cn.iocoder.yudao.module.promotion.api.price;  
2 -  
3 -import cn.iocoder.yudao.module.promotion.api.price.dto.PriceCalculateReqDTO;  
4 -import cn.iocoder.yudao.module.promotion.api.price.dto.PriceCalculateRespDTO;  
5 -  
6 -/**  
7 - * 价格 API 接口  
8 - *  
9 - * @author 芋道源码  
10 - */  
11 -public interface PriceApi {  
12 -  
13 - /**  
14 - * 计算商品的价格  
15 - *  
16 - * @param calculateReqDTO 价格请求  
17 - * @return 价格相应  
18 - */  
19 - PriceCalculateRespDTO calculatePrice(PriceCalculateReqDTO calculateReqDTO);  
20 -  
21 -}  
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/price/dto/CouponMeetRespDTO.java deleted
1 -package cn.iocoder.yudao.module.promotion.api.price.dto;  
2 -  
3 -import lombok.Data;  
4 -  
5 -/**  
6 - * 优惠劵的匹配信息 Response DTO  
7 - *  
8 - * why 放在 price 包下?主要获取的时候,需要涉及到较多的价格计算逻辑,放在 price 可以更好的复用逻辑  
9 - *  
10 - * @author 芋道源码  
11 - */  
12 -@Data  
13 -public class CouponMeetRespDTO {  
14 -  
15 - /**  
16 - * 优惠劵编号  
17 - */  
18 - private Long id;  
19 -  
20 - // ========== 非优惠劵的基本信息字段 ==========  
21 - /**  
22 - * 是否匹配  
23 - */  
24 - private Boolean meet;  
25 - /**  
26 - * 不匹配的提示,即 {@link #meet} = true 才有值  
27 - *  
28 - * 例如说:  
29 - * 1. 所结算商品没有符合条件的商品  
30 - * 2. 差 XXX 元可用优惠劵  
31 - * 3. 优惠劵未到使用时间  
32 - */  
33 - private String meetTip;  
34 -  
35 -}  
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/price/dto/PriceCalculateReqDTO.java deleted
1 -package cn.iocoder.yudao.module.promotion.api.price.dto;  
2 -  
3 -import lombok.Data;  
4 -  
5 -import javax.validation.constraints.Min;  
6 -import javax.validation.constraints.NotNull;  
7 -import java.util.List;  
8 -  
9 -/**  
10 - * 价格计算 Request DTO  
11 - *  
12 - * @author 芋道源码  
13 - */  
14 -@Data  
15 -@Deprecated  
16 -public class PriceCalculateReqDTO {  
17 -  
18 - /**  
19 - * 用户编号  
20 - *  
21 - * 对应 MemberUserDO 的 id 编号  
22 - */  
23 - private Long userId;  
24 -  
25 - /**  
26 - * 优惠劵编号  
27 - */  
28 - private Long couponId;  
29 -  
30 - /**  
31 - * 收货地址编号  
32 - */  
33 - private Long addressId;  
34 -  
35 - /**  
36 - * 商品 SKU 数组  
37 - */  
38 - @NotNull(message = "商品数组不能为空")  
39 - private List<Item> items;  
40 -  
41 - /**  
42 - * 商品 SKU  
43 - */  
44 - @Data  
45 - public static class Item {  
46 -  
47 - /**  
48 - * SKU 编号  
49 - */  
50 - @NotNull(message = "商品 SKU 编号不能为空")  
51 - private Long skuId;  
52 -  
53 - /**  
54 - * SKU 数量  
55 - */  
56 - @NotNull(message = "商品 SKU 数量不能为空")  
57 - @Min(value = 0L, message = "商品 SKU 数量必须大于等于 0") // 可传递 0 数量,用于购物车未选中的情况  
58 - private Integer count;  
59 -  
60 - }  
61 -  
62 -}  
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/price/dto/PriceCalculateRespDTO.java deleted
1 -package cn.iocoder.yudao.module.promotion.api.price.dto;  
2 -  
3 -import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;  
4 -import lombok.Data;  
5 -  
6 -import java.util.List;  
7 -  
8 -/**  
9 - * 价格计算 Response DTO  
10 - *  
11 - * 整体设计,参考 taobao 的技术文档:  
12 - * 1. <a href="https://developer.alibaba.com/docs/doc.htm?treeId=1&articleId=1029&docType=1">订单管理</a>  
13 - * 2. <a href="https://open.taobao.com/docV3.htm?docId=108471&docType=1">常用订单金额说明</a>  
14 - *  
15 - * 举个例子:<a href="https://img.alicdn.com/top/i1/LB1mALAi4HI8KJjy1zbXXaxdpXa">订单图</a>  
16 - * 输入:  
17 - * 1. 订单实付: trade.payment = 198.00;订单邮费:5 元;  
18 - * 2. 商品级优惠 圣诞价: 省 29.00 元 和 圣诞价:省 150.00 元; 订单级优惠,圣诞 2:省 5.00 元;  
19 - * 分摊:  
20 - * 1. 商品 1:原价 108 元,优惠 29 元,子订单实付 79 元,分摊主订单优惠 1.99 元;  
21 - * 2. 商品 2:原价 269 元,优惠 150 元,子订单实付 119 元,分摊主订单优惠 3.01 元;  
22 - *  
23 - * @author 芋道源码  
24 - */  
25 -@Data  
26 -@Deprecated  
27 -public class PriceCalculateRespDTO {  
28 -  
29 - /**  
30 - * 订单  
31 - */  
32 - private Order order;  
33 -  
34 - /**  
35 - * 营销活动数组  
36 - *  
37 - * 只对应 {@link Order#items} 商品匹配的活动  
38 - */  
39 - private List<Promotion> promotions;  
40 -  
41 - // TODO @芋艿:需要改造下,主要是价格字段  
42 - /**  
43 - * 订单  
44 - */  
45 - @Data  
46 - public static class Order {  
47 -  
48 - /**  
49 - * 商品原价(总),单位:分  
50 - *  
51 - * 基于 {@link OrderItem#getOriginalPrice()} 求和  
52 - *  
53 - * 对应 taobao 的 trade.total_fee 字段  
54 - */  
55 - private Integer totalPrice;  
56 - /**  
57 - * 订单优惠(总),单位:分  
58 - *  
59 - * 订单级优惠:对主订单的优惠,常见如:订单满 200 元减 10 元;订单满 80 包邮。  
60 - *  
61 - * 对应 taobao 的 order.discount_fee 字段  
62 - */  
63 - private Integer discountPrice;  
64 - /**  
65 - * 优惠劵减免金额(总),单位:分  
66 - *  
67 - * 对应 taobao 的 trade.coupon_fee 字段  
68 - */  
69 - private Integer couponPrice;  
70 - /**  
71 - * 积分减免金额(总),单位:分  
72 - *  
73 - * 对应 taobao 的 trade.point_fee 字段  
74 - */  
75 - private Integer pointPrice;  
76 - /**  
77 - * 运费金额,单位:分  
78 - */  
79 - private Integer deliveryPrice;  
80 - /**  
81 - * 最终购买金额(总),单位:分  
82 - *  
83 - * = {@link OrderItem#getPayPrice()} 求和  
84 - * - {@link #couponPrice}  
85 - * - {@link #pointPrice}  
86 - * + {@link #deliveryPrice}  
87 - * - {@link #discountPrice}  
88 - */  
89 - private Integer payPrice;  
90 - /**  
91 - * 商品 SKU 数组  
92 - */  
93 - private List<OrderItem> items;  
94 -  
95 - // ========== 营销基本信息 ==========  
96 - /**  
97 - * 优惠劵编号  
98 - */  
99 - private Long couponId;  
100 -  
101 - }  
102 -  
103 - /**  
104 - * 订单商品 SKU  
105 - */  
106 - @Data  
107 - public static class OrderItem {  
108 -  
109 - /**  
110 - * SPU 编号  
111 - */  
112 - private Long spuId;  
113 - /**  
114 - * SKU 编号  
115 - */  
116 - private Long skuId;  
117 - /**  
118 - * 购买数量  
119 - */  
120 - private Integer count;  
121 -  
122 - /**  
123 - * 商品原价(总),单位:分  
124 - *  
125 - * = {@link #originalUnitPrice} * {@link #getCount()}  
126 - */  
127 - private Integer originalPrice;  
128 - /**  
129 - * 商品原价(单),单位:分  
130 - *  
131 - * 对应 ProductSkuDO 的 price 字段  
132 - * 对应 taobao 的 order.price 字段  
133 - */  
134 - private Integer originalUnitPrice;  
135 - /**  
136 - * 商品优惠(总),单位:分  
137 - *  
138 - * 商品级优惠:对单个商品的,常见如:商品原价的 8 折;商品原价的减 50 元  
139 - *  
140 - * 对应 taobao 的 order.discount_fee 字段  
141 - */  
142 - private Integer discountPrice;  
143 - /**  
144 - * 子订单实付金额,不算主订单分摊金额,单位:分  
145 - *  
146 - * = {@link #originalPrice}  
147 - * - {@link #discountPrice}  
148 - *  
149 - * 对应 taobao 的 order.payment 字段  
150 - */  
151 - private Integer payPrice;  
152 -  
153 - /**  
154 - * 子订单分摊金额(总),单位:分  
155 - * 需要分摊 {@link Order#discountPrice}、{@link Order#couponPrice}、{@link Order#pointPrice}  
156 - *  
157 - * 对应 taobao 的 order.part_mjz_discount 字段  
158 - * 淘宝说明:子订单分摊优惠基础逻辑:一般正常优惠券和满减优惠按照子订单的金额进行分摊,特殊情况如果优惠券是指定商品使用的,只会分摊到对应商品子订单上不分摊。  
159 - */  
160 - private Integer orderPartPrice;  
161 - /**  
162 - * 分摊后子订单实付金额(总),单位:分  
163 - *  
164 - * = {@link #payPrice}  
165 - * - {@link #orderPartPrice}  
166 - *  
167 - * 对应 taobao 的 divide_order_fee 字段  
168 - */  
169 - private Integer orderDividePrice;  
170 -  
171 - }  
172 -  
173 - /**  
174 - * 营销明细  
175 - */  
176 - @Data  
177 - @Deprecated  
178 - public static class Promotion {  
179 -  
180 - /**  
181 - * 营销编号  
182 - *  
183 - * 例如说:营销活动的编号、优惠劵的编号  
184 - */  
185 - private Long id;  
186 - /**  
187 - * 营销名字  
188 - */  
189 - private String name;  
190 - /**  
191 - * 营销类型  
192 - *  
193 - * 枚举 {@link PromotionTypeEnum}  
194 - */  
195 - private Integer type;  
196 - /**  
197 - * 营销级别  
198 - *  
199 - * 枚举 @link PromotionLevelEnum} TODO PromotionLevelEnum 没有这个枚举类  
200 - */  
201 - private Integer level;  
202 - /**  
203 - * 计算时的原价(总),单位:分  
204 - */  
205 - private Integer totalPrice;  
206 - /**  
207 - * 计算时的优惠(总),单位:分  
208 - */  
209 - private Integer discountPrice;  
210 - /**  
211 - * 匹配的商品 SKU 数组  
212 - */  
213 - private List<PromotionItem> items;  
214 -  
215 - // ========== 匹配情况 ==========  
216 -  
217 - /**  
218 - * 是否满足优惠条件  
219 - */  
220 - private Boolean match;  
221 - /**  
222 - * 满足条件的提示  
223 - *  
224 - * 如果 {@link #match} = true 满足,则提示“圣诞价:省 150.00 元”  
225 - * 如果 {@link #match} = false 不满足,则提示“购满 85 元,可减 40 元”  
226 - */  
227 - private String description;  
228 -  
229 - }  
230 -  
231 - /**  
232 - * 营销匹配的商品 SKU  
233 - */  
234 - @Data  
235 - public static class PromotionItem {  
236 -  
237 - /**  
238 - * 商品 SKU 编号  
239 - */  
240 - private Long skuId;  
241 - /**  
242 - * 计算时的原价(总),单位:分  
243 - */  
244 - private Integer originalPrice;  
245 - /**  
246 - * 计算时的优惠(总),单位:分  
247 - */  
248 - private Integer discountPrice;  
249 -  
250 - }  
251 -  
252 -}  
yudao-module-mall/yudao-module-promotion-biz/pom.xml
@@ -14,8 +14,8 @@ @@ -14,8 +14,8 @@
14 <name>${project.artifactId}</name> 14 <name>${project.artifactId}</name>
15 15
16 <description> 16 <description>
17 - market模块,主要实现营销相关功能  
18 - 例如:营销活动、banner广告、优惠券、优惠码等功能。 17 + promotion 模块,主要实现营销相关功能
  18 + 例如:营销活动、banner 广告、优惠券、优惠码等功能。
19 </description> 19 </description>
20 20
21 <dependencies> 21 <dependencies>
@@ -49,10 +49,6 @@ @@ -49,10 +49,6 @@
49 <groupId>cn.iocoder.boot</groupId> 49 <groupId>cn.iocoder.boot</groupId>
50 <artifactId>yudao-spring-boot-starter-biz-tenant</artifactId> 50 <artifactId>yudao-spring-boot-starter-biz-tenant</artifactId>
51 </dependency> 51 </dependency>
52 - <dependency>  
53 - <groupId>cn.iocoder.boot</groupId>  
54 - <artifactId>yudao-spring-boot-starter-biz-weixin</artifactId>  
55 - </dependency>  
56 52
57 <!-- Web 相关 --> 53 <!-- Web 相关 -->
58 <dependency> 54 <dependency>
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/bargain/BargainActivityApiImpl.java
@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.promotion.api.bargain; @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.promotion.api.bargain;
2 2
3 import cn.iocoder.yudao.module.promotion.service.bargain.BargainActivityService; 3 import cn.iocoder.yudao.module.promotion.service.bargain.BargainActivityService;
4 import org.springframework.stereotype.Service; 4 import org.springframework.stereotype.Service;
  5 +import org.springframework.validation.annotation.Validated;
5 6
6 import javax.annotation.Resource; 7 import javax.annotation.Resource;
7 8
@@ -11,6 +12,7 @@ import javax.annotation.Resource; @@ -11,6 +12,7 @@ import javax.annotation.Resource;
11 * @author HUIHUI 12 * @author HUIHUI
12 */ 13 */
13 @Service 14 @Service
  15 +@Validated
14 public class BargainActivityApiImpl implements BargainActivityApi { 16 public class BargainActivityApiImpl implements BargainActivityApi {
15 17
16 @Resource 18 @Resource
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/bargain/BargainRecordApiImpl.java
@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.promotion.api.bargain; @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.promotion.api.bargain;
3 import cn.iocoder.yudao.module.promotion.api.bargain.dto.BargainValidateJoinRespDTO; 3 import cn.iocoder.yudao.module.promotion.api.bargain.dto.BargainValidateJoinRespDTO;
4 import cn.iocoder.yudao.module.promotion.service.bargain.BargainRecordService; 4 import cn.iocoder.yudao.module.promotion.service.bargain.BargainRecordService;
5 import org.springframework.stereotype.Service; 5 import org.springframework.stereotype.Service;
  6 +import org.springframework.validation.annotation.Validated;
6 7
7 import javax.annotation.Resource; 8 import javax.annotation.Resource;
8 9
@@ -12,6 +13,7 @@ import javax.annotation.Resource; @@ -12,6 +13,7 @@ import javax.annotation.Resource;
12 * @author HUIHUI 13 * @author HUIHUI
13 */ 14 */
14 @Service 15 @Service
  16 +@Validated
15 public class BargainRecordApiImpl implements BargainRecordApi { 17 public class BargainRecordApiImpl implements BargainRecordApi {
16 18
17 @Resource 19 @Resource
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/CombinationActivityApiImpl.java deleted
1 -package cn.iocoder.yudao.module.promotion.api.combination;  
2 -  
3 -import org.springframework.stereotype.Service;  
4 -  
5 -/**  
6 - * 拼团活动 Api 接口实现类  
7 - *  
8 - * @author HUIHUI  
9 - */  
10 -@Service  
11 -public class CombinationActivityApiImpl implements CombinationActivityApi {  
12 -  
13 -}  
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/CombinationRecordApiImpl.java
@@ -8,6 +8,7 @@ import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationR @@ -8,6 +8,7 @@ import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationR
8 import cn.iocoder.yudao.module.promotion.enums.combination.CombinationRecordStatusEnum; 8 import cn.iocoder.yudao.module.promotion.enums.combination.CombinationRecordStatusEnum;
9 import cn.iocoder.yudao.module.promotion.service.combination.CombinationRecordService; 9 import cn.iocoder.yudao.module.promotion.service.combination.CombinationRecordService;
10 import org.springframework.stereotype.Service; 10 import org.springframework.stereotype.Service;
  11 +import org.springframework.validation.annotation.Validated;
11 12
12 import javax.annotation.Resource; 13 import javax.annotation.Resource;
13 14
@@ -20,6 +21,7 @@ import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.COMBINA @@ -20,6 +21,7 @@ import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.COMBINA
20 * @author HUIHUI 21 * @author HUIHUI
21 */ 22 */
22 @Service 23 @Service
  24 +@Validated
23 public class CombinationRecordApiImpl implements CombinationRecordApi { 25 public class CombinationRecordApiImpl implements CombinationRecordApi {
24 26
25 @Resource 27 @Resource
@@ -45,11 +47,6 @@ public class CombinationRecordApiImpl implements CombinationRecordApi { @@ -45,11 +47,6 @@ public class CombinationRecordApiImpl implements CombinationRecordApi {
45 } 47 }
46 48
47 @Override 49 @Override
48 - public void updateRecordStatusToFailed(Long userId, Long orderId) {  
49 - recordService.updateCombinationRecordStatusByUserIdAndOrderId(CombinationRecordStatusEnum.FAILED.getStatus(), userId, orderId);  
50 - }  
51 -  
52 - @Override  
53 public CombinationValidateJoinRespDTO validateJoinCombination(Long userId, Long activityId, Long headId, Long skuId, Integer count) { 50 public CombinationValidateJoinRespDTO validateJoinCombination(Long userId, Long activityId, Long headId, Long skuId, Integer count) {
54 return recordService.validateJoinCombination(userId, activityId, headId, skuId, count); 51 return recordService.validateJoinCombination(userId, activityId, headId, skuId, count);
55 } 52 }
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApiImpl.java
@@ -8,6 +8,7 @@ import cn.iocoder.yudao.module.promotion.convert.coupon.CouponConvert; @@ -8,6 +8,7 @@ import cn.iocoder.yudao.module.promotion.convert.coupon.CouponConvert;
8 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO; 8 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;
9 import cn.iocoder.yudao.module.promotion.service.coupon.CouponService; 9 import cn.iocoder.yudao.module.promotion.service.coupon.CouponService;
10 import org.springframework.stereotype.Service; 10 import org.springframework.stereotype.Service;
  11 +import org.springframework.validation.annotation.Validated;
11 12
12 import javax.annotation.Resource; 13 import javax.annotation.Resource;
13 14
@@ -17,6 +18,7 @@ import javax.annotation.Resource; @@ -17,6 +18,7 @@ import javax.annotation.Resource;
17 * @author 芋道源码 18 * @author 芋道源码
18 */ 19 */
19 @Service 20 @Service
  21 +@Validated
20 public class CouponApiImpl implements CouponApi { 22 public class CouponApiImpl implements CouponApi {
21 23
22 @Resource 24 @Resource
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/discount/DiscountActivityApiImpl.java
@@ -4,6 +4,7 @@ import cn.iocoder.yudao.module.promotion.api.discount.dto.DiscountProductRespDTO @@ -4,6 +4,7 @@ import cn.iocoder.yudao.module.promotion.api.discount.dto.DiscountProductRespDTO
4 import cn.iocoder.yudao.module.promotion.convert.discount.DiscountActivityConvert; 4 import cn.iocoder.yudao.module.promotion.convert.discount.DiscountActivityConvert;
5 import cn.iocoder.yudao.module.promotion.service.discount.DiscountActivityService; 5 import cn.iocoder.yudao.module.promotion.service.discount.DiscountActivityService;
6 import org.springframework.stereotype.Service; 6 import org.springframework.stereotype.Service;
  7 +import org.springframework.validation.annotation.Validated;
7 8
8 import javax.annotation.Resource; 9 import javax.annotation.Resource;
9 import java.util.Collection; 10 import java.util.Collection;
@@ -15,6 +16,7 @@ import java.util.List; @@ -15,6 +16,7 @@ import java.util.List;
15 * @author 芋道源码 16 * @author 芋道源码
16 */ 17 */
17 @Service 18 @Service
  19 +@Validated
18 public class DiscountActivityApiImpl implements DiscountActivityApi { 20 public class DiscountActivityApiImpl implements DiscountActivityApi {
19 21
20 @Resource 22 @Resource
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/price/PriceApiImpl.java deleted
1 -package cn.iocoder.yudao.module.promotion.api.price;  
2 -  
3 -import cn.iocoder.yudao.module.promotion.api.price.dto.PriceCalculateReqDTO;  
4 -import cn.iocoder.yudao.module.promotion.api.price.dto.PriceCalculateRespDTO;  
5 -import cn.iocoder.yudao.module.promotion.service.price.PriceService;  
6 -import org.springframework.stereotype.Service;  
7 -  
8 -import javax.annotation.Resource;  
9 -  
10 -/**  
11 - * 价格 API 实现类  
12 - *  
13 - * @author 芋道源码  
14 - */  
15 -@Service  
16 -public class PriceApiImpl implements PriceApi {  
17 -  
18 - @Resource  
19 - private PriceService priceService;  
20 -  
21 - @Override  
22 - public PriceCalculateRespDTO calculatePrice(PriceCalculateReqDTO calculateReqDTO) {  
23 - //return priceService.calculatePrice(calculateReqDTO); TODO 没有 calculatePrice 这个方法  
24 -  
25 - return null;  
26 - }  
27 -  
28 -}  
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/reward/RewardActivityApiImpl.java
@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.promotion.api.reward; @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.promotion.api.reward;
3 import cn.iocoder.yudao.module.promotion.api.reward.dto.RewardActivityMatchRespDTO; 3 import cn.iocoder.yudao.module.promotion.api.reward.dto.RewardActivityMatchRespDTO;
4 import cn.iocoder.yudao.module.promotion.service.reward.RewardActivityService; 4 import cn.iocoder.yudao.module.promotion.service.reward.RewardActivityService;
5 import org.springframework.stereotype.Service; 5 import org.springframework.stereotype.Service;
  6 +import org.springframework.validation.annotation.Validated;
6 7
7 import javax.annotation.Resource; 8 import javax.annotation.Resource;
8 import java.util.Collection; 9 import java.util.Collection;
@@ -14,6 +15,7 @@ import java.util.List; @@ -14,6 +15,7 @@ import java.util.List;
14 * @author 芋道源码 15 * @author 芋道源码
15 */ 16 */
16 @Service 17 @Service
  18 +@Validated
17 public class RewardActivityApiImpl implements RewardActivityApi { 19 public class RewardActivityApiImpl implements RewardActivityApi {
18 20
19 @Resource 21 @Resource
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/seckill/SeckillActivityApiImpl.java
@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.promotion.api.seckill; @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.promotion.api.seckill;
3 import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillValidateJoinRespDTO; 3 import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillValidateJoinRespDTO;
4 import cn.iocoder.yudao.module.promotion.service.seckill.SeckillActivityService; 4 import cn.iocoder.yudao.module.promotion.service.seckill.SeckillActivityService;
5 import org.springframework.stereotype.Service; 5 import org.springframework.stereotype.Service;
  6 +import org.springframework.validation.annotation.Validated;
6 7
7 import javax.annotation.Resource; 8 import javax.annotation.Resource;
8 9
@@ -12,6 +13,7 @@ import javax.annotation.Resource; @@ -12,6 +13,7 @@ import javax.annotation.Resource;
12 * @author HUIHUI 13 * @author HUIHUI
13 */ 14 */
14 @Service 15 @Service
  16 +@Validated
15 public class SeckillActivityApiImpl implements SeckillActivityApi { 17 public class SeckillActivityApiImpl implements SeckillActivityApi {
16 18
17 @Resource 19 @Resource
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/banner/vo/BannerRespVO.java
@@ -4,6 +4,8 @@ import io.swagger.v3.oas.annotations.media.Schema; @@ -4,6 +4,8 @@ import io.swagger.v3.oas.annotations.media.Schema;
4 import lombok.Data; 4 import lombok.Data;
5 import lombok.ToString; 5 import lombok.ToString;
6 6
  7 +import java.time.LocalDateTime;
  8 +
7 @Schema(description = "管理后台 - Banner Response VO") 9 @Schema(description = "管理后台 - Banner Response VO")
8 @Data 10 @Data
9 @ToString(callSuper = true) 11 @ToString(callSuper = true)
@@ -12,4 +14,7 @@ public class BannerRespVO extends BannerBaseVO { @@ -12,4 +14,7 @@ public class BannerRespVO extends BannerBaseVO {
12 @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED) 14 @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED)
13 private Long id; 15 private Long id;
14 16
  17 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "2022-07-01 23:59:59")
  18 + private LocalDateTime createTime;
  19 +
15 } 20 }
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/AppCouponTemplateController.java
@@ -96,13 +96,13 @@ public class AppCouponTemplateController { @@ -96,13 +96,13 @@ public class AppCouponTemplateController {
96 */ 96 */
97 private Long getProductScopeValue(Integer productScope, Long spuId) { 97 private Long getProductScopeValue(Integer productScope, Long spuId) {
98 // 通用券:没有商品范围 98 // 通用券:没有商品范围
99 - if (productScope == null || ObjectUtils.equalsAny(productScope, PromotionProductScopeEnum.ALL.getScope(), null)) { 99 + if (ObjectUtils.equalsAny(productScope, PromotionProductScopeEnum.ALL.getScope(), null)) {
100 return null; 100 return null;
101 } 101 }
102 // 品类券:查询商品的品类编号 102 // 品类券:查询商品的品类编号
103 if (Objects.equals(productScope, PromotionProductScopeEnum.CATEGORY.getScope()) && spuId != null) { 103 if (Objects.equals(productScope, PromotionProductScopeEnum.CATEGORY.getScope()) && spuId != null) {
104 - return Optional.ofNullable(productSpuApi.getSpu(spuId))  
105 - .map(ProductSpuRespDTO::getCategoryId).orElse(null); 104 + ProductSpuRespDTO spu = productSpuApi.getSpu(spuId);
  105 + return spu != null ? spu.getCategoryId() : null;
106 } 106 }
107 // 商品卷:直接返回 107 // 商品卷:直接返回
108 return spuId; 108 return spuId;