Commit dad0cc4cb1d00840b5df2c80b549e3ac1bafaac2

Authored by YunaiV
2 parents 5a906a7b cb701d7d

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

Showing 30 changed files with 270 additions and 138 deletions
yudao-dependencies/pom.xml
... ... @@ -71,7 +71,7 @@
71 71 <!-- 三方云服务相关 -->
72 72 <commons-io.version>2.17.0</commons-io.version>
73 73 <commons-compress.version>1.27.1</commons-compress.version>
74   - <aws-java-sdk-s3.version>1.12.777</aws-java-sdk-s3.version>
  74 + <awssdk.version>2.30.14</awssdk.version>
75 75 <justauth.version>2.0.5</justauth.version>
76 76 <jimureport.version>1.8.1</jimureport.version>
77 77 <weixin-java.version>4.7.2.B</weixin-java.version>
... ... @@ -553,9 +553,9 @@
553 553  
554 554 <!-- 三方云服务相关 -->
555 555 <dependency>
556   - <groupId>com.amazonaws</groupId>
557   - <artifactId>aws-java-sdk-s3</artifactId>
558   - <version>${aws-java-sdk-s3.version}</version>
  556 + <groupId>software.amazon.awssdk</groupId>
  557 + <artifactId>s3</artifactId>
  558 + <version>${awssdk.version}</version>
559 559 </dependency>
560 560  
561 561 <dependency>
... ...
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java
... ... @@ -11,6 +11,7 @@ import java.util.function.*;
11 11 import java.util.stream.Collectors;
12 12 import java.util.stream.Stream;
13 13  
  14 +import static cn.hutool.core.convert.Convert.toCollection;
14 15 import static java.util.Arrays.asList;
15 16  
16 17 /**
... ... @@ -335,4 +336,17 @@ public class CollectionUtils {
335 336 return list.stream().filter(Objects::nonNull).flatMap(Collection::stream).collect(Collectors.toList());
336 337 }
337 338  
  339 + /**
  340 + * 转换为 LinkedHashSet
  341 + *
  342 + * @param <T> 元素类型
  343 + * @param elementType 集合中元素类型
  344 + * @param value 被转换的值
  345 + * @return {@link LinkedHashSet}
  346 + */
  347 + @SuppressWarnings("unchecked")
  348 + public static <T> LinkedHashSet<T> toLinkedHashSet(Class<T> elementType, Object value) {
  349 + return (LinkedHashSet<T>) toCollection(LinkedHashSet.class, elementType, value);
  350 + }
  351 +
338 352 }
339 353 \ No newline at end of file
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/form/BpmTaskCandidateFormUserStrategy.java
1 1 package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.form;
2 2  
3   -import cn.hutool.core.convert.Convert;
4 3 import cn.hutool.core.lang.Assert;
  4 +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
5 5 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
6 6 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.user.BpmTaskCandidateUserStrategy;
7 7 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
... ... @@ -33,7 +33,7 @@ public class BpmTaskCandidateFormUserStrategy implements BpmTaskCandidateStrateg
33 33 @Override
34 34 public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) {
35 35 Object result = execution.getVariable(param);
36   - return Convert.toSet(Long.class, result);
  36 + return CollectionUtils.toLinkedHashSet(Long.class, result);
37 37 }
38 38  
39 39 @Override
... ... @@ -41,7 +41,7 @@ public class BpmTaskCandidateFormUserStrategy implements BpmTaskCandidateStrateg
41 41 String param, Long startUserId, String processDefinitionId,
42 42 Map<String, Object> processVariables) {
43 43 Object result = processVariables == null ? null : processVariables.get(param);
44   - return Convert.toSet(Long.class, result);
  44 + return CollectionUtils.toLinkedHashSet(Long.class, result);
45 45 }
46 46  
47 47 }
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/other/BpmTaskCandidateExpressionStrategy.java
1 1 package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.other;
2 2  
3   -import cn.hutool.core.convert.Convert;
  3 +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
4 4 import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
5 5 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
6 6 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
... ... @@ -37,7 +37,7 @@ public class BpmTaskCandidateExpressionStrategy implements BpmTaskCandidateStrat
37 37 @Override
38 38 public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) {
39 39 Object result = FlowableUtils.getExpressionValue(execution, param);
40   - return Convert.toSet(Long.class, result);
  40 + return CollectionUtils.toLinkedHashSet(Long.class, result);
41 41 }
42 42  
43 43 @Override
... ... @@ -46,7 +46,7 @@ public class BpmTaskCandidateExpressionStrategy implements BpmTaskCandidateStrat
46 46 Map<String, Object> variables = processVariables == null ? new HashMap<>() : processVariables;
47 47 try {
48 48 Object result = FlowableUtils.getExpressionValue(variables, param);
49   - return Convert.toSet(Long.class, result);
  49 + return CollectionUtils.toLinkedHashSet(Long.class, result);
50 50 } catch (FlowableException ex) {
51 51 // 预测未运行的节点时候,表达式如果包含 execution 或者不存在的流程变量会抛异常,
52 52 log.warn("[calculateUsersByActivity][表达式({}) 变量({}) 解析报错", param, variables, ex);
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java
... ... @@ -6,7 +6,6 @@ import cn.hutool.core.lang.Assert;
6 6 import cn.hutool.core.util.*;
7 7 import cn.hutool.extra.spring.SpringUtil;
8 8 import cn.iocoder.yudao.framework.common.pojo.PageResult;
9   -import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
10 9 import cn.iocoder.yudao.framework.common.util.date.DateUtils;
11 10 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
12 11 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
... ... @@ -882,6 +881,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
882 881 return;
883 882 }
884 883 runExecutionIds.add(task.getExecutionId());
  884 +
885 885 // 判断是否分配给自己任务,因为会签任务,一个节点会有多个任务
886 886 if (isAssignUserTask(userId, task)) { // 情况一:自己的任务,进行 RETURN 标记
887 887 // 2.1.1 添加评论
... ...
yudao-module-infra/yudao-module-infra-biz/pom.xml
... ... @@ -115,9 +115,10 @@
115 115 <groupId>com.jcraft</groupId>
116 116 <artifactId>jsch</artifactId> <!-- 文件客户端:解决 sftp 连接 -->
117 117 </dependency>
  118 + <!-- 文件客户端:解决阿里云、腾讯云、minio 等 S3 连接 -->
118 119 <dependency>
119   - <groupId>com.amazonaws</groupId>
120   - <artifactId>aws-java-sdk-s3</artifactId><!-- 文件客户端:解决阿里云、腾讯云、minio 等 S3 连接 -->
  120 + <groupId>software.amazon.awssdk</groupId>
  121 + <artifactId>s3</artifactId>
121 122 </dependency>
122 123  
123 124 <dependency>
... ...
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;
13 13 public enum CodegenFrontTypeEnum {
14 14  
15 15 VUE2_ELEMENT_UI(10), // Vue2 Element UI 标准模版
  16 +
16 17 VUE3_ELEMENT_PLUS(20), // Vue3 Element Plus 标准模版
  18 +
17 19 VUE3_VBEN2_ANTD_SCHEMA(30), // Vue3 VBEN2 + ANTD + Schema 模版
  20 +
18 21 VUE3_VBEN5_ANTD_SCHEMA(40), // Vue3 VBEN5 + ANTD + schema 模版
19   - VUE3_VBEN5_ANTD(50), // Vue3 VBEN5 + ANTD 模版
  22 + VUE3_VBEN5_ANTD_GENERAL(41), // Vue3 VBEN5 + ANTD 标准模版
20 23 ;
21 24  
22 25 /**
... ...
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/s3/S3FileClient.java
... ... @@ -4,29 +4,31 @@ import cn.hutool.core.io.IoUtil;
4 4 import cn.hutool.core.util.StrUtil;
5 5 import cn.hutool.http.HttpUtil;
6 6 import cn.iocoder.yudao.module.infra.framework.file.core.client.AbstractFileClient;
7   -import com.amazonaws.HttpMethod;
8   -import com.amazonaws.auth.AWSStaticCredentialsProvider;
9   -import com.amazonaws.auth.BasicAWSCredentials;
10   -import com.amazonaws.client.builder.AwsClientBuilder;
11   -import com.amazonaws.services.s3.AmazonS3Client;
12   -import com.amazonaws.services.s3.AmazonS3ClientBuilder;
13   -import com.amazonaws.services.s3.model.ObjectMetadata;
14   -import com.amazonaws.services.s3.model.S3Object;
  7 +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
  8 +import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
  9 +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
  10 +import software.amazon.awssdk.core.sync.RequestBody;
  11 +import software.amazon.awssdk.regions.Region;
  12 +import software.amazon.awssdk.services.s3.S3Client;
  13 +import software.amazon.awssdk.services.s3.S3Configuration;
  14 +import software.amazon.awssdk.services.s3.model.DeleteObjectRequest;
  15 +import software.amazon.awssdk.services.s3.model.GetObjectRequest;
  16 +import software.amazon.awssdk.services.s3.model.PutObjectRequest;
  17 +import software.amazon.awssdk.services.s3.presigner.S3Presigner;
  18 +import software.amazon.awssdk.services.s3.presigner.model.PutObjectPresignRequest;
15 19  
16   -import java.io.ByteArrayInputStream;
17   -import java.util.Date;
18   -import java.util.concurrent.TimeUnit;
  20 +import java.net.URI;
  21 +import java.time.Duration;
19 22  
20 23 /**
21 24 * 基于 S3 协议的文件客户端,实现 MinIO、阿里云、腾讯云、七牛云、华为云等云服务
22   - * <p>
23   - * S3 协议的客户端,采用亚马逊提供的 software.amazon.awssdk.s3 库
24 25 *
25 26 * @author 芋道源码
26 27 */
27 28 public class S3FileClient extends AbstractFileClient<S3FileClientConfig> {
28 29  
29   - private AmazonS3Client client;
  30 + private S3Client client;
  31 + private S3Presigner presigner;
30 32  
31 33 public S3FileClient(Long id, S3FileClientConfig config) {
32 34 super(id, config);
... ... @@ -38,31 +40,80 @@ public class S3FileClient extends AbstractFileClient&lt;S3FileClientConfig&gt; {
38 40 if (StrUtil.isEmpty(config.getDomain())) {
39 41 config.setDomain(buildDomain());
40 42 }
41   - // 初始化客户端
42   - client = (AmazonS3Client)AmazonS3ClientBuilder.standard()
43   - .withCredentials(buildCredentials())
44   - .withEndpointConfiguration(buildEndpointConfiguration())
  43 + // 初始化 S3 客户端
  44 + Region region = Region.of("us-east-1"); // 必须填,但填什么都行,常见的值有 "us-east-1",不填会报错
  45 + AwsCredentialsProvider credentialsProvider = StaticCredentialsProvider.create(
  46 + AwsBasicCredentials.create(config.getAccessKey(), config.getAccessSecret()));
  47 + URI endpoint = URI.create(buildEndpoint());
  48 + S3Configuration serviceConfiguration = S3Configuration.builder() // Path-style 访问
  49 + .pathStyleAccessEnabled(Boolean.TRUE.equals(config.getEnablePathStyleAccess()))
  50 + .chunkedEncodingEnabled(false) // 禁用分块编码,参见 https://t.zsxq.com/kBy57
  51 + .build();
  52 + client = S3Client.builder()
  53 + .credentialsProvider(credentialsProvider)
  54 + .region(region)
  55 + .endpointOverride(endpoint)
  56 + .serviceConfiguration(serviceConfiguration)
  57 + .build();
  58 + presigner = S3Presigner.builder()
  59 + .credentialsProvider(credentialsProvider)
  60 + .region(region)
  61 + .endpointOverride(endpoint)
  62 + .serviceConfiguration(serviceConfiguration)
45 63 .build();
46 64 }
47 65  
48   - /**
49   - * 基于 config 秘钥,构建 S3 客户端的认证信息
50   - *
51   - * @return S3 客户端的认证信息
52   - */
53   - private AWSStaticCredentialsProvider buildCredentials() {
54   - return new AWSStaticCredentialsProvider(
55   - new BasicAWSCredentials(config.getAccessKey(), config.getAccessSecret()));
  66 + @Override
  67 + public String upload(byte[] content, String path, String type) {
  68 + // 构造 PutObjectRequest
  69 + PutObjectRequest putRequest = PutObjectRequest.builder()
  70 + .bucket(config.getBucket())
  71 + .key(path)
  72 + .contentType(type)
  73 + .contentLength((long) content.length)
  74 + .build();
  75 + // 上传文件
  76 + client.putObject(putRequest, RequestBody.fromBytes(content));
  77 + // 拼接返回路径
  78 + return config.getDomain() + "/" + path;
  79 + }
  80 +
  81 + @Override
  82 + public void delete(String path) {
  83 + DeleteObjectRequest deleteRequest = DeleteObjectRequest.builder()
  84 + .bucket(config.getBucket())
  85 + .key(path)
  86 + .build();
  87 + client.deleteObject(deleteRequest);
  88 + }
  89 +
  90 + @Override
  91 + public byte[] getContent(String path) {
  92 + GetObjectRequest getRequest = GetObjectRequest.builder()
  93 + .bucket(config.getBucket())
  94 + .key(path)
  95 + .build();
  96 + return IoUtil.readBytes(client.getObject(getRequest));
  97 + }
  98 +
  99 + @Override
  100 + public FilePresignedUrlRespDTO getPresignedObjectUrl(String path) {
  101 + Duration expiration = Duration.ofHours(24);
  102 + return new FilePresignedUrlRespDTO(getPresignedUrl(path, expiration), config.getDomain() + "/" + path);
56 103 }
57 104  
58 105 /**
59   - * 构建 S3 客户端的 Endpoint 配置,包括 region、endpoint
  106 + * 生成动态的预签名上传 URL
60 107 *
61   - * @return S3 客户端的 EndpointConfiguration 配置
  108 + * @param path 相对路径
  109 + * @param expiration 过期时间
  110 + * @return 生成的上传 URL
62 111 */
63   - private AwsClientBuilder.EndpointConfiguration buildEndpointConfiguration() {
64   - return new AwsClientBuilder.EndpointConfiguration(config.getEndpoint(),
65   - null); // 无需设置 region
  112 + private String getPresignedUrl(String path, Duration expiration) {
  113 + return presigner.presignPutObject(PutObjectPresignRequest.builder()
  114 + .signatureDuration(expiration)
  115 + .putObjectRequest(b -> b.bucket(config.getBucket()).key(path))
  116 + .build()).url().toString();
66 117 }
67 118  
68 119 /**
... ... @@ -79,40 +130,17 @@ public class S3FileClient extends AbstractFileClient&lt;S3FileClientConfig&gt; {
79 130 return StrUtil.format("https://{}.{}", config.getBucket(), config.getEndpoint());
80 131 }
81 132  
82   - @Override
83   - public String upload(byte[] content, String path, String type) throws Exception {
84   - // 元数据,主要用于设置文件类型
85   - ObjectMetadata objectMetadata = new ObjectMetadata();
86   - objectMetadata.setContentType(type);
87   - objectMetadata.setContentLength(content.length); // 如果不设置,会有 “ No content length specified for stream data” 警告日志
88   - // 执行上传
89   - client.putObject(config.getBucket(),
90   - path, // 相对路径
91   - new ByteArrayInputStream(content), // 文件内容
92   - objectMetadata);
93   -
94   - // 拼接返回路径
95   - return config.getDomain() + "/" + path;
96   - }
97   -
98   - @Override
99   - public void delete(String path) throws Exception {
100   - client.deleteObject(config.getBucket(), path);
101   - }
102   -
103   - @Override
104   - public byte[] getContent(String path) throws Exception {
105   - S3Object tempS3Object = client.getObject(config.getBucket(), path);
106   - return IoUtil.readBytes(tempS3Object.getObjectContent());
107   - }
108   -
109   - @Override
110   - public FilePresignedUrlRespDTO getPresignedObjectUrl(String path) throws Exception {
111   - // 设定过期时间为 10 分钟。取值范围:1 秒 ~ 7 天
112   - Date expiration = new Date(System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(10));
113   - // 生成上传 URL
114   - String uploadUrl = String.valueOf(client.generatePresignedUrl(config.getBucket(), path, expiration , HttpMethod.PUT));
115   - return new FilePresignedUrlRespDTO(uploadUrl, config.getDomain() + "/" + path);
  133 + /**
  134 + * 节点地址补全协议头
  135 + *
  136 + * @return 节点地址
  137 + */
  138 + private String buildEndpoint() {
  139 + // 如果已经是 http 或者 https,则不进行拼接
  140 + if (HttpUtil.isHttp(config.getEndpoint()) || HttpUtil.isHttps(config.getEndpoint())) {
  141 + return config.getEndpoint();
  142 + }
  143 + return StrUtil.format("https://{}", config.getEndpoint());
116 144 }
117 145  
118 146 }
... ...
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/s3/S3FileClientConfig.java
... ... @@ -67,6 +67,12 @@ public class S3FileClientConfig implements FileClientConfig {
67 67 @NotNull(message = "accessSecret 不能为空")
68 68 private String accessSecret;
69 69  
  70 + /**
  71 + * 是否启用 PathStyle 访问
  72 + */
  73 + @NotNull(message = "enablePathStyleAccess 不能为空")
  74 + private Boolean enablePathStyleAccess;
  75 +
70 76 @SuppressWarnings("RedundantIfStatement")
71 77 @AssertTrue(message = "domain 不能为空")
72 78 @JsonIgnore
... ...
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 {
164 164 .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_SCHEMA.getType(), vue3Vben5AntdSchemaTemplatePath("views/modules/list_sub_erp.vue"), // 特殊:主子表专属逻辑
165 165 vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/${subSimpleClassName_strikeCase}-list.vue"))
166 166 // VUE3_VBEN5_ANTD
167   - .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD.getType(), vue3Vben5AntdGeneralTemplatePath("views/data.ts"),
  167 + .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_GENERAL.getType(), vue3Vben5AntdGeneralTemplatePath("views/data.ts"),
168 168 vue3FilePath("views/${table.moduleName}/${table.businessName}/data.ts"))
169   - .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD.getType(), vue3Vben5AntdGeneralTemplatePath("views/index.vue"),
  169 + .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_GENERAL.getType(), vue3Vben5AntdGeneralTemplatePath("views/index.vue"),
170 170 vue3FilePath("views/${table.moduleName}/${table.businessName}/index.vue"))
171   - .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD.getType(), vue3Vben5AntdGeneralTemplatePath("views/form.vue"),
  171 + .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_GENERAL.getType(), vue3Vben5AntdGeneralTemplatePath("views/form.vue"),
172 172 vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/form.vue"))
173   - .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD.getType(), vue3Vben5AntdGeneralTemplatePath("api/api.ts"),
  173 + .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_GENERAL.getType(), vue3Vben5AntdGeneralTemplatePath("api/api.ts"),
174 174 vue3FilePath("api/${table.moduleName}/${table.businessName}/index.ts"))
175   - .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD.getType(), vue3Vben5AntdGeneralTemplatePath("views/modules/form_sub_normal.vue"), // 特殊:主子表专属逻辑
  175 + .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_GENERAL.getType(), vue3Vben5AntdGeneralTemplatePath("views/modules/form_sub_normal.vue"), // 特殊:主子表专属逻辑
176 176 vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/${subSimpleClassName_strikeCase}-form.vue"))
177   - .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD.getType(), vue3Vben5AntdGeneralTemplatePath("views/modules/form_sub_inner.vue"), // 特殊:主子表专属逻辑
  177 + .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_GENERAL.getType(), vue3Vben5AntdGeneralTemplatePath("views/modules/form_sub_inner.vue"), // 特殊:主子表专属逻辑
178 178 vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/${subSimpleClassName_strikeCase}-form.vue"))
179   - .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD.getType(), vue3Vben5AntdGeneralTemplatePath("views/modules/form_sub_erp.vue"), // 特殊:主子表专属逻辑
  179 + .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_GENERAL.getType(), vue3Vben5AntdGeneralTemplatePath("views/modules/form_sub_erp.vue"), // 特殊:主子表专属逻辑
180 180 vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/${subSimpleClassName_strikeCase}-form.vue"))
181   - .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD.getType(), vue3Vben5AntdGeneralTemplatePath("views/modules/list_sub_inner.vue"), // 特殊:主子表专属逻辑
  181 + .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_GENERAL.getType(), vue3Vben5AntdGeneralTemplatePath("views/modules/list_sub_inner.vue"), // 特殊:主子表专属逻辑
182 182 vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/${subSimpleClassName_strikeCase}-list.vue"))
183   - .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD.getType(), vue3Vben5AntdGeneralTemplatePath("views/modules/list_sub_erp.vue"), // 特殊:主子表专属逻辑
  183 + .put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_GENERAL.getType(), vue3Vben5AntdGeneralTemplatePath("views/modules/list_sub_erp.vue"), // 特殊:主子表专属逻辑
184 184 vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/${subSimpleClassName_strikeCase}-list.vue"))
185 185 .build();
186 186  
... ...
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/ProductSkuApi.java
... ... @@ -5,6 +5,9 @@ import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuUpdateStockReqDTO;
5 5  
6 6 import java.util.Collection;
7 7 import java.util.List;
  8 +import java.util.Map;
  9 +
  10 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
8 11  
9 12 /**
10 13 * 商品 SKU API 接口
... ... @@ -31,6 +34,16 @@ public interface ProductSkuApi {
31 34 List<ProductSkuRespDTO> getSkuList(Collection<Long> ids);
32 35  
33 36 /**
  37 + * 批量查询 SKU MAP
  38 + *
  39 + * @param ids SKU 编号列表
  40 + * @return SKU MAP
  41 + */
  42 + default Map<Long, ProductSkuRespDTO> getSkuMap(Collection<Long> ids) {
  43 + return convertMap(getSkuList(ids), ProductSkuRespDTO::getId);
  44 + }
  45 +
  46 + /**
34 47 * 批量查询 SKU 数组
35 48 *
36 49 * @param spuIds SPU 编号列表
... ...
yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/spu/ProductSpuApi.java
... ... @@ -30,7 +30,7 @@ public interface ProductSpuApi {
30 30 * @param ids SPU 编号列表
31 31 * @return SPU MAP
32 32 */
33   - default Map<Long, ProductSpuRespDTO> getSpusMap(Collection<Long> ids) {
  33 + default Map<Long, ProductSpuRespDTO> getSpuMap(Collection<Long> ids) {
34 34 return convertMap(getSpuList(ids), ProductSpuRespDTO::getId);
35 35 }
36 36  
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/point/PointActivityController.java
... ... @@ -124,7 +124,7 @@ public class PointActivityController {
124 124 List<PointProductDO> products = pointActivityService.getPointProductListByActivityIds(
125 125 convertSet(activityList, PointActivityDO::getId));
126 126 Map<Long, List<PointProductDO>> productsMap = convertMultiMap(products, PointProductDO::getActivityId);
127   - Map<Long, ProductSpuRespDTO> spuMap = productSpuApi.getSpusMap(
  127 + Map<Long, ProductSpuRespDTO> spuMap = productSpuApi.getSpuMap(
128 128 convertSet(activityList, PointActivityDO::getSpuId));
129 129 List<PointActivityRespVO> result = BeanUtils.toBean(activityList, PointActivityRespVO.class);
130 130 result.forEach(activity -> {
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/point/AppPointActivityController.java
... ... @@ -104,7 +104,7 @@ public class AppPointActivityController {
104 104 List<PointProductDO> products = pointActivityService.getPointProductListByActivityIds(
105 105 convertSet(activityList, PointActivityDO::getId));
106 106 Map<Long, List<PointProductDO>> productsMap = convertMultiMap(products, PointProductDO::getActivityId);
107   - Map<Long, ProductSpuRespDTO> spuMap = productSpuApi.getSpusMap(
  107 + Map<Long, ProductSpuRespDTO> spuMap = productSpuApi.getSpuMap(
108 108 convertSet(activityList, PointActivityDO::getSpuId));
109 109 List<AppPointActivityRespVO> result = BeanUtils.toBean(activityList, AppPointActivityRespVO.class);
110 110 result.forEach(activity -> {
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java
... ... @@ -22,6 +22,7 @@ import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTemplateValidityType
22 22 import jakarta.annotation.Resource;
23 23 import lombok.extern.slf4j.Slf4j;
24 24 import org.springframework.stereotype.Service;
  25 +import org.springframework.transaction.annotation.Propagation;
25 26 import org.springframework.transaction.annotation.Transactional;
26 27 import org.springframework.validation.annotation.Validated;
27 28  
... ... @@ -180,7 +181,7 @@ public class CouponServiceImpl implements CouponService {
180 181 * @param couponId 模版编号
181 182 * @param userId 用户编号
182 183 */
183   - @Transactional(rollbackFor = Exception.class)
  184 + @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW) // 每次调用开启一个新的事务,避免在一个大的事务里面
184 185 public void invalidateCoupon(Long couponId, Long userId) {
185 186 if (couponId == null || couponId <= 0) {
186 187 return;
... ... @@ -270,13 +271,17 @@ public class CouponServiceImpl implements CouponService {
270 271 if (CollUtil.isEmpty(userIds)) {
271 272 throw exception(COUPON_TEMPLATE_USER_ALREADY_TAKE);
272 273 }
273   -
274 274 // 校验模板
275 275 if (couponTemplate == null) {
276 276 throw exception(COUPON_TEMPLATE_NOT_EXISTS);
277 277 }
278   - // 校验剩余数量
279   - if (ObjUtil.notEqual(couponTemplate.getTakeLimitCount(), CouponTemplateDO.TIME_LIMIT_COUNT_MAX) // 非不限制
  278 + // 校验领取方式
  279 + if (ObjUtil.notEqual(couponTemplate.getTakeType(), takeType.getType())) {
  280 + throw exception(COUPON_TEMPLATE_CANNOT_TAKE);
  281 + }
  282 + // 校验发放数量不能过小(仅在 CouponTakeTypeEnum.USER 用户领取时)
  283 + if (CouponTakeTypeEnum.isUser(couponTemplate.getTakeType())
  284 + && ObjUtil.notEqual(couponTemplate.getTakeLimitCount(), CouponTemplateDO.TIME_LIMIT_COUNT_MAX) // 非不限制
280 285 && couponTemplate.getTakeCount() + userIds.size() > couponTemplate.getTotalCount()) {
281 286 throw exception(COUPON_TEMPLATE_NOT_ENOUGH);
282 287 }
... ... @@ -286,10 +291,6 @@ public class CouponServiceImpl implements CouponService {
286 291 throw exception(COUPON_TEMPLATE_EXPIRED);
287 292 }
288 293 }
289   - // 校验领取方式
290   - if (ObjectUtil.notEqual(couponTemplate.getTakeType(), takeType.getType())) {
291   - throw exception(COUPON_TEMPLATE_CANNOT_TAKE);
292   - }
293 294 }
294 295  
295 296 /**
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/aftersale/AppAfterSaleController.java
1 1 package cn.iocoder.yudao.module.trade.controller.app.aftersale;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4   -import cn.iocoder.yudao.framework.common.pojo.PageParam;
5 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  5 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
6 6 import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSaleCreateReqVO;
7 7 import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSaleDeliveryReqVO;
  8 +import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSalePageReqVO;
8 9 import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSaleRespVO;
9   -import cn.iocoder.yudao.module.trade.convert.aftersale.AfterSaleConvert;
  10 +import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.AfterSaleDO;
10 11 import cn.iocoder.yudao.module.trade.service.aftersale.AfterSaleService;
11 12 import io.swagger.v3.oas.annotations.Operation;
12 13 import io.swagger.v3.oas.annotations.Parameter;
... ... @@ -31,16 +32,17 @@ public class AppAfterSaleController {
31 32  
32 33 @GetMapping(value = "/page")
33 34 @Operation(summary = "获得售后分页")
34   - public CommonResult<PageResult<AppAfterSaleRespVO>> getAfterSalePage(PageParam pageParam) {
35   - return success(AfterSaleConvert.INSTANCE.convertPage02(
36   - afterSaleService.getAfterSalePage(getLoginUserId(), pageParam)));
  35 + public CommonResult<PageResult<AppAfterSaleRespVO>> getAfterSalePage(AppAfterSalePageReqVO pageReqVO) {
  36 + PageResult<AfterSaleDO> pageResult = afterSaleService.getAfterSalePage(getLoginUserId(), pageReqVO);
  37 + return success(BeanUtils.toBean(pageResult, AppAfterSaleRespVO.class));
37 38 }
38 39  
39 40 @GetMapping(value = "/get")
40 41 @Operation(summary = "获得售后订单")
41 42 @Parameter(name = "id", description = "售后编号", required = true, example = "1")
42 43 public CommonResult<AppAfterSaleRespVO> getAfterSale(@RequestParam("id") Long id) {
43   - return success(AfterSaleConvert.INSTANCE.convert(afterSaleService.getAfterSale(getLoginUserId(), id)));
  44 + AfterSaleDO afterSale = afterSaleService.getAfterSale(getLoginUserId(), id);
  45 + return success(BeanUtils.toBean(afterSale, AppAfterSaleRespVO.class));
44 46 }
45 47  
46 48 @PostMapping(value = "/create")
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/aftersale/vo/AppAfterSalePageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.trade.controller.app.aftersale.vo;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +import lombok.EqualsAndHashCode;
  7 +import lombok.ToString;
  8 +
  9 +import java.util.Set;
  10 +
  11 +@Schema(description = "用户 App - 交易售后分页 Request VO")
  12 +@Data
  13 +@EqualsAndHashCode(callSuper = true)
  14 +@ToString(callSuper = true)
  15 +public class AppAfterSalePageReqVO extends PageParam {
  16 +
  17 + @Schema(description = "售后状态", example = "10, 20")
  18 + private Set<Integer> statuses;
  19 +
  20 +}
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/base/spu/AppProductSpuBaseRespVO.java
... ... @@ -3,8 +3,6 @@ package cn.iocoder.yudao.module.trade.controller.app.base.spu;
3 3 import io.swagger.v3.oas.annotations.media.Schema;
4 4 import lombok.Data;
5 5  
6   -import java.util.List;
7   -
8 6 /**
9 7 * 商品 SPU 基础 Response VO
10 8 *
... ... @@ -25,4 +23,10 @@ public class AppProductSpuBaseRespVO {
25 23 @Schema(description = "商品分类编号", example = "1")
26 24 private Long categoryId;
27 25  
  26 + @Schema(description = "商品库存", requiredMode = Schema.RequiredMode.REQUIRED, example = "10000")
  27 + private Integer stock;
  28 +
  29 + @Schema(description = "商品状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  30 + private Integer status;
  31 +
28 32 }
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/delivery/vo/pickup/AppDeliveryPickUpStoreRespVO.java
1 1 package cn.iocoder.yudao.module.trade.controller.app.delivery.vo.pickup;
2 2  
  3 +import com.fasterxml.jackson.annotation.JsonFormat;
3 4 import io.swagger.v3.oas.annotations.media.Schema;
  5 +import jakarta.validation.constraints.NotNull;
4 6 import lombok.Data;
5 7  
  8 +import java.time.LocalTime;
  9 +
6 10 @Schema(description = "用户 App - 自提门店 Response VO")
7 11 @Data
8 12 public class AppDeliveryPickUpStoreRespVO {
... ... @@ -28,6 +32,16 @@ public class AppDeliveryPickUpStoreRespVO {
28 32 @Schema(description = "门店详细地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "复旦大学路 188 号")
29 33 private String detailAddress;
30 34  
  35 + @Schema(description = "营业开始时间", requiredMode = Schema.RequiredMode.REQUIRED)
  36 + @NotNull(message = "营业开始时间不能为空")
  37 + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "HH:mm")
  38 + private LocalTime openingTime;
  39 +
  40 + @Schema(description = "营业结束时间", requiredMode = Schema.RequiredMode.REQUIRED)
  41 + @NotNull(message = "营业结束时间不能为空")
  42 + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "HH:mm")
  43 + private LocalTime closingTime;
  44 +
31 45 @Schema(description = "纬度", requiredMode = Schema.RequiredMode.REQUIRED, example = "5.88")
32 46 private Double latitude;
33 47  
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/aftersale/AfterSaleConvert.java
... ... @@ -11,7 +11,6 @@ import cn.iocoder.yudao.module.trade.controller.admin.base.member.user.MemberUse
11 11 import cn.iocoder.yudao.module.trade.controller.admin.base.product.property.ProductPropertyValueDetailRespVO;
12 12 import cn.iocoder.yudao.module.trade.controller.admin.order.vo.TradeOrderBaseVO;
13 13 import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSaleCreateReqVO;
14   -import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSaleRespVO;
15 14 import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.AfterSaleDO;
16 15 import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.AfterSaleLogDO;
17 16 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
... ... @@ -63,10 +62,6 @@ public interface AfterSaleConvert {
63 62  
64 63 ProductPropertyValueDetailRespVO convert(ProductPropertyValueDetailRespDTO bean);
65 64  
66   - AppAfterSaleRespVO convert(AfterSaleDO bean);
67   -
68   - PageResult<AppAfterSaleRespVO> convertPage02(PageResult<AfterSaleDO> page);
69   -
70 65 default AfterSaleDetailRespVO convert(AfterSaleDO afterSale, TradeOrderDO order, TradeOrderItemDO orderItem,
71 66 MemberUserRespDTO user, List<AfterSaleLogDO> logs) {
72 67 AfterSaleDetailRespVO respVO = convert02(afterSale);
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/cart/TradeCartConvert.java
1 1 package cn.iocoder.yudao.module.trade.convert.cart;
2 2  
  3 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
3 4 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
4 5 import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
5 6 import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum;
... ... @@ -33,21 +34,18 @@ public interface TradeCartConvert {
33 34 cartVO.setId(cart.getId()).setCount(cart.getCount()).setSelected(cart.getSelected());
34 35 ProductSpuRespDTO spu = spuMap.get(cart.getSpuId());
35 36 ProductSkuRespDTO sku = skuMap.get(cart.getSkuId());
36   - cartVO.setSpu(convert(spu)).setSku(convert(sku));
  37 + cartVO.setSpu(BeanUtils.toBean(spu, AppProductSpuBaseRespVO.class))
  38 + .setSku(BeanUtils.toBean(sku, AppProductSkuBaseRespVO.class));
37 39 // 如果 SPU 不存在,或者下架,或者库存不足,说明是无效的
38 40 if (spu == null
39 41 || !ProductSpuStatusEnum.isEnable(spu.getStatus())
40 42 || spu.getStock() <= 0) {
41   - cartVO.setSelected(false); // 强制设置成不可选中
42 43 invalidList.add(cartVO);
43 44 } else {
44   - // 虽然 SKU 可能也会不存在,但是可以通过购物车重新选择
45 45 validList.add(cartVO);
46 46 }
47 47 });
48 48 return new AppCartListRespVO().setValidList(validList).setInvalidList(invalidList);
49 49 }
50   - AppProductSpuBaseRespVO convert(ProductSpuRespDTO spu);
51   - AppProductSkuBaseRespVO convert(ProductSkuRespDTO sku);
52 50  
53 51 }
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/aftersale/AfterSaleMapper.java
1 1 package cn.iocoder.yudao.module.trade.dal.mysql.aftersale;
2 2  
3   -import cn.iocoder.yudao.framework.common.pojo.PageParam;
4 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
6 5 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
7 6 import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.AfterSalePageReqVO;
  7 +import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSalePageReqVO;
8 8 import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.AfterSaleDO;
9 9 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
10 10 import org.apache.ibatis.annotations.Mapper;
... ... @@ -27,9 +27,10 @@ public interface AfterSaleMapper extends BaseMapperX&lt;AfterSaleDO&gt; {
27 27 .orderByDesc(AfterSaleDO::getId));
28 28 }
29 29  
30   - default PageResult<AfterSaleDO> selectPage(Long userId, PageParam pageParam) {
31   - return selectPage(pageParam, new LambdaQueryWrapperX<AfterSaleDO>()
32   - .eqIfPresent(AfterSaleDO::getUserId, userId)
  30 + default PageResult<AfterSaleDO> selectPage(Long userId, AppAfterSalePageReqVO pageReqVO) {
  31 + return selectPage(pageReqVO, new LambdaQueryWrapperX<AfterSaleDO>()
  32 + .eq(AfterSaleDO::getUserId, userId)
  33 + .inIfPresent(AfterSaleDO::getStatus, pageReqVO.getStatuses())
33 34 .orderByDesc(AfterSaleDO::getId));
34 35 }
35 36  
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleService.java
1 1 package cn.iocoder.yudao.module.trade.service.aftersale;
2 2  
3   -import cn.iocoder.yudao.framework.common.pojo.PageParam;
4 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 4 import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.AfterSaleDisagreeReqVO;
6 5 import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.AfterSalePageReqVO;
7 6 import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.AfterSaleRefuseReqVO;
8 7 import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSaleCreateReqVO;
9 8 import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSaleDeliveryReqVO;
  9 +import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSalePageReqVO;
10 10 import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.AfterSaleDO;
11 11  
12 12 /**
... ... @@ -28,10 +28,10 @@ public interface AfterSaleService {
28 28 * 【会员】获得售后订单分页
29 29 *
30 30 * @param userId 用户编号
31   - * @param pageParam 分页参数
  31 + * @param pageReqVO 分页参数
32 32 * @return 售后订单分页
33 33 */
34   - PageResult<AfterSaleDO> getAfterSalePage(Long userId, PageParam pageParam);
  34 + PageResult<AfterSaleDO> getAfterSalePage(Long userId, AppAfterSalePageReqVO pageReqVO);
35 35  
36 36 /**
37 37 * 【会员】获得售后单
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleServiceImpl.java
... ... @@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.trade.service.aftersale;
3 3 import cn.hutool.core.map.MapUtil;
4 4 import cn.hutool.core.util.ObjectUtil;
5 5 import cn.hutool.core.util.StrUtil;
6   -import cn.iocoder.yudao.framework.common.pojo.PageParam;
7 6 import cn.iocoder.yudao.framework.common.pojo.PageResult;
8 7 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
9 8 import cn.iocoder.yudao.module.pay.api.refund.PayRefundApi;
... ... @@ -16,6 +15,7 @@ import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.AfterSalePage
16 15 import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.AfterSaleRefuseReqVO;
17 16 import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSaleCreateReqVO;
18 17 import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSaleDeliveryReqVO;
  18 +import cn.iocoder.yudao.module.trade.controller.app.aftersale.vo.AppAfterSalePageReqVO;
19 19 import cn.iocoder.yudao.module.trade.convert.aftersale.AfterSaleConvert;
20 20 import cn.iocoder.yudao.module.trade.dal.dataobject.aftersale.AfterSaleDO;
21 21 import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressDO;
... ... @@ -36,6 +36,7 @@ import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties
36 36 import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressService;
37 37 import cn.iocoder.yudao.module.trade.service.order.TradeOrderQueryService;
38 38 import cn.iocoder.yudao.module.trade.service.order.TradeOrderUpdateService;
  39 +import jakarta.annotation.Resource;
39 40 import lombok.extern.slf4j.Slf4j;
40 41 import org.springframework.context.annotation.Lazy;
41 42 import org.springframework.stereotype.Service;
... ... @@ -44,7 +45,6 @@ import org.springframework.transaction.support.TransactionSynchronization;
44 45 import org.springframework.transaction.support.TransactionSynchronizationManager;
45 46 import org.springframework.validation.annotation.Validated;
46 47  
47   -import jakarta.annotation.Resource;
48 48 import java.time.LocalDateTime;
49 49  
50 50 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
... ... @@ -87,8 +87,8 @@ public class AfterSaleServiceImpl implements AfterSaleService {
87 87 }
88 88  
89 89 @Override
90   - public PageResult<AfterSaleDO> getAfterSalePage(Long userId, PageParam pageParam) {
91   - return tradeAfterSaleMapper.selectPage(userId, pageParam);
  90 + public PageResult<AfterSaleDO> getAfterSalePage(Long userId, AppAfterSalePageReqVO pageReqVO) {
  91 + return tradeAfterSaleMapper.selectPage(userId, pageReqVO);
92 92 }
93 93  
94 94 @Override
... ... @@ -386,7 +386,7 @@ public class AfterSaleServiceImpl implements AfterSaleService {
386 386 public void afterCommit() {
387 387 // 创建退款单
388 388 PayRefundCreateReqDTO createReqDTO = AfterSaleConvert.INSTANCE.convert(userIp, afterSale, tradeOrderProperties)
389   - .setReason(StrUtil.format("退款【{}】", afterSale.getSpuName()));;
  389 + .setReason(StrUtil.format("退款【{}】", afterSale.getSpuName()));
390 390 Long payRefundId = payRefundApi.createRefund(createReqDTO);
391 391 // 更新售后单的退款单号
392 392 tradeAfterSaleMapper.updateById(new AfterSaleDO().setId(afterSale.getId()).setPayRefundId(payRefundId));
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java
... ... @@ -545,6 +545,14 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
545 545 if (ObjectUtil.notEqual(order.getStatus(), TradeOrderStatusEnum.UNPAID.getStatus())) {
546 546 throw exception(ORDER_CANCEL_FAIL_STATUS_NOT_UNPAID);
547 547 }
  548 + // 1.3 校验是否支持延迟(不允许取消)
  549 + if (TradeOrderStatusEnum.isUnpaid(order.getStatus())) {
  550 + PayOrderRespDTO payOrder = payOrderApi.getOrder(order.getPayOrderId());
  551 + if (payOrder != null && PayOrderStatusEnum.isSuccess(payOrder.getStatus())) {
  552 + log.warn("[cancelOrderByMember][order({}) 支付单已支付(支付回调延迟),不支持取消]", order.getId());
  553 + throw exception(ORDER_CANCEL_FAIL_STATUS_NOT_UNPAID);
  554 + }
  555 + }
548 556  
549 557 // 2. 取消订单
550 558 cancelOrder0(order, TradeOrderCancelTypeEnum.MEMBER_CANCEL);
... ... @@ -581,6 +589,15 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
581 589 @Transactional(rollbackFor = Exception.class)
582 590 @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.SYSTEM_CANCEL)
583 591 public void cancelOrderBySystem(TradeOrderDO order) {
  592 + // 校验是否支持延迟(不允许取消)
  593 + if (TradeOrderStatusEnum.isUnpaid(order.getStatus())) {
  594 + PayOrderRespDTO payOrder = payOrderApi.getOrder(order.getPayOrderId());
  595 + if (payOrder != null && PayOrderStatusEnum.isSuccess(payOrder.getStatus())) {
  596 + log.warn("[cancelOrderBySystem][order({}) 支付单已支付(支付回调延迟),不支持取消]", order.getId());
  597 + return;
  598 + }
  599 + }
  600 +
584 601 cancelOrder0(order, TradeOrderCancelTypeEnum.PAY_TIMEOUT);
585 602 }
586 603  
... ... @@ -895,12 +912,11 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
895 912 if (order == null) {
896 913 throw exception(ORDER_NOT_FOUND);
897 914 }
898   -
899 915 // 1.3 校验订单是否支付
900 916 if (!order.getPayStatus()) {
901 917 throw exception(ORDER_CANCEL_PAID_FAIL, "已支付");
902 918 }
903   - // 1.3 校验订单是否未退款
  919 + // 1.4 校验订单是否未退款
904 920 if (ObjUtil.notEqual(TradeOrderRefundStatusEnum.NONE.getStatus(), order.getRefundStatus())) {
905 921 throw exception(ORDER_CANCEL_PAID_FAIL, "未退款");
906 922 }
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeBrokerageOrderHandler.java
... ... @@ -20,6 +20,7 @@ import org.springframework.stereotype.Component;
20 20  
21 21 import jakarta.annotation.Resource;
22 22 import java.util.List;
  23 +import java.util.Map;
23 24  
24 25 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
25 26  
... ... @@ -101,13 +102,17 @@ public class TradeBrokerageOrderHandler implements TradeOrderHandler {
101 102 protected void addBrokerage(Long userId, List<TradeOrderItemDO> orderItems) {
102 103 MemberUserRespDTO user = memberUserApi.getUser(userId);
103 104 Assert.notNull(user);
104   - ProductSpuRespDTO spu = productSpuApi.getSpu(orderItems.get(0).getSpuId());
105   - Assert.notNull(spu);
106   - ProductSkuRespDTO sku = productSkuApi.getSku(orderItems.get(0).getSkuId());
  105 + Map<Long, ProductSpuRespDTO> spusMap = productSpuApi.getSpuMap(convertList(orderItems, TradeOrderItemDO::getSpuId));
  106 + Map<Long, ProductSkuRespDTO> skusMap = productSkuApi.getSkuMap(convertList(orderItems, TradeOrderItemDO::getSkuId));
107 107  
108 108 // 每一个订单项,都会去生成分销记录
109   - List<BrokerageAddReqBO> addList = convertList(orderItems,
110   - item -> TradeOrderConvert.INSTANCE.convert(user, item, spu, sku));
  109 + List<BrokerageAddReqBO> addList = convertList(orderItems, item -> {
  110 + ProductSpuRespDTO spu = spusMap.get(item.getSpuId());
  111 + Assert.notNull(spu);
  112 + ProductSkuRespDTO sku = skusMap.get(item.getSkuId());
  113 + Assert.notNull(sku);
  114 + return TradeOrderConvert.INSTANCE.convert(user, item, spu, sku);
  115 + });
111 116 brokerageRecordService.addBrokerage(userId, BrokerageRecordBizTypeEnum.ORDER, addList);
112 117 }
113 118  
... ...
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java
... ... @@ -45,6 +45,7 @@ public interface ErrorCodeConstants {
45 45 ErrorCode USER_COUNT_MAX = new ErrorCode(1_002_003_008, "创建用户失败,原因:超过租户最大租户配额({})!");
46 46 ErrorCode USER_IMPORT_INIT_PASSWORD = new ErrorCode(1_002_003_009, "初始密码不能为空");
47 47 ErrorCode USER_MOBILE_NOT_EXISTS = new ErrorCode(1_002_003_010, "该手机号尚未注册");
  48 + ErrorCode USER_REGISTER_DISABLED = new ErrorCode(1_002_003_011, "注册功能已关闭");
48 49  
49 50 // ========== 部门模块 1-002-004-000 ==========
50 51 ErrorCode DEPT_NAME_DUPLICATE = new ErrorCode(1_002_004_000, "已经存在该名字的部门");
... ...
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;
9 9 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
10 10 import cn.iocoder.yudao.framework.common.util.date.DateUtils;
11 11 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  12 +import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
12 13 import cn.iocoder.yudao.framework.tenant.config.TenantProperties;
13 14 import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
14 15 import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
... ... @@ -96,6 +97,7 @@ public class TenantServiceImpl implements TenantService {
96 97  
97 98 @Override
98 99 @DSTransactional // 多数据源,使用 @DSTransactional 保证本地事务,以及数据源的切换
  100 + @DataPermission(enable = false) // 参见 https://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/1154 说明
99 101 public Long createTenant(TenantSaveReqVO createReqVO) {
100 102 // 校验租户名称是否重复
101 103 validTenantNameDuplicate(createReqVO.getName(), null);
... ...
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;
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.collection.CollectionUtil;
5 5 import cn.hutool.core.io.IoUtil;
  6 +import cn.hutool.core.util.ObjUtil;
6 7 import cn.hutool.core.util.StrUtil;
7 8 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
8 9 import cn.iocoder.yudao.framework.common.exception.ServiceException;
... ... @@ -61,6 +62,8 @@ public class AdminUserServiceImpl implements AdminUserService {
61 62  
62 63 static final String USER_INIT_PASSWORD_KEY = "system.user.init-password";
63 64  
  65 + static final String USER_REGISTER_ENABLED_KEY = "system.user.register-enabled";
  66 +
64 67 @Resource
65 68 private AdminUserMapper userMapper;
66 69  
... ... @@ -117,14 +120,18 @@ public class AdminUserServiceImpl implements AdminUserService {
117 120  
118 121 @Override
119 122 public Long registerUser(AuthRegisterReqVO registerReqVO) {
120   - // 1.1 校验账户配合
  123 + // 1.1 校验是否开启注册
  124 + if (ObjUtil.notEqual(configApi.getConfigValueByKey(USER_REGISTER_ENABLED_KEY), "true")) {
  125 + throw exception(USER_REGISTER_DISABLED);
  126 + }
  127 + // 1.2 校验账户配合
121 128 tenantService.handleTenantInfo(tenant -> {
122 129 long count = userMapper.selectCount();
123 130 if (count >= tenant.getAccountCount()) {
124 131 throw exception(USER_COUNT_MAX, tenant.getAccountCount());
125 132 }
126 133 });
127   - // 1.2 校验正确性
  134 + // 1.3 校验正确性
128 135 validateUserForCreateOrUpdate(null, registerReqVO.getUsername(), null, null, null, null);
129 136  
130 137 // 2. 插入用户
... ...
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 {
212 212 UserProfileUpdateReqVO reqVO = randomPojo(UserProfileUpdateReqVO.class, o -> {
213 213 o.setMobile(randomString());
214 214 o.setSex(RandomUtil.randomEle(SexEnum.values()).getSex());
  215 + o.setAvatar(randomURL());
215 216 });
216 217  
217 218 // 调用
... ...