Prechádzať zdrojové kódy

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

YunaiV 6 mesiacov pred
rodič
commit
dad0cc4cb1
30 zmenil súbory, kde vykonal 270 pridanie a 138 odobranie
  1. 4 4
      yudao-dependencies/pom.xml
  2. 14 0
      yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java
  3. 3 3
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/form/BpmTaskCandidateFormUserStrategy.java
  4. 3 3
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/other/BpmTaskCandidateExpressionStrategy.java
  5. 1 1
      yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java
  6. 3 2
      yudao-module-infra/yudao-module-infra-biz/pom.xml
  7. 4 1
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/enums/codegen/CodegenFrontTypeEnum.java
  8. 93 65
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/s3/S3FileClient.java
  9. 6 0
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/s3/S3FileClientConfig.java
  10. 9 9
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngine.java
  11. 13 0
      yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/ProductSkuApi.java
  12. 1 1
      yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/spu/ProductSpuApi.java
  13. 1 1
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/point/PointActivityController.java
  14. 1 1
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/point/AppPointActivityController.java
  15. 9 8
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java
  16. 8 6
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/aftersale/AppAfterSaleController.java
  17. 20 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/aftersale/vo/AppAfterSalePageReqVO.java
  18. 6 2
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/base/spu/AppProductSpuBaseRespVO.java
  19. 14 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/delivery/vo/pickup/AppDeliveryPickUpStoreRespVO.java
  20. 0 5
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/aftersale/AfterSaleConvert.java
  21. 3 5
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/cart/TradeCartConvert.java
  22. 5 4
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/aftersale/AfterSaleMapper.java
  23. 3 3
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleService.java
  24. 5 5
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleServiceImpl.java
  25. 18 2
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java
  26. 10 5
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeBrokerageOrderHandler.java
  27. 1 0
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java
  28. 2 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImpl.java
  29. 9 2
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java
  30. 1 0
      yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImplTest.java

+ 4 - 4
yudao-dependencies/pom.xml

@@ -71,7 +71,7 @@
         <!-- 三方云服务相关 -->
         <commons-io.version>2.17.0</commons-io.version>
         <commons-compress.version>1.27.1</commons-compress.version>
-        <aws-java-sdk-s3.version>1.12.777</aws-java-sdk-s3.version>
+        <awssdk.version>2.30.14</awssdk.version>
         <justauth.version>2.0.5</justauth.version>
         <jimureport.version>1.8.1</jimureport.version>
         <weixin-java.version>4.7.2.B</weixin-java.version>
@@ -553,9 +553,9 @@
 
             <!-- 三方云服务相关 -->
             <dependency>
-                <groupId>com.amazonaws</groupId>
-                <artifactId>aws-java-sdk-s3</artifactId>
-                <version>${aws-java-sdk-s3.version}</version>
+                <groupId>software.amazon.awssdk</groupId>
+                <artifactId>s3</artifactId>
+                <version>${awssdk.version}</version>
             </dependency>
 
             <dependency>

+ 14 - 0
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java

@@ -11,6 +11,7 @@ import java.util.function.*;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
+import static cn.hutool.core.convert.Convert.toCollection;
 import static java.util.Arrays.asList;
 
 /**
@@ -335,4 +336,17 @@ public class CollectionUtils {
         return list.stream().filter(Objects::nonNull).flatMap(Collection::stream).collect(Collectors.toList());
     }
 
+    /**
+     * 转换为 LinkedHashSet
+     *
+     * @param <T>         元素类型
+     * @param elementType 集合中元素类型
+     * @param value       被转换的值
+     * @return {@link LinkedHashSet}
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> LinkedHashSet<T> toLinkedHashSet(Class<T> elementType, Object value) {
+        return (LinkedHashSet<T>) toCollection(LinkedHashSet.class, elementType, value);
+    }
+
 }

+ 3 - 3
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/form/BpmTaskCandidateFormUserStrategy.java

@@ -1,7 +1,7 @@
 package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.form;
 
-import cn.hutool.core.convert.Convert;
 import cn.hutool.core.lang.Assert;
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.user.BpmTaskCandidateUserStrategy;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
@@ -33,7 +33,7 @@ public class BpmTaskCandidateFormUserStrategy implements BpmTaskCandidateStrateg
     @Override
     public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) {
         Object result = execution.getVariable(param);
-        return Convert.toSet(Long.class, result);
+        return CollectionUtils.toLinkedHashSet(Long.class, result);
     }
 
     @Override
@@ -41,7 +41,7 @@ public class BpmTaskCandidateFormUserStrategy implements BpmTaskCandidateStrateg
                                               String param, Long startUserId, String processDefinitionId,
                                               Map<String, Object> processVariables) {
         Object result = processVariables == null ? null : processVariables.get(param);
-        return Convert.toSet(Long.class, result);
+        return CollectionUtils.toLinkedHashSet(Long.class, result);
     }
 
 }

+ 3 - 3
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/other/BpmTaskCandidateExpressionStrategy.java

@@ -1,6 +1,6 @@
 package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.other;
 
-import cn.hutool.core.convert.Convert;
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
@@ -37,7 +37,7 @@ public class BpmTaskCandidateExpressionStrategy implements BpmTaskCandidateStrat
     @Override
     public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) {
         Object result = FlowableUtils.getExpressionValue(execution, param);
-        return Convert.toSet(Long.class, result);
+        return CollectionUtils.toLinkedHashSet(Long.class, result);
     }
 
     @Override
@@ -46,7 +46,7 @@ public class BpmTaskCandidateExpressionStrategy implements BpmTaskCandidateStrat
         Map<String, Object> variables = processVariables == null ? new HashMap<>() : processVariables;
         try {
             Object result = FlowableUtils.getExpressionValue(variables, param);
-            return Convert.toSet(Long.class, result);
+            return CollectionUtils.toLinkedHashSet(Long.class, result);
         } catch (FlowableException ex) {
             // 预测未运行的节点时候,表达式如果包含 execution 或者不存在的流程变量会抛异常,
             log.warn("[calculateUsersByActivity][表达式({}) 变量({}) 解析报错", param, variables, ex);

+ 1 - 1
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java

@@ -6,7 +6,6 @@ import cn.hutool.core.lang.Assert;
 import cn.hutool.core.util.*;
 import cn.hutool.extra.spring.SpringUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
 import cn.iocoder.yudao.framework.common.util.date.DateUtils;
 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
@@ -882,6 +881,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
                 return;
             }
             runExecutionIds.add(task.getExecutionId());
+
             // 判断是否分配给自己任务,因为会签任务,一个节点会有多个任务
             if (isAssignUserTask(userId, task)) { // 情况一:自己的任务,进行 RETURN 标记
                 // 2.1.1 添加评论

+ 3 - 2
yudao-module-infra/yudao-module-infra-biz/pom.xml

@@ -115,9 +115,10 @@
             <groupId>com.jcraft</groupId>
             <artifactId>jsch</artifactId> <!-- 文件客户端:解决 sftp 连接 -->
         </dependency>
+        <!-- 文件客户端:解决阿里云、腾讯云、minio 等 S3 连接 -->
         <dependency>
-            <groupId>com.amazonaws</groupId>
-            <artifactId>aws-java-sdk-s3</artifactId><!-- 文件客户端:解决阿里云、腾讯云、minio 等 S3 连接 -->
+            <groupId>software.amazon.awssdk</groupId>
+            <artifactId>s3</artifactId>
         </dependency>
 
         <dependency>

+ 4 - 1
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/enums/codegen/CodegenFrontTypeEnum.java

@@ -13,10 +13,13 @@ import lombok.Getter;
 public enum CodegenFrontTypeEnum {
 
     VUE2_ELEMENT_UI(10), // Vue2 Element UI 标准模版
+
     VUE3_ELEMENT_PLUS(20), // Vue3 Element Plus 标准模版
+
     VUE3_VBEN2_ANTD_SCHEMA(30), // Vue3 VBEN2 + ANTD + Schema 模版
+
     VUE3_VBEN5_ANTD_SCHEMA(40), // Vue3 VBEN5 + ANTD + schema 模版
-    VUE3_VBEN5_ANTD(50), // Vue3 VBEN5 + ANTD 模版
+    VUE3_VBEN5_ANTD_GENERAL(41), // Vue3 VBEN5 + ANTD 标准模版
     ;
 
     /**

+ 93 - 65
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/s3/S3FileClient.java

@@ -4,29 +4,31 @@ import cn.hutool.core.io.IoUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.http.HttpUtil;
 import cn.iocoder.yudao.module.infra.framework.file.core.client.AbstractFileClient;
-import com.amazonaws.HttpMethod;
-import com.amazonaws.auth.AWSStaticCredentialsProvider;
-import com.amazonaws.auth.BasicAWSCredentials;
-import com.amazonaws.client.builder.AwsClientBuilder;
-import com.amazonaws.services.s3.AmazonS3Client;
-import com.amazonaws.services.s3.AmazonS3ClientBuilder;
-import com.amazonaws.services.s3.model.ObjectMetadata;
-import com.amazonaws.services.s3.model.S3Object;
+import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
+import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
+import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
+import software.amazon.awssdk.core.sync.RequestBody;
+import software.amazon.awssdk.regions.Region;
+import software.amazon.awssdk.services.s3.S3Client;
+import software.amazon.awssdk.services.s3.S3Configuration;
+import software.amazon.awssdk.services.s3.model.DeleteObjectRequest;
+import software.amazon.awssdk.services.s3.model.GetObjectRequest;
+import software.amazon.awssdk.services.s3.model.PutObjectRequest;
+import software.amazon.awssdk.services.s3.presigner.S3Presigner;
+import software.amazon.awssdk.services.s3.presigner.model.PutObjectPresignRequest;
 
-import java.io.ByteArrayInputStream;
-import java.util.Date;
-import java.util.concurrent.TimeUnit;
+import java.net.URI;
+import java.time.Duration;
 
 /**
  * 基于 S3 协议的文件客户端,实现 MinIO、阿里云、腾讯云、七牛云、华为云等云服务
- * <p>
- * S3 协议的客户端,采用亚马逊提供的 software.amazon.awssdk.s3 库
  *
  * @author 芋道源码
  */
 public class S3FileClient extends AbstractFileClient<S3FileClientConfig> {
 
-    private AmazonS3Client client;
+    private S3Client client;
+    private S3Presigner presigner;
 
     public S3FileClient(Long id, S3FileClientConfig config) {
         super(id, config);
@@ -38,31 +40,80 @@ public class S3FileClient extends AbstractFileClient<S3FileClientConfig> {
         if (StrUtil.isEmpty(config.getDomain())) {
             config.setDomain(buildDomain());
         }
-        // 初始化客户端
-        client = (AmazonS3Client)AmazonS3ClientBuilder.standard()
-                .withCredentials(buildCredentials())
-                .withEndpointConfiguration(buildEndpointConfiguration())
+        // 初始化 S3 客户端
+        Region region = Region.of("us-east-1"); // 必须填,但填什么都行,常见的值有 "us-east-1",不填会报错
+        AwsCredentialsProvider credentialsProvider = StaticCredentialsProvider.create(
+                AwsBasicCredentials.create(config.getAccessKey(), config.getAccessSecret()));
+        URI endpoint = URI.create(buildEndpoint());
+        S3Configuration serviceConfiguration = S3Configuration.builder() // Path-style 访问
+                .pathStyleAccessEnabled(Boolean.TRUE.equals(config.getEnablePathStyleAccess()))
+                .chunkedEncodingEnabled(false) // 禁用分块编码,参见 https://t.zsxq.com/kBy57
+                .build();
+        client = S3Client.builder()
+                .credentialsProvider(credentialsProvider)
+                .region(region)
+                .endpointOverride(endpoint)
+                .serviceConfiguration(serviceConfiguration)
+                .build();
+        presigner = S3Presigner.builder()
+                .credentialsProvider(credentialsProvider)
+                .region(region)
+                .endpointOverride(endpoint)
+                .serviceConfiguration(serviceConfiguration)
                 .build();
     }
 
-    /**
-     * 基于 config 秘钥,构建 S3 客户端的认证信息
-     *
-     * @return S3 客户端的认证信息
-     */
-    private AWSStaticCredentialsProvider buildCredentials() {
-        return new AWSStaticCredentialsProvider(
-                new BasicAWSCredentials(config.getAccessKey(), config.getAccessSecret()));
+    @Override
+    public String upload(byte[] content, String path, String type) {
+        // 构造 PutObjectRequest
+        PutObjectRequest putRequest = PutObjectRequest.builder()
+                .bucket(config.getBucket())
+                .key(path)
+                .contentType(type)
+                .contentLength((long) content.length)
+                .build();
+        // 上传文件
+        client.putObject(putRequest, RequestBody.fromBytes(content));
+        // 拼接返回路径
+        return config.getDomain() + "/" + path;
+    }
+
+    @Override
+    public void delete(String path) {
+        DeleteObjectRequest deleteRequest = DeleteObjectRequest.builder()
+                .bucket(config.getBucket())
+                .key(path)
+                .build();
+        client.deleteObject(deleteRequest);
+    }
+
+    @Override
+    public byte[] getContent(String path) {
+        GetObjectRequest getRequest = GetObjectRequest.builder()
+                .bucket(config.getBucket())
+                .key(path)
+                .build();
+        return IoUtil.readBytes(client.getObject(getRequest));
+    }
+
+    @Override
+    public FilePresignedUrlRespDTO getPresignedObjectUrl(String path) {
+        Duration expiration = Duration.ofHours(24);
+        return new FilePresignedUrlRespDTO(getPresignedUrl(path, expiration), config.getDomain() + "/" + path);
     }
 
     /**
-     * 构建 S3 客户端的 Endpoint 配置,包括 region、endpoint
+     * 生成动态的预签名上传 URL
      *
-     * @return  S3 客户端的 EndpointConfiguration 配置
+     * @param path     相对路径
+     * @param expiration 过期时间
+     * @return 生成的上传 URL
      */
-    private AwsClientBuilder.EndpointConfiguration buildEndpointConfiguration() {
-        return new AwsClientBuilder.EndpointConfiguration(config.getEndpoint(),
-                null); // 无需设置 region
+    private String getPresignedUrl(String path, Duration expiration) {
+        return presigner.presignPutObject(PutObjectPresignRequest.builder()
+                .signatureDuration(expiration)
+                .putObjectRequest(b -> b.bucket(config.getBucket()).key(path))
+                .build()).url().toString();
     }
 
     /**
@@ -79,40 +130,17 @@ public class S3FileClient extends AbstractFileClient<S3FileClientConfig> {
         return StrUtil.format("https://{}.{}", config.getBucket(), config.getEndpoint());
     }
 
-    @Override
-    public String upload(byte[] content, String path, String type) throws Exception {
-        // 元数据,主要用于设置文件类型
-        ObjectMetadata objectMetadata = new ObjectMetadata();
-        objectMetadata.setContentType(type);
-        objectMetadata.setContentLength(content.length); // 如果不设置,会有 “ No content length specified for stream data” 警告日志
-        // 执行上传
-        client.putObject(config.getBucket(),
-                path, // 相对路径
-                new ByteArrayInputStream(content), // 文件内容
-                objectMetadata);
-
-        // 拼接返回路径
-        return config.getDomain() + "/" + path;
-    }
-
-    @Override
-    public void delete(String path) throws Exception {
-        client.deleteObject(config.getBucket(), path);
-    }
-
-    @Override
-    public byte[] getContent(String path) throws Exception {
-        S3Object tempS3Object = client.getObject(config.getBucket(), path);
-        return IoUtil.readBytes(tempS3Object.getObjectContent());
-    }
-
-    @Override
-    public FilePresignedUrlRespDTO getPresignedObjectUrl(String path) throws Exception {
-        // 设定过期时间为 10 分钟。取值范围:1 秒 ~ 7 天
-        Date expiration = new Date(System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(10));
-        // 生成上传 URL
-        String uploadUrl = String.valueOf(client.generatePresignedUrl(config.getBucket(), path, expiration , HttpMethod.PUT));
-        return new FilePresignedUrlRespDTO(uploadUrl, config.getDomain() + "/" + path);
+    /**
+     * 节点地址补全协议头
+     *
+     * @return 节点地址
+     */
+    private String buildEndpoint() {
+        // 如果已经是 http 或者 https,则不进行拼接
+        if (HttpUtil.isHttp(config.getEndpoint()) || HttpUtil.isHttps(config.getEndpoint())) {
+            return config.getEndpoint();
+        }
+        return StrUtil.format("https://{}", config.getEndpoint());
     }
 
 }

+ 6 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/s3/S3FileClientConfig.java

@@ -67,6 +67,12 @@ public class S3FileClientConfig implements FileClientConfig {
     @NotNull(message = "accessSecret 不能为空")
     private String accessSecret;
 
+    /**
+     * 是否启用 PathStyle 访问
+     */
+    @NotNull(message = "enablePathStyleAccess 不能为空")
+    private Boolean enablePathStyleAccess;
+
     @SuppressWarnings("RedundantIfStatement")
     @AssertTrue(message = "domain 不能为空")
     @JsonIgnore

+ 9 - 9
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngine.java

@@ -164,23 +164,23 @@ public class CodegenEngine {
             .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_SCHEMA.getType(), vue3Vben5AntdSchemaTemplatePath("views/modules/list_sub_erp.vue"),  // 特殊:主子表专属逻辑
                     vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/${subSimpleClassName_strikeCase}-list.vue"))
             // VUE3_VBEN5_ANTD
-            .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD.getType(), vue3Vben5AntdGeneralTemplatePath("views/data.ts"),
+            .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_GENERAL.getType(), vue3Vben5AntdGeneralTemplatePath("views/data.ts"),
                     vue3FilePath("views/${table.moduleName}/${table.businessName}/data.ts"))
-            .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD.getType(), vue3Vben5AntdGeneralTemplatePath("views/index.vue"),
+            .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_GENERAL.getType(), vue3Vben5AntdGeneralTemplatePath("views/index.vue"),
                     vue3FilePath("views/${table.moduleName}/${table.businessName}/index.vue"))
-            .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD.getType(), vue3Vben5AntdGeneralTemplatePath("views/form.vue"),
+            .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_GENERAL.getType(), vue3Vben5AntdGeneralTemplatePath("views/form.vue"),
                     vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/form.vue"))
-            .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD.getType(), vue3Vben5AntdGeneralTemplatePath("api/api.ts"),
+            .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_GENERAL.getType(), vue3Vben5AntdGeneralTemplatePath("api/api.ts"),
                     vue3FilePath("api/${table.moduleName}/${table.businessName}/index.ts"))
-            .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD.getType(), vue3Vben5AntdGeneralTemplatePath("views/modules/form_sub_normal.vue"),  // 特殊:主子表专属逻辑
+            .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_GENERAL.getType(), vue3Vben5AntdGeneralTemplatePath("views/modules/form_sub_normal.vue"),  // 特殊:主子表专属逻辑
                     vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/${subSimpleClassName_strikeCase}-form.vue"))
-            .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD.getType(), vue3Vben5AntdGeneralTemplatePath("views/modules/form_sub_inner.vue"),  // 特殊:主子表专属逻辑
+            .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_GENERAL.getType(), vue3Vben5AntdGeneralTemplatePath("views/modules/form_sub_inner.vue"),  // 特殊:主子表专属逻辑
                     vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/${subSimpleClassName_strikeCase}-form.vue"))
-            .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD.getType(), vue3Vben5AntdGeneralTemplatePath("views/modules/form_sub_erp.vue"),  // 特殊:主子表专属逻辑
+            .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_GENERAL.getType(), vue3Vben5AntdGeneralTemplatePath("views/modules/form_sub_erp.vue"),  // 特殊:主子表专属逻辑
                     vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/${subSimpleClassName_strikeCase}-form.vue"))
-            .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD.getType(), vue3Vben5AntdGeneralTemplatePath("views/modules/list_sub_inner.vue"),  // 特殊:主子表专属逻辑
+            .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_GENERAL.getType(), vue3Vben5AntdGeneralTemplatePath("views/modules/list_sub_inner.vue"),  // 特殊:主子表专属逻辑
                     vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/${subSimpleClassName_strikeCase}-list.vue"))
-            .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD.getType(), vue3Vben5AntdGeneralTemplatePath("views/modules/list_sub_erp.vue"),  // 特殊:主子表专属逻辑
+            .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_GENERAL.getType(), vue3Vben5AntdGeneralTemplatePath("views/modules/list_sub_erp.vue"),  // 特殊:主子表专属逻辑
                     vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/${subSimpleClassName_strikeCase}-list.vue"))
             .build();
 

+ 13 - 0
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/ProductSkuApi.java

@@ -5,6 +5,9 @@ import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO;
 
 import java.util.Collection;
 import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
 
 /**
  * 商品 SKU API 接口
@@ -30,6 +33,16 @@ public interface ProductSkuApi {
      */
     List<ProductSkuRespDTO> getSkuList(Collection<Long> ids);
 
+    /**
+     * 批量查询 SKU MAP
+     *
+     * @param ids SKU 编号列表
+     * @return SKU MAP
+     */
+    default Map<Long, ProductSkuRespDTO> getSkuMap(Collection<Long> ids) {
+        return convertMap(getSkuList(ids), ProductSkuRespDTO::getId);
+    }
+
     /**
      * 批量查询 SKU 数组
      *

+ 1 - 1
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/spu/ProductSpuApi.java

@@ -30,7 +30,7 @@ public interface ProductSpuApi {
      * @param ids SPU 编号列表
      * @return SPU MAP
      */
-    default Map<Long, ProductSpuRespDTO> getSpusMap(Collection<Long> ids) {
+    default Map<Long, ProductSpuRespDTO> getSpuMap(Collection<Long> ids) {
         return convertMap(getSpuList(ids), ProductSpuRespDTO::getId);
     }
 

+ 1 - 1
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/point/PointActivityController.java

@@ -124,7 +124,7 @@ public class PointActivityController {
         List<PointProductDO> products = pointActivityService.getPointProductListByActivityIds(
                 convertSet(activityList, PointActivityDO::getId));
         Map<Long, List<PointProductDO>> productsMap = convertMultiMap(products, PointProductDO::getActivityId);
-        Map<Long, ProductSpuRespDTO> spuMap = productSpuApi.getSpusMap(
+        Map<Long, ProductSpuRespDTO> spuMap = productSpuApi.getSpuMap(
                 convertSet(activityList, PointActivityDO::getSpuId));
         List<PointActivityRespVO> result = BeanUtils.toBean(activityList, PointActivityRespVO.class);
         result.forEach(activity -> {

+ 1 - 1
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/point/AppPointActivityController.java

@@ -104,7 +104,7 @@ public class AppPointActivityController {
         List<PointProductDO> products = pointActivityService.getPointProductListByActivityIds(
                 convertSet(activityList, PointActivityDO::getId));
         Map<Long, List<PointProductDO>> productsMap = convertMultiMap(products, PointProductDO::getActivityId);
-        Map<Long, ProductSpuRespDTO> spuMap = productSpuApi.getSpusMap(
+        Map<Long, ProductSpuRespDTO> spuMap = productSpuApi.getSpuMap(
                 convertSet(activityList, PointActivityDO::getSpuId));
         List<AppPointActivityRespVO> result = BeanUtils.toBean(activityList, AppPointActivityRespVO.class);
         result.forEach(activity -> {

+ 9 - 8
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java

@@ -22,6 +22,7 @@ import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTemplateValidityType
 import jakarta.annotation.Resource;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Propagation;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
 
@@ -180,7 +181,7 @@ public class CouponServiceImpl implements CouponService {
      * @param couponId 模版编号
      * @param userId   用户编号
      */
-    @Transactional(rollbackFor = Exception.class)
+    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW) // 每次调用开启一个新的事务,避免在一个大的事务里面
     public void invalidateCoupon(Long couponId, Long userId) {
         if (couponId == null || couponId <= 0) {
             return;
@@ -270,13 +271,17 @@ public class CouponServiceImpl implements CouponService {
         if (CollUtil.isEmpty(userIds)) {
             throw exception(COUPON_TEMPLATE_USER_ALREADY_TAKE);
         }
-
         // 校验模板
         if (couponTemplate == null) {
             throw exception(COUPON_TEMPLATE_NOT_EXISTS);
         }
-        // 校验剩余数量
-        if (ObjUtil.notEqual(couponTemplate.getTakeLimitCount(), CouponTemplateDO.TIME_LIMIT_COUNT_MAX) // 非不限制
+        // 校验领取方式
+        if (ObjUtil.notEqual(couponTemplate.getTakeType(), takeType.getType())) {
+            throw exception(COUPON_TEMPLATE_CANNOT_TAKE);
+        }
+        // 校验发放数量不能过小(仅在 CouponTakeTypeEnum.USER 用户领取时)
+        if (CouponTakeTypeEnum.isUser(couponTemplate.getTakeType())
+                && ObjUtil.notEqual(couponTemplate.getTakeLimitCount(), CouponTemplateDO.TIME_LIMIT_COUNT_MAX) // 非不限制
                 && couponTemplate.getTakeCount() + userIds.size() > couponTemplate.getTotalCount()) {
             throw exception(COUPON_TEMPLATE_NOT_ENOUGH);
         }
@@ -286,10 +291,6 @@ public class CouponServiceImpl implements CouponService {
                 throw exception(COUPON_TEMPLATE_EXPIRED);
             }
         }
-        // 校验领取方式
-        if (ObjectUtil.notEqual(couponTemplate.getTakeType(), takeType.getType())) {
-            throw exception(COUPON_TEMPLATE_CANNOT_TAKE);
-        }
     }
 
     /**

+ 8 - 6
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/aftersale/AppAfterSaleController.java

@@ -1,12 +1,13 @@
 package cn.iocoder.yudao.module.trade.controller.app.aftersale;
 
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
-import cn.iocoder.yudao.framework.common.pojo.PageParam;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSaleCreateReqVO;
 import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSaleDeliveryReqVO;
+import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSalePageReqVO;
 import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSaleRespVO;
-import cn.iocoder.yudao.module.trade.convert.aftersale.AfterSaleConvert;
+import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.AfterSaleDO;
 import cn.iocoder.yudao.module.trade.service.aftersale.AfterSaleService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
@@ -31,16 +32,17 @@ public class AppAfterSaleController {
 
     @GetMapping(value = "/page")
     @Operation(summary = "获得售后分页")
-    public CommonResult<PageResult<AppAfterSaleRespVO>> getAfterSalePage(PageParam pageParam) {
-        return success(AfterSaleConvert.INSTANCE.convertPage02(
-                afterSaleService.getAfterSalePage(getLoginUserId(), pageParam)));
+    public CommonResult<PageResult<AppAfterSaleRespVO>> getAfterSalePage(AppAfterSalePageReqVO pageReqVO) {
+        PageResult<AfterSaleDO> pageResult = afterSaleService.getAfterSalePage(getLoginUserId(), pageReqVO);
+        return success(BeanUtils.toBean(pageResult, AppAfterSaleRespVO.class));
     }
 
     @GetMapping(value = "/get")
     @Operation(summary = "获得售后订单")
     @Parameter(name = "id", description = "售后编号", required = true, example = "1")
     public CommonResult<AppAfterSaleRespVO> getAfterSale(@RequestParam("id") Long id) {
-        return success(AfterSaleConvert.INSTANCE.convert(afterSaleService.getAfterSale(getLoginUserId(), id)));
+        AfterSaleDO afterSale = afterSaleService.getAfterSale(getLoginUserId(), id);
+        return success(BeanUtils.toBean(afterSale, AppAfterSaleRespVO.class));
     }
 
     @PostMapping(value = "/create")

+ 20 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/aftersale/vo/AppAfterSalePageReqVO.java

@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.module.trade.controller.app.aftersale.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.util.Set;
+
+@Schema(description = "用户 App - 交易售后分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class AppAfterSalePageReqVO extends PageParam {
+
+    @Schema(description = "售后状态", example = "10, 20")
+    private Set<Integer> statuses;
+
+}

+ 6 - 2
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/base/spu/AppProductSpuBaseRespVO.java

@@ -3,8 +3,6 @@ package cn.iocoder.yudao.module.trade.controller.app.base.spu;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
-import java.util.List;
-
 /**
  * 商品 SPU 基础 Response VO
  *
@@ -25,4 +23,10 @@ public class AppProductSpuBaseRespVO {
     @Schema(description = "商品分类编号", example = "1")
     private Long categoryId;
 
+    @Schema(description = "商品库存", requiredMode = Schema.RequiredMode.REQUIRED, example = "10000")
+    private Integer stock;
+
+    @Schema(description = "商品状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer status;
+
 }

+ 14 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/delivery/vo/pickup/AppDeliveryPickUpStoreRespVO.java

@@ -1,8 +1,12 @@
 package cn.iocoder.yudao.module.trade.controller.app.delivery.vo.pickup;
 
+import com.fasterxml.jackson.annotation.JsonFormat;
 import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
 import lombok.Data;
 
+import java.time.LocalTime;
+
 @Schema(description = "用户 App - 自提门店 Response VO")
 @Data
 public class AppDeliveryPickUpStoreRespVO {
@@ -28,6 +32,16 @@ public class AppDeliveryPickUpStoreRespVO {
     @Schema(description = "门店详细地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "复旦大学路 188 号")
     private String detailAddress;
 
+    @Schema(description = "营业开始时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "营业开始时间不能为空")
+    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "HH:mm")
+    private LocalTime openingTime;
+
+    @Schema(description = "营业结束时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "营业结束时间不能为空")
+    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "HH:mm")
+    private LocalTime closingTime;
+
     @Schema(description = "纬度", requiredMode = Schema.RequiredMode.REQUIRED, example = "5.88")
     private Double latitude;
 

+ 0 - 5
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/aftersale/AfterSaleConvert.java

@@ -11,7 +11,6 @@ import cn.iocoder.yudao.module.trade.controller.admin.base.member.user.MemberUse
 import cn.iocoder.yudao.module.trade.controller.admin.base.product.property.ProductPropertyValueDetailRespVO;
 import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderBaseVO;
 import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSaleCreateReqVO;
-import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSaleRespVO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.AfterSaleDO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.AfterSaleLogDO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
@@ -63,10 +62,6 @@ public interface AfterSaleConvert {
 
     ProductPropertyValueDetailRespVO convert(ProductPropertyValueDetailRespDTO bean);
 
-    AppAfterSaleRespVO convert(AfterSaleDO bean);
-
-    PageResult<AppAfterSaleRespVO> convertPage02(PageResult<AfterSaleDO> page);
-
     default AfterSaleDetailRespVO convert(AfterSaleDO afterSale, TradeOrderDO order, TradeOrderItemDO orderItem,
                                           MemberUserRespDTO user, List<AfterSaleLogDO> logs) {
         AfterSaleDetailRespVO respVO = convert02(afterSale);

+ 3 - 5
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/cart/TradeCartConvert.java

@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.trade.convert.cart;
 
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
 import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
 import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum;
@@ -33,21 +34,18 @@ public interface TradeCartConvert {
             cartVO.setId(cart.getId()).setCount(cart.getCount()).setSelected(cart.getSelected());
             ProductSpuRespDTO spu = spuMap.get(cart.getSpuId());
             ProductSkuRespDTO sku = skuMap.get(cart.getSkuId());
-            cartVO.setSpu(convert(spu)).setSku(convert(sku));
+            cartVO.setSpu(BeanUtils.toBean(spu, AppProductSpuBaseRespVO.class))
+                    .setSku(BeanUtils.toBean(sku, AppProductSkuBaseRespVO.class));
             // 如果 SPU 不存在,或者下架,或者库存不足,说明是无效的
             if (spu == null
                 || !ProductSpuStatusEnum.isEnable(spu.getStatus())
                 || spu.getStock() <= 0) {
-                cartVO.setSelected(false); // 强制设置成不可选中
                 invalidList.add(cartVO);
             } else {
-                // 虽然 SKU 可能也会不存在,但是可以通过购物车重新选择
                 validList.add(cartVO);
             }
         });
         return new AppCartListRespVO().setValidList(validList).setInvalidList(invalidList);
     }
-    AppProductSpuBaseRespVO convert(ProductSpuRespDTO spu);
-    AppProductSkuBaseRespVO convert(ProductSkuRespDTO sku);
 
 }

+ 5 - 4
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/aftersale/AfterSaleMapper.java

@@ -1,10 +1,10 @@
 package cn.iocoder.yudao.module.trade.dal.mysql.aftersale;
 
-import cn.iocoder.yudao.framework.common.pojo.PageParam;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
 import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.AfterSalePageReqVO;
+import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSalePageReqVO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.AfterSaleDO;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import org.apache.ibatis.annotations.Mapper;
@@ -27,9 +27,10 @@ public interface AfterSaleMapper extends BaseMapperX<AfterSaleDO> {
                 .orderByDesc(AfterSaleDO::getId));
     }
 
-    default PageResult<AfterSaleDO> selectPage(Long userId, PageParam pageParam) {
-        return selectPage(pageParam, new LambdaQueryWrapperX<AfterSaleDO>()
-                .eqIfPresent(AfterSaleDO::getUserId, userId)
+    default PageResult<AfterSaleDO> selectPage(Long userId, AppAfterSalePageReqVO pageReqVO) {
+        return selectPage(pageReqVO, new LambdaQueryWrapperX<AfterSaleDO>()
+                .eq(AfterSaleDO::getUserId, userId)
+                .inIfPresent(AfterSaleDO::getStatus, pageReqVO.getStatuses())
                 .orderByDesc(AfterSaleDO::getId));
     }
 

+ 3 - 3
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleService.java

@@ -1,12 +1,12 @@
 package cn.iocoder.yudao.module.trade.service.aftersale;
 
-import cn.iocoder.yudao.framework.common.pojo.PageParam;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.AfterSaleDisagreeReqVO;
 import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.AfterSalePageReqVO;
 import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.AfterSaleRefuseReqVO;
 import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSaleCreateReqVO;
 import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSaleDeliveryReqVO;
+import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSalePageReqVO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.AfterSaleDO;
 
 /**
@@ -28,10 +28,10 @@ public interface AfterSaleService {
      * 【会员】获得售后订单分页
      *
      * @param userId    用户编号
-     * @param pageParam 分页参数
+     * @param pageReqVO 分页参数
      * @return 售后订单分页
      */
-    PageResult<AfterSaleDO> getAfterSalePage(Long userId, PageParam pageParam);
+    PageResult<AfterSaleDO> getAfterSalePage(Long userId, AppAfterSalePageReqVO pageReqVO);
 
     /**
      * 【会员】获得售后单

+ 5 - 5
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleServiceImpl.java

@@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.trade.service.aftersale;
 import cn.hutool.core.map.MapUtil;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.StrUtil;
-import cn.iocoder.yudao.framework.common.pojo.PageParam;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
 import cn.iocoder.yudao.module.pay.api.refund.PayRefundApi;
@@ -16,6 +15,7 @@ import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.AfterSalePage
 import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.AfterSaleRefuseReqVO;
 import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSaleCreateReqVO;
 import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSaleDeliveryReqVO;
+import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSalePageReqVO;
 import cn.iocoder.yudao.module.trade.convert.aftersale.AfterSaleConvert;
 import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.AfterSaleDO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressDO;
@@ -36,6 +36,7 @@ import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties
 import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressService;
 import cn.iocoder.yudao.module.trade.service.order.TradeOrderQueryService;
 import cn.iocoder.yudao.module.trade.service.order.TradeOrderUpdateService;
+import jakarta.annotation.Resource;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
@@ -44,7 +45,6 @@ import org.springframework.transaction.support.TransactionSynchronization;
 import org.springframework.transaction.support.TransactionSynchronizationManager;
 import org.springframework.validation.annotation.Validated;
 
-import jakarta.annotation.Resource;
 import java.time.LocalDateTime;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -87,8 +87,8 @@ public class AfterSaleServiceImpl implements AfterSaleService {
     }
 
     @Override
-    public PageResult<AfterSaleDO> getAfterSalePage(Long userId, PageParam pageParam) {
-        return tradeAfterSaleMapper.selectPage(userId, pageParam);
+    public PageResult<AfterSaleDO> getAfterSalePage(Long userId, AppAfterSalePageReqVO pageReqVO) {
+        return tradeAfterSaleMapper.selectPage(userId, pageReqVO);
     }
 
     @Override
@@ -386,7 +386,7 @@ public class AfterSaleServiceImpl implements AfterSaleService {
             public void afterCommit() {
                 // 创建退款单
                 PayRefundCreateReqDTO createReqDTO = AfterSaleConvert.INSTANCE.convert(userIp, afterSale, tradeOrderProperties)
-                        .setReason(StrUtil.format("退款【{}】", afterSale.getSpuName()));;
+                        .setReason(StrUtil.format("退款【{}】", afterSale.getSpuName()));
                 Long payRefundId = payRefundApi.createRefund(createReqDTO);
                 // 更新售后单的退款单号
                 tradeAfterSaleMapper.updateById(new AfterSaleDO().setId(afterSale.getId()).setPayRefundId(payRefundId));

+ 18 - 2
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java

@@ -545,6 +545,14 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
         if (ObjectUtil.notEqual(order.getStatus(), TradeOrderStatusEnum.UNPAID.getStatus())) {
             throw exception(ORDER_CANCEL_FAIL_STATUS_NOT_UNPAID);
         }
+        // 1.3 校验是否支持延迟(不允许取消)
+        if (TradeOrderStatusEnum.isUnpaid(order.getStatus())) {
+            PayOrderRespDTO payOrder = payOrderApi.getOrder(order.getPayOrderId());
+            if (payOrder != null && PayOrderStatusEnum.isSuccess(payOrder.getStatus())) {
+                log.warn("[cancelOrderByMember][order({}) 支付单已支付(支付回调延迟),不支持取消]", order.getId());
+                throw exception(ORDER_CANCEL_FAIL_STATUS_NOT_UNPAID);
+            }
+        }
 
         // 2. 取消订单
         cancelOrder0(order, TradeOrderCancelTypeEnum.MEMBER_CANCEL);
@@ -581,6 +589,15 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
     @Transactional(rollbackFor = Exception.class)
     @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.SYSTEM_CANCEL)
     public void cancelOrderBySystem(TradeOrderDO order) {
+        // 校验是否支持延迟(不允许取消)
+        if (TradeOrderStatusEnum.isUnpaid(order.getStatus())) {
+            PayOrderRespDTO payOrder = payOrderApi.getOrder(order.getPayOrderId());
+            if (payOrder != null && PayOrderStatusEnum.isSuccess(payOrder.getStatus())) {
+                log.warn("[cancelOrderBySystem][order({}) 支付单已支付(支付回调延迟),不支持取消]", order.getId());
+                return;
+            }
+        }
+
         cancelOrder0(order, TradeOrderCancelTypeEnum.PAY_TIMEOUT);
     }
 
@@ -895,12 +912,11 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
         if (order == null) {
             throw exception(ORDER_NOT_FOUND);
         }
-
         // 1.3 校验订单是否支付
         if (!order.getPayStatus()) {
             throw exception(ORDER_CANCEL_PAID_FAIL, "已支付");
         }
-        // 1.3 校验订单是否未退款
+        // 1.4 校验订单是否未退款
         if (ObjUtil.notEqual(TradeOrderRefundStatusEnum.NONE.getStatus(), order.getRefundStatus())) {
             throw exception(ORDER_CANCEL_PAID_FAIL, "未退款");
         }

+ 10 - 5
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeBrokerageOrderHandler.java

@@ -20,6 +20,7 @@ import org.springframework.stereotype.Component;
 
 import jakarta.annotation.Resource;
 import java.util.List;
+import java.util.Map;
 
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
 
@@ -101,13 +102,17 @@ public class TradeBrokerageOrderHandler implements TradeOrderHandler {
     protected void addBrokerage(Long userId, List<TradeOrderItemDO> orderItems) {
         MemberUserRespDTO user = memberUserApi.getUser(userId);
         Assert.notNull(user);
-        ProductSpuRespDTO spu = productSpuApi.getSpu(orderItems.get(0).getSpuId());
-        Assert.notNull(spu);
-        ProductSkuRespDTO sku = productSkuApi.getSku(orderItems.get(0).getSkuId());
+        Map<Long, ProductSpuRespDTO> spusMap = productSpuApi.getSpuMap(convertList(orderItems, TradeOrderItemDO::getSpuId));
+        Map<Long, ProductSkuRespDTO> skusMap = productSkuApi.getSkuMap(convertList(orderItems, TradeOrderItemDO::getSkuId));
 
         // 每一个订单项,都会去生成分销记录
-        List<BrokerageAddReqBO> addList = convertList(orderItems,
-                item -> TradeOrderConvert.INSTANCE.convert(user, item, spu, sku));
+        List<BrokerageAddReqBO> addList = convertList(orderItems, item -> {
+            ProductSpuRespDTO spu = spusMap.get(item.getSpuId());
+            Assert.notNull(spu);
+            ProductSkuRespDTO sku = skusMap.get(item.getSkuId());
+            Assert.notNull(sku);
+            return TradeOrderConvert.INSTANCE.convert(user, item, spu, sku);
+        });
         brokerageRecordService.addBrokerage(userId, BrokerageRecordBizTypeEnum.ORDER, addList);
     }
 

+ 1 - 0
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java

@@ -45,6 +45,7 @@ public interface ErrorCodeConstants {
     ErrorCode USER_COUNT_MAX = new ErrorCode(1_002_003_008, "创建用户失败,原因:超过租户最大租户配额({})!");
     ErrorCode USER_IMPORT_INIT_PASSWORD = new ErrorCode(1_002_003_009, "初始密码不能为空");
     ErrorCode USER_MOBILE_NOT_EXISTS = new ErrorCode(1_002_003_010, "该手机号尚未注册");
+    ErrorCode USER_REGISTER_DISABLED = new ErrorCode(1_002_003_011, "注册功能已关闭");
 
     // ========== 部门模块 1-002-004-000 ==========
     ErrorCode DEPT_NAME_DUPLICATE = new ErrorCode(1_002_004_000, "已经存在该名字的部门");

+ 2 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImpl.java

@@ -9,6 +9,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.framework.common.util.date.DateUtils;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
 import cn.iocoder.yudao.framework.tenant.config.TenantProperties;
 import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
 import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
@@ -96,6 +97,7 @@ public class TenantServiceImpl implements TenantService {
 
     @Override
     @DSTransactional // 多数据源,使用 @DSTransactional 保证本地事务,以及数据源的切换
+    @DataPermission(enable = false) // 参见 https://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/1154 说明
     public Long createTenant(TenantSaveReqVO createReqVO) {
         // 校验租户名称是否重复
         validTenantNameDuplicate(createReqVO.getName(), null);

+ 9 - 2
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java

@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.system.service.user;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.collection.CollectionUtil;
 import cn.hutool.core.io.IoUtil;
+import cn.hutool.core.util.ObjUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.exception.ServiceException;
@@ -61,6 +62,8 @@ public class AdminUserServiceImpl implements AdminUserService {
 
     static final String USER_INIT_PASSWORD_KEY = "system.user.init-password";
 
+    static final String USER_REGISTER_ENABLED_KEY = "system.user.register-enabled";
+
     @Resource
     private AdminUserMapper userMapper;
 
@@ -117,14 +120,18 @@ public class AdminUserServiceImpl implements AdminUserService {
 
     @Override
     public Long registerUser(AuthRegisterReqVO registerReqVO) {
-        // 1.1 校验账户配合
+        // 1.1 校验是否开启注册
+        if (ObjUtil.notEqual(configApi.getConfigValueByKey(USER_REGISTER_ENABLED_KEY), "true")) {
+            throw exception(USER_REGISTER_DISABLED);
+        }
+        // 1.2 校验账户配合
         tenantService.handleTenantInfo(tenant -> {
             long count = userMapper.selectCount();
             if (count >= tenant.getAccountCount()) {
                 throw exception(USER_COUNT_MAX, tenant.getAccountCount());
             }
         });
-        // 1.2 校验正确性
+        // 1.3 校验正确性
         validateUserForCreateOrUpdate(null, registerReqVO.getUsername(), null, null, null, null);
 
         // 2. 插入用户

+ 1 - 0
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImplTest.java

@@ -212,6 +212,7 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest {
         UserProfileUpdateReqVO reqVO = randomPojo(UserProfileUpdateReqVO.class, o -> {
             o.setMobile(randomString());
             o.setSex(RandomUtil.randomEle(SexEnum.values()).getSex());
+            o.setAvatar(randomURL());
         });
 
         // 调用