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

This commit is contained in:
YunaiV 2025-04-29 22:12:35 +08:00
commit dad0cc4cb1
30 changed files with 270 additions and 138 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -13,10 +13,13 @@ import lombok.Getter;
public enum CodegenFrontTypeEnum { public enum CodegenFrontTypeEnum {
VUE2_ELEMENT_UI(10), // Vue2 Element UI 标准模版 VUE2_ELEMENT_UI(10), // Vue2 Element UI 标准模版
VUE3_ELEMENT_PLUS(20), // Vue3 Element Plus 标准模版 VUE3_ELEMENT_PLUS(20), // Vue3 Element Plus 标准模版
VUE3_VBEN2_ANTD_SCHEMA(30), // Vue3 VBEN2 + ANTD + Schema 模版 VUE3_VBEN2_ANTD_SCHEMA(30), // Vue3 VBEN2 + ANTD + Schema 模版
VUE3_VBEN5_ANTD_SCHEMA(40), // Vue3 VBEN5 + 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 标准模版
; ;
/** /**

View File

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

View File

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

View File

@ -164,23 +164,23 @@ public class CodegenEngine {
.put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_SCHEMA.getType(), vue3Vben5AntdSchemaTemplatePath("views/modules/list_sub_erp.vue"), // 特殊主子表专属逻辑 .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")) vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/${subSimpleClassName_strikeCase}-list.vue"))
// VUE3_VBEN5_ANTD // 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")) 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")) 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")) 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")) 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")) 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")) 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")) 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")) 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")) vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/${subSimpleClassName_strikeCase}-list.vue"))
.build(); .build();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,8 +3,6 @@ package cn.iocoder.yudao.module.trade.controller.app.base.spu;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
import java.util.List;
/** /**
* 商品 SPU 基础 Response VO * 商品 SPU 基础 Response VO
* *
@ -25,4 +23,10 @@ public class AppProductSpuBaseRespVO {
@Schema(description = "商品分类编号", example = "1") @Schema(description = "商品分类编号", example = "1")
private Long categoryId; 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;
} }

View File

@ -1,8 +1,12 @@
package cn.iocoder.yudao.module.trade.controller.app.delivery.vo.pickup; 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 io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data; import lombok.Data;
import java.time.LocalTime;
@Schema(description = "用户 App - 自提门店 Response VO") @Schema(description = "用户 App - 自提门店 Response VO")
@Data @Data
public class AppDeliveryPickUpStoreRespVO { public class AppDeliveryPickUpStoreRespVO {
@ -28,6 +32,16 @@ public class AppDeliveryPickUpStoreRespVO {
@Schema(description = "门店详细地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "复旦大学路 188 号") @Schema(description = "门店详细地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "复旦大学路 188 号")
private String detailAddress; 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") @Schema(description = "纬度", requiredMode = Schema.RequiredMode.REQUIRED, example = "5.88")
private Double latitude; private Double latitude;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -545,6 +545,14 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
if (ObjectUtil.notEqual(order.getStatus(), TradeOrderStatusEnum.UNPAID.getStatus())) { if (ObjectUtil.notEqual(order.getStatus(), TradeOrderStatusEnum.UNPAID.getStatus())) {
throw exception(ORDER_CANCEL_FAIL_STATUS_NOT_UNPAID); 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. 取消订单 // 2. 取消订单
cancelOrder0(order, TradeOrderCancelTypeEnum.MEMBER_CANCEL); cancelOrder0(order, TradeOrderCancelTypeEnum.MEMBER_CANCEL);
@ -581,6 +589,15 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
@TradeOrderLog(operateType = TradeOrderOperateTypeEnum.SYSTEM_CANCEL) @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.SYSTEM_CANCEL)
public void cancelOrderBySystem(TradeOrderDO order) { 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); cancelOrder0(order, TradeOrderCancelTypeEnum.PAY_TIMEOUT);
} }
@ -895,12 +912,11 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
if (order == null) { if (order == null) {
throw exception(ORDER_NOT_FOUND); throw exception(ORDER_NOT_FOUND);
} }
// 1.3 校验订单是否支付 // 1.3 校验订单是否支付
if (!order.getPayStatus()) { if (!order.getPayStatus()) {
throw exception(ORDER_CANCEL_PAID_FAIL, "已支付"); throw exception(ORDER_CANCEL_PAID_FAIL, "已支付");
} }
// 1.3 校验订单是否未退款 // 1.4 校验订单是否未退款
if (ObjUtil.notEqual(TradeOrderRefundStatusEnum.NONE.getStatus(), order.getRefundStatus())) { if (ObjUtil.notEqual(TradeOrderRefundStatusEnum.NONE.getStatus(), order.getRefundStatus())) {
throw exception(ORDER_CANCEL_PAID_FAIL, "未退款"); throw exception(ORDER_CANCEL_PAID_FAIL, "未退款");
} }

View File

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

View File

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

View File

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

View File

@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.system.service.user;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.IoUtil; import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.exception.ServiceException; 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_INIT_PASSWORD_KEY = "system.user.init-password";
static final String USER_REGISTER_ENABLED_KEY = "system.user.register-enabled";
@Resource @Resource
private AdminUserMapper userMapper; private AdminUserMapper userMapper;
@ -117,14 +120,18 @@ public class AdminUserServiceImpl implements AdminUserService {
@Override @Override
public Long registerUser(AuthRegisterReqVO registerReqVO) { 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 -> { tenantService.handleTenantInfo(tenant -> {
long count = userMapper.selectCount(); long count = userMapper.selectCount();
if (count >= tenant.getAccountCount()) { if (count >= tenant.getAccountCount()) {
throw exception(USER_COUNT_MAX, tenant.getAccountCount()); throw exception(USER_COUNT_MAX, tenant.getAccountCount());
} }
}); });
// 1.2 校验正确性 // 1.3 校验正确性
validateUserForCreateOrUpdate(null, registerReqVO.getUsername(), null, null, null, null); validateUserForCreateOrUpdate(null, registerReqVO.getUsername(), null, null, null, null);
// 2. 插入用户 // 2. 插入用户

View File

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