diff --git a/yudao-dependencies/pom.xml b/yudao-dependencies/pom.xml index 270bdc7015..c9ebf24e58 100644 --- a/yudao-dependencies/pom.xml +++ b/yudao-dependencies/pom.xml @@ -71,7 +71,7 @@ 2.17.0 1.27.1 - 1.12.777 + 2.30.14 2.0.5 1.8.1 4.7.2.B @@ -553,9 +553,9 @@ - com.amazonaws - aws-java-sdk-s3 - ${aws-java-sdk-s3.version} + software.amazon.awssdk + s3 + ${awssdk.version} diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java index ed58c4f166..aa523b94db 100644 --- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java +++ b/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 元素类型 + * @param elementType 集合中元素类型 + * @param value 被转换的值 + * @return {@link LinkedHashSet} + */ + @SuppressWarnings("unchecked") + public static LinkedHashSet toLinkedHashSet(Class elementType, Object value) { + return (LinkedHashSet) toCollection(LinkedHashSet.class, elementType, value); + } + } \ No newline at end of file diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/form/BpmTaskCandidateFormUserStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/form/BpmTaskCandidateFormUserStrategy.java index 2d315979a0..6298c3259f 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/form/BpmTaskCandidateFormUserStrategy.java +++ b/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 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 processVariables) { Object result = processVariables == null ? null : processVariables.get(param); - return Convert.toSet(Long.class, result); + return CollectionUtils.toLinkedHashSet(Long.class, result); } } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/other/BpmTaskCandidateExpressionStrategy.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/other/BpmTaskCandidateExpressionStrategy.java index c008c1cb6f..64ca9e8538 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/other/BpmTaskCandidateExpressionStrategy.java +++ b/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 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 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); diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java index e59144cb3a..0be674682a 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java +++ b/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 添加评论 diff --git a/yudao-module-infra/yudao-module-infra-biz/pom.xml b/yudao-module-infra/yudao-module-infra-biz/pom.xml index 9786c000dd..8dc7d96e0e 100644 --- a/yudao-module-infra/yudao-module-infra-biz/pom.xml +++ b/yudao-module-infra/yudao-module-infra-biz/pom.xml @@ -115,9 +115,10 @@ com.jcraft jsch + - com.amazonaws - aws-java-sdk-s3 + software.amazon.awssdk + s3 diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/enums/codegen/CodegenFrontTypeEnum.java b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/enums/codegen/CodegenFrontTypeEnum.java index edcb85e474..741d7d9664 100644 --- a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/enums/codegen/CodegenFrontTypeEnum.java +++ b/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 标准模版 ; /** diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/s3/S3FileClient.java b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/s3/S3FileClient.java index 5c76e1a7c8..a33f0d738c 100644 --- a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/s3/S3FileClient.java +++ b/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、阿里云、腾讯云、七牛云、华为云等云服务 - *

- * S3 协议的客户端,采用亚马逊提供的 software.amazon.awssdk.s3 库 * * @author 芋道源码 */ public class S3FileClient extends AbstractFileClient { - 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 { 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 { 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()); } } diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/s3/S3FileClientConfig.java b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/s3/S3FileClientConfig.java index e59a7594d2..fb19317e02 100644 --- a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/framework/file/core/client/s3/S3FileClientConfig.java +++ b/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 diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngine.java b/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngine.java index 89737fe937..0b5b870eb9 100644 --- a/yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngine.java +++ b/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(); diff --git a/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/ProductSkuApi.java b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/ProductSkuApi.java index 3581fdb913..f6e6034875 100644 --- a/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/sku/ProductSkuApi.java +++ b/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 getSkuList(Collection ids); + /** + * 批量查询 SKU MAP + * + * @param ids SKU 编号列表 + * @return SKU MAP + */ + default Map getSkuMap(Collection ids) { + return convertMap(getSkuList(ids), ProductSkuRespDTO::getId); + } + /** * 批量查询 SKU 数组 * diff --git a/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/spu/ProductSpuApi.java b/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/spu/ProductSpuApi.java index 64d24c399a..499a0ca896 100644 --- a/yudao-module-mall/yudao-module-product-api/src/main/java/cn/iocoder/yudao/module/product/api/spu/ProductSpuApi.java +++ b/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 getSpusMap(Collection ids) { + default Map getSpuMap(Collection ids) { return convertMap(getSpuList(ids), ProductSpuRespDTO::getId); } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/point/PointActivityController.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/point/PointActivityController.java index d8fb54b081..f545db6f50 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/point/PointActivityController.java +++ b/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 products = pointActivityService.getPointProductListByActivityIds( convertSet(activityList, PointActivityDO::getId)); Map> productsMap = convertMultiMap(products, PointProductDO::getActivityId); - Map spuMap = productSpuApi.getSpusMap( + Map spuMap = productSpuApi.getSpuMap( convertSet(activityList, PointActivityDO::getSpuId)); List result = BeanUtils.toBean(activityList, PointActivityRespVO.class); result.forEach(activity -> { diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/point/AppPointActivityController.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/point/AppPointActivityController.java index c364b64f2a..9730056870 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/point/AppPointActivityController.java +++ b/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 products = pointActivityService.getPointProductListByActivityIds( convertSet(activityList, PointActivityDO::getId)); Map> productsMap = convertMultiMap(products, PointProductDO::getActivityId); - Map spuMap = productSpuApi.getSpusMap( + Map spuMap = productSpuApi.getSpuMap( convertSet(activityList, PointActivityDO::getSpuId)); List result = BeanUtils.toBean(activityList, AppPointActivityRespVO.class); result.forEach(activity -> { diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java index 1c5db82889..e6f82a69fc 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java +++ b/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); - } } /** diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/aftersale/AppAfterSaleController.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/aftersale/AppAfterSaleController.java index 2328119d06..46495c003e 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/aftersale/AppAfterSaleController.java +++ b/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> getAfterSalePage(PageParam pageParam) { - return success(AfterSaleConvert.INSTANCE.convertPage02( - afterSaleService.getAfterSalePage(getLoginUserId(), pageParam))); + public CommonResult> getAfterSalePage(AppAfterSalePageReqVO pageReqVO) { + PageResult 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 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") diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/aftersale/vo/AppAfterSalePageReqVO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/aftersale/vo/AppAfterSalePageReqVO.java new file mode 100644 index 0000000000..35e2429b39 --- /dev/null +++ b/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 statuses; + +} diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/base/spu/AppProductSpuBaseRespVO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/base/spu/AppProductSpuBaseRespVO.java index d30417818f..c7a3c1d9e7 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/base/spu/AppProductSpuBaseRespVO.java +++ b/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; + } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/delivery/vo/pickup/AppDeliveryPickUpStoreRespVO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/delivery/vo/pickup/AppDeliveryPickUpStoreRespVO.java index 1ca25ade5f..685a8f23fc 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/delivery/vo/pickup/AppDeliveryPickUpStoreRespVO.java +++ b/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; diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/aftersale/AfterSaleConvert.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/aftersale/AfterSaleConvert.java index 45f6e31891..54480f696b 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/aftersale/AfterSaleConvert.java +++ b/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 convertPage02(PageResult page); - default AfterSaleDetailRespVO convert(AfterSaleDO afterSale, TradeOrderDO order, TradeOrderItemDO orderItem, MemberUserRespDTO user, List logs) { AfterSaleDetailRespVO respVO = convert02(afterSale); diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/cart/TradeCartConvert.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/cart/TradeCartConvert.java index 83cd459542..fa5fccd54e 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/convert/cart/TradeCartConvert.java +++ b/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); } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/aftersale/AfterSaleMapper.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/aftersale/AfterSaleMapper.java index 341dabc45e..9901792e37 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/aftersale/AfterSaleMapper.java +++ b/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 { .orderByDesc(AfterSaleDO::getId)); } - default PageResult selectPage(Long userId, PageParam pageParam) { - return selectPage(pageParam, new LambdaQueryWrapperX() - .eqIfPresent(AfterSaleDO::getUserId, userId) + default PageResult selectPage(Long userId, AppAfterSalePageReqVO pageReqVO) { + return selectPage(pageReqVO, new LambdaQueryWrapperX() + .eq(AfterSaleDO::getUserId, userId) + .inIfPresent(AfterSaleDO::getStatus, pageReqVO.getStatuses()) .orderByDesc(AfterSaleDO::getId)); } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleService.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleService.java index 1a0c1e95d9..486a68b7c0 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleService.java +++ b/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 getAfterSalePage(Long userId, PageParam pageParam); + PageResult getAfterSalePage(Long userId, AppAfterSalePageReqVO pageReqVO); /** * 【会员】获得售后单 diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleServiceImpl.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleServiceImpl.java index bcac6b5b6a..915f253f0f 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleServiceImpl.java +++ b/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 getAfterSalePage(Long userId, PageParam pageParam) { - return tradeAfterSaleMapper.selectPage(userId, pageParam); + public PageResult 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)); diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java index 92036fa7e7..d47da484e2 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java +++ b/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, "未退款"); } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeBrokerageOrderHandler.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeBrokerageOrderHandler.java index 026d98c238..36d31402eb 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeBrokerageOrderHandler.java +++ b/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 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 spusMap = productSpuApi.getSpuMap(convertList(orderItems, TradeOrderItemDO::getSpuId)); + Map skusMap = productSkuApi.getSkuMap(convertList(orderItems, TradeOrderItemDO::getSkuId)); // 每一个订单项,都会去生成分销记录 - List addList = convertList(orderItems, - item -> TradeOrderConvert.INSTANCE.convert(user, item, spu, sku)); + List 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); } diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java index 6db0e567c4..6ed77aeef7 100644 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java +++ b/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, "已经存在该名字的部门"); diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImpl.java index c3b09ec5a9..5060b16e05 100755 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/tenant/TenantServiceImpl.java +++ b/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); diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java index 09e7b61e1f..eaa5710074 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java +++ b/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. 插入用户 diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImplTest.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImplTest.java index c811c3c741..5286f7a0f1 100644 --- a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImplTest.java +++ b/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()); }); // 调用