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 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 260 | [MySQL](https://www.mysql.com/cn/) | 鏁版嵁搴撴湇鍔″櫒 | 5.7 / 8.0+ | |
261 261 | [Druid](https://github.com/alibaba/druid) | JDBC 杩炴帴姹犮佺洃鎺х粍浠 | 1.2.19 | [鏂囨。](http://www.iocoder.cn/Spring-Boot/datasource-pool/?yudao) |
262 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 13 <!-- Server 主项目 -->
14 14 <module>yudao-server</module>
15 15 <!-- 各种 module 拓展 -->
16   - <module>yudao-module-member</module>
17 16 <module>yudao-module-system</module>
18 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 20 <!-- <module>yudao-module-report</module>-->
21 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 26 </modules>
27 27  
28 28 <name>${project.artifactId}</name>
... ... @@ -30,7 +30,7 @@
30 30 <url>https://github.com/YunaiV/ruoyi-vue-pro</url>
31 31  
32 32 <properties>
33   - <revision>1.8.2-snapshot</revision>
  33 + <revision>1.8.3-snapshot</revision>
34 34 <!-- Maven 相关 -->
35 35 <java.version>1.8</java.version>
36 36 <maven.compiler.source>${java.version}</maven.compiler.source>
... ... @@ -40,7 +40,7 @@
40 40 <flatten-maven-plugin.version>1.5.0</flatten-maven-plugin.version>
41 41 <!-- 看看咋放到 bom 里 -->
42 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 44 <mapstruct.version>1.5.5.Final</mapstruct.version>
45 45 <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
46 46 </properties>
... ...
yudao-dependencies/pom.xml
... ... @@ -14,28 +14,30 @@
14 14 <url>https://github.com/YunaiV/ruoyi-vue-pro</url>
15 15  
16 16 <properties>
17   - <revision>1.8.2-snapshot</revision>
  17 + <revision>1.8.3-snapshot</revision>
18 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 21 <!-- Web 相关 -->
22 22 <springdoc.version>1.6.15</springdoc.version>
23 23 <knife4j.version>4.3.0</knife4j.version>
24 24 <servlet.versoin>2.5</servlet.versoin>
25 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 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 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 37 <resilience4j.version>1.7.1</resilience4j.version>
36 38 <!-- 监控相关 -->
37 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 41 <opentracing.version>0.33.0</opentracing.version>
40 42 <!-- Test 测试相关 -->
41 43 <podam.version>7.2.11.RELEASE</podam.version>
... ... @@ -44,8 +46,8 @@
44 46 <!-- Bpm 工作流相关 -->
45 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 51 <lombok.version>1.18.30</lombok.version>
50 52 <mapstruct.version>1.5.5.Final</mapstruct.version>
51 53 <hutool.version>5.8.22</hutool.version>
... ... @@ -53,10 +55,10 @@
53 55 <velocity.version>2.3</velocity.version>
54 56 <screw.version>1.0.5</screw.version>
55 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 59 <guice.version>5.1.0</guice.version>
58 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 62 <jsch.version>0.1.55</jsch.version>
61 63 <tika-core.version>2.7.0</tika-core.version>
62 64 <ip2region.version>2.7.0</ip2region.version>
... ... @@ -67,8 +69,8 @@
67 69 <minio.version>8.5.6</minio.version>
68 70 <aliyun-java-sdk-core.version>4.6.4</aliyun-java-sdk-core.version>
69 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 74 <jimureport.version>1.6.1</jimureport.version>
73 75 <xercesImpl.version>2.12.2</xercesImpl.version>
74 76 <weixin-java.version>4.5.0</weixin-java.version>
... ... @@ -98,11 +100,6 @@
98 100 </dependency>
99 101 <dependency>
100 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 103 <artifactId>yudao-spring-boot-starter-biz-dict</artifactId>
107 104 <version>${revision}</version>
108 105 </dependency>
... ... @@ -260,6 +257,12 @@
260 257 <version>${revision}</version>
261 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 267 <dependency>
265 268 <groupId>cn.iocoder.boot</groupId>
... ...
yudao-example/yudao-sso-demo-by-code/pom.xml
... ... @@ -21,7 +21,7 @@
21 21 <maven.compiler.target>8</maven.compiler.target>
22 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 25 </properties>
26 26  
27 27 <dependencyManagement>
... ...
yudao-example/yudao-sso-demo-by-password/pom.xml
... ... @@ -21,7 +21,7 @@
21 21 <maven.compiler.target>8</maven.compiler.target>
22 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 25 </properties>
26 26  
27 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 4 import lombok.Data;
5 5 import lombok.NoArgsConstructor;
6 6  
  7 +import java.io.Serializable;
  8 +
7 9 /**
8 10 * Key Value 的键值对
9 11 *
... ... @@ -12,7 +14,7 @@ import lombok.NoArgsConstructor;
12 14 @Data
13 15 @NoArgsConstructor
14 16 @AllArgsConstructor
15   -public class KeyValue<K, V> {
  17 +public class KeyValue<K, V> implements Serializable {
16 18  
17 19 private K key;
18 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 8 import java.util.*;
9 9 import java.util.function.*;
10 10 import java.util.stream.Collectors;
  11 +import java.util.stream.Stream;
11 12  
12 13 import static java.util.Arrays.asList;
13 14  
... ... @@ -271,4 +272,20 @@ public class CollectionUtils {
271 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 21 @ConditionalOnProperty(prefix = "yudao.error-code", value = "enable", matchIfMissing = true) // 允许使用 yudao.error-code.enable=false 禁用访问日志
22 22 @EnableConfigurationProperties(ErrorCodeProperties.class)
23 23 @EnableScheduling // 开启调度任务的功能,因为 ErrorCodeRemoteLoader 通过定时刷新错误码
24   -public class YudaoErrorCodeConfiguration {
  24 +public class YudaoErrorCodeAutoConfiguration {
25 25  
26 26 @Bean
27 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 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 48 <dependency>
49 49 <groupId>cn.iocoder.boot</groupId>
50 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 67 </dependency>
52 68  
53 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 6 import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnoreAspect;
7 7 import cn.iocoder.yudao.framework.tenant.core.db.TenantDatabaseInterceptor;
8 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 12 import cn.iocoder.yudao.framework.tenant.core.redis.TenantRedisCacheManager;
11 13 import cn.iocoder.yudao.framework.tenant.core.security.TenantSecurityWebFilter;
12 14 import cn.iocoder.yudao.framework.tenant.core.service.TenantFrameworkService;
... ... @@ -18,6 +20,7 @@ import cn.iocoder.yudao.module.system.api.tenant.TenantApi;
18 20 import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
19 21 import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
20 22 import org.springframework.boot.autoconfigure.AutoConfiguration;
  23 +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
21 24 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
22 25 import org.springframework.boot.context.properties.EnableConfigurationProperties;
23 26 import org.springframework.boot.web.servlet.FilterRegistrationBean;
... ... @@ -92,6 +95,18 @@ public class YudaoTenantAutoConfiguration {
92 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 110 // ========== Job ==========
96 111  
97 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 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 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 6 import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
7 7  
8 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 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 12 <packaging>jar</packaging>
13 13  
14 14 <name>${project.artifactId}</name>
15   - <description>消息队列,基于 Redis Pub/Sub 实现广播消费,基于 Stream 实现集群消费</description>
  15 + <description>消息队列,支持 Redis、RocketMQ、RabbitMQ、Kafka 四种</description>
16 16 <url>https://github.com/YunaiV/ruoyi-vue-pro</url>
17 17  
18 18 <dependencies>
... ... @@ -21,6 +21,23 @@
21 21 <groupId>cn.iocoder.boot</groupId>
22 22 <artifactId>yudao-spring-boot-starter-redis</artifactId>
23 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 41 </dependencies>
25 42  
26 43 -</project>
  44 +</project>
27 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 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 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 3 import cn.hutool.core.map.MapUtil;
4 4 import cn.hutool.core.util.StrUtil;
5 5 import cn.hutool.system.SystemUtil;
6 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 12 import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration;
13 13 import lombok.extern.slf4j.Slf4j;
14 14 import org.redisson.api.RedissonClient;
15 15 import org.springframework.beans.factory.annotation.Value;
16 16 import org.springframework.boot.autoconfigure.AutoConfiguration;
17 17 import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
18   -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
19 18 import org.springframework.context.annotation.Bean;
20 19 import org.springframework.data.redis.connection.RedisServerCommands;
21 20 import org.springframework.data.redis.connection.stream.Consumer;
... ... @@ -27,7 +26,6 @@ import org.springframework.data.redis.core.RedisTemplate;
27 26 import org.springframework.data.redis.core.StringRedisTemplate;
28 27 import org.springframework.data.redis.listener.ChannelTopic;
29 28 import org.springframework.data.redis.listener.RedisMessageListenerContainer;
30   -import org.springframework.data.redis.stream.DefaultStreamMessageListenerContainerX;
31 29 import org.springframework.data.redis.stream.StreamMessageListenerContainer;
32 30 import org.springframework.scheduling.annotation.EnableScheduling;
33 31  
... ... @@ -42,7 +40,7 @@ import java.util.Properties;
42 40 @Slf4j
43 41 @EnableScheduling // 启用定时任务,用于 RedisPendingMessageResendJob 重发消息
44 42 @AutoConfiguration(after = YudaoRedisAutoConfiguration.class)
45   -public class YudaoMQAutoConfiguration {
  43 +public class YudaoRedisMQAutoConfiguration {
46 44  
47 45 @Bean
48 46 public RedisMQTemplate redisMQTemplate(StringRedisTemplate redisTemplate,
... ... @@ -59,10 +57,9 @@ public class YudaoMQAutoConfiguration {
59 57 * 创建 Redis Pub/Sub 广播消费的容器
60 58 */
61 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 61 public RedisMessageListenerContainer redisMessageListenerContainer(
65   - RedisMQTemplate redisMQTemplate, List<AbstractChannelMessageListener<?>> listeners) {
  62 + RedisMQTemplate redisMQTemplate, List<AbstractRedisChannelMessageListener<?>> listeners) {
66 63 // 创建 RedisMessageListenerContainer 对象
67 64 RedisMessageListenerContainer container = new RedisMessageListenerContainer();
68 65 // 设置 RedisConnection 工厂。
... ... @@ -81,9 +78,8 @@ public class YudaoMQAutoConfiguration {
81 78 * 创建 Redis Stream 重新消费的任务
82 79 */
83 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 83 RedisMQTemplate redisTemplate,
88 84 @Value("${spring.application.name}") String groupName,
89 85 RedissonClient redissonClient) {
... ... @@ -92,14 +88,13 @@ public class YudaoMQAutoConfiguration {
92 88  
93 89 /**
94 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 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 96 public StreamMessageListenerContainer<String, ObjectRecord<String, String>> redisStreamMessageListenerContainer(
102   - RedisMQTemplate redisMQTemplate, List<AbstractStreamMessageListener<?>> listeners) {
  97 + RedisMQTemplate redisMQTemplate, List<AbstractRedisStreamMessageListener<?>> listeners) {
103 98 RedisTemplate<String, ?> redisTemplate = redisMQTemplate.getRedisTemplate();
104 99 checkRedisVersion(redisTemplate);
105 100 // 第一步,创建 StreamMessageListenerContainer 容器
... ... @@ -111,8 +106,7 @@ public class YudaoMQAutoConfiguration {
111 106 .build();
112 107 // 创建 container 对象
113 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 111 // 第二步,注册监听器,消费对应的 Stream 主题
118 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 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 8 import lombok.AllArgsConstructor;
9 9 import lombok.Getter;
10 10 import org.springframework.data.redis.connection.stream.RecordId;
... ... @@ -35,7 +35,7 @@ public class RedisMQTemplate {
35 35 *
36 36 * @param message 消息
37 37 */
38   - public <T extends AbstractChannelMessage> void send(T message) {
  38 + public <T extends AbstractRedisChannelMessage> void send(T message) {
39 39 try {
40 40 sendMessageBefore(message);
41 41 // 发送消息
... ... @@ -51,7 +51,7 @@ public class RedisMQTemplate {
51 51 * @param message 消息
52 52 * @return 消息记录的编号对象
53 53 */
54   - public <T extends AbstractStreamMessage> RecordId send(T message) {
  54 + public <T extends AbstractRedisStreamMessage> RecordId send(T message) {
55 55 try {
56 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 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 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 6 import lombok.AllArgsConstructor;
7 7 import lombok.extern.slf4j.Slf4j;
8 8 import org.redisson.api.RLock;
... ... @@ -33,7 +33,7 @@ public class RedisPendingMessageResendJob {
33 33 */
34 34 private static final int EXPIRE_TIME = 5 * 60;
35 35  
36   - private final List<AbstractStreamMessageListener<?>> listeners;
  36 + private final List<AbstractRedisStreamMessageListener<?>> listeners;
37 37 private final RedisMQTemplate redisTemplate;
38 38 private final String groupName;
39 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 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 4 import com.fasterxml.jackson.annotation.JsonIgnore;
5 5  
6 6 /**
... ... @@ -8,14 +8,16 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
8 8 *
9 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 16 * @return Channel
17 17 */
18 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 3 import cn.hutool.core.util.TypeUtil;
4 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 8 import lombok.Setter;
9 9 import lombok.SneakyThrows;
10 10 import org.springframework.data.redis.connection.Message;
... ... @@ -20,7 +20,7 @@ import java.util.List;
20 20 *
21 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 37 private RedisMQTemplate redisMQTemplate;
38 38  
39 39 @SneakyThrows
40   - protected AbstractChannelMessageListener() {
  40 + protected AbstractRedisChannelMessageListener() {
41 41 this.messageType = getMessageClass();
42 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 4 import com.fasterxml.jackson.annotation.JsonIgnore;
5 5  
6 6 /**
... ... @@ -8,14 +8,16 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
8 8 *
9 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 16 * @return Channel
17 17 */
18 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 3 import cn.hutool.core.util.TypeUtil;
4 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 8 import lombok.Getter;
9 9 import lombok.Setter;
10 10 import lombok.SneakyThrows;
... ... @@ -22,7 +22,7 @@ import java.util.List;
22 22 *
23 23 * @author 芋道源码
24 24 */
25   -public abstract class AbstractStreamMessageListener<T extends AbstractStreamMessage>
  25 +public abstract class AbstractRedisStreamMessageListener<T extends AbstractRedisStreamMessage>
26 26 implements StreamListener<String, ObjectRecord<String, String>> {
27 27  
28 28 /**
... ... @@ -48,7 +48,7 @@ public abstract class AbstractStreamMessageListener&lt;T extends AbstractStreamMess
48 48 private RedisMQTemplate redisMQTemplate;
49 49  
50 50 @SneakyThrows
51   - protected AbstractStreamMessageListener() {
  51 + protected AbstractRedisStreamMessageListener() {
52 52 this.messageType = getMessageClass();
53 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 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 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 5 import javax.validation.constraints.NotNull;
6 6 import java.time.LocalDateTime;
7 7  
8   -// TODO @小吉祥:搞个 job,清理 14 天外的访问日志;
9 8 /**
10 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 5 import javax.validation.constraints.NotNull;
6 6 import java.time.LocalDateTime;
7 7  
8   -// TODO @小吉祥:搞个 job,清理 14 天外的异常日志;
9 8 /**
10 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 20 ;
21 21  
22 22 /**
23   - * 结果
  23 + * 操作类型
24 24 */
25 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 30 * 相当于是 通过 APPROVE 的特殊状态
31 31 * 例如:A审批, A 后加签了 B,并且审批通过了任务,但是 B 还未审批,则当前任务状态为“待后加签任务完成”
32 32 */
33   - ADD_SIGN_AFTER(7, "待后加签任务完成"),
  33 + SIGN_AFTER(7, "待后加签任务完成"),
34 34 /**
35 35 * 【加签】源任务未审批,但是向前加签了,所以源任务状态变为“待前加签任务完成”
36 36 * 相当于是 处理中 PROCESS 的特殊状态
37 37 * 例如:A 审批, A 前加签了 B,B 还未审核
38 38 */
39   - ADD_SIGN_BEFORE(8, "待前加签任务完成"),
  39 + SIGN_BEFORE(8, "待前加签任务完成"),
40 40 /**
41 41 * 【加签】后加签任务被创建时的初始状态
42 42 * 相当于是 处理中 PROCESS 的特殊状态
... ... @@ -71,7 +71,7 @@ public enum BpmProcessInstanceResultEnum {
71 71 public static boolean isEndResult(Integer result) {
72 72 return ObjectUtils.equalsAny(result, APPROVE.getResult(), REJECT.getResult(),
73 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 3 import lombok.AllArgsConstructor;
4 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 74 return success(true);
75 75 }
76 76  
77   - @GetMapping("/get-return-list")
  77 + @GetMapping("/return-list")
78 78 @Operation(summary = "获取所有可回退的节点", description = "用于【流程详情】的【回退】按钮")
79 79 @Parameter(name = "taskId", description = "当前任务ID", required = true)
80 80 @PreAuthorize("@ss.hasPermission('bpm:task:update')")
... ... @@ -98,27 +98,28 @@ public class BpmTaskController {
98 98 return success(true);
99 99 }
100 100  
101   - @PutMapping("/add-sign")
  101 + @PutMapping("/create-sign")
102 102 @Operation(summary = "加签", description = "before 前加签,after 后加签")
103 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 106 return success(true);
107 107 }
108 108  
109   - @PutMapping("/sub-sign")
  109 + @DeleteMapping("/delete-sign")
110 110 @Operation(summary = "减签")
111 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 114 return success(true);
115 115 }
116 116  
117   - @GetMapping("/get-children-task-list")
  117 + @GetMapping("children-list")
118 118 @Operation(summary = "获取能被减签的任务")
  119 + @Parameter(name = "parentId", description = "父级任务 ID", required = true)
119 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 import javax.validation.constraints.NotEmpty;
7 7 import java.util.Set;
8 8  
  9 +// TODO @海洋:类名,应该是 create 哈
9 10 @Schema(description = "管理后台 - 加签流程任务的 Request VO")
10 11 @Data
11 12 public class BpmTaskAddSignReqVO {
... ... @@ -26,4 +27,4 @@ public class BpmTaskAddSignReqVO {
26 27 @NotEmpty(message = "加签原因不能为空")
27 28 private String reason;
28 29  
29   -}
30 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 5  
6 6 import javax.validation.constraints.NotEmpty;
7 7  
  8 +// TODO @海洋:类名,应该是 delete 哈
8 9 @Schema(description = "管理后台 - 减签流程任务的 Request VO")
9 10 @Data
10 11 public class BpmTaskSubSignReqVO {
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmTaskConvert.java
1 1 package cn.iocoder.yudao.module.bpm.convert.task;
2 2  
3   -import cn.hutool.core.collection.CollUtil;
4 3 import cn.hutool.core.date.LocalDateTimeUtil;
  4 +import cn.hutool.core.util.ObjectUtil;
5 5 import cn.hutool.core.util.StrUtil;
6 6 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
  7 +import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
7 8 import cn.iocoder.yudao.framework.common.util.date.DateUtils;
8 9 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
9 10 import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*;
... ... @@ -25,6 +26,9 @@ import java.util.Date;
25 26 import java.util.List;
26 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 33 * Bpm 任务 Convert
30 34 *
... ... @@ -167,32 +171,31 @@ public interface BpmTaskConvert {
167 171 default List<BpmTaskSubSignRespVO> convertList(List<BpmTaskExtDO> bpmTaskExtDOList,
168 172 Map<Long, AdminUserRespDTO> userMap,
169 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 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 182 return bpmTaskSubSignRespVO;
182 183 });
183 184 }
184 185  
185 186 /**
186 187 * 转换任务为父子级
  188 + *
187 189 * @param sourceList 原始数据
188 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 195 for (BpmTaskRespVO bpmTaskRespVO : sourceList) {
194 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 21 return selectList(BpmTaskExtDO::getTaskId, taskIds);
22 22 }
23 23  
  24 + // TODO @海:BpmProcessInstanceResultEnum.CAN_SUB_SIGN_STATUS_LIST) 应该作为条件,mapper 不要有业务
24 25 default List<BpmTaskExtDO> selectProcessListByTaskIds(Collection<String> taskIds) {
25 26 return selectList(new LambdaQueryWrapperX<BpmTaskExtDO>()
26 27 .in(BpmTaskExtDO::getTaskId, taskIds)
27 28 .in(BpmTaskExtDO::getResult, BpmProcessInstanceResultEnum.CAN_SUB_SIGN_STATUS_LIST));
28 29 }
29 30  
30   -
31 31 default BpmTaskExtDO selectByTaskId(String taskId) {
32 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 12 * @author yunlongn
13 13 */
14 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 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 163 * @param userId 被加签的用户和任务 ID,加签类型
164 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 171 * @param userId 当前用户ID
172 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 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 53 import javax.validation.Valid;
54 54 import java.time.LocalDateTime;
55 55 import java.util.*;
56   -import java.util.stream.Collectors;
57 56 import java.util.stream.Stream;
58 57  
59 58 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
... ... @@ -240,10 +239,11 @@ public class BpmTaskServiceImpl implements BpmTaskService {
240 239 handleParentTask(task);
241 240 }
242 241  
  242 +
243 243 /**
244 244 * 审批通过存在“后加签”的任务。
245 245 * <p>
246   - * 注意:该任务不能马上完成,需要一个中间状态(ADD_SIGN_AFTER),并激活剩余所有子任务(PROCESS)为可审批处理
  246 + * 注意:该任务不能马上完成,需要一个中间状态(SIGN_AFTER),并激活剩余所有子任务(PROCESS)为可审批处理
247 247 *
248 248 * @param task 当前任务
249 249 * @param reqVO 前端请求参数
... ... @@ -251,7 +251,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
251 251 private void approveAfterSignTask(Task task, BpmTaskApproveReqVO reqVO) {
252 252 // 1. 有向后加签,则该任务状态临时设置为 ADD_SIGN_AFTER 状态
253 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 255 .setReason(reqVO.getReason()).setEndTime(LocalDateTime.now()));
256 256  
257 257 // 2. 激活子任务
... ... @@ -265,7 +265,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
265 265 }
266 266  
267 267 /**
268   - * 处理当前任务的父任务
  268 + * 处理当前任务的父任务,主要处理“加签”的情况
269 269 *
270 270 * @param task 当前任务
271 271 */
... ... @@ -274,76 +274,79 @@ public class BpmTaskServiceImpl implements BpmTaskService {
274 274 if (StrUtil.isBlank(parentTaskId)) {
275 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 317 * @param parentTask 当前审批任务的父任务
315 318 */
316   - private void handleAfterSign(Task parentTask) {
  319 + // TODO @海:这个逻辑,怎么感觉可以是 parentTask 的 parent,再去调用 handleParentTask 方法;可以微信聊下;
  320 + private void handleParentTaskForAfterSign(Task parentTask) {
317 321 String parentTaskId = parentTask.getId();
318   - //1. 更新 parentTask 的任务拓展表为通过,并调用 complete 完成自己
  322 + // 1. 更新 parentTask 的任务拓展表为通过,并调用 complete 完成自己
319 323 BpmTaskExtDO currentTaskExt = taskExtMapper.selectByTaskId(parentTask.getId());
320   - BpmTaskExtDO currentTaskUpdateEntity = new BpmTaskExtDO().setTaskId(parentTask.getId())
  324 + BpmTaskExtDO currentTaskExtUpdateObj = new BpmTaskExtDO().setTaskId(parentTask.getId())
321 325 .setResult(BpmProcessInstanceResultEnum.APPROVE.getResult());
322 326 if (currentTaskExt.getEndTime() == null) {
323 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 332 taskService.complete(parentTaskId);
329 333  
330 334 // 2. 如果有父级,递归查询上级任务是否都已经完成
331 335 if (StrUtil.isEmpty(parentTask.getParentTaskId())) {
332 336 return;
333 337 }
334   - // TODO @海:这块待讨论,脑子略乱;感觉 handleAfterSign 的后半段,和 handleParentTask 有点重叠???
335 338 // 2.1 判断整条链路的任务是否完成
336 339 // 例如从 A 任务加签了一个 B 任务,B 任务又加签了一个 C 任务,C 任务加签了 D 任务
337 340 // 此时,D 任务完成,要一直往上找到祖先任务 A调用 complete 方法完成 A 任务
338 341 boolean allChildrenTaskFinish = true;
339 342 while (StrUtil.isNotBlank(parentTask.getParentTaskId())) {
340 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 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 350 if (allChildrenTaskFinish) {
348 351 allChildrenTaskFinish = currentTaskFinish;
349 352 }
... ... @@ -354,19 +357,19 @@ public class BpmTaskServiceImpl implements BpmTaskService {
354 357  
355 358 // 3 处理非完成状态的任务
356 359 // 3.1 判断当前任务的父任务是否还有子任务
357   - Long childrenTaskCount = getChildrenTaskCount(bpmTaskExtDO.getTaskId());
  360 + Long childrenTaskCount = getChildrenTaskCount(parentTaskExt.getTaskId());
358 361 if (childrenTaskCount > 0) {
359 362 continue;
360 363 }
361 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 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 371 // 3.4 清空 scopeType 字段,用于任务没有子任务时使用该方法,方便任务可以再次被不同的方式加签
369   - parentTask = validateTaskExist(bpmTaskExtDO.getTaskId());
  372 + parentTask = validateTaskExist(parentTaskExt.getTaskId());
370 373 clearTaskScopeTypeAndSave(parentTask);
371 374 }
372 375  
... ... @@ -387,7 +390,6 @@ public class BpmTaskServiceImpl implements BpmTaskService {
387 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 711  
710 712 @Override
711 713 @Transactional(rollbackFor = Exception.class)
712   - public void addSignTask(Long userId, BpmTaskAddSignReqVO reqVO) {
  714 + public void createSignTask(Long userId, BpmTaskAddSignReqVO reqVO) {
713 715 // 1. 获取和校验任务
714 716 TaskEntityImpl taskEntity = validateAddSign(userId, reqVO);
715 717 List<AdminUserRespDTO> userList = adminUserApi.getUserList(reqVO.getUserIdList());
... ... @@ -728,7 +730,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
728 730 // 2.3 更新扩展表状态
729 731 taskExtMapper.updateByTaskId(
730 732 new BpmTaskExtDO().setTaskId(taskEntity.getId())
731   - .setResult(BpmProcessInstanceResultEnum.ADD_SIGN_BEFORE.getResult())
  733 + .setResult(BpmProcessInstanceResultEnum.SIGN_BEFORE.getResult())
732 734 .setReason(reqVO.getReason()));
733 735 }
734 736 // 2.4 记录加签方式,完成任务时需要用到判断
... ... @@ -737,12 +739,12 @@ public class BpmTaskServiceImpl implements BpmTaskService {
737 739 taskService.saveTask(taskEntity);
738 740  
739 741 // 3. 创建加签任务
740   - createAddSignChildrenTasks(convertList(reqVO.getUserIdList(), String::valueOf), taskEntity);
  742 + createSignTask(convertList(reqVO.getUserIdList(), String::valueOf), taskEntity);
741 743  
742 744 // 4. 记录加签 comment,拼接结果为: [当前用户]向前加签/向后加签给了[多个用户],理由为:reason
743 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 748 taskService.addComment(reqVO.getId(), taskEntity.getProcessInstanceId(),
747 749 BpmCommentTypeEnum.ADD_SIGN.getType().toString(), comment);
748 750 }
... ... @@ -786,7 +788,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
786 788 * @param addSingUserIdList 被加签的用户 ID
787 789 * @param taskEntity 被加签的任务
788 790 */
789   - private void createAddSignChildrenTasks(List<String> addSingUserIdList, TaskEntityImpl taskEntity) {
  791 + private void createSignTask(List<String> addSingUserIdList, TaskEntityImpl taskEntity) {
790 792 if (CollUtil.isEmpty(addSingUserIdList)) {
791 793 return;
792 794 }
... ... @@ -795,23 +797,22 @@ public class BpmTaskServiceImpl implements BpmTaskService {
795 797 if (StrUtil.isBlank(addSignId)) {
796 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 807 * @param parentTask 父任务
806 808 * @param assignee 子任务的执行人
807   - * @return
808 809 */
809   - private void createChildrenTask(TaskEntityImpl parentTask, String assignee) {
  810 + private void createSignTask(TaskEntityImpl parentTask, String assignee) {
810 811 // 1. 生成子任务
811 812 TaskEntityImpl task = (TaskEntityImpl) taskService.newTask(IdUtil.fastSimpleUUID());
812 813 task = BpmTaskConvert.INSTANCE.convert(task, parentTask);
813 814 if (BpmTaskAddSignTypeEnum.BEFORE.getType().equals(parentTask.getScopeType())) {
814   - // 2.1 前加签,才设置审批人,否则设置 owner
  815 + // 2.1 前加签,设置审批人
815 816 task.setAssignee(assignee);
816 817 } else {
817 818 // 2.2.1 设置 owner 不设置 assignee 是因为不能同时审批,需要等父任务完成
... ... @@ -825,9 +826,10 @@ public class BpmTaskServiceImpl implements BpmTaskService {
825 826  
826 827 @Override
827 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 831 Task task = validateSubSign(reqVO.getId());
830   - AdminUserRespDTO user = adminUserApi.getUser(userId);
  832 + // 1.2 校验取消人存在
831 833 AdminUserRespDTO cancelUser = null;
832 834 if (StrUtil.isNotBlank(task.getAssignee())) {
833 835 cancelUser = adminUserApi.getUser(NumberUtils.parseLong(task.getAssignee()));
... ... @@ -836,20 +838,24 @@ public class BpmTaskServiceImpl implements BpmTaskService {
836 838 cancelUser = adminUserApi.getUser(NumberUtils.parseLong(task.getOwner()));
837 839 }
838 840 Assert.notNull(cancelUser, "任务中没有所有者和审批人,数据错误");
839   - //1. 获取所有需要删除的任务 ID ,包含当前任务和所有子任务
  841 +
  842 + // 2. 删除任务和对应子任务
  843 + // 2.1 获取所有需要删除的任务 ID ,包含当前任务和所有子任务
840 844 List<String> allTaskIdList = getAllChildTaskIds(task.getId());
841   - //2. 删除任务和所有子任务
  845 + // 2.2 删除任务和所有子任务
842 846 taskService.deleteTasks(allTaskIdList);
843   - //3. 修改扩展表状态为取消
  847 + // 2.3 修改扩展表状态为取消
  848 + AdminUserRespDTO user = adminUserApi.getUser(userId);
844 849 taskExtMapper.updateBatchByTaskIdList(allTaskIdList, new BpmTaskExtDO().setResult(BpmProcessInstanceResultEnum.CANCEL.getResult())
845 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 854 taskService.addComment(task.getParentTaskId(), task.getProcessInstanceId(),
849 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 866 */
861 867 private Task validateSubSign(String id) {
862 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 877 throw exception(TASK_SUB_SIGN_NO_PARENT);
866 878 }
867 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 892 * 获取所有要被取消的删除的任务 ID 集合
872 893 *
873 894 * @param parentTaskId 父级任务ID
... ... @@ -875,25 +896,40 @@ public class BpmTaskServiceImpl implements BpmTaskService {
875 896 */
876 897 public List<String> getAllChildTaskIds(String parentTaskId) {
877 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 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 938 * @param parentTaskId 父任务 ID
903 939 * @return 所有子任务的 ID 集合
904 940 */
905   - private List<String> getChildrenTaskIdList(String parentTaskId) {
  941 + private List<Task> getChildrenTaskList0(String parentTaskId) {
906 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 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 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 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 147 */
148 148 private static ProcessConfig buildProcessConfig() {
149 149 return ProcessConfig.builder()
150   - .ignoreTablePrefix(Arrays.asList("QRTZ_", "ACT_")) // 忽略表前缀
  150 + .ignoreTablePrefix(Arrays.asList("QRTZ_", "ACT_", "FLW_")) // 忽略表前缀
151 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 29 .antMatchers("/swagger-resources/**").anonymous()
30 30 .antMatchers("/webjars/**").anonymous()
31 31 .antMatchers("/*/api-docs").anonymous();
32   - // 积木报表
33   - registry.antMatchers("/jmreport/**").permitAll();
34 32 // Spring Boot Actuator 的安全配置
35 33 registry.antMatchers("/actuator").anonymous()
36 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 3 import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
4 4 import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
... ... @@ -33,7 +33,7 @@ public class JobLogCleanJob implements JobHandler {
33 33 @TenantIgnore
34 34 public String execute(String param) {
35 35 Integer count = jobLogService.cleanJobLog(JOB_CLEAN_RETAIN_DAY, DELETE_LIMIT);
36   - log.info("[count][定时执行清理定时任务日志数量 ({}) 个]", count);
  36 + log.info("[execute][定时执行清理定时任务日志数量 ({}) 个]", count);
37 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 3 import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
4 4 import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
... ... @@ -34,7 +34,7 @@ public class AccessLogCleanJob implements JobHandler {
34 34 @TenantIgnore
35 35 public String execute(String param) {
36 36 Integer count = apiAccessLogService.cleanAccessLog(JOB_CLEAN_RETAIN_DAY, DELETE_LIMIT);
37   - log.info("[count][定时执行清理访问日志数量 ({}) 个]", count);
  37 + log.info("[execute][定时执行清理访问日志数量 ({}) 个]", count);
38 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 3 import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
4 4 import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
... ... @@ -34,7 +34,7 @@ public class ErrorLogCleanJob implements JobHandler {
34 34 @TenantIgnore
35 35 public String execute(String param) {
36 36 Integer count = apiErrorLogService.cleanErrorLog(JOB_CLEAN_RETAIN_DAY,DELETE_LIMIT);
37   - log.info("[count][定时执行清理错误日志数量 ({}) 个]", count);
  37 + log.info("[execute][定时执行清理错误日志数量 ({}) 个]", count);
38 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 58 FileConfigDO config = Objects.equals(CACHE_MASTER_ID, id) ?
59 59 fileConfigMapper.selectByMaster() : fileConfigMapper.selectById(id);
60 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 28  
29 29 // 导出${table.classComment} Excel
30 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 5 export const columns: BasicColumn[] = [
6 6 #foreach($column in $columns)
... ... @@ -50,7 +50,7 @@ export const searchFormSchema: FormSchema[] = [
50 50 field: '${javaField}',
51 51 #if ($column.htmlType == "input")
52 52 component: 'Input',
53   - #elseif ($column.htmlType == "select" || $column.htmlType == "radio")
  53 + #elseif ($column.htmlType == "select")
54 54 component: 'Select',
55 55 componentProps: {
56 56 #if ("" != $dictType)## 设置了 dictType 数据字典的情况
... ... @@ -59,6 +59,15 @@ export const searchFormSchema: FormSchema[] = [
59 59 options: [],
60 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 71 #elseif($column.htmlType == "datetime")
63 72 component: 'RangePicker',
64 73 #end
... ... @@ -181,7 +190,8 @@ export const updateFormSchema: FormSchema[] = [
181 190 fileType: 'file',
182 191 maxCount: 1,
183 192 },
184   - #elseif($column.htmlType == "editor")## 文本编辑器component: 'Editor',
  193 + #elseif($column.htmlType == "editor")## 文本编辑器
  194 + component: 'Editor',
185 195 #elseif($column.htmlType == "select")## 下拉框
186 196 component: 'Select',
187 197 componentProps: {
... ...
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben/views/index.vue.vm
1 1 <script lang="ts" setup>
2   -import ${ simpleClassName }Modal from './${simpleClassName}Modal.vue'
  2 +import ${simpleClassName}Modal from './${simpleClassName}Modal.vue'
3 3 import { columns, searchFormSchema } from './${classNameVar}.data'
4 4 import { useI18n } from '@/hooks/web/useI18n'
5 5 import { useMessage } from '@/hooks/web/useMessage'
6 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 10 defineOptions({ name: '${table.className}' })
12 11  
... ... @@ -16,7 +15,7 @@ const [registerModal, { openModal }] = useModal()
16 15  
17 16 const [registerTable, { getForm, reload }] = useTable({
18 17 title: '${table.classComment}列表',
19   - api: get${ simpleClassName }Page,
  18 + api: get${simpleClassName}Page,
20 19 columns,
21 20 formConfig: { labelWidth: 120, schemas: searchFormSchema },
22 21 useSearchForm: true,
... ... @@ -43,14 +42,14 @@ async function handleExport() {
43 42 iconType: 'warning',
44 43 content: t('common.exportMessage'),
45 44 async onOk() {
46   - await export${ simpleClassName } (getForm().getFieldsValue())
  45 + await export${simpleClassName}(getForm().getFieldsValue())
47 46 createMessage.success(t('common.exportSuccessText'))
48 47 },
49 48 })
50 49 }
51 50  
52 51 async function handleDelete(record: Recordable) {
53   - await delete${ simpleClassName } (record.id)
  52 + await delete${simpleClassName}(record.id)
54 53 createMessage.success(t('common.delSuccessText'))
55 54 reload()
56 55 }
... ... @@ -62,7 +61,7 @@ async function handleDelete(record: Recordable) {
62 61 <a-button type="primary" v-auth="['${permissionPrefix}:create']" :preIcon="IconEnum.ADD" @click="handleCreate">
63 62 {{ t('action.create') }}
64 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 65 {{ t('action.export') }}
67 66 </a-button>
68 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 258 Long id = fileConfig.getId();
259 259 // mock 获得 Client
260 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 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 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 2  
3 3 import lombok.Data;
4 4  
  5 +import javax.validation.constraints.NotNull;
5 6 import java.util.List;
6 7  
7 8 /**
... ... @@ -15,6 +16,7 @@ public class ProductCommentCreateReqDTO {
15 16 /**
16 17 * 商品 SKU 编号
17 18 */
  19 + @NotNull(message = "商品 SKU 编号不能为空")
18 20 private Long skuId;
19 21 /**
20 22 * 订单编号
... ... @@ -26,20 +28,19 @@ public class ProductCommentCreateReqDTO {
26 28 private Long orderItemId;
27 29  
28 30 /**
29   - * 评分星级 1-5 分
30   - */
31   - private Integer scores;
32   - /**
33 31 * 描述星级 1-5 分
34 32 */
  33 + @NotNull(message = "描述星级不能为空")
35 34 private Integer descriptionScores;
36 35 /**
37 36 * 服务星级 1-5 分
38 37 */
  38 + @NotNull(message = "服务星级不能为空")
39 39 private Integer benefitScores;
40 40 /**
41 41 * 评论内容
42 42 */
  43 + @NotNull(message = "评论内容不能为空")
43 44 private String content;
44 45 /**
45 46 * 评论图片地址数组,以逗号分隔最多上传 9 张
... ... @@ -49,11 +50,12 @@ public class ProductCommentCreateReqDTO {
49 50 /**
50 51 * 是否匿名
51 52 */
  53 + @NotNull(message = "是否匿名不能为空")
52 54 private Boolean anonymous;
53 55 /**
54 56 * 评价人
55 57 */
  58 + @NotNull(message = "评价人不能为空")
56 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 3 import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum;
4 4 import lombok.Data;
5 5  
6   -import java.util.List;
7   -
8 6 // TODO @LeeYan9: ProductSpuRespDTO
9 7 /**
10 8 * 商品 SPU 信息 Response DTO
... ... @@ -27,55 +25,22 @@ public class ProductSpuRespDTO {
27 25 */
28 26 private String name;
29 27 /**
30   - * 关键字
31   - */
32   - private String keyword;
33   - /**
34 28 * 单位
35 29 *
36 30 * 对应 product_unit 数据字典
37 31 */
38 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 37 private Long categoryId;
57 38 /**
58   - * 商品品牌编号
59   - */
60   - private Long brandId;
61   - /**
62 39 * 商品封面图
63 40 */
64 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 45 * <p>
81 46 * 枚举 {@link ProductSpuStatusEnum}
... ... @@ -124,22 +89,6 @@ public class ProductSpuRespDTO {
124 89 */
125 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 1 package cn.iocoder.yudao.module.product.api.sku;
2 2  
3   -import cn.hutool.core.collection.CollUtil;
4 3 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
5 4 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO;
6 5 import cn.iocoder.yudao.module.product.convert.sku.ProductSkuConvert;
... ... @@ -11,7 +10,6 @@ import org.springframework.validation.annotation.Validated;
11 10  
12 11 import javax.annotation.Resource;
13 12 import java.util.Collection;
14   -import java.util.Collections;
15 13 import java.util.List;
16 14  
17 15 /**
... ... @@ -35,18 +33,12 @@ public class ProductSkuApiImpl implements ProductSkuApi {
35 33  
36 34 @Override
37 35 public List<ProductSkuRespDTO> getSkuList(Collection<Long> ids) {
38   - if (CollUtil.isEmpty(ids)) {
39   - return Collections.emptyList();
40   - }
41 36 List<ProductSkuDO> skus = productSkuService.getSkuList(ids);
42 37 return ProductSkuConvert.INSTANCE.convertList04(skus);
43 38 }
44 39  
45 40 @Override
46 41 public List<ProductSkuRespDTO> getSkuListBySpuId(Collection<Long> spuIds) {
47   - if (CollUtil.isEmpty(spuIds)) {
48   - return Collections.emptyList();
49   - }
50 42 List<ProductSkuDO> skus = productSkuService.getSkuListBySpuId(spuIds);
51 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 27  
28 28 @Override
29 29 public List<ProductSpuRespDTO> getSpuList(Collection<Long> ids) {
30   - if (CollectionUtil.isEmpty(ids)) {
31   - return Collections.emptyList();
32   - }
33 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 1 package cn.iocoder.yudao.module.product.convert.property;
2 2  
3 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 4 import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValueCreateReqVO;
8 5 import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValueRespVO;
9 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 7 import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyValueDO;
12   -import cn.iocoder.yudao.module.product.service.property.bo.ProductPropertyValueDetailRespBO;
13 8 import org.mapstruct.Mapper;
14 9 import org.mapstruct.factory.Mappers;
15 10  
16 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 14 * 属性值 Convert
... ... @@ -38,18 +30,4 @@ public interface ProductPropertyValueConvert {
38 30  
39 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 5 import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValuePageReqVO;
6 6 import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValueUpdateReqVO;
7 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 9 import java.util.Collection;
11 10 import java.util.List;
... ... @@ -57,14 +56,6 @@ public interface ProductPropertyValueService {
57 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 61 * @param propertyId 属性项编号数
... ...
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/property/ProductPropertyValueServiceImpl.java
1 1 package cn.iocoder.yudao.module.product.service.property;
2 2  
3   -import cn.hutool.core.collection.CollUtil;
4 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 4 import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValueCreateReqVO;
6 5 import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValuePageReqVO;
7 6 import cn.iocoder.yudao.module.product.controller.admin.property.vo.value.ProductPropertyValueUpdateReqVO;
8 7 import cn.iocoder.yudao.module.product.convert.property.ProductPropertyValueConvert;
9   -import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyDO;
10 8 import cn.iocoder.yudao.module.product.dal.dataobject.property.ProductPropertyValueDO;
11 9 import cn.iocoder.yudao.module.product.dal.mysql.property.ProductPropertyValueMapper;
12   -import cn.iocoder.yudao.module.product.service.property.bo.ProductPropertyValueDetailRespBO;
13 10 import cn.iocoder.yudao.module.product.service.sku.ProductSkuService;
14 11 import org.springframework.context.annotation.Lazy;
15 12 import org.springframework.stereotype.Service;
... ... @@ -17,11 +14,9 @@ import org.springframework.validation.annotation.Validated;
17 14  
18 15 import javax.annotation.Resource;
19 16 import java.util.Collection;
20   -import java.util.Collections;
21 17 import java.util.List;
22 18  
23 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 20 import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.PROPERTY_VALUE_EXISTS;
26 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 95 }
101 96  
102 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 98 public Integer getPropertyValueCountByPropertyId(Long propertyId) {
121 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 153  
154 154 @Override
155 155 public List<ProductSkuDO> getSkuListBySpuId(Collection<Long> spuIds) {
  156 + if (CollUtil.isEmpty(spuIds)) {
  157 + return Collections.emptyList();
  158 + }
156 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 188  
189 189 @Override
190 190 public List<ProductSpuDO> getSpuList(Collection<Long> ids) {
  191 + if (CollUtil.isEmpty(ids)) {
  192 + return Collections.emptyList();
  193 + }
191 194 return productSpuMapper.selectBatchIds(ids);
192 195 }
193 196  
... ...
yudao-module-mall/yudao-module-promotion-api/pom.xml
... ... @@ -13,7 +13,7 @@
13 13  
14 14 <name>${project.artifactId}</name>
15 15 <description>
16   - market 模块 API,暴露给其它模块调用
  16 + promotion 模块 API,暴露给其它模块调用
17 17 </description>
18 18  
19 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 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 14 <name>${project.artifactId}</name>
15 15  
16 16 <description>
17   - market模块,主要实现营销相关功能
18   - 例如:营销活动、banner广告、优惠券、优惠码等功能。
  17 + promotion 模块,主要实现营销相关功能
  18 + 例如:营销活动、banner 广告、优惠券、优惠码等功能。
19 19 </description>
20 20  
21 21 <dependencies>
... ... @@ -49,10 +49,6 @@
49 49 <groupId>cn.iocoder.boot</groupId>
50 50 <artifactId>yudao-spring-boot-starter-biz-tenant</artifactId>
51 51 </dependency>
52   - <dependency>
53   - <groupId>cn.iocoder.boot</groupId>
54   - <artifactId>yudao-spring-boot-starter-biz-weixin</artifactId>
55   - </dependency>
56 52  
57 53 <!-- Web 相关 -->
58 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 2  
3 3 import cn.iocoder.yudao.module.promotion.service.bargain.BargainActivityService;
4 4 import org.springframework.stereotype.Service;
  5 +import org.springframework.validation.annotation.Validated;
5 6  
6 7 import javax.annotation.Resource;
7 8  
... ... @@ -11,6 +12,7 @@ import javax.annotation.Resource;
11 12 * @author HUIHUI
12 13 */
13 14 @Service
  15 +@Validated
14 16 public class BargainActivityApiImpl implements BargainActivityApi {
15 17  
16 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 3 import cn.iocoder.yudao.module.promotion.api.bargain.dto.BargainValidateJoinRespDTO;
4 4 import cn.iocoder.yudao.module.promotion.service.bargain.BargainRecordService;
5 5 import org.springframework.stereotype.Service;
  6 +import org.springframework.validation.annotation.Validated;
6 7  
7 8 import javax.annotation.Resource;
8 9  
... ... @@ -12,6 +13,7 @@ import javax.annotation.Resource;
12 13 * @author HUIHUI
13 14 */
14 15 @Service
  16 +@Validated
15 17 public class BargainRecordApiImpl implements BargainRecordApi {
16 18  
17 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 8 import cn.iocoder.yudao.module.promotion.enums.combination.CombinationRecordStatusEnum;
9 9 import cn.iocoder.yudao.module.promotion.service.combination.CombinationRecordService;
10 10 import org.springframework.stereotype.Service;
  11 +import org.springframework.validation.annotation.Validated;
11 12  
12 13 import javax.annotation.Resource;
13 14  
... ... @@ -20,6 +21,7 @@ import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.COMBINA
20 21 * @author HUIHUI
21 22 */
22 23 @Service
  24 +@Validated
23 25 public class CombinationRecordApiImpl implements CombinationRecordApi {
24 26  
25 27 @Resource
... ... @@ -45,11 +47,6 @@ public class CombinationRecordApiImpl implements CombinationRecordApi {
45 47 }
46 48  
47 49 @Override
48   - public void updateRecordStatusToFailed(Long userId, Long orderId) {
49   - recordService.updateCombinationRecordStatusByUserIdAndOrderId(CombinationRecordStatusEnum.FAILED.getStatus(), userId, orderId);
50   - }
51   -
52   - @Override
53 50 public CombinationValidateJoinRespDTO validateJoinCombination(Long userId, Long activityId, Long headId, Long skuId, Integer count) {
54 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 8 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;
9 9 import cn.iocoder.yudao.module.promotion.service.coupon.CouponService;
10 10 import org.springframework.stereotype.Service;
  11 +import org.springframework.validation.annotation.Validated;
11 12  
12 13 import javax.annotation.Resource;
13 14  
... ... @@ -17,6 +18,7 @@ import javax.annotation.Resource;
17 18 * @author 芋道源码
18 19 */
19 20 @Service
  21 +@Validated
20 22 public class CouponApiImpl implements CouponApi {
21 23  
22 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 4 import cn.iocoder.yudao.module.promotion.convert.discount.DiscountActivityConvert;
5 5 import cn.iocoder.yudao.module.promotion.service.discount.DiscountActivityService;
6 6 import org.springframework.stereotype.Service;
  7 +import org.springframework.validation.annotation.Validated;
7 8  
8 9 import javax.annotation.Resource;
9 10 import java.util.Collection;
... ... @@ -15,6 +16,7 @@ import java.util.List;
15 16 * @author 芋道源码
16 17 */
17 18 @Service
  19 +@Validated
18 20 public class DiscountActivityApiImpl implements DiscountActivityApi {
19 21  
20 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 3 import cn.iocoder.yudao.module.promotion.api.reward.dto.RewardActivityMatchRespDTO;
4 4 import cn.iocoder.yudao.module.promotion.service.reward.RewardActivityService;
5 5 import org.springframework.stereotype.Service;
  6 +import org.springframework.validation.annotation.Validated;
6 7  
7 8 import javax.annotation.Resource;
8 9 import java.util.Collection;
... ... @@ -14,6 +15,7 @@ import java.util.List;
14 15 * @author 芋道源码
15 16 */
16 17 @Service
  18 +@Validated
17 19 public class RewardActivityApiImpl implements RewardActivityApi {
18 20  
19 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 3 import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillValidateJoinRespDTO;
4 4 import cn.iocoder.yudao.module.promotion.service.seckill.SeckillActivityService;
5 5 import org.springframework.stereotype.Service;
  6 +import org.springframework.validation.annotation.Validated;
6 7  
7 8 import javax.annotation.Resource;
8 9  
... ... @@ -12,6 +13,7 @@ import javax.annotation.Resource;
12 13 * @author HUIHUI
13 14 */
14 15 @Service
  16 +@Validated
15 17 public class SeckillActivityApiImpl implements SeckillActivityApi {
16 18  
17 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 4 import lombok.Data;
5 5 import lombok.ToString;
6 6  
  7 +import java.time.LocalDateTime;
  8 +
7 9 @Schema(description = "管理后台 - Banner Response VO")
8 10 @Data
9 11 @ToString(callSuper = true)
... ... @@ -12,4 +14,7 @@ public class BannerRespVO extends BannerBaseVO {
12 14 @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED)
13 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 96 */
97 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 100 return null;
101 101 }
102 102 // 品类券:查询商品的品类编号
103 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 108 return spuId;
... ...