Commit 2876c7ce1626d93ace59aa4c539f5a2154bf7e08

Authored by YunaiV
2 parents 24195750 3ea63b5b

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

# Conflicts:
#	yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/AppCouponController.java
#	yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponMapper.java
#	yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponService.java
#	yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java
#	yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/seckill/SeckillActivityServiceImpl.java
#	yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeRewardActivityPriceCalculator.java
Showing 70 changed files with 671 additions and 890 deletions
... ... @@ -15,12 +15,12 @@
15 15 <!-- 各种 module 拓展 -->
16 16 <module>yudao-module-system</module>
17 17 <module>yudao-module-infra</module>
18   - <module>yudao-module-member</module>
  18 +<!-- <module>yudao-module-member</module>-->
19 19 <!-- <module>yudao-module-bpm</module>-->
20 20 <!-- <module>yudao-module-report</module>-->
21 21 <!-- <module>yudao-module-mp</module>-->
22   - <module>yudao-module-pay</module>
23   - <module>yudao-module-mall</module>
  22 +<!-- <module>yudao-module-pay</module>-->
  23 +<!-- <module>yudao-module-mall</module>-->
24 24 <!-- <module>yudao-module-crm</module>-->
25 25 <!-- <module>yudao-module-erp</module>-->
26 26 <!-- <module>yudao-module-ai</module>-->
... ...
yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/job/TenantJobAspect.java
... ... @@ -2,6 +2,7 @@ package cn.iocoder.yudao.framework.tenant.core.job;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.exceptions.ExceptionUtil;
  5 +import cn.hutool.core.util.StrUtil;
5 6 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
6 7 import cn.iocoder.yudao.framework.tenant.core.service.TenantFrameworkService;
7 8 import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
... ... @@ -44,7 +45,8 @@ public class TenantJobAspect {
44 45 // TODO 芋艿:先通过 parallel 实现并行;1)多个租户,是一条执行日志;2)异常的情况
45 46 TenantUtils.execute(tenantId, () -> {
46 47 try {
47   - joinPoint.proceed();
  48 + Object result = joinPoint.proceed();
  49 + results.put(tenantId, StrUtil.toStringOrNull(result));
48 50 } catch (Throwable e) {
49 51 log.error("[execute][租户({}) 执行 Job 发生异常", tenantId, e);
50 52 results.put(tenantId, ExceptionUtil.getRootCauseMessage(e));
... ...
yudao-framework/yudao-spring-boot-starter-security/src/main/java/cn/iocoder/yudao/framework/operatelog/core/service/LogRecordServiceImpl.java
... ... @@ -11,7 +11,6 @@ import com.mzt.logapi.service.ILogRecordService;
11 11 import jakarta.annotation.Resource;
12 12 import jakarta.servlet.http.HttpServletRequest;
13 13 import lombok.extern.slf4j.Slf4j;
14   -import org.springframework.scheduling.annotation.Async;
15 14  
16 15 import java.util.List;
17 16  
... ... @@ -29,7 +28,6 @@ public class LogRecordServiceImpl implements ILogRecordService {
29 28 private OperateLogApi operateLogApi;
30 29  
31 30 @Override
32   - @Async
33 31 public void record(LogRecord logRecord) {
34 32 OperateLogCreateReqDTO reqDTO = new OperateLogCreateReqDTO();
35 33 try {
... ... @@ -42,7 +40,7 @@ public class LogRecordServiceImpl implements ILogRecordService {
42 40 fillRequestFields(reqDTO);
43 41  
44 42 // 2. 异步记录日志
45   - operateLogApi.createOperateLog(reqDTO);
  43 + operateLogApi.createOperateLogAsync(reqDTO);
46 44 } catch (Throwable ex) {
47 45 // 由于 @Async 异步调用,这里打印下日志,更容易跟进
48 46 log.error("[record][url({}) log({}) 发生异常]", reqDTO.getRequestUrl(), reqDTO, ex);
... ...
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/config/YudaoApiLogAutoConfiguration.java
... ... @@ -2,15 +2,10 @@ package cn.iocoder.yudao.framework.apilog.config;
2 2  
3 3 import cn.iocoder.yudao.framework.apilog.core.filter.ApiAccessLogFilter;
4 4 import cn.iocoder.yudao.framework.apilog.core.interceptor.ApiAccessLogInterceptor;
5   -import cn.iocoder.yudao.framework.apilog.core.service.ApiAccessLogFrameworkService;
6   -import cn.iocoder.yudao.framework.apilog.core.service.ApiAccessLogFrameworkServiceImpl;
7   -import cn.iocoder.yudao.framework.apilog.core.service.ApiErrorLogFrameworkService;
8   -import cn.iocoder.yudao.framework.apilog.core.service.ApiErrorLogFrameworkServiceImpl;
9 5 import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum;
10 6 import cn.iocoder.yudao.framework.web.config.WebProperties;
11 7 import cn.iocoder.yudao.framework.web.config.YudaoWebAutoConfiguration;
12 8 import cn.iocoder.yudao.module.infra.api.logger.ApiAccessLogApi;
13   -import cn.iocoder.yudao.module.infra.api.logger.ApiErrorLogApi;
14 9 import jakarta.servlet.Filter;
15 10 import org.springframework.beans.factory.annotation.Value;
16 11 import org.springframework.boot.autoconfigure.AutoConfiguration;
... ... @@ -23,18 +18,6 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
23 18 @AutoConfiguration(after = YudaoWebAutoConfiguration.class)
24 19 public class YudaoApiLogAutoConfiguration implements WebMvcConfigurer {
25 20  
26   - @Bean
27   - @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
28   - public ApiAccessLogFrameworkService apiAccessLogFrameworkService(ApiAccessLogApi apiAccessLogApi) {
29   - return new ApiAccessLogFrameworkServiceImpl(apiAccessLogApi);
30   - }
31   -
32   - @Bean
33   - @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
34   - public ApiErrorLogFrameworkService apiErrorLogFrameworkService(ApiErrorLogApi apiErrorLogApi) {
35   - return new ApiErrorLogFrameworkServiceImpl(apiErrorLogApi);
36   - }
37   -
38 21 /**
39 22 * 创建 ApiAccessLogFilter Bean,记录 API 请求日志
40 23 */
... ... @@ -42,8 +25,8 @@ public class YudaoApiLogAutoConfiguration implements WebMvcConfigurer {
42 25 @ConditionalOnProperty(prefix = "yudao.access-log", value = "enable", matchIfMissing = true) // 允许使用 yudao.access-log.enable=false 禁用访问日志
43 26 public FilterRegistrationBean<ApiAccessLogFilter> apiAccessLogFilter(WebProperties webProperties,
44 27 @Value("${spring.application.name}") String applicationName,
45   - ApiAccessLogFrameworkService apiAccessLogFrameworkService) {
46   - ApiAccessLogFilter filter = new ApiAccessLogFilter(webProperties, applicationName, apiAccessLogFrameworkService);
  28 + ApiAccessLogApi apiAccessLogApi) {
  29 + ApiAccessLogFilter filter = new ApiAccessLogFilter(webProperties, applicationName, apiAccessLogApi);
47 30 return createFilterBean(filter, WebFilterOrderEnum.API_ACCESS_LOG_FILTER);
48 31 }
49 32  
... ...
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/core/filter/ApiAccessLogFilter.java
... ... @@ -9,7 +9,6 @@ import cn.hutool.core.util.BooleanUtil;
9 9 import cn.hutool.core.util.StrUtil;
10 10 import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
11 11 import cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum;
12   -import cn.iocoder.yudao.framework.apilog.core.service.ApiAccessLogFrameworkService;
13 12 import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
14 13 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
15 14 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
... ... @@ -18,6 +17,7 @@ import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
18 17 import cn.iocoder.yudao.framework.web.config.WebProperties;
19 18 import cn.iocoder.yudao.framework.web.core.filter.ApiRequestFilter;
20 19 import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
  20 +import cn.iocoder.yudao.module.infra.api.logger.ApiAccessLogApi;
21 21 import cn.iocoder.yudao.module.infra.api.logger.dto.ApiAccessLogCreateReqDTO;
22 22 import com.fasterxml.jackson.databind.JsonNode;
23 23 import io.swagger.v3.oas.annotations.Operation;
... ... @@ -36,7 +36,7 @@ import java.time.temporal.ChronoUnit;
36 36 import java.util.Iterator;
37 37 import java.util.Map;
38 38  
39   -import static cn.iocoder.yudao.framework.apilog.core.interceptor.ApiAccessLogInterceptor.*;
  39 +import static cn.iocoder.yudao.framework.apilog.core.interceptor.ApiAccessLogInterceptor.ATTRIBUTE_HANDLER_METHOD;
40 40 import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
41 41  
42 42 /**
... ... @@ -53,12 +53,12 @@ public class ApiAccessLogFilter extends ApiRequestFilter {
53 53  
54 54 private final String applicationName;
55 55  
56   - private final ApiAccessLogFrameworkService apiAccessLogFrameworkService;
  56 + private final ApiAccessLogApi apiAccessLogApi;
57 57  
58   - public ApiAccessLogFilter(WebProperties webProperties, String applicationName, ApiAccessLogFrameworkService apiAccessLogFrameworkService) {
  58 + public ApiAccessLogFilter(WebProperties webProperties, String applicationName, ApiAccessLogApi apiAccessLogApi) {
59 59 super(webProperties);
60 60 this.applicationName = applicationName;
61   - this.apiAccessLogFrameworkService = apiAccessLogFrameworkService;
  61 + this.apiAccessLogApi = apiAccessLogApi;
62 62 }
63 63  
64 64 @Override
... ... @@ -91,7 +91,7 @@ public class ApiAccessLogFilter extends ApiRequestFilter {
91 91 if (!enable) {
92 92 return;
93 93 }
94   - apiAccessLogFrameworkService.createApiAccessLog(accessLog);
  94 + apiAccessLogApi.createApiAccessLogAsync(accessLog);
95 95 } catch (Throwable th) {
96 96 log.error("[createApiAccessLog][url({}) log({}) 发生异常]", request.getRequestURI(), toJsonString(accessLog), th);
97 97 }
... ...
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/core/service/ApiAccessLogFrameworkService.java deleted
1   -package cn.iocoder.yudao.framework.apilog.core.service;
2   -
3   -import cn.iocoder.yudao.module.infra.api.logger.dto.ApiAccessLogCreateReqDTO;
4   -
5   -/**
6   - * API 访问日志 Framework Service 接口
7   - *
8   - * @author 芋道源码
9   - */
10   -public interface ApiAccessLogFrameworkService {
11   -
12   - /**
13   - * 创建 API 访问日志
14   - *
15   - * @param reqDTO API 访问日志
16   - */
17   - void createApiAccessLog(ApiAccessLogCreateReqDTO reqDTO);
18   -
19   -}
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/core/service/ApiAccessLogFrameworkServiceImpl.java deleted
1   -package cn.iocoder.yudao.framework.apilog.core.service;
2   -
3   -import cn.iocoder.yudao.module.infra.api.logger.ApiAccessLogApi;
4   -import cn.iocoder.yudao.module.infra.api.logger.dto.ApiAccessLogCreateReqDTO;
5   -import lombok.RequiredArgsConstructor;
6   -import lombok.extern.slf4j.Slf4j;
7   -import org.springframework.scheduling.annotation.Async;
8   -
9   -/**
10   - * API 访问日志 Framework Service 实现类
11   - *
12   - * 基于 {@link ApiAccessLogApi} 服务,记录访问日志
13   - *
14   - * @author 芋道源码
15   - */
16   -@RequiredArgsConstructor
17   -@Slf4j
18   -public class ApiAccessLogFrameworkServiceImpl implements ApiAccessLogFrameworkService {
19   -
20   - private final ApiAccessLogApi apiAccessLogApi;
21   -
22   - @Override
23   - @Async
24   - public void createApiAccessLog(ApiAccessLogCreateReqDTO reqDTO) {
25   - try {
26   - apiAccessLogApi.createApiAccessLog(reqDTO);
27   - } catch (Throwable ex) {
28   - // 由于 @Async 异步调用,这里打印下日志,更容易跟进
29   - log.error("[createApiAccessLog][url({}) log({}) 发生异常]", reqDTO.getRequestUrl(), reqDTO, ex);
30   - }
31   - }
32   -
33   -}
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/core/service/ApiErrorLogFrameworkService.java deleted
1   -package cn.iocoder.yudao.framework.apilog.core.service;
2   -
3   -import cn.iocoder.yudao.module.infra.api.logger.dto.ApiErrorLogCreateReqDTO;
4   -
5   -/**
6   - * API 错误日志 Framework Service 接口
7   - *
8   - * @author 芋道源码
9   - */
10   -public interface ApiErrorLogFrameworkService {
11   -
12   - /**
13   - * 创建 API 错误日志
14   - *
15   - * @param reqDTO API 错误日志
16   - */
17   - void createApiErrorLog(ApiErrorLogCreateReqDTO reqDTO);
18   -
19   -}
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/core/service/ApiErrorLogFrameworkServiceImpl.java deleted
1   -package cn.iocoder.yudao.framework.apilog.core.service;
2   -
3   -import cn.iocoder.yudao.module.infra.api.logger.ApiErrorLogApi;
4   -import cn.iocoder.yudao.module.infra.api.logger.dto.ApiErrorLogCreateReqDTO;
5   -import lombok.RequiredArgsConstructor;
6   -import lombok.extern.slf4j.Slf4j;
7   -import org.springframework.scheduling.annotation.Async;
8   -
9   -/**
10   - * API 错误日志 Framework Service 实现类
11   - *
12   - * 基于 {@link ApiErrorLogApi} 服务,记录错误日志
13   - *
14   - * @author 芋道源码
15   - */
16   -@RequiredArgsConstructor
17   -@Slf4j
18   -public class ApiErrorLogFrameworkServiceImpl implements ApiErrorLogFrameworkService {
19   -
20   - private final ApiErrorLogApi apiErrorLogApi;
21   -
22   - @Override
23   - @Async
24   - public void createApiErrorLog(ApiErrorLogCreateReqDTO reqDTO) {
25   - try {
26   - apiErrorLogApi.createApiErrorLog(reqDTO);
27   - } catch (Throwable ex) {
28   - // 由于 @Async 异步调用,这里打印下日志,更容易跟进
29   - log.error("[createApiErrorLog][url({}) log({}) 发生异常]", reqDTO.getRequestUrl(), reqDTO, ex);
30   - }
31   - }
32   -
33   -}
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/config/YudaoWebAutoConfiguration.java
1 1 package cn.iocoder.yudao.framework.web.config;
2 2  
3   -import cn.iocoder.yudao.framework.apilog.core.service.ApiErrorLogFrameworkService;
4 3 import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum;
5 4 import cn.iocoder.yudao.framework.web.core.filter.CacheRequestBodyFilter;
6 5 import cn.iocoder.yudao.framework.web.core.filter.DemoFilter;
7 6 import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
8 7 import cn.iocoder.yudao.framework.web.core.handler.GlobalResponseBodyHandler;
9 8 import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
  9 +import cn.iocoder.yudao.module.infra.api.logger.ApiErrorLogApi;
  10 +import jakarta.annotation.Resource;
  11 +import jakarta.servlet.Filter;
10 12 import org.springframework.beans.factory.annotation.Value;
11 13 import org.springframework.boot.autoconfigure.AutoConfiguration;
12 14 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
... ... @@ -25,9 +27,6 @@ import org.springframework.web.filter.CorsFilter;
25 27 import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
26 28 import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
27 29  
28   -import jakarta.annotation.Resource;
29   -import jakarta.servlet.Filter;
30   -
31 30 @AutoConfiguration
32 31 @EnableConfigurationProperties(WebProperties.class)
33 32 public class YudaoWebAutoConfiguration implements WebMvcConfigurer {
... ... @@ -59,8 +58,9 @@ public class YudaoWebAutoConfiguration implements WebMvcConfigurer {
59 58 }
60 59  
61 60 @Bean
62   - public GlobalExceptionHandler globalExceptionHandler(ApiErrorLogFrameworkService apiErrorLogFrameworkService) {
63   - return new GlobalExceptionHandler(applicationName, apiErrorLogFrameworkService);
  61 + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
  62 + public GlobalExceptionHandler globalExceptionHandler(ApiErrorLogApi apiErrorLogApi) {
  63 + return new GlobalExceptionHandler(applicationName, apiErrorLogApi);
64 64 }
65 65  
66 66 @Bean
... ...
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.java
... ... @@ -5,7 +5,6 @@ import cn.hutool.core.map.MapUtil;
5 5 import cn.hutool.core.util.ObjUtil;
6 6 import cn.hutool.core.util.StrUtil;
7 7 import cn.hutool.extra.servlet.JakartaServletUtil;
8   -import cn.iocoder.yudao.framework.apilog.core.service.ApiErrorLogFrameworkService;
9 8 import cn.iocoder.yudao.framework.common.exception.ServiceException;
10 9 import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
11 10 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
... ... @@ -14,6 +13,7 @@ import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
14 13 import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
15 14 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
16 15 import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
  16 +import cn.iocoder.yudao.module.infra.api.logger.ApiErrorLogApi;
17 17 import cn.iocoder.yudao.module.infra.api.logger.dto.ApiErrorLogCreateReqDTO;
18 18 import com.fasterxml.jackson.databind.exc.InvalidFormatException;
19 19 import jakarta.servlet.http.HttpServletRequest;
... ... @@ -40,12 +40,7 @@ import java.time.LocalDateTime;
40 40 import java.util.Map;
41 41 import java.util.Set;
42 42  
43   -import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST;
44   -import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.FORBIDDEN;
45   -import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR;
46   -import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.METHOD_NOT_ALLOWED;
47   -import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.NOT_FOUND;
48   -import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.NOT_IMPLEMENTED;
  43 +import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.*;
49 44  
50 45 /**
51 46 * 全局异常处理器,将 Exception 翻译成 CommonResult + 对应的异常编号
... ... @@ -65,7 +60,7 @@ public class GlobalExceptionHandler {
65 60 @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
66 61 private final String applicationName;
67 62  
68   - private final ApiErrorLogFrameworkService apiErrorLogFrameworkService;
  63 + private final ApiErrorLogApi apiErrorLogApi;
69 64  
70 65 /**
71 66 * 处理所有异常,主要是提供给 Filter 使用
... ... @@ -288,7 +283,7 @@ public class GlobalExceptionHandler {
288 283 // 初始化 errorLog
289 284 buildExceptionLog(errorLog, req, e);
290 285 // 执行插入 errorLog
291   - apiErrorLogFrameworkService.createApiErrorLog(errorLog);
  286 + apiErrorLogApi.createApiErrorLogAsync(errorLog);
292 287 } catch (Throwable th) {
293 288 log.error("[createExceptionLog][url({}) log({}) 发生异常]", req.getRequestURI(), JsonUtils.toJsonString(errorLog), th);
294 289 }
... ...
yudao-framework/yudao-spring-boot-starter-websocket/src/main/java/cn/iocoder/yudao/framework/websocket/config/YudaoWebSocketAutoConfiguration.java
... ... @@ -5,6 +5,7 @@ import cn.iocoder.yudao.framework.mq.redis.core.RedisMQTemplate;
5 5 import cn.iocoder.yudao.framework.websocket.core.handler.JsonWebSocketMessageHandler;
6 6 import cn.iocoder.yudao.framework.websocket.core.listener.WebSocketMessageListener;
7 7 import cn.iocoder.yudao.framework.websocket.core.security.LoginUserHandshakeInterceptor;
  8 +import cn.iocoder.yudao.framework.websocket.core.security.WebSocketAuthorizeRequestsCustomizer;
8 9 import cn.iocoder.yudao.framework.websocket.core.sender.kafka.KafkaWebSocketMessageConsumer;
9 10 import cn.iocoder.yudao.framework.websocket.core.sender.kafka.KafkaWebSocketMessageSender;
10 11 import cn.iocoder.yudao.framework.websocket.core.sender.local.LocalWebSocketMessageSender;
... ... @@ -76,6 +77,11 @@ public class YudaoWebSocketAutoConfiguration {
76 77 return new WebSocketSessionManagerImpl();
77 78 }
78 79  
  80 + @Bean
  81 + public WebSocketAuthorizeRequestsCustomizer webSocketAuthorizeRequestsCustomizer(WebSocketProperties webSocketProperties) {
  82 + return new WebSocketAuthorizeRequestsCustomizer(webSocketProperties);
  83 + }
  84 +
79 85 // ==================== Sender 相关 ====================
80 86  
81 87 @Configuration
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmModelConvert.java
... ... @@ -13,6 +13,7 @@ import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModel
13 13 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO;
14 14 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO;
15 15 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
  16 +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
16 17 import cn.iocoder.yudao.module.bpm.service.definition.dto.BpmModelMetaInfoRespDTO;
17 18 import org.flowable.common.engine.impl.db.SuspensionState;
18 19 import org.flowable.engine.repository.Deployment;
... ... @@ -55,7 +56,7 @@ public interface BpmModelConvert {
55 56 BpmModelMetaInfoRespDTO metaInfo = buildMetaInfo(model);
56 57 BpmModelRespVO modelVO = buildModel0(model, metaInfo, null, null, null, null);
57 58 if (ArrayUtil.isNotEmpty(bpmnBytes)) {
58   - modelVO.setBpmnXml(new String(bpmnBytes));
  59 + modelVO.setBpmnXml(BpmnModelUtils.getBpmnXml(bpmnBytes));
59 60 }
60 61 return modelVO;
61 62 }
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java
... ... @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.util;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.util.ArrayUtil;
  5 +import cn.hutool.core.util.StrUtil;
5 6 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
6 7 import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
7 8 import org.flowable.bpmn.converter.BpmnXMLConverter;
... ... @@ -108,7 +109,14 @@ public class BpmnModelUtils {
108 109 return null;
109 110 }
110 111 BpmnXMLConverter converter = new BpmnXMLConverter();
111   - return new String(converter.convertToXML(model));
  112 + return StrUtil.utf8Str(converter.convertToXML(model));
  113 + }
  114 +
  115 + public static String getBpmnXml(byte[] bpmnBytes) {
  116 + if (ArrayUtil.isEmpty(bpmnBytes)) {
  117 + return null;
  118 + }
  119 + return StrUtil.utf8Str(bpmnBytes);
112 120 }
113 121  
114 122 // ========== 遍历相关的方法 ==========
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/receivable/CrmReceivableMapper.java
... ... @@ -92,7 +92,7 @@ public interface CrmReceivableMapper extends BaseMapperX&lt;CrmReceivableDO&gt; {
92 92 List<Map<String, Object>> result = selectMaps(new QueryWrapper<CrmReceivableDO>()
93 93 .select("contract_id, SUM(price) AS total_price")
94 94 .in("audit_status", CrmAuditStatusEnum.DRAFT.getStatus(), // 草稿 + 审批中 + 审批通过
95   - CrmAuditStatusEnum.PROCESS, CrmAuditStatusEnum.APPROVE.getStatus())
  95 + CrmAuditStatusEnum.PROCESS.getStatus(), CrmAuditStatusEnum.APPROVE.getStatus())
96 96 .groupBy("contract_id")
97 97 .in("contract_id", contractIds));
98 98 // 获得金额
... ...
yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/api/logger/ApiAccessLogApi.java
... ... @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.infra.api.logger;
3 3 import cn.iocoder.yudao.module.infra.api.logger.dto.ApiAccessLogCreateReqDTO;
4 4  
5 5 import jakarta.validation.Valid;
  6 +import org.springframework.scheduling.annotation.Async;
6 7  
7 8 /**
8 9 * API 访问日志的 API 接口
... ... @@ -18,4 +19,14 @@ public interface ApiAccessLogApi {
18 19 */
19 20 void createApiAccessLog(@Valid ApiAccessLogCreateReqDTO createDTO);
20 21  
  22 + /**
  23 + * 【异步】创建 API 访问日志
  24 + *
  25 + * @param createDTO 访问日志 DTO
  26 + */
  27 + @Async
  28 + default void createApiAccessLogAsync(ApiAccessLogCreateReqDTO createDTO) {
  29 + createApiAccessLog(createDTO);
  30 + }
  31 +
21 32 }
... ...
yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/api/logger/ApiErrorLogApi.java
... ... @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.infra.api.logger;
3 3 import cn.iocoder.yudao.module.infra.api.logger.dto.ApiErrorLogCreateReqDTO;
4 4  
5 5 import jakarta.validation.Valid;
  6 +import org.springframework.scheduling.annotation.Async;
6 7  
7 8 /**
8 9 * API 错误日志的 API 接口
... ... @@ -18,4 +19,14 @@ public interface ApiErrorLogApi {
18 19 */
19 20 void createApiErrorLog(@Valid ApiErrorLogCreateReqDTO createDTO);
20 21  
  22 + /**
  23 + * 【异步】创建 API 异常日志
  24 + *
  25 + * @param createDTO 异常日志 DTO
  26 + */
  27 + @Async
  28 + default void createApiErrorLogAsync(ApiErrorLogCreateReqDTO createDTO) {
  29 + createApiErrorLog(createDTO);
  30 + }
  31 +
21 32 }
... ...
yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/enums/ErrorCodeConstants.java
... ... @@ -22,7 +22,7 @@ public interface ErrorCodeConstants {
22 22 ErrorCode JOB_CHANGE_STATUS_EQUALS = new ErrorCode(1_001_001_003, "定时任务已经处于该状态,无需修改");
23 23 ErrorCode JOB_UPDATE_ONLY_NORMAL_STATUS = new ErrorCode(1_001_001_004, "只有开启状态的任务,才可以修改");
24 24 ErrorCode JOB_CRON_EXPRESSION_VALID = new ErrorCode(1_001_001_005, "CRON 表达式不正确");
25   - ErrorCode JOB_HANDLER_BEAN_NOT_EXISTS = new ErrorCode(1_001_001_006, "定时任务的处理器 Bean 不存在");
  25 + ErrorCode JOB_HANDLER_BEAN_NOT_EXISTS = new ErrorCode(1_001_001_006, "定时任务的处理器 Bean 不存在,注意 Bean 默认首字母小写");
26 26 ErrorCode JOB_HANDLER_BEAN_TYPE_ERROR = new ErrorCode(1_001_001_007, "定时任务的处理器 Bean 类型不正确,未实现 JobHandler 接口");
27 27  
28 28 // ========== API 错误日志 1-001-002-000 ==========
... ...
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/enums/codegen/CodegenFrontTypeEnum.java
... ... @@ -14,7 +14,6 @@ public enum CodegenFrontTypeEnum {
14 14  
15 15 VUE2(10), // Vue2 Element UI 标准模版
16 16 VUE3(20), // Vue3 Element Plus 标准模版
17   - VUE3_SCHEMA(21), // Vue3 Element Plus Schema 模版
18 17 VUE3_VBEN(30), // Vue3 VBEN 模版
19 18 ;
20 19  
... ...
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngine.java
... ... @@ -135,15 +135,6 @@ public class CodegenEngine {
135 135 vue3FilePath("views/${table.moduleName}/${table.businessName}/components/${subSimpleClassName}List.vue"))
136 136 .put(CodegenFrontTypeEnum.VUE3.getType(), vue3TemplatePath("api/api.ts"),
137 137 vue3FilePath("api/${table.moduleName}/${table.businessName}/index.ts"))
138   - // Vue3 Schema 模版
139   - .put(CodegenFrontTypeEnum.VUE3_SCHEMA.getType(), vue3SchemaTemplatePath("views/data.ts"),
140   - vue3FilePath("views/${table.moduleName}/${table.businessName}/${classNameVar}.data.ts"))
141   - .put(CodegenFrontTypeEnum.VUE3_SCHEMA.getType(), vue3SchemaTemplatePath("views/index.vue"),
142   - vue3FilePath("views/${table.moduleName}/${table.businessName}/index.vue"))
143   - .put(CodegenFrontTypeEnum.VUE3_SCHEMA.getType(), vue3SchemaTemplatePath("views/form.vue"),
144   - vue3FilePath("views/${table.moduleName}/${table.businessName}/${simpleClassName}Form.vue"))
145   - .put(CodegenFrontTypeEnum.VUE3_SCHEMA.getType(), vue3SchemaTemplatePath("api/api.ts"),
146   - vue3FilePath("api/${table.moduleName}/${table.businessName}/index.ts"))
147 138 // Vue3 vben 模版
148 139 .put(CodegenFrontTypeEnum.VUE3_VBEN.getType(), vue3VbenTemplatePath("views/data.ts"),
149 140 vue3FilePath("views/${table.moduleName}/${table.businessName}/${classNameVar}.data.ts"))
... ... @@ -496,10 +487,6 @@ public class CodegenEngine {
496 487 "src/" + path;
497 488 }
498 489  
499   - private static String vue3SchemaTemplatePath(String path) {
500   - return "codegen/vue3_schema/" + path + ".vm";
501   - }
502   -
503 490 private static String vue3VbenTemplatePath(String path) {
504 491 return "codegen/vue3_vben/" + path + ".vm";
505 492 }
... ...
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/job/JobServiceImpl.java
... ... @@ -14,6 +14,7 @@ import cn.iocoder.yudao.module.infra.enums.job.JobStatusEnum;
14 14 import jakarta.annotation.Resource;
15 15 import lombok.extern.slf4j.Slf4j;
16 16 import org.quartz.SchedulerException;
  17 +import org.springframework.beans.factory.NoSuchBeanDefinitionException;
17 18 import org.springframework.stereotype.Service;
18 19 import org.springframework.transaction.annotation.Transactional;
19 20 import org.springframework.validation.annotation.Validated;
... ... @@ -91,13 +92,15 @@ public class JobServiceImpl implements JobService {
91 92 }
92 93  
93 94 private void validateJobHandlerExists(String handlerName) {
94   - Object handler = SpringUtil.getBean(handlerName);
95   - if (handler == null) {
  95 + try {
  96 + Object handler = SpringUtil.getBean(handlerName);
  97 + assert handler != null;
  98 + if (!(handler instanceof JobHandler)) {
  99 + throw exception(JOB_HANDLER_BEAN_TYPE_ERROR);
  100 + }
  101 + } catch (NoSuchBeanDefinitionException e) {
96 102 throw exception(JOB_HANDLER_BEAN_NOT_EXISTS);
97 103 }
98   - if (!(handler instanceof JobHandler)) {
99   - throw exception(JOB_HANDLER_BEAN_TYPE_ERROR);
100   - }
101 104 }
102 105  
103 106 @Override
... ...
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_schema/api/api.ts.vm deleted
1   -import request from '@/config/axios'
2   -#set ($baseURL = "/${table.moduleName}/${simpleClassName_strikeCase}")
3   -
4   -export interface ${simpleClassName}VO {
5   - #foreach ($column in $columns)
6   - #if ($column.createOperation || $column.updateOperation)
7   - #if(${column.javaType.toLowerCase()} == "long" || ${column.javaType.toLowerCase()} == "integer" || ${column.javaType.toLowerCase()} == "double" || ${column.javaType.toLowerCase()} == "bigdecimal")
8   - ${column.javaField}: number
9   - #elseif(${column.javaType.toLowerCase()} == "date" || ${column.javaType.toLowerCase()} == "localdatetime")
10   - ${column.javaField}: Date
11   - #else
12   - ${column.javaField}: ${column.javaType.toLowerCase()}
13   - #end
14   - #end
15   - #end
16   -}
17   -
18   -// 查询${table.classComment}列表
19   -export const get${simpleClassName}Page = async (params) => {
20   - return await request.get({ url: '${baseURL}/page', params })
21   -}
22   -
23   -// 查询${table.classComment}详情
24   -export const get${simpleClassName} = async (id: number) => {
25   - return await request.get({ url: '${baseURL}/get?id=' + id })
26   -}
27   -
28   -// 新增${table.classComment}
29   -export const create${simpleClassName} = async (data: ${simpleClassName}VO) => {
30   - return await request.post({ url: '${baseURL}/create', data })
31   -}
32   -
33   -// 修改${table.classComment}
34   -export const update${simpleClassName} = async (data: ${simpleClassName}VO) => {
35   - return await request.put({ url: '${baseURL}/update', data })
36   -}
37   -
38   -// 删除${table.classComment}
39   -export const delete${simpleClassName} = async (id: number) => {
40   - return await request.delete({ url: '${baseURL}/delete?id=' + id })
41   -}
42   -
43   -// 导出${table.classComment} Excel
44   -export const export${simpleClassName}Api = async (params) => {
45   - return await request.download({ url: '${baseURL}/export-excel', params })
46   -}
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_schema/views/data.ts.vm deleted
1   -import type { CrudSchema } from '@/hooks/web/useCrudSchemas'
2   -import { dateFormatter } from '@/utils/formatTime'
3   -
4   -// 表单校验
5   -export const rules = reactive({
6   -#foreach ($column in $columns)
7   -#if (($column.createOperation || $column.updateOperation) && !$column.nullable && !${column.primaryKey})## 创建或者更新操作 && 要求非空 && 非主键
8   -#set($comment=$column.columnComment)
9   - $column.javaField: [required],
10   -#end
11   -#end
12   -})
13   -
14   -// CrudSchema https://doc.iocoder.cn/vue3/crud-schema/
15   -const crudSchemas = reactive<CrudSchema[]>([
16   -#foreach($column in $columns)
17   -#if ($column.listOperation || $column.listOperationResult || $column.createOperation || $column.updateOperation)
18   -#set ($dictType = $column.dictType)
19   -#set ($javaField = $column.javaField)
20   -#set ($javaType = $column.javaType)
21   - {
22   - label: '${column.columnComment}',
23   - field: '${column.javaField}',
24   -## ========= 字典部分 =========
25   - #if ("" != $dictType)## 有数据字典
26   - dictType: DICT_TYPE.$dictType.toUpperCase(),
27   - #if ($javaType == "Integer" || $javaType == "Long" || $javaType == "Byte" || $javaType == "Short")
28   - dictClass: 'number',
29   - #elseif ($javaType == "String")
30   - dictClass: 'string',
31   - #elseif ($javaType == "Boolean")
32   - dictClass: 'boolean',
33   - #end
34   - #end
35   -## ========= Table 表格部分 =========
36   - #if (!$column.listOperationResult)
37   - isTable: false,
38   - #else
39   - #if ($column.htmlType == "datetime")
40   - formatter: dateFormatter,
41   - #end
42   - #end
43   -## ========= Search 表格部分 =========
44   - #if ($column.listOperation)
45   - isSearch: true,
46   - #if ($column.htmlType == "datetime")
47   - search: {
48   - component: 'DatePicker',
49   - componentProps: {
50   - valueFormat: 'YYYY-MM-DD HH:mm:ss',
51   - type: 'daterange',
52   - defaultTime: [new Date('1 00:00:00'), new Date('1 23:59:59')]
53   - }
54   - },
55   - #end
56   - #end
57   -## ========= Form 表单部分 =========
58   - #if ((!$column.createOperation && !$column.updateOperation) || $column.primaryKey)
59   - isForm: false,
60   - #else
61   - #if($column.htmlType == "imageUpload")## 图片上传
62   - form: {
63   - component: 'UploadImg'
64   - },
65   - #elseif($column.htmlType == "fileUpload")## 文件上传
66   - form: {
67   - component: 'UploadFile'
68   - },
69   - #elseif($column.htmlType == "editor")## 文本编辑器
70   - form: {
71   - component: 'Editor',
72   - componentProps: {
73   - valueHtml: '',
74   - height: 200
75   - }
76   - },
77   - #elseif($column.htmlType == "select")## 下拉框
78   - form: {
79   - component: 'SelectV2'
80   - },
81   - #elseif($column.htmlType == "checkbox")## 多选框
82   - form: {
83   - component: 'Checkbox'
84   - },
85   - #elseif($column.htmlType == "radio")## 单选框
86   - form: {
87   - component: 'Radio'
88   - },
89   - #elseif($column.htmlType == "datetime")## 时间框
90   - form: {
91   - component: 'DatePicker',
92   - componentProps: {
93   - type: 'datetime',
94   - valueFormat: 'x'
95   - }
96   - },
97   - #elseif($column.htmlType == "textarea")## 文本框
98   - form: {
99   - component: 'Input',
100   - componentProps: {
101   - type: 'textarea',
102   - rows: 4
103   - },
104   - colProps: {
105   - span: 24
106   - }
107   - },
108   - #elseif(${javaType.toLowerCase()} == "long" || ${javaType.toLowerCase()} == "integer")## 文本框
109   - form: {
110   - component: 'InputNumber',
111   - value: 0
112   - },
113   - #end
114   - #end
115   - },
116   -#end
117   -#end
118   - {
119   - label: '操作',
120   - field: 'action',
121   - isForm: false
122   - }
123   -])
124   -export const { allSchemas } = useCrudSchemas(crudSchemas)
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_schema/views/form.vue.vm deleted
1   -<template>
2   - <Dialog :title="dialogTitle" v-model="dialogVisible">
3   - <Form ref="formRef" :schema="allSchemas.formSchema" :rules="rules" v-loading="formLoading" />
4   - <template #footer>
5   - <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
6   - <el-button @click="dialogVisible = false">取 消</el-button>
7   - </template>
8   - </Dialog>
9   -</template>
10   -<script setup lang="ts">
11   -import * as ${simpleClassName}Api from '@/api/${table.moduleName}/${table.businessName}'
12   -import { rules, allSchemas } from './${classNameVar}.data'
13   -const { t } = useI18n() // 国际化
14   -const message = useMessage() // 消息弹窗
15   -
16   -const dialogVisible = ref(false) // 弹窗的是否展示
17   -const dialogTitle = ref('') // 弹窗的标题
18   -const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
19   -const formType = ref('') // 表单的类型:create - 新增;update - 修改
20   -const formRef = ref() // 表单 Ref
21   -
22   -/** 打开弹窗 */
23   -const open = async (type: string, id?: number) => {
24   - dialogVisible.value = true
25   - dialogTitle.value = t('action.' + type)
26   - formType.value = type
27   - // 修改时,设置数据
28   - if (id) {
29   - formLoading.value = true
30   - try {
31   - const data = await ${simpleClassName}Api.get${simpleClassName}(id)
32   - formRef.value.setValues(data)
33   - } finally {
34   - formLoading.value = false
35   - }
36   - }
37   -}
38   -defineExpose({ open }) // 提供 open 方法,用于打开弹窗
39   -
40   -/** 提交表单 */
41   -const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
42   -const submitForm = async () => {
43   - // 校验表单
44   - if (!formRef) return
45   - const valid = await formRef.value.getElFormRef().validate()
46   - if (!valid) return
47   - // 提交请求
48   - formLoading.value = true
49   - try {
50   - const data = formRef.value.formModel as ${simpleClassName}Api.${simpleClassName}VO
51   - if (formType.value === 'create') {
52   - await ${simpleClassName}Api.create${simpleClassName}(data)
53   - message.success(t('common.createSuccess'))
54   - } else {
55   - await ${simpleClassName}Api.update${simpleClassName}(data)
56   - message.success(t('common.updateSuccess'))
57   - }
58   - dialogVisible.value = false
59   - // 发送操作成功的事件
60   - emit('success')
61   - } finally {
62   - formLoading.value = false
63   - }
64   -}
65   -</script>
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_schema/views/index.vue.vm deleted
1   -<template>
2   - <!-- 搜索工作栏 -->
3   - <ContentWrap>
4   - <Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams">
5   - <!-- 新增等操作按钮 -->
6   - <template #actionMore>
7   - <el-button
8   - type="primary"
9   - plain
10   - @click="openForm('create')"
11   - v-hasPermi="['${permissionPrefix}:create']"
12   - >
13   - <Icon icon="ep:plus" class="mr-5px" /> 新增
14   - </el-button>
15   - </template>
16   - </Search>
17   - </ContentWrap>
18   -
19   - <!-- 列表 -->
20   - <ContentWrap>
21   - <Table
22   - :columns="allSchemas.tableColumns"
23   - :data="tableObject.tableList"
24   - :loading="tableObject.loading"
25   - :pagination="{
26   - total: tableObject.total
27   - }"
28   - v-model:pageSize="tableObject.pageSize"
29   - v-model:currentPage="tableObject.currentPage"
30   - >
31   - <template #action="{ row }">
32   - <el-button
33   - link
34   - type="primary"
35   - @click="openForm('update', row.id)"
36   - v-hasPermi="['${permissionPrefix}:update']"
37   - >
38   - 编辑
39   - </el-button>
40   - <el-button
41   - link
42   - type="danger"
43   - v-hasPermi="['${permissionPrefix}:delete']"
44   - @click="handleDelete(row.id)"
45   - >
46   - 删除
47   - </el-button>
48   - </template>
49   - </Table>
50   - </ContentWrap>
51   -
52   - <!-- 表单弹窗:添加/修改 -->
53   - <${simpleClassName}Form ref="formRef" @success="getList" />
54   -</template>
55   -<script setup lang="ts" name="${table.className}">
56   -import { allSchemas } from './${classNameVar}.data'
57   -import * as ${simpleClassName}Api from '@/api/${table.moduleName}/${table.businessName}'
58   -import ${simpleClassName}Form from './${simpleClassName}Form.vue'
59   -
60   -// tableObject:表格的属性对象,可获得分页大小、条数等属性
61   -// tableMethods:表格的操作对象,可进行获得分页、删除记录等操作
62   -// 详细可见:https://doc.iocoder.cn/vue3/crud-schema/
63   -const { tableObject, tableMethods } = useTable({
64   - getListApi: ${simpleClassName}Api.get${simpleClassName}Page, // 分页接口
65   - delListApi: ${simpleClassName}Api.delete${simpleClassName} // 删除接口
66   -})
67   -// 获得表格的各种操作
68   -const { getList, setSearchParams } = tableMethods
69   -
70   -/** 添加/修改操作 */
71   -const formRef = ref()
72   -const openForm = (type: string, id?: number) => {
73   - formRef.value.open(type, id)
74   -}
75   -
76   -/** 删除按钮操作 */
77   -const handleDelete = (id: number) => {
78   - tableMethods.delList(id, false)
79   -}
80   -
81   -/** 初始化 **/
82   -onMounted(() => {
83   - getList()
84   -})
85   -</script>
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben/views/data.ts.vm
... ... @@ -42,9 +42,17 @@ export const searchFormSchema: FormSchema[] = [
42 42 #foreach($column in $columns)
43 43 #if ($column.listOperation)
44 44 #set ($dictType=$column.dictType)
  45 + #set ($javaType = $column.javaType)
45 46 #set ($javaField = $column.javaField)
46 47 #set ($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
47 48 #set ($comment=$column.columnComment)
  49 + #if ($javaType == "Integer" || $javaType == "Long" || $javaType == "Byte" || $javaType == "Short")
  50 + #set ($dictMethod = "number")
  51 + #elseif ($javaType == "String")
  52 + #set ($dictMethod = "string")
  53 + #elseif ($javaType == "Boolean")
  54 + #set ($dictMethod = "boolean")
  55 + #end
48 56 {
49 57 label: '${comment}',
50 58 field: '${javaField}',
... ... @@ -54,16 +62,16 @@ export const searchFormSchema: FormSchema[] = [
54 62 component: 'Select',
55 63 componentProps: {
56 64 #if ("" != $dictType)## 设置了 dictType 数据字典的情况
57   - options: getDictOptions(DICT_TYPE.$dictType.toUpperCase()),
  65 + options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod'),
58 66 #else## 未设置 dictType 数据字典的情况
59 67 options: [],
60 68 #end
61 69 },
62 70 #elseif ($column.htmlType == "radio")
63   - component: 'Radio',
  71 + component: 'RadioButtonGroup',
64 72 componentProps: {
65 73 #if ("" != $dictType)## 设置了 dictType 数据字典的情况
66   - options: getDictOptions(DICT_TYPE.$dictType.toUpperCase()),
  74 + options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod'),
67 75 #else## 未设置 dictType 数据字典的情况
68 76 options: [],
69 77 #end
... ... @@ -87,9 +95,17 @@ export const createFormSchema: FormSchema[] = [
87 95 #foreach($column in $columns)
88 96 #if ($column.createOperation)
89 97 #set ($dictType = $column.dictType)
  98 + #set ($javaType = $column.javaType)
90 99 #set ($javaField = $column.javaField)
91 100 #set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
92 101 #set ($comment = $column.columnComment)
  102 + #if ($javaType == "Integer" || $javaType == "Long" || $javaType == "Byte" || $javaType == "Short")
  103 + #set ($dictMethod = "number")
  104 + #elseif ($javaType == "String")
  105 + #set ($dictMethod = "string")
  106 + #elseif ($javaType == "Boolean")
  107 + #set ($dictMethod = "boolean")
  108 + #end
93 109 #if (!$column.primaryKey)## 忽略主键,不用在表单里
94 110 {
95 111 label: '${comment}',
... ... @@ -117,7 +133,7 @@ export const createFormSchema: FormSchema[] = [
117 133 component: 'Select',
118 134 componentProps: {
119 135 #if ("" != $dictType)## 有数据字典
120   - options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), 'number'),
  136 + options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod'),
121 137 #else##没数据字典
122 138 options:[],
123 139 #end
... ... @@ -126,7 +142,7 @@ export const createFormSchema: FormSchema[] = [
126 142 component: 'Checkbox',
127 143 componentProps: {
128 144 #if ("" != $dictType)## 有数据字典
129   - options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), 'number'),
  145 + options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod'),
130 146 #else##没数据字典
131 147 options:[],
132 148 #end
... ... @@ -135,7 +151,7 @@ export const createFormSchema: FormSchema[] = [
135 151 component: 'RadioButtonGroup',
136 152 componentProps: {
137 153 #if ("" != $dictType)## 有数据字典
138   - options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), 'number'),
  154 + options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod'),
139 155 #else##没数据字典
140 156 options:[],
141 157 #end
... ... @@ -166,9 +182,17 @@ export const updateFormSchema: FormSchema[] = [
166 182 #foreach($column in $columns)
167 183 #if ($column.updateOperation)
168 184 #set ($dictType = $column.dictType)
  185 +#set ($javaType = $column.javaType)
169 186 #set ($javaField = $column.javaField)
170 187 #set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
171 188 #set ($comment = $column.columnComment)
  189 +#if ($javaType == "Integer" || $javaType == "Long" || $javaType == "Byte" || $javaType == "Short")
  190 + #set ($dictMethod = "number")
  191 +#elseif ($javaType == "String")
  192 + #set ($dictMethod = "string")
  193 +#elseif ($javaType == "Boolean")
  194 + #set ($dictMethod = "boolean")
  195 +#end
172 196 #if (!$column.primaryKey)## 忽略主键,不用在表单里
173 197 {
174 198 label: '${comment}',
... ... @@ -196,7 +220,7 @@ export const updateFormSchema: FormSchema[] = [
196 220 component: 'Select',
197 221 componentProps: {
198 222 #if ("" != $dictType)## 有数据字典
199   - options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), 'number'),
  223 + options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod'),
200 224 #else##没数据字典
201 225 options:[],
202 226 #end
... ... @@ -205,7 +229,7 @@ export const updateFormSchema: FormSchema[] = [
205 229 component: 'Checkbox',
206 230 componentProps: {
207 231 #if ("" != $dictType)## 有数据字典
208   - options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), 'number'),
  232 + options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod'),
209 233 #else##没数据字典
210 234 options:[],
211 235 #end
... ... @@ -214,7 +238,7 @@ export const updateFormSchema: FormSchema[] = [
214 238 component: 'RadioButtonGroup',
215 239 componentProps: {
216 240 #if ("" != $dictType)## 有数据字典
217   - options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), 'number'),
  241 + options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod'),
218 242 #else##没数据字典
219 243 options:[],
220 244 #end
... ...
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/CombinationRecordApi.java
... ... @@ -2,8 +2,8 @@ package cn.iocoder.yudao.module.promotion.api.combination;
2 2  
3 3 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO;
4 4 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateRespDTO;
  5 +import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordRespDTO;
5 6 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationValidateJoinRespDTO;
6   -
7 7 import jakarta.validation.Valid;
8 8  
9 9 /**
... ... @@ -33,13 +33,13 @@ public interface CombinationRecordApi {
33 33 CombinationRecordCreateRespDTO createCombinationRecord(@Valid CombinationRecordCreateReqDTO reqDTO);
34 34  
35 35 /**
36   - * 查询拼团记录是否成功
  36 + * 基于订单编号,查询拼团记录
37 37 *
38 38 * @param userId 用户编号
39 39 * @param orderId 订单编号
40   - * @return 拼团是否成功
  40 + * @return 拼团记录
41 41 */
42   - boolean isCombinationRecordSuccess(Long userId, Long orderId);
  42 + CombinationRecordRespDTO getCombinationRecordByOrderId(Long userId, Long orderId);
43 43  
44 44 /**
45 45 * 【下单前】校验是否满足拼团活动条件
... ...
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/dto/CombinationRecordRespDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.promotion.api.combination.dto;
  2 +
  3 +import cn.iocoder.yudao.module.promotion.enums.combination.CombinationRecordStatusEnum;
  4 +import lombok.Data;
  5 +
  6 +import java.time.LocalDateTime;
  7 +
  8 +/**
  9 + * 拼团记录 Response DTO
  10 + *
  11 + * @author 芋道源码
  12 + */
  13 +@Data
  14 +public class CombinationRecordRespDTO {
  15 +
  16 + /**
  17 + * 编号,主键自增
  18 + */
  19 + private Long id;
  20 +
  21 + /**
  22 + * 拼团活动编号
  23 + *
  24 + * 关联 CombinationActivityDO 的 id 字段
  25 + */
  26 + private Long activityId;
  27 + /**
  28 + * 拼团商品单价
  29 + *
  30 + * 冗余 CombinationProductDO 的 combinationPrice 字段
  31 + */
  32 + private Integer combinationPrice;
  33 + /**
  34 + * SPU 编号
  35 + */
  36 + private Long spuId;
  37 + /**
  38 + * 商品名字
  39 + */
  40 + private String spuName;
  41 + /**
  42 + * 商品图片
  43 + */
  44 + private String picUrl;
  45 + /**
  46 + * SKU 编号
  47 + */
  48 + private Long skuId;
  49 + /**
  50 + * 购买的商品数量
  51 + */
  52 + private Integer count;
  53 +
  54 + /**
  55 + * 用户编号
  56 + */
  57 + private Long userId;
  58 +
  59 + /**
  60 + * 用户昵称
  61 + */
  62 + private String nickname;
  63 + /**
  64 + * 用户头像
  65 + */
  66 + private String avatar;
  67 +
  68 + /**
  69 + * 团长编号
  70 + */
  71 + private Long headId;
  72 + /**
  73 + * 开团状态
  74 + *
  75 + * 关联 {@link CombinationRecordStatusEnum}
  76 + */
  77 + private Integer status;
  78 + /**
  79 + * 订单编号
  80 + */
  81 + private Long orderId;
  82 + /**
  83 + * 开团需要人数
  84 + *
  85 + * 关联 CombinationActivityDO 的 userSize 字段
  86 + */
  87 + private Integer userSize;
  88 + /**
  89 + * 已加入拼团人数
  90 + */
  91 + private Integer userCount;
  92 + /**
  93 + * 是否虚拟成团
  94 + */
  95 + private Boolean virtualGroup;
  96 +
  97 + /**
  98 + * 过期时间
  99 + */
  100 + private LocalDateTime expireTime;
  101 + /**
  102 + * 开始时间 (订单付款后开始的时间)
  103 + */
  104 + private LocalDateTime startTime;
  105 + /**
  106 + * 结束时间(成团时间/失败时间)
  107 + */
  108 + private LocalDateTime endTime;
  109 +
  110 +}
... ...
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApi.java
... ... @@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.promotion.api.coupon;
2 2  
3 3 import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponRespDTO;
4 4 import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponUseReqDTO;
5   -import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponValidReqDTO;
6 5 import jakarta.validation.Valid;
7 6  
8 7 import java.util.List;
... ... @@ -16,6 +15,15 @@ import java.util.Map;
16 15 public interface CouponApi {
17 16  
18 17 /**
  18 + * 获得用户的优惠劵列表
  19 + *
  20 + * @param userId 用户编号
  21 + * @param status 优惠劵状态
  22 + * @return 优惠劵列表
  23 + */
  24 + List<CouponRespDTO> getCouponListByUserId(Long userId, Integer status);
  25 +
  26 + /**
19 27 * 使用优惠劵
20 28 *
21 29 * @param useReqDTO 使用请求
... ... @@ -30,14 +38,6 @@ public interface CouponApi {
30 38 void returnUsedCoupon(Long id);
31 39  
32 40 /**
33   - * 校验优惠劵
34   - *
35   - * @param validReqDTO 校验请求
36   - * @return 优惠劵
37   - */
38   - CouponRespDTO validateCoupon(@Valid CouponValidReqDTO validReqDTO);
39   -
40   - /**
41 41 * 【管理员】给指定用户批量发送优惠券
42 42 *
43 43 * @param giveCoupons key: 优惠劵模版编号,value:对应的数量
... ...
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/dto/CouponValidReqDTO.java deleted
1   -package cn.iocoder.yudao.module.promotion.api.coupon.dto;
2   -
3   -import lombok.Data;
4   -
5   -import jakarta.validation.constraints.NotNull;
6   -
7   -/**
8   - * 优惠劵使用 Request DTO
9   - *
10   - * @author 芋道源码
11   - */
12   -@Data
13   -public class CouponValidReqDTO {
14   -
15   - /**
16   - * 优惠劵编号
17   - */
18   - @NotNull(message = "优惠劵编号不能为空")
19   - private Long id;
20   -
21   - /**
22   - * 用户编号
23   - */
24   - @NotNull(message = "用户编号不能为空")
25   - private Long userId;
26   -
27   -}
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/ErrorCodeConstants.java
... ... @@ -21,8 +21,6 @@ public interface ErrorCodeConstants {
21 21 ErrorCode BANNER_NOT_EXISTS = new ErrorCode(1_013_002_000, "Banner 不存在");
22 22  
23 23 // ========== Coupon 相关 1-013-003-000 ============
24   - ErrorCode COUPON_NO_MATCH_SPU = new ErrorCode(1_013_003_000, "优惠劵没有可使用的商品!");
25   - ErrorCode COUPON_NO_MATCH_MIN_PRICE = new ErrorCode(1_013_003_001, "所结算的商品中未满足使用的金额");
26 24  
27 25 // ========== 优惠劵模板 1-013-004-000 ==========
28 26 ErrorCode COUPON_TEMPLATE_NOT_EXISTS = new ErrorCode(1_013_004_000, "优惠劵模板不存在");
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/combination/CombinationRecordApiImpl.java
1 1 package cn.iocoder.yudao.module.promotion.api.combination;
2 2  
  3 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
3 4 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateReqDTO;
4 5 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateRespDTO;
  6 +import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordRespDTO;
5 7 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationValidateJoinRespDTO;
6 8 import cn.iocoder.yudao.module.promotion.convert.combination.CombinationActivityConvert;
7 9 import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationRecordDO;
8   -import cn.iocoder.yudao.module.promotion.enums.combination.CombinationRecordStatusEnum;
9 10 import cn.iocoder.yudao.module.promotion.service.combination.CombinationRecordService;
10 11 import jakarta.annotation.Resource;
11 12 import org.springframework.stereotype.Service;
12 13 import org.springframework.validation.annotation.Validated;
13 14  
14   -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
15   -import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.COMBINATION_RECORD_NOT_EXISTS;
16   -
17 15 /**
18 16 * 拼团活动 API 实现类
19 17 *
... ... @@ -37,12 +35,9 @@ public class CombinationRecordApiImpl implements CombinationRecordApi {
37 35 }
38 36  
39 37 @Override
40   - public boolean isCombinationRecordSuccess(Long userId, Long orderId) {
  38 + public CombinationRecordRespDTO getCombinationRecordByOrderId(Long userId, Long orderId) {
41 39 CombinationRecordDO record = combinationRecordService.getCombinationRecord(userId, orderId);
42   - if (record == null) {
43   - throw exception(COMBINATION_RECORD_NOT_EXISTS);
44   - }
45   - return CombinationRecordStatusEnum.isSuccess(record.getStatus());
  40 + return BeanUtils.toBean(record, CombinationRecordRespDTO.class);
46 41 }
47 42  
48 43 @Override
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/coupon/CouponApiImpl.java
1 1 package cn.iocoder.yudao.module.promotion.api.coupon;
2 2  
3 3  
  4 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
4 5 import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponRespDTO;
5 6 import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponUseReqDTO;
6   -import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponValidReqDTO;
7   -import cn.iocoder.yudao.module.promotion.convert.coupon.CouponConvert;
8   -import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;
9 7 import cn.iocoder.yudao.module.promotion.service.coupon.CouponService;
10 8 import jakarta.annotation.Resource;
11 9 import org.springframework.stereotype.Service;
... ... @@ -27,6 +25,11 @@ public class CouponApiImpl implements CouponApi {
27 25 private CouponService couponService;
28 26  
29 27 @Override
  28 + public List<CouponRespDTO> getCouponListByUserId(Long userId, Integer status) {
  29 + return BeanUtils.toBean(couponService.getCouponList(userId, status), CouponRespDTO.class);
  30 + }
  31 +
  32 + @Override
30 33 public void useCoupon(CouponUseReqDTO useReqDTO) {
31 34 couponService.useCoupon(useReqDTO.getId(), useReqDTO.getUserId(),
32 35 useReqDTO.getOrderId());
... ... @@ -38,12 +41,6 @@ public class CouponApiImpl implements CouponApi {
38 41 }
39 42  
40 43 @Override
41   - public CouponRespDTO validateCoupon(CouponValidReqDTO validReqDTO) {
42   - CouponDO coupon = couponService.validCoupon(validReqDTO.getId(), validReqDTO.getUserId());
43   - return CouponConvert.INSTANCE.convert(coupon);
44   - }
45   -
46   - @Override
47 44 public List<Long> takeCouponsByAdmin(Map<Long, Integer> giveCoupons, Long userId) {
48 45 return couponService.takeCouponsByAdmin(giveCoupons, userId);
49 46 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/combination/CombinationActivityController.java
1 1 package cn.iocoder.yudao.module.promotion.controller.admin.combination;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
  4 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
4 5 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5 6 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 7 import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
... ... @@ -16,18 +17,20 @@ import cn.iocoder.yudao.module.promotion.service.combination.CombinationRecordSe
16 17 import io.swagger.v3.oas.annotations.Operation;
17 18 import io.swagger.v3.oas.annotations.Parameter;
18 19 import io.swagger.v3.oas.annotations.tags.Tag;
  20 +import jakarta.annotation.Resource;
  21 +import jakarta.validation.Valid;
19 22 import org.springframework.security.access.prepost.PreAuthorize;
20 23 import org.springframework.validation.annotation.Validated;
21 24 import org.springframework.web.bind.annotation.*;
22 25  
23   -import jakarta.annotation.Resource;
24   -import jakarta.validation.Valid;
  26 +import java.util.Collections;
25 27 import java.util.List;
26 28 import java.util.Map;
27 29 import java.util.Set;
28 30  
29 31 import static cn.hutool.core.collection.CollectionUtil.newArrayList;
30 32 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  33 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
31 34 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
32 35  
33 36 @Tag(name = "管理后台 - 拼团活动")
... ... @@ -87,6 +90,23 @@ public class CombinationActivityController {
87 90 return success(CombinationActivityConvert.INSTANCE.convert(activity, products));
88 91 }
89 92  
  93 + @GetMapping("/list-by-ids")
  94 + @Operation(summary = "获得拼团活动列表,基于活动编号数组")
  95 + @Parameter(name = "ids", description = "活动编号数组", required = true, example = "[1024, 1025]")
  96 + public CommonResult<List<CombinationActivityRespVO>> getCombinationActivityListByIds(@RequestParam("ids") List<Long> ids) {
  97 + // 1. 获得开启的活动列表
  98 + List<CombinationActivityDO> activityList = combinationActivityService.getCombinationActivityListByIds(ids);
  99 + activityList.removeIf(activity -> CommonStatusEnum.isDisable(activity.getStatus()));
  100 + if (CollUtil.isEmpty(activityList)) {
  101 + return success(Collections.emptyList());
  102 + }
  103 + // 2. 拼接返回
  104 + List<CombinationProductDO> productList = combinationActivityService.getCombinationProductListByActivityIds(
  105 + convertList(activityList, CombinationActivityDO::getId));
  106 + List<ProductSpuRespDTO> spuList = productSpuApi.getSpuList(convertList(activityList, CombinationActivityDO::getSpuId));
  107 + return success(CombinationActivityConvert.INSTANCE.convertList(activityList, productList, spuList));
  108 + }
  109 +
90 110 @GetMapping("/page")
91 111 @Operation(summary = "获得拼团活动分页")
92 112 @PreAuthorize("@ss.hasPermission('promotion:combination-activity:query')")
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/combination/vo/activity/CombinationActivityRespVO.java
... ... @@ -27,4 +27,14 @@ public class CombinationActivityRespVO extends CombinationActivityBaseVO {
27 27 @Schema(description = "拼团商品", requiredMode = Schema.RequiredMode.REQUIRED)
28 28 private List<CombinationProductRespVO> products;
29 29  
  30 + @Schema(description = "商品 SPU 名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "一个白菜")
  31 + private String spuName; // 从 SPU 的 name 读取
  32 + @Schema(description = "商品图片", requiredMode = Schema.RequiredMode.REQUIRED, example = "4096")
  33 + private String picUrl; // 从 SPU 的 picUrl 读取
  34 + @Schema(description = "商品市场价,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "50")
  35 + private Integer marketPrice; // 从 SPU 的 marketPrice 读取
  36 +
  37 + @Schema(description = "拼团金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
  38 + private Integer combinationPrice; // 从 products 获取最小 price 读取
  39 +
30 40 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/seckill/SeckillActivityController.java
1 1 package cn.iocoder.yudao.module.promotion.controller.admin.seckill;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
  4 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
4 5 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5 6 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 7 import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
... ... @@ -13,15 +14,17 @@ import cn.iocoder.yudao.module.promotion.service.seckill.SeckillActivityService;
13 14 import io.swagger.v3.oas.annotations.Operation;
14 15 import io.swagger.v3.oas.annotations.Parameter;
15 16 import io.swagger.v3.oas.annotations.tags.Tag;
  17 +import jakarta.annotation.Resource;
  18 +import jakarta.validation.Valid;
16 19 import org.springframework.security.access.prepost.PreAuthorize;
17 20 import org.springframework.validation.annotation.Validated;
18 21 import org.springframework.web.bind.annotation.*;
19 22  
20   -import jakarta.annotation.Resource;
21   -import jakarta.validation.Valid;
  23 +import java.util.Collections;
22 24 import java.util.List;
23 25  
24 26 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  27 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
25 28 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
26 29  
27 30 @Tag(name = "管理后台 - 秒杀活动")
... ... @@ -89,11 +92,28 @@ public class SeckillActivityController {
89 92 }
90 93  
91 94 // 拼接数据
92   - List<SeckillProductDO> products = seckillActivityService.getSeckillProductListByActivityId(
  95 + List<SeckillProductDO> products = seckillActivityService.getSeckillProductListByActivityIds(
93 96 convertSet(pageResult.getList(), SeckillActivityDO::getId));
94 97 List<ProductSpuRespDTO> spuList = productSpuApi.getSpuList(
95 98 convertSet(pageResult.getList(), SeckillActivityDO::getSpuId));
96 99 return success(SeckillActivityConvert.INSTANCE.convertPage(pageResult, products, spuList));
97 100 }
98 101  
  102 + @GetMapping("/list-by-ids")
  103 + @Operation(summary = "获得秒杀活动列表,基于活动编号数组")
  104 + @Parameter(name = "ids", description = "活动编号数组", required = true, example = "[1024, 1025]")
  105 + public CommonResult<List<SeckillActivityRespVO>> getCombinationActivityListByIds(@RequestParam("ids") List<Long> ids) {
  106 + // 1. 获得开启的活动列表
  107 + List<SeckillActivityDO> activityList = seckillActivityService.getSeckillActivityListByIds(ids);
  108 + activityList.removeIf(activity -> CommonStatusEnum.isDisable(activity.getStatus()));
  109 + if (CollUtil.isEmpty(activityList)) {
  110 + return success(Collections.emptyList());
  111 + }
  112 + // 2. 拼接返回
  113 + List<SeckillProductDO> productList = seckillActivityService.getSeckillProductListByActivityIds(
  114 + convertList(activityList, SeckillActivityDO::getId));
  115 + List<ProductSpuRespDTO> spuList = productSpuApi.getSpuList(convertList(activityList, SeckillActivityDO::getSpuId));
  116 + return success(SeckillActivityConvert.INSTANCE.convertList(activityList, productList, spuList));
  117 + }
  118 +
99 119 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/seckill/vo/activity/SeckillActivityRespVO.java
... ... @@ -54,4 +54,7 @@ public class SeckillActivityRespVO extends SeckillActivityBaseVO {
54 54 example = "50")
55 55 private Integer marketPrice;
56 56  
  57 + @Schema(description = "拼团金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
  58 + private Integer seckillPrice; // 从 products 获取最小 price 读取
  59 +
57 60 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/combination/AppCombinationActivityController.java
... ... @@ -14,24 +14,20 @@ import cn.iocoder.yudao.module.promotion.convert.combination.CombinationActivity
14 14 import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationActivityDO;
15 15 import cn.iocoder.yudao.module.promotion.dal.dataobject.combination.CombinationProductDO;
16 16 import cn.iocoder.yudao.module.promotion.service.combination.CombinationActivityService;
17   -import com.google.common.cache.CacheLoader;
18   -import com.google.common.cache.LoadingCache;
19 17 import io.swagger.v3.oas.annotations.Operation;
20 18 import io.swagger.v3.oas.annotations.Parameter;
21 19 import io.swagger.v3.oas.annotations.tags.Tag;
  20 +import jakarta.annotation.Resource;
22 21 import org.springframework.validation.annotation.Validated;
23 22 import org.springframework.web.bind.annotation.GetMapping;
24 23 import org.springframework.web.bind.annotation.RequestMapping;
25 24 import org.springframework.web.bind.annotation.RequestParam;
26 25 import org.springframework.web.bind.annotation.RestController;
27 26  
28   -import jakarta.annotation.Resource;
29   -import java.time.Duration;
30 27 import java.util.Collections;
31 28 import java.util.List;
32 29  
33 30 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
34   -import static cn.iocoder.yudao.framework.common.util.cache.CacheUtils.buildAsyncReloadingCache;
35 31 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
36 32  
37 33 @Tag(name = "用户 APP - 拼团活动")
... ... @@ -40,45 +36,12 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
40 36 @Validated
41 37 public class AppCombinationActivityController {
42 38  
43   - /**
44   - * {@link AppCombinationActivityRespVO} 缓存,通过它异步刷新 {@link #getCombinationActivityList0(Integer)} 所要的首页数据
45   - */
46   - private final LoadingCache<Integer, List<AppCombinationActivityRespVO>> combinationActivityListCache = buildAsyncReloadingCache(Duration.ofSeconds(10L),
47   - new CacheLoader<Integer, List<AppCombinationActivityRespVO>>() {
48   -
49   - @Override
50   - public List<AppCombinationActivityRespVO> load(Integer count) {
51   - return getCombinationActivityList0(count);
52   - }
53   -
54   - });
55   -
56 39 @Resource
57 40 private CombinationActivityService activityService;
58 41  
59 42 @Resource
60 43 private ProductSpuApi spuApi;
61 44  
62   - @GetMapping("/list")
63   - @Operation(summary = "获得拼团活动列表", description = "用于小程序首页")
64   - @Parameter(name = "count", description = "需要展示的数量", example = "6")
65   - public CommonResult<List<AppCombinationActivityRespVO>> getCombinationActivityList(
66   - @RequestParam(name = "count", defaultValue = "6") Integer count) {
67   - return success(combinationActivityListCache.getUnchecked(count));
68   - }
69   -
70   - private List<AppCombinationActivityRespVO> getCombinationActivityList0(Integer count) {
71   - List<CombinationActivityDO> activityList = activityService.getCombinationActivityListByCount(count);
72   - if (CollUtil.isEmpty(activityList)) {
73   - return Collections.emptyList();
74   - }
75   - // 拼接返回
76   - List<CombinationProductDO> productList = activityService.getCombinationProductListByActivityIds(
77   - convertList(activityList, CombinationActivityDO::getId));
78   - List<ProductSpuRespDTO> spuList = spuApi.getSpuList(convertList(activityList, CombinationActivityDO::getSpuId));
79   - return CombinationActivityConvert.INSTANCE.convertAppList(activityList, productList, spuList);
80   - }
81   -
82 45 @GetMapping("/page")
83 46 @Operation(summary = "获得拼团活动分页")
84 47 public CommonResult<PageResult<AppCombinationActivityRespVO>> getCombinationActivityPage(PageParam pageParam) {
... ... @@ -93,6 +56,23 @@ public class AppCombinationActivityController {
93 56 return success(CombinationActivityConvert.INSTANCE.convertAppPage(pageResult, productList, spuList));
94 57 }
95 58  
  59 + @GetMapping("/list-by-ids")
  60 + @Operation(summary = "获得拼团活动列表,基于活动编号数组")
  61 + @Parameter(name = "ids", description = "活动编号数组", required = true, example = "[1024, 1025]")
  62 + public CommonResult<List<AppCombinationActivityRespVO>> getCombinationActivityListByIds(@RequestParam("ids") List<Long> ids) {
  63 + // 1. 获得开启的活动列表
  64 + List<CombinationActivityDO> activityList = activityService.getCombinationActivityListByIds(ids);
  65 + activityList.removeIf(activity -> CommonStatusEnum.isDisable(activity.getStatus()));
  66 + if (CollUtil.isEmpty(activityList)) {
  67 + return success(Collections.emptyList());
  68 + }
  69 + // 2. 拼接返回
  70 + List<CombinationProductDO> productList = activityService.getCombinationProductListByActivityIds(
  71 + convertList(activityList, CombinationActivityDO::getId));
  72 + List<ProductSpuRespDTO> spuList = spuApi.getSpuList(convertList(activityList, CombinationActivityDO::getSpuId));
  73 + return success(CombinationActivityConvert.INSTANCE.convertAppList(activityList, productList, spuList));
  74 + }
  75 +
96 76 @GetMapping("/get-detail")
97 77 @Operation(summary = "获得拼团活动明细")
98 78 @Parameter(name = "id", description = "活动编号", required = true, example = "1024")
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/combination/vo/activity/AppCombinationActivityRespVO.java
... ... @@ -19,15 +19,14 @@ public class AppCombinationActivityRespVO {
19 19 @Schema(description = "商品 SPU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
20 20 private Long spuId;
21 21  
  22 + @Schema(description = "商品 SPU 名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "一个白菜")
  23 + private String spuName; // 从 SPU 的 name 读取
22 24 @Schema(description = "商品图片", requiredMode = Schema.RequiredMode.REQUIRED, example = "4096")
23   - // 从 SPU 的 picUrl 读取
24   - private String picUrl;
25   -
  25 + private String picUrl; // 从 SPU 的 picUrl 读取
26 26 @Schema(description = "商品市场价,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "50")
27   - // 从 SPU 的 marketPrice 读取
28   - private Integer marketPrice;
  27 + private Integer marketPrice; // 从 SPU 的 marketPrice 读取
29 28  
30 29 @Schema(description = "拼团金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
31   - private Integer combinationPrice;
  30 + private Integer combinationPrice; // 从 products 获取最小 price 读取
32 31  
33 32 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/AppCouponController.java
... ... @@ -5,7 +5,9 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 6 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
7 7 import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
8   -import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.*;
  8 +import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponPageReqVO;
  9 +import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponRespVO;
  10 +import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponTakeReqVO;
9 11 import cn.iocoder.yudao.module.promotion.convert.coupon.CouponConvert;
10 12 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;
11 13 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO;
... ... @@ -15,13 +17,12 @@ import cn.iocoder.yudao.module.promotion.service.coupon.CouponTemplateService;
15 17 import io.swagger.v3.oas.annotations.Operation;
16 18 import io.swagger.v3.oas.annotations.Parameter;
17 19 import io.swagger.v3.oas.annotations.tags.Tag;
  20 +import jakarta.annotation.Resource;
  21 +import jakarta.validation.Valid;
18 22 import org.springframework.validation.annotation.Validated;
19 23 import org.springframework.web.bind.annotation.*;
20 24  
21   -import jakarta.annotation.Resource;
22   -import jakarta.validation.Valid;
23 25 import java.util.Collections;
24   -import java.util.List;
25 26  
26 27 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
27 28 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
... ... @@ -56,14 +57,6 @@ public class AppCouponController {
56 57 return success(canTakeAgain);
57 58 }
58 59  
59   - @GetMapping("/match-list")
60   - @Operation(summary = "获得匹配指定商品的优惠劵列表", description = "用于下单页,展示优惠劵列表")
61   - public CommonResult<List<AppCouponMatchRespVO>> getMatchCouponList(AppCouponMatchReqVO matchReqVO) {
62   - // todo: 优化:优惠金额倒序
63   - List<AppCouponMatchRespVO> list = couponService.getMatchCouponList(getLoginUserId(), matchReqVO);
64   - return success(list);
65   - }
66   -
67 60 @GetMapping("/page")
68 61 @Operation(summary = "我的优惠劵列表")
69 62 @PreAuthenticated
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/vo/coupon/AppCouponMatchReqVO.java deleted
1   -package cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon;
2   -
3   -import io.swagger.v3.oas.annotations.media.Schema;
4   -import lombok.Data;
5   -
6   -import jakarta.validation.constraints.NotEmpty;
7   -import jakarta.validation.constraints.NotNull;
8   -import java.util.List;
9   -
10   -@Schema(description = "用户 App - 优惠劵的匹配 Request VO")
11   -@Data
12   -public class AppCouponMatchReqVO {
13   -
14   - @Schema(description = "商品金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
15   - @NotNull(message = "商品金额不能为空")
16   - private Integer price;
17   -
18   - @Schema(description = "商品 SPU 编号的数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1, 2]")
19   - @NotEmpty(message = "商品 SPU 编号不能为空")
20   - private List<Long> spuIds;
21   -
22   - @Schema(description = "商品 SKU 编号的数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1, 2]")
23   - @NotEmpty(message = "商品 SKU 编号不能为空")
24   - private List<Long> skuIds;
25   -
26   - @Schema(description = "分类编号的数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "[10, 20]")
27   - @NotEmpty(message = "分类编号不能为空")
28   - private List<Long> categoryIds;
29   -
30   -}
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/vo/coupon/AppCouponMatchRespVO.java deleted
1   -package cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon;
2   -
3   -import io.swagger.v3.oas.annotations.media.Schema;
4   -import lombok.Data;
5   -
6   -@Schema(description = "用户 App - 优惠劵 Response VO")
7   -@Data
8   -public class AppCouponMatchRespVO extends AppCouponRespVO {
9   -
10   - @Schema(description = "是否匹配", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
11   - private Boolean match;
12   -
13   - @Schema(description = "匹配条件的提示", example = "所结算商品没有符合条件的商品")
14   - private String description;
15   -
16   -}
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/coupon/vo/coupon/AppCouponRespVO.java
... ... @@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon;
3 3 import io.swagger.v3.oas.annotations.media.Schema;
4 4 import lombok.Data;
5 5  
6   -import jakarta.validation.constraints.Min;
7 6 import java.time.LocalDateTime;
8 7 import java.util.List;
9 8  
... ... @@ -42,7 +41,6 @@ public class AppCouponRespVO {
42 41 private Integer discountPercent;
43 42  
44 43 @Schema(description = "优惠金额", example = "10")
45   - @Min(value = 0, message = "优惠金额需要大于等于 0")
46 44 private Integer discountPrice;
47 45  
48 46 @Schema(description = "折扣上限", example = "100") // 单位:分,仅在 discountType 为 PERCENT 使用
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/seckill/AppSeckillActivityController.java
... ... @@ -23,6 +23,7 @@ import com.google.common.cache.LoadingCache;
23 23 import io.swagger.v3.oas.annotations.Operation;
24 24 import io.swagger.v3.oas.annotations.Parameter;
25 25 import io.swagger.v3.oas.annotations.tags.Tag;
  26 +import jakarta.annotation.Resource;
26 27 import org.springframework.context.annotation.Lazy;
27 28 import org.springframework.validation.annotation.Validated;
28 29 import org.springframework.web.bind.annotation.GetMapping;
... ... @@ -30,11 +31,11 @@ import org.springframework.web.bind.annotation.RequestMapping;
30 31 import org.springframework.web.bind.annotation.RequestParam;
31 32 import org.springframework.web.bind.annotation.RestController;
32 33  
33   -import jakarta.annotation.Resource;
34 34 import java.time.Duration;
35 35 import java.time.LocalDate;
36 36 import java.time.LocalDateTime;
37 37 import java.time.LocalTime;
  38 +import java.util.Collections;
38 39 import java.util.List;
39 40  
40 41 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
... ... @@ -86,7 +87,7 @@ public class AppSeckillActivityController {
86 87  
87 88 // 2.1 查询满足当前阶段的活动
88 89 List<SeckillActivityDO> activityList = activityService.getSeckillActivityListByConfigIdAndStatus(config.getId(), CommonStatusEnum.ENABLE.getStatus());
89   - List<SeckillProductDO> productList = activityService.getSeckillProductListByActivityId(
  90 + List<SeckillProductDO> productList = activityService.getSeckillProductListByActivityIds(
90 91 convertList(activityList, SeckillActivityDO::getId));
91 92 // 2.2 获取 spu 信息
92 93 List<ProductSpuRespDTO> spuList = spuApi.getSpuList(convertList(activityList, SeckillActivityDO::getSpuId));
... ... @@ -101,7 +102,7 @@ public class AppSeckillActivityController {
101 102 if (CollUtil.isEmpty(pageResult.getList())) {
102 103 return success(PageResult.empty(pageResult.getTotal()));
103 104 }
104   - List<SeckillProductDO> productList = activityService.getSeckillProductListByActivityId(
  105 + List<SeckillProductDO> productList = activityService.getSeckillProductListByActivityIds(
105 106 convertList(pageResult.getList(), SeckillActivityDO::getId));
106 107  
107 108 // 2. 拼接数据
... ... @@ -149,4 +150,21 @@ public class AppSeckillActivityController {
149 150 return success(SeckillActivityConvert.INSTANCE.convert3(activity, productList, startTime, endTime));
150 151 }
151 152  
  153 + @GetMapping("/list-by-ids")
  154 + @Operation(summary = "获得拼团活动列表,基于活动编号数组")
  155 + @Parameter(name = "ids", description = "活动编号数组", required = true, example = "[1024, 1025]")
  156 + public CommonResult<List<AppSeckillActivityRespVO>> getCombinationActivityListByIds(@RequestParam("ids") List<Long> ids) {
  157 + // 1. 获得开启的活动列表
  158 + List<SeckillActivityDO> activityList = activityService.getSeckillActivityListByIds(ids);
  159 + activityList.removeIf(activity -> CommonStatusEnum.isDisable(activity.getStatus()));
  160 + if (CollUtil.isEmpty(activityList)) {
  161 + return success(Collections.emptyList());
  162 + }
  163 + // 2. 拼接返回
  164 + List<SeckillProductDO> productList = activityService.getSeckillProductListByActivityIds(
  165 + convertList(activityList, SeckillActivityDO::getId));
  166 + List<ProductSpuRespDTO> spuList = spuApi.getSpuList(convertList(activityList, SeckillActivityDO::getSpuId));
  167 + return success(SeckillActivityConvert.INSTANCE.convertAppList(activityList, productList, spuList));
  168 + }
  169 +
152 170 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/seckill/vo/activity/AppSeckillActivityRespVO.java
... ... @@ -16,6 +16,9 @@ public class AppSeckillActivityRespVO {
16 16 @Schema(description = "商品 SPU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
17 17 private Long spuId;
18 18  
  19 + @Schema(description = "商品 SPU 名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "一个白菜")
  20 + private String spuName; // 从 SPU 的 name 读取
  21 +
19 22 @Schema(description = "商品图片", requiredMode = Schema.RequiredMode.REQUIRED, // 从 SPU 的 picUrl 读取
20 23 example = "https://www.iocoder.cn/xx.png")
21 24 private String picUrl;
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/combination/CombinationActivityConvert.java
... ... @@ -4,6 +4,7 @@ import cn.hutool.core.util.ObjectUtil;
4 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 5 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
6 6 import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
  7 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
7 8 import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
8 9 import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
9 10 import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
... ... @@ -127,40 +128,42 @@ public interface CombinationActivityConvert {
127 128 .setSpuName(spu.getName()).setPicUrl(sku.getPicUrl());
128 129 }
129 130  
130   - List<AppCombinationActivityRespVO> convertAppList(List<CombinationActivityDO> list);
131   -
132   - default List<AppCombinationActivityRespVO> convertAppList(List<CombinationActivityDO> list,
133   - List<CombinationProductDO> productList,
134   - List<ProductSpuRespDTO> spuList) {
135   - List<AppCombinationActivityRespVO> activityList = convertAppList(list);
  131 + default List<CombinationActivityRespVO> convertList(List<CombinationActivityDO> list,
  132 + List<CombinationProductDO> productList,
  133 + List<ProductSpuRespDTO> spuList) {
  134 + List<CombinationActivityRespVO> activityList = BeanUtils.toBean(list, CombinationActivityRespVO.class);
136 135 Map<Long, ProductSpuRespDTO> spuMap = convertMap(spuList, ProductSpuRespDTO::getId);
137 136 Map<Long, List<CombinationProductDO>> productMap = convertMultiMap(productList, CombinationProductDO::getActivityId);
138 137 return CollectionUtils.convertList(activityList, item -> {
139 138 // 设置 product 信息
140 139 item.setCombinationPrice(getMinValue(productMap.get(item.getId()), CombinationProductDO::getCombinationPrice));
141 140 // 设置 SPU 信息
142   - findAndThen(spuMap, item.getSpuId(), spu -> item.setPicUrl(spu.getPicUrl()).setMarketPrice(spu.getMarketPrice()));
  141 + findAndThen(spuMap, item.getSpuId(), spu -> item.setSpuName(spu.getName())
  142 + .setPicUrl(spu.getPicUrl()).setMarketPrice(spu.getMarketPrice()));
143 143 return item;
144 144 });
145 145 }
146 146  
147   - PageResult<AppCombinationActivityRespVO> convertAppPage(PageResult<CombinationActivityDO> result);
148   -
149   - default PageResult<AppCombinationActivityRespVO> convertAppPage(PageResult<CombinationActivityDO> result,
150   - List<CombinationProductDO> productList,
151   - List<ProductSpuRespDTO> spuList) {
152   - PageResult<AppCombinationActivityRespVO> appPage = convertAppPage(result);
  147 + default List<AppCombinationActivityRespVO> convertAppList(List<CombinationActivityDO> list,
  148 + List<CombinationProductDO> productList,
  149 + List<ProductSpuRespDTO> spuList) {
  150 + List<AppCombinationActivityRespVO> activityList = BeanUtils.toBean(list, AppCombinationActivityRespVO.class);
153 151 Map<Long, ProductSpuRespDTO> spuMap = convertMap(spuList, ProductSpuRespDTO::getId);
154 152 Map<Long, List<CombinationProductDO>> productMap = convertMultiMap(productList, CombinationProductDO::getActivityId);
155   - List<AppCombinationActivityRespVO> list = CollectionUtils.convertList(appPage.getList(), item -> {
  153 + return CollectionUtils.convertList(activityList, item -> {
156 154 // 设置 product 信息
157 155 item.setCombinationPrice(getMinValue(productMap.get(item.getId()), CombinationProductDO::getCombinationPrice));
158 156 // 设置 SPU 信息
159   - findAndThen(spuMap, item.getSpuId(), spu -> item.setPicUrl(spu.getPicUrl()).setMarketPrice(spu.getMarketPrice()));
  157 + findAndThen(spuMap, item.getSpuId(), spu -> item.setSpuName(spu.getName())
  158 + .setPicUrl(spu.getPicUrl()).setMarketPrice(spu.getMarketPrice()));
160 159 return item;
161 160 });
162   - appPage.setList(list);
163   - return appPage;
  161 + }
  162 +
  163 + default PageResult<AppCombinationActivityRespVO> convertAppPage(PageResult<CombinationActivityDO> result,
  164 + List<CombinationProductDO> productList,
  165 + List<ProductSpuRespDTO> spuList) {
  166 + return new PageResult<>(convertAppList(result.getList(), productList, spuList), result.getTotal());
164 167 }
165 168  
166 169 AppCombinationActivityDetailRespVO convert2(CombinationActivityDO combinationActivity);
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/coupon/CouponConvert.java
... ... @@ -4,9 +4,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 4 import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponRespDTO;
5 5 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageItemRespVO;
6 6 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageReqVO;
7   -import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponMatchRespVO;
8 7 import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponPageReqVO;
9   -import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponRespVO;
10 8 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;
11 9 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO;
12 10 import cn.iocoder.yudao.module.promotion.enums.coupon.CouponStatusEnum;
... ... @@ -16,7 +14,6 @@ import org.mapstruct.factory.Mappers;
16 14  
17 15 import java.time.LocalDateTime;
18 16 import java.util.Collection;
19   -import java.util.List;
20 17  
21 18 /**
22 19 * 优惠劵 Convert
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/seckill/SeckillActivityConvert.java
... ... @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.promotion.convert.seckill;
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 4 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
5 5 import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
  6 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
6 7 import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
7 8 import cn.iocoder.yudao.module.promotion.api.seckill.dto.SeckillValidateJoinRespDTO;
8 9 import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.SeckillActivityCreateReqVO;
... ... @@ -87,6 +88,38 @@ public interface SeckillActivityConvert {
87 88 return CollectionUtils.convertList(products, item -> convert(activity, item).setActivityStatus(activity.getStatus()));
88 89 }
89 90  
  91 + default List<SeckillActivityRespVO> convertList(List<SeckillActivityDO> list,
  92 + List<SeckillProductDO> productList,
  93 + List<ProductSpuRespDTO> spuList) {
  94 + List<SeckillActivityRespVO> activityList = BeanUtils.toBean(list, SeckillActivityRespVO.class);
  95 + Map<Long, ProductSpuRespDTO> spuMap = convertMap(spuList, ProductSpuRespDTO::getId);
  96 + Map<Long, List<SeckillProductDO>> productMap = convertMultiMap(productList, SeckillProductDO::getActivityId);
  97 + return CollectionUtils.convertList(activityList, item -> {
  98 + // 设置 product 信息
  99 + item.setSeckillPrice(getMinValue(productMap.get(item.getId()), SeckillProductDO::getSeckillPrice));
  100 + // 设置 SPU 信息
  101 + findAndThen(spuMap, item.getSpuId(), spu -> item.setSpuName(spu.getName())
  102 + .setPicUrl(spu.getPicUrl()).setMarketPrice(spu.getMarketPrice()));
  103 + return item;
  104 + });
  105 + }
  106 +
  107 + default List<AppSeckillActivityRespVO> convertAppList(List<SeckillActivityDO> list,
  108 + List<SeckillProductDO> productList,
  109 + List<ProductSpuRespDTO> spuList) {
  110 + List<AppSeckillActivityRespVO> activityList = BeanUtils.toBean(list, AppSeckillActivityRespVO.class);
  111 + Map<Long, ProductSpuRespDTO> spuMap = convertMap(spuList, ProductSpuRespDTO::getId);
  112 + Map<Long, List<SeckillProductDO>> productMap = convertMultiMap(productList, SeckillProductDO::getActivityId);
  113 + return CollectionUtils.convertList(activityList, item -> {
  114 + // 设置 product 信息
  115 + item.setSeckillPrice(getMinValue(productMap.get(item.getId()), SeckillProductDO::getSeckillPrice));
  116 + // 设置 SPU 信息
  117 + findAndThen(spuMap, item.getSpuId(), spu -> item.setSpuName(spu.getName())
  118 + .setPicUrl(spu.getPicUrl()).setMarketPrice(spu.getMarketPrice()));
  119 + return item;
  120 + });
  121 + }
  122 +
90 123 List<SeckillProductRespVO> convertList2(List<SeckillProductDO> list);
91 124  
92 125 List<AppSeckillActivityRespVO> convertList3(List<SeckillActivityDO> activityList);
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/coupon/CouponMapper.java
1 1 package cn.iocoder.yudao.module.promotion.dal.mysql.coupon;
2 2  
3 3 import cn.hutool.core.map.MapUtil;
4   -import cn.hutool.core.util.StrUtil;
5 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 5 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
7 6 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
8 7 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageReqVO;
9 8 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;
10   -import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
11 9 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
12 10 import com.github.yulichang.toolkit.MPJWrappers;
13 11 import org.apache.ibatis.annotations.Mapper;
... ... @@ -16,8 +14,6 @@ import java.time.LocalDateTime;
16 14 import java.util.Collection;
17 15 import java.util.List;
18 16 import java.util.Map;
19   -import java.util.function.Function;
20   -import java.util.stream.Collectors;
21 17  
22 18 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
23 19  
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationActivityService.java
... ... @@ -103,14 +103,6 @@ public interface CombinationActivityService {
103 103 /**
104 104 * 获取正在进行的活动分页数据
105 105 *
106   - * @param count 需要的数量
107   - * @return 拼团活动分页
108   - */
109   - List<CombinationActivityDO> getCombinationActivityListByCount(Integer count);
110   -
111   - /**
112   - * 获取正在进行的活动分页数据
113   - *
114 106 * @param pageParam 分页请求
115 107 * @return 拼团活动分页
116 108 */
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/combination/CombinationActivityServiceImpl.java
... ... @@ -226,11 +226,6 @@ public class CombinationActivityServiceImpl implements CombinationActivityServic
226 226 }
227 227  
228 228 @Override
229   - public List<CombinationActivityDO> getCombinationActivityListByCount(Integer count) {
230   - return combinationActivityMapper.selectListByStatus(CommonStatusEnum.ENABLE.getStatus(), count);
231   - }
232   -
233   - @Override
234 229 public PageResult<CombinationActivityDO> getCombinationActivityPage(PageParam pageParam) {
235 230 return combinationActivityMapper.selectPage(pageParam, CommonStatusEnum.ENABLE.getStatus());
236 231 }
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponService.java
... ... @@ -4,8 +4,6 @@ import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.map.MapUtil;
5 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 6 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageReqVO;
7   -import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponMatchReqVO;
8   -import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponMatchRespVO;
9 7 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;
10 8 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO;
11 9 import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTakeTypeEnum;
... ... @@ -20,26 +18,6 @@ import java.util.*;
20 18 public interface CouponService {
21 19  
22 20 /**
23   - * 校验优惠劵,包括状态、有限期
24   - * <p>
25   - * 1. 如果校验通过,则返回优惠劵信息
26   - * 2. 如果校验不通过,则直接抛出业务异常
27   - *
28   - * @param id 优惠劵编号
29   - * @param userId 用户编号
30   - * @return 优惠劵信息
31   - */
32   - CouponDO validCoupon(Long id, Long userId);
33   -
34   - /**
35   - * 校验优惠劵,包括状态、有限期
36   - *
37   - * @param coupon 优惠劵
38   - * @see #validCoupon(Long, Long) 逻辑相同,只是入参不同
39   - */
40   - void validCoupon(CouponDO coupon);
41   -
42   - /**
43 21 * 使用优惠劵
44 22 *
45 23 * @param id 优惠劵编号
... ... @@ -173,15 +151,6 @@ public interface CouponService {
173 151 }
174 152  
175 153 /**
176   - * 获取用户匹配的优惠券列表
177   - *
178   - * @param userId 用户编号
179   - * @param matchReqVO 匹配参数
180   - * @return 优惠券列表
181   - */
182   - List<AppCouponMatchRespVO> getMatchCouponList(Long userId, AppCouponMatchReqVO matchReqVO);
183   -
184   - /**
185 154 * 获取用户是否可以领取优惠券
186 155 *
187 156 * @param userId 用户编号
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/coupon/CouponServiceImpl.java
... ... @@ -12,13 +12,10 @@ import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
12 12 import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
13 13 import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
14 14 import cn.iocoder.yudao.module.promotion.controller.admin.coupon.vo.coupon.CouponPageReqVO;
15   -import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponMatchReqVO;
16   -import cn.iocoder.yudao.module.promotion.controller.app.coupon.vo.coupon.AppCouponMatchRespVO;
17 15 import cn.iocoder.yudao.module.promotion.convert.coupon.CouponConvert;
18 16 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponDO;
19 17 import cn.iocoder.yudao.module.promotion.dal.dataobject.coupon.CouponTemplateDO;
20 18 import cn.iocoder.yudao.module.promotion.dal.mysql.coupon.CouponMapper;
21   -import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
22 19 import cn.iocoder.yudao.module.promotion.enums.coupon.CouponStatusEnum;
23 20 import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTakeTypeEnum;
24 21 import cn.iocoder.yudao.module.promotion.enums.coupon.CouponTemplateValidityTypeEnum;
... ... @@ -58,18 +55,9 @@ public class CouponServiceImpl implements CouponService {
58 55 private MemberUserApi memberUserApi;
59 56  
60 57 @Override
61   - public CouponDO validCoupon(Long id, Long userId) {
62   - CouponDO coupon = couponMapper.selectByIdAndUserId(id, userId);
63   - if (coupon == null) {
64   - throw exception(COUPON_NOT_EXISTS);
65   - }
66   - validCoupon(coupon);
67   - return coupon;
68   - }
69   -
70   - @Override
71   - public void validCoupon(CouponDO coupon) {
  58 + public void useCoupon(Long id, Long userId, Long orderId) {
72 59 // 校验状态
  60 + CouponDO coupon = couponMapper.selectByIdAndUserId(id, userId);
73 61 if (ObjectUtil.notEqual(coupon.getStatus(), CouponStatusEnum.UNUSED.getStatus())) {
74 62 throw exception(COUPON_STATUS_NOT_UNUSED);
75 63 }
... ... @@ -77,12 +65,6 @@ public class CouponServiceImpl implements CouponService {
77 65 if (!LocalDateTimeUtils.isBetween(coupon.getValidStartTime(), coupon.getValidEndTime())) {
78 66 throw exception(COUPON_VALID_TIME_NOT_NOW);
79 67 }
80   - }
81   -
82   - @Override
83   - public void useCoupon(Long id, Long userId, Long orderId) {
84   - // 校验优惠劵
85   - validCoupon(id, userId);
86 68  
87 69 // 更新状态
88 70 int updateCount = couponMapper.updateByIdAndStatus(id, CouponStatusEnum.UNUSED.getStatus(),
... ... @@ -286,10 +268,8 @@ public class CouponServiceImpl implements CouponService {
286 268 if (couponTemplate == null) {
287 269 throw exception(COUPON_TEMPLATE_NOT_EXISTS);
288 270 }
289   - // 校验剩余数量(仅在 CouponTakeTypeEnum.USER 用户领取时)
290   - if (CouponTakeTypeEnum.isUser(couponTemplate.getTakeCount())
291   - && couponTemplate.getTotalCount() != null
292   - && couponTemplate.getTakeCount() + userIds.size() > couponTemplate.getTotalCount()) {
  271 + // 校验剩余数量
  272 + if (couponTemplate.getTakeCount() + userIds.size() > couponTemplate.getTotalCount()) {
293 273 throw exception(COUPON_TEMPLATE_NOT_ENOUGH);
294 274 }
295 275 // 校验"固定日期"的有效期类型是否过期
... ... @@ -299,7 +279,7 @@ public class CouponServiceImpl implements CouponService {
299 279 }
300 280 }
301 281 // 校验领取方式
302   - if (ObjectUtil.notEqual(couponTemplate.getTakeType(), takeType.getType())) {
  282 + if (ObjectUtil.notEqual(couponTemplate.getTakeType(), takeType.getValue())) {
303 283 throw exception(COUPON_TEMPLATE_CANNOT_TAKE);
304 284 }
305 285 }
... ... @@ -311,7 +291,7 @@ public class CouponServiceImpl implements CouponService {
311 291 * @param couponTemplate 优惠劵模版
312 292 */
313 293 private void removeTakeLimitUser(Set<Long> userIds, CouponTemplateDO couponTemplate) {
314   - if (couponTemplate.getTakeLimitCount() == null || couponTemplate.getTakeLimitCount() <= 0) {
  294 + if (couponTemplate.getTakeLimitCount() <= 0) {
315 295 return;
316 296 }
317 297 // 查询已领过券的用户
... ... @@ -359,48 +339,6 @@ public class CouponServiceImpl implements CouponService {
359 339 }
360 340  
361 341 @Override
362   - public List<AppCouponMatchRespVO> getMatchCouponList(Long userId, AppCouponMatchReqVO matchReqVO) {
363   - List<AppCouponMatchRespVO> couponMatchist = new ArrayList<>();
364   - List<CouponDO> list = couponMapper.selectListByUserIdAndStatusAndUsePriceLeAndProductScope(userId,
365   - CouponStatusEnum.UNUSED.getStatus());
366   - for (CouponDO couponDO : list) {
367   - AppCouponMatchRespVO appCouponMatchRespVO = CouponConvert.INSTANCE.convert2(couponDO);
368   - Integer productScope = appCouponMatchRespVO.getProductScope();
369   - List<Long> productScopeValues = appCouponMatchRespVO.getProductScopeValues();
370   - Integer usePrice = appCouponMatchRespVO.getUsePrice();
371   - if(matchReqVO.getPrice() < usePrice){
372   - // 价格小于等于,满足价格使用条件
373   - appCouponMatchRespVO.setMatch(false);
374   - appCouponMatchRespVO.setDescription("未达到使用门槛");
375   - }else if(!LocalDateTimeUtils.isBetween(appCouponMatchRespVO.getValidStartTime(), appCouponMatchRespVO.getValidEndTime())) {
376   - //判断时间
377   - appCouponMatchRespVO.setMatch(false);
378   - appCouponMatchRespVO.setDescription("使用时间未到");
379   - }else if (PromotionProductScopeEnum.ALL.getScope().equals(productScope)){
380   - appCouponMatchRespVO.setMatch(true);
381   - }else if (PromotionProductScopeEnum.SPU.getScope().equals(productScope)){
382   - boolean spu = new HashSet<>(productScopeValues).containsAll(matchReqVO.getSpuIds());
383   - if(spu){
384   - appCouponMatchRespVO.setMatch(true);
385   - }else {
386   - appCouponMatchRespVO.setMatch(false);
387   - appCouponMatchRespVO.setDescription("与商品不匹配");
388   - }
389   - }else if (PromotionProductScopeEnum.CATEGORY.getScope().equals(productScope)){
390   - boolean category = new HashSet<>(productScopeValues).containsAll(matchReqVO.getCategoryIds());
391   - if(category){
392   - appCouponMatchRespVO.setMatch(true);
393   - }else {
394   - appCouponMatchRespVO.setMatch(false);
395   - appCouponMatchRespVO.setDescription("与商品类型不匹配");
396   - }
397   - }
398   - couponMatchist.add(appCouponMatchRespVO);
399   - }
400   - return couponMatchist;
401   - }
402   -
403   - @Override
404 342 public Map<Long, Boolean> getUserCanCanTakeMap(Long userId, List<CouponTemplateDO> templates) {
405 343 // 1. 未登录时,都显示可以领取
406 344 Map<Long, Boolean> userCanTakeMap = convertMap(templates, CouponTemplateDO::getId, templateId -> true);
... ...
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/seckill/SeckillActivityService.java
... ... @@ -8,8 +8,8 @@ import cn.iocoder.yudao.module.promotion.controller.admin.seckill.vo.activity.Se
8 8 import cn.iocoder.yudao.module.promotion.controller.app.seckill.vo.activity.AppSeckillActivityPageReqVO;
9 9 import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillActivityDO;
10 10 import cn.iocoder.yudao.module.promotion.dal.dataobject.seckill.SeckillProductDO;
11   -
12 11 import jakarta.validation.Valid;
  12 +
13 13 import java.time.LocalDateTime;
14 14 import java.util.Collection;
15 15 import java.util.List;
... ... @@ -98,7 +98,7 @@ public interface SeckillActivityService {
98 98 * @param activityIds 活动编号
99 99 * @return 活动商品列表
100 100 */
101   - List<SeckillProductDO> getSeckillProductListByActivityId(Collection<Long> activityIds);
  101 + List<SeckillProductDO> getSeckillProductListByActivityIds(Collection<Long> activityIds);
102 102  
103 103 /**
104 104 * 通过活动时段编号获取指定 status 的秒杀活动
... ... @@ -139,4 +139,12 @@ public interface SeckillActivityService {
139 139 */
140 140 List<SeckillActivityDO> getSeckillActivityBySpuIdsAndStatusAndDateTimeLt(Collection<Long> spuIds, Integer status, LocalDateTime dateTime);
141 141  
  142 + /**
  143 + * 获得拼团活动列表
  144 + *
  145 + * @param ids 拼团活动编号数组
  146 + * @return 拼团活动的列表
  147 + */
  148 + List<SeckillActivityDO> getSeckillActivityListByIds(Collection<Long> ids);
  149 +
142 150 }
... ...
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java
... ... @@ -51,6 +51,7 @@ public interface ErrorCodeConstants {
51 51 ErrorCode AFTER_SALE_REFUND_FAIL_STATUS_NOT_WAIT_REFUND = new ErrorCode(1_011_000_110, "退款失败,售后单状态不是【待退款】");
52 52 ErrorCode AFTER_SALE_CANCEL_FAIL_STATUS_NOT_APPLY_OR_AGREE_OR_BUYER_DELIVERY =
53 53 new ErrorCode(1_011_000_111, "取消售后单失败,售后单状态不是【待审核】或【卖家同意】或【商家待收货】");
  54 + ErrorCode AFTER_SALE_CREATE_FAIL_ORDER_STATUS_COMBINATION_IN_PROGRESS = new ErrorCode(1_011_000_112, "订单拼团中,无法申请售后");
54 55  
55 56 // ========== Cart 模块 1-011-002-000 ==========
56 57 ErrorCode CARD_ITEM_NOT_FOUND = new ErrorCode(1_011_002_000, "购物车项不存在");
... ... @@ -61,7 +62,7 @@ public interface ErrorCodeConstants {
61 62 ErrorCode PRICE_CALCULATE_COUPON_NOT_MATCH_NORMAL_ORDER = new ErrorCode(1_011_003_004, "参与秒杀、拼团、砍价的营销商品,无法使用优惠劵");
62 63 ErrorCode PRICE_CALCULATE_SECKILL_TOTAL_LIMIT_COUNT = new ErrorCode(1_011_003_005, "参与秒杀的商品,超过了秒杀总限购数量");
63 64 ErrorCode PRICE_CALCULATE_DELIVERY_PRICE_TYPE_ILLEGAL = new ErrorCode(1_011_003_006, "计算快递运费异常,配送方式不匹配");
64   - ErrorCode PRICE_CALCULATE_COUPON_PRICE_TOO_MUCH = new ErrorCode(1_011_003_007, "该优惠劵无法使用,原因:优惠金额超过订单金额");
  65 + ErrorCode PRICE_CALCULATE_COUPON_CAN_NOT_USE = new ErrorCode(1_011_003_007, "该优惠劵无法使用,原因:{}」");
65 66  
66 67 // ========== 物流 Express 模块 1-011-004-000 ==========
67 68 ErrorCode EXPRESS_NOT_EXISTS = new ErrorCode(1_011_004_000, "快递公司不存在");
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/withdraw/AppBrokerageWithdrawCreateReqVO.java
... ... @@ -44,7 +44,7 @@ public class AppBrokerageWithdrawCreateReqVO {
44 44 private String name;
45 45 @Schema(description = "提现银行", example = "1")
46 46 @NotNull(message = "提现银行不能为空", groups = {Bank.class})
47   - private Integer bankName;
  47 + private String bankName;
48 48 @Schema(description = "开户地址", example = "海淀支行")
49 49 private String bankAddress;
50 50  
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeOrderSettlementRespVO.java
... ... @@ -3,11 +3,11 @@ package cn.iocoder.yudao.module.trade.controller.app.order.vo;
3 3 import cn.iocoder.yudao.module.trade.controller.app.base.property.AppProductPropertyValueDetailRespVO;
4 4 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
5 5 import io.swagger.v3.oas.annotations.media.Schema;
6   -import jakarta.validation.constraints.NotNull;
7 6 import lombok.AllArgsConstructor;
8 7 import lombok.Data;
9 8 import lombok.NoArgsConstructor;
10 9  
  10 +import java.time.LocalDateTime;
11 11 import java.util.List;
12 12  
13 13 @Schema(description = "用户 App - 交易订单结算信息 Response VO")
... ... @@ -20,6 +20,9 @@ public class AppTradeOrderSettlementRespVO {
20 20 @Schema(description = "购物项数组", requiredMode = Schema.RequiredMode.REQUIRED)
21 21 private List<Item> items;
22 22  
  23 + @Schema(description = "优惠劵数组", requiredMode = Schema.RequiredMode.REQUIRED)
  24 + private List<Coupon> coupons; // 可用 + 不可用
  25 +
23 26 @Schema(description = "费用", requiredMode = Schema.RequiredMode.REQUIRED)
24 27 private Price price;
25 28  
... ... @@ -117,7 +120,6 @@ public class AppTradeOrderSettlementRespVO {
117 120 private String mobile;
118 121  
119 122 @Schema(description = "地区编号", requiredMode = Schema.RequiredMode.REQUIRED)
120   - @NotNull(message = "地区编号不能为空")
121 123 private Long areaId;
122 124 @Schema(description = "地区名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "上海上海市普陀区")
123 125 private String areaName;
... ... @@ -130,4 +132,43 @@ public class AppTradeOrderSettlementRespVO {
130 132  
131 133 }
132 134  
  135 + @Schema(description = "优惠劵信息")
  136 + @Data
  137 + public static class Coupon {
  138 +
  139 + @Schema(description = "优惠劵编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  140 + private Long id;
  141 +
  142 + @Schema(description = "优惠劵名", requiredMode = Schema.RequiredMode.REQUIRED, example = "春节送送送")
  143 + private String name;
  144 +
  145 + @Schema(description = "是否设置满多少金额可用", requiredMode = Schema.RequiredMode.REQUIRED, example = "100") // 单位:分;0 - 不限制
  146 + private Integer usePrice;
  147 +
  148 + @Schema(description = "固定日期 - 生效开始时间")
  149 + private LocalDateTime validStartTime;
  150 +
  151 + @Schema(description = "固定日期 - 生效结束时间")
  152 + private LocalDateTime validEndTime;
  153 +
  154 + @Schema(description = "优惠类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  155 + private Integer discountType;
  156 +
  157 + @Schema(description = "折扣百分比", example = "80") // 例如说,80% 为 80
  158 + private Integer discountPercent;
  159 +
  160 + @Schema(description = "优惠金额", example = "10")
  161 + private Integer discountPrice;
  162 +
  163 + @Schema(description = "折扣上限", example = "100") // 单位:分,仅在 discountType 为 PERCENT 使用
  164 + private Integer discountLimitPrice;
  165 +
  166 + @Schema(description = "是否可用", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
  167 + private Boolean match;
  168 +
  169 + @Schema(description = "不可用原因", example = "优惠劵已过期")
  170 + private String mismatchReason;
  171 +
  172 + }
  173 +
133 174 }
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleServiceImpl.java
... ... @@ -8,6 +8,9 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
8 8 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
9 9 import cn.iocoder.yudao.module.pay.api.refund.PayRefundApi;
10 10 import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO;
  11 +import cn.iocoder.yudao.module.promotion.api.combination.CombinationRecordApi;
  12 +import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordRespDTO;
  13 +import cn.iocoder.yudao.module.promotion.enums.combination.CombinationRecordStatusEnum;
11 14 import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.AfterSaleDisagreeReqVO;
12 15 import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.AfterSalePageReqVO;
13 16 import cn.iocoder.yudao.module.trade.controller.admin.aftersale.vo.AfterSaleRefuseReqVO;
... ... @@ -26,6 +29,7 @@ import cn.iocoder.yudao.module.trade.enums.aftersale.AfterSaleTypeEnum;
26 29 import cn.iocoder.yudao.module.trade.enums.aftersale.AfterSaleWayEnum;
27 30 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderItemAfterSaleStatusEnum;
28 31 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderStatusEnum;
  32 +import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
29 33 import cn.iocoder.yudao.module.trade.framework.aftersale.core.annotations.AfterSaleLog;
30 34 import cn.iocoder.yudao.module.trade.framework.aftersale.core.utils.AfterSaleLogUtils;
31 35 import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties;
... ... @@ -71,6 +75,8 @@ public class AfterSaleServiceImpl implements AfterSaleService {
71 75  
72 76 @Resource
73 77 private PayRefundApi payRefundApi;
  78 + @Resource
  79 + private CombinationRecordApi combinationRecordApi;
74 80  
75 81 @Resource
76 82 private TradeOrderProperties tradeOrderProperties;
... ... @@ -148,6 +154,14 @@ public class AfterSaleServiceImpl implements AfterSaleService {
148 154 && !TradeOrderStatusEnum.haveDelivered(order.getStatus())) {
149 155 throw exception(AFTER_SALE_CREATE_FAIL_ORDER_STATUS_NO_DELIVERED);
150 156 }
  157 + // 如果是拼团订单,则进行中不允许售后
  158 + if (TradeOrderTypeEnum.isCombination(order.getType())) {
  159 + CombinationRecordRespDTO combinationRecord = combinationRecordApi.getCombinationRecordByOrderId(
  160 + order.getUserId(), order.getId());
  161 + if (combinationRecord != null && CombinationRecordStatusEnum.isInProgress(combinationRecord.getStatus())) {
  162 + throw exception(AFTER_SALE_CREATE_FAIL_ORDER_STATUS_COMBINATION_IN_PROGRESS);
  163 + }
  164 + }
151 165 return orderItem;
152 166 }
153 167  
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java
... ... @@ -887,7 +887,7 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
887 887 .setAppKey(tradeOrderProperties.getPayAppKey()).setUserIp(getClientIP()) // 支付应用
888 888 .setMerchantOrderId(String.valueOf(order.getId())) // 支付单号
889 889 .setMerchantRefundId(String.valueOf(order.getId()))
890   - .setReason(TradeOrderCancelTypeEnum.COMBINATION_CLOSE.getName()).setPrice(order.getPayPrice()));// 价格信息
  890 + .setReason(TradeOrderCancelTypeEnum.COMBINATION_CLOSE.getName()).setPrice(order.getPayPrice())); // 价格信息
891 891 }
892 892  
893 893 @Override
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/handler/TradeCombinationOrderHandler.java
... ... @@ -3,6 +3,8 @@ package cn.iocoder.yudao.module.trade.service.order.handler;
3 3 import cn.hutool.core.lang.Assert;
4 4 import cn.iocoder.yudao.module.promotion.api.combination.CombinationRecordApi;
5 5 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordCreateRespDTO;
  6 +import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordRespDTO;
  7 +import cn.iocoder.yudao.module.promotion.enums.combination.CombinationRecordStatusEnum;
6 8 import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert;
7 9 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
8 10 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
... ... @@ -84,7 +86,9 @@ public class TradeCombinationOrderHandler implements TradeOrderHandler {
84 86 return;
85 87 }
86 88 // 校验订单拼团是否成功
87   - if (!combinationRecordApi.isCombinationRecordSuccess(order.getUserId(), order.getId())) {
  89 + CombinationRecordRespDTO combinationRecord = combinationRecordApi.getCombinationRecordByOrderId(order.getUserId(), order.getId());
  90 + Assert.notNull(combinationRecord, "订单({})对应的拼团记录不存在", order.getId());
  91 + if (!CombinationRecordStatusEnum.isSuccess(combinationRecord.getStatus())) {
88 92 throw exception(ORDER_DELIVERY_FAIL_COMBINATION_RECORD_STATUS_NOT_SUCCESS);
89 93 }
90 94 }
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/bo/TradePriceCalculateRespBO.java
... ... @@ -5,6 +5,7 @@ import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
5 5 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
6 6 import lombok.Data;
7 7  
  8 +import java.time.LocalDateTime;
8 9 import java.util.List;
9 10 import java.util.Map;
10 11  
... ... @@ -45,9 +46,13 @@ public class TradePriceCalculateRespBO {
45 46 private List<Promotion> promotions;
46 47  
47 48 /**
48   - * 优惠劵编号
  49 + * 使用的优惠劵编号
49 50 */
50 51 private Long couponId;
  52 + /**
  53 + * 用户的优惠劵列表(可用 + 不可用)
  54 + */
  55 + private List<Coupon> coupons;
51 56  
52 57 /**
53 58 * 会员剩余积分
... ... @@ -340,4 +345,62 @@ public class TradePriceCalculateRespBO {
340 345  
341 346 }
342 347  
  348 + /**
  349 + * 优惠劵信息
  350 + */
  351 + @Data
  352 + public static class Coupon {
  353 +
  354 + /**
  355 + * 优惠劵编号
  356 + */
  357 + private Long id;
  358 + /**
  359 + * 优惠劵名
  360 + */
  361 + private String name;
  362 +
  363 + /**
  364 + * 是否设置满多少金额可用,单位:分
  365 + */
  366 + private Integer usePrice;
  367 +
  368 + /**
  369 + * 生效开始时间
  370 + */
  371 + private LocalDateTime validStartTime;
  372 + /**
  373 + * 生效结束时间
  374 + */
  375 + private LocalDateTime validEndTime;
  376 +
  377 + /**
  378 + * 优惠类型
  379 + */
  380 + private Integer discountType;
  381 + /**
  382 + * 折扣百分比
  383 + */
  384 + private Integer discountPercent;
  385 + /**
  386 + * 优惠金额,单位:分
  387 + */
  388 + private Integer discountPrice;
  389 + /**
  390 + * 折扣上限,单位:分
  391 + */
  392 + private Integer discountLimitPrice;
  393 +
  394 + /**
  395 + * 是否匹配
  396 + */
  397 + private Boolean match;
  398 + /**
  399 + * 不匹配的原因
  400 + */
  401 + private String mismatchReason;
  402 +
  403 + }
  404 +
  405 +
343 406 }
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeCouponPriceCalculator.java
1 1 package cn.iocoder.yudao.module.trade.service.price.calculator;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
4   -import cn.hutool.core.lang.Assert;
5 4 import cn.hutool.core.util.ObjectUtil;
6 5 import cn.hutool.core.util.StrUtil;
  6 +import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
  7 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
7 8 import cn.iocoder.yudao.module.promotion.api.coupon.CouponApi;
8 9 import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponRespDTO;
9   -import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponValidReqDTO;
10 10 import cn.iocoder.yudao.module.promotion.enums.common.PromotionDiscountTypeEnum;
11 11 import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
12 12 import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
  13 +import cn.iocoder.yudao.module.promotion.enums.coupon.CouponStatusEnum;
13 14 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
14 15 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
15 16 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
  17 +import jakarta.annotation.Resource;
16 18 import org.springframework.core.annotation.Order;
17 19 import org.springframework.stereotype.Component;
18 20  
19   -import jakarta.annotation.Resource;
20 21 import java.util.List;
21 22 import java.util.function.Predicate;
22 23  
23 24 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  25 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
24 26 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList;
25   -import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.COUPON_NO_MATCH_MIN_PRICE;
26   -import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.COUPON_NO_MATCH_SPU;
  27 +import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PRICE_CALCULATE_COUPON_CAN_NOT_USE;
27 28 import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PRICE_CALCULATE_COUPON_NOT_MATCH_NORMAL_ORDER;
28   -import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.PRICE_CALCULATE_COUPON_PRICE_TOO_MUCH;
29 29  
30 30 /**
31 31 * 优惠劵的 {@link TradePriceCalculator} 实现类
... ... @@ -41,34 +41,37 @@ public class TradeCouponPriceCalculator implements TradePriceCalculator {
41 41  
42 42 @Override
43 43 public void calculate(TradePriceCalculateReqBO param, TradePriceCalculateRespBO result) {
44   - // 1.1 校验优惠劵
45   - if (param.getCouponId() == null) {
46   - return;
47   - }
48   - CouponRespDTO coupon = couponApi.validateCoupon(new CouponValidReqDTO()
49   - .setId(param.getCouponId()).setUserId(param.getUserId()));
50   - Assert.notNull(coupon, "校验通过的优惠劵({}),不能为空", param.getCouponId());
51   - // 1.2 只有【普通】订单,才允许使用优惠劵
  44 + // 只有【普通】订单,才允许使用优惠劵
52 45 if (ObjectUtil.notEqual(result.getType(), TradeOrderTypeEnum.NORMAL.getType())) {
53   - throw exception(PRICE_CALCULATE_COUPON_NOT_MATCH_NORMAL_ORDER);
  46 + if (param.getCouponId() != null) {
  47 + throw exception(PRICE_CALCULATE_COUPON_NOT_MATCH_NORMAL_ORDER);
  48 + }
  49 + return;
54 50 }
55 51  
56   - // 2.1 获得匹配的商品 SKU 数组
57   - List<TradePriceCalculateRespBO.OrderItem> orderItems = filterMatchCouponOrderItems(result, coupon);
58   - if (CollUtil.isEmpty(orderItems)) {
59   - throw exception(COUPON_NO_MATCH_SPU);
  52 + // 1.1 加载用户的优惠劵列表
  53 + List<CouponRespDTO> coupons = couponApi.getCouponListByUserId(param.getUserId(), CouponStatusEnum.UNUSED.getStatus());
  54 + coupons.removeIf(coupon -> LocalDateTimeUtils.beforeNow(coupon.getValidEndTime()));
  55 + // 1.2 计算优惠劵的使用条件
  56 + result.setCoupons(calculateCoupons(coupons, result));
  57 +
  58 + // 2. 校验优惠劵是否可用
  59 + if (param.getCouponId() == null) {
  60 + return;
60 61 }
61   - // 2.2 计算是否满足优惠劵的使用金额
62   - Integer totalPayPrice = TradePriceCalculatorHelper.calculateTotalPayPrice(orderItems);
63   - if (totalPayPrice < coupon.getUsePrice()) {
64   - throw exception(COUPON_NO_MATCH_MIN_PRICE);
  62 + TradePriceCalculateRespBO.Coupon couponBO = CollUtil.findOne(result.getCoupons(), item -> item.getId().equals(param.getCouponId()));
  63 + CouponRespDTO coupon = CollUtil.findOne(coupons, item -> item.getId().equals(param.getCouponId()));
  64 + if (couponBO == null || coupon == null) {
  65 + throw exception(PRICE_CALCULATE_COUPON_CAN_NOT_USE, "优惠劵不存在");
  66 + }
  67 + if (Boolean.FALSE.equals(couponBO.getMatch())) {
  68 + throw exception(PRICE_CALCULATE_COUPON_CAN_NOT_USE, couponBO.getMismatchReason());
65 69 }
66 70  
67 71 // 3.1 计算可以优惠的金额
  72 + List<TradePriceCalculateRespBO.OrderItem> orderItems = filterMatchCouponOrderItems(result, coupon);
  73 + Integer totalPayPrice = TradePriceCalculatorHelper.calculateTotalPayPrice(orderItems);
68 74 Integer couponPrice = getCouponPrice(coupon, totalPayPrice);
69   - if (couponPrice <= totalPayPrice) {
70   - throw exception(PRICE_CALCULATE_COUPON_PRICE_TOO_MUCH);
71   - }
72 75 // 3.2 计算分摊的优惠金额
73 76 List<Integer> divideCouponPrices = TradePriceCalculatorHelper.dividePrice(orderItems, couponPrice);
74 77  
... ... @@ -76,7 +79,7 @@ public class TradeCouponPriceCalculator implements TradePriceCalculator {
76 79 result.setCouponId(param.getCouponId());
77 80 // 4.2 记录优惠明细
78 81 TradePriceCalculatorHelper.addPromotion(result, orderItems,
79   - param.getCouponId(), coupon.getName(), PromotionTypeEnum.COUPON.getType(),
  82 + param.getCouponId(), couponBO.getName(), PromotionTypeEnum.COUPON.getType(),
80 83 StrUtil.format("优惠劵:省 {} 元", TradePriceCalculatorHelper.formatPrice(couponPrice)),
81 84 divideCouponPrices);
82 85 // 4.3 更新 SKU 优惠金额
... ... @@ -88,6 +91,43 @@ public class TradeCouponPriceCalculator implements TradePriceCalculator {
88 91 TradePriceCalculatorHelper.recountAllPrice(result);
89 92 }
90 93  
  94 + /**
  95 + * 计算用户的优惠劵列表(可用 + 不可用)
  96 + *
  97 + * @param coupons 优惠劵
  98 + * @param result 计算结果
  99 + * @return 优惠劵列表
  100 + */
  101 + private List<TradePriceCalculateRespBO.Coupon> calculateCoupons(List<CouponRespDTO> coupons,
  102 + TradePriceCalculateRespBO result) {
  103 + return convertList(coupons, coupon -> {
  104 + TradePriceCalculateRespBO.Coupon matchCoupon = BeanUtils.toBean(coupon, TradePriceCalculateRespBO.Coupon.class);
  105 + // 1.1 优惠劵未到使用时间
  106 + if (LocalDateTimeUtils.afterNow(coupon.getValidStartTime())) {
  107 + return matchCoupon.setMatch(false).setMismatchReason("优惠劵未到使用时间");
  108 + }
  109 + // 1.2 优惠劵没有匹配的商品
  110 + List<TradePriceCalculateRespBO.OrderItem> orderItems = filterMatchCouponOrderItems(result, coupon);
  111 + if (CollUtil.isEmpty(orderItems)) {
  112 + return matchCoupon.setMatch(false).setMismatchReason("优惠劵没有匹配的商品");
  113 + }
  114 + // 1.3 差 %1$,.2f 元可用优惠劵
  115 + Integer totalPayPrice = TradePriceCalculatorHelper.calculateTotalPayPrice(orderItems);
  116 + if (totalPayPrice < coupon.getUsePrice()) {
  117 + return matchCoupon.setMatch(false)
  118 + .setMismatchReason(String.format("差 %1$,.2f 元可用优惠劵", (coupon.getUsePrice() - totalPayPrice) / 100D));
  119 + }
  120 + // 1.4 优惠金额超过订单金额
  121 + Integer couponPrice = getCouponPrice(coupon, totalPayPrice);
  122 + if (couponPrice >= totalPayPrice) {
  123 + return matchCoupon.setMatch(false).setMismatchReason("优惠金额超过订单金额");
  124 + }
  125 +
  126 + // 2. 满足条件
  127 + return matchCoupon.setMatch(true);
  128 + });
  129 + }
  130 +
91 131 private Integer getCouponPrice(CouponRespDTO coupon, Integer totalPayPrice) {
92 132 if (PromotionDiscountTypeEnum.PRICE.getType().equals(coupon.getDiscountType())) { // 减价
93 133 return coupon.getDiscountPrice();
... ...
yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/price/calculator/TradeCouponPriceCalculatorTest.java
1 1 package cn.iocoder.yudao.module.trade.service.price.calculator;
2 2  
  3 +import cn.hutool.core.collection.ListUtil;
3 4 import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
4 5 import cn.iocoder.yudao.module.promotion.api.coupon.CouponApi;
5 6 import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponRespDTO;
6   -import cn.iocoder.yudao.module.promotion.api.coupon.dto.CouponValidReqDTO;
7 7 import cn.iocoder.yudao.module.promotion.enums.common.PromotionDiscountTypeEnum;
8 8 import cn.iocoder.yudao.module.promotion.enums.common.PromotionProductScopeEnum;
9 9 import cn.iocoder.yudao.module.promotion.enums.common.PromotionTypeEnum;
  10 +import cn.iocoder.yudao.module.promotion.enums.coupon.CouponStatusEnum;
10 11 import cn.iocoder.yudao.module.trade.enums.order.TradeOrderTypeEnum;
11 12 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateReqBO;
12 13 import cn.iocoder.yudao.module.trade.service.price.bo.TradePriceCalculateRespBO;
... ... @@ -14,8 +15,10 @@ import org.junit.jupiter.api.Test;
14 15 import org.mockito.InjectMocks;
15 16 import org.mockito.Mock;
16 17  
  18 +import java.time.Duration;
17 19 import java.util.ArrayList;
18 20  
  21 +import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.addTime;
19 22 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
20 23 import static java.util.Arrays.asList;
21 24 import static org.junit.jupiter.api.Assertions.assertEquals;
... ... @@ -69,8 +72,10 @@ public class TradeCouponPriceCalculatorTest extends BaseMockitoUnitTest {
69 72 CouponRespDTO coupon = randomPojo(CouponRespDTO.class, o -> o.setId(1024L).setName("程序员节")
70 73 .setProductScope(PromotionProductScopeEnum.SPU.getScope()).setProductScopeValues(asList(1L, 2L))
71 74 .setUsePrice(350).setDiscountType(PromotionDiscountTypeEnum.PERCENT.getType())
72   - .setDiscountPercent(50).setDiscountLimitPrice(70));
73   - when(couponApi.validateCoupon(eq(new CouponValidReqDTO().setId(1024L).setUserId(233L)))).thenReturn(coupon);
  75 + .setDiscountPercent(50).setDiscountLimitPrice(70))
  76 + .setValidStartTime(addTime(Duration.ofDays(1))).setValidEndTime(addTime(Duration.ofDays(2)));
  77 + when(couponApi.getCouponListByUserId(eq(233L), eq(CouponStatusEnum.UNUSED.getStatus())))
  78 + .thenReturn(ListUtil.toList(coupon));
74 79  
75 80 // 调用
76 81 tradeCouponPriceCalculator.calculate(param, result);
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceImpl.java
... ... @@ -431,9 +431,7 @@ public class PayOrderServiceImpl implements PayOrderService {
431 431 return;
432 432 }
433 433  
434   - // TODO 芋艿:应该 new 出来更新
435   - order.setPrice(payPrice);
436   - orderMapper.updateById(order);
  434 + orderMapper.updateById(new PayOrderDO().setId(order.getId()).setPrice(payPrice));
437 435 }
438 436  
439 437 @Override
... ...
yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/AbstractWxPayClient.java
... ... @@ -266,7 +266,7 @@ public abstract class AbstractWxPayClient extends AbstractPayClient&lt;WxPayClientC
266 266 String errorCode = getErrorCode(e);
267 267 String errorMessage = getErrorMessage(e);
268 268 return PayRefundRespDTO.failureOf(errorCode, errorMessage,
269   - reqDTO.getOutTradeNo(), e.getXmlString());
  269 + reqDTO.getOutRefundNo(), e.getXmlString());
270 270 }
271 271 }
272 272  
... ...
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/logger/OperateLogApi.java
... ... @@ -5,6 +5,7 @@ import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogCreateReqDTO;
5 5 import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogPageReqDTO;
6 6 import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogRespDTO;
7 7 import jakarta.validation.Valid;
  8 +import org.springframework.scheduling.annotation.Async;
8 9  
9 10 /**
10 11 * 操作日志 API 接口
... ... @@ -21,6 +22,16 @@ public interface OperateLogApi {
21 22 void createOperateLog(@Valid OperateLogCreateReqDTO createReqDTO);
22 23  
23 24 /**
  25 + * 【异步】创建操作日志
  26 + *
  27 + * @param createReqDTO 请求
  28 + */
  29 + @Async
  30 + default void createOperateLogAsync(OperateLogCreateReqDTO createReqDTO) {
  31 + createOperateLog(createReqDTO);
  32 + }
  33 +
  34 + /**
24 35 * 获取指定模块的指定数据的操作日志分页
25 36 *
26 37 * @param pageReqDTO 请求
... ...
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/logger/OperateLogApiImpl.java
... ... @@ -9,7 +9,6 @@ import cn.iocoder.yudao.module.system.dal.dataobject.logger.OperateLogDO;
9 9 import cn.iocoder.yudao.module.system.service.logger.OperateLogService;
10 10 import com.fhs.core.trans.anno.TransMethodResult;
11 11 import jakarta.annotation.Resource;
12   -import org.springframework.scheduling.annotation.Async;
13 12 import org.springframework.stereotype.Service;
14 13 import org.springframework.validation.annotation.Validated;
15 14  
... ... @@ -26,7 +25,6 @@ public class OperateLogApiImpl implements OperateLogApi {
26 25 private OperateLogService operateLogService;
27 26  
28 27 @Override
29   - @Async
30 28 public void createOperateLog(OperateLogCreateReqDTO createReqDTO) {
31 29 operateLogService.createOperateLog(createReqDTO);
32 30 }
... ...
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2TokenServiceImplTest.java
... ... @@ -144,7 +144,7 @@ public class OAuth2TokenServiceImplTest extends BaseDbAndRedisUnitTest {
144 144 // 调用,并断言
145 145 assertServiceException(() -> oauth2TokenService.refreshAccessToken(refreshToken, clientId),
146 146 new ErrorCode(401, "刷新令牌已过期"));
147   - assertEquals(0, oauth2RefreshTokenMapper.selectCount());
  147 + assertEquals(0, oauth2AccessTokenMapper.selectCount());
148 148 }
149 149  
150 150 @Test
... ...
yudao-server/pom.xml
... ... @@ -33,11 +33,11 @@
33 33 </dependency>
34 34  
35 35 <!-- 会员中心。默认注释,保证编译速度 -->
36   - <dependency>
37   - <groupId>cn.iocoder.boot</groupId>
38   - <artifactId>yudao-module-member-biz</artifactId>
39   - <version>${revision}</version>
40   - </dependency>
  36 +<!-- <dependency>-->
  37 +<!-- <groupId>cn.iocoder.boot</groupId>-->
  38 +<!-- <artifactId>yudao-module-member-biz</artifactId>-->
  39 +<!-- <version>${revision}</version>-->
  40 +<!-- </dependency>-->
41 41  
42 42 <!-- 数据报表。默认注释,保证编译速度 -->
43 43 <!-- <dependency>-->
... ... @@ -52,11 +52,11 @@
52 52 <!-- <version>${revision}</version>-->
53 53 <!-- </dependency>-->
54 54 <!-- 支付服务。默认注释,保证编译速度 -->
55   - <dependency>
56   - <groupId>cn.iocoder.boot</groupId>
57   - <artifactId>yudao-module-pay-biz</artifactId>
58   - <version>${revision}</version>
59   - </dependency>
  55 +<!-- <dependency>-->
  56 +<!-- <groupId>cn.iocoder.boot</groupId>-->
  57 +<!-- <artifactId>yudao-module-pay-biz</artifactId>-->
  58 +<!-- <version>${revision}</version>-->
  59 +<!-- </dependency>-->
60 60  
61 61 <!-- 微信公众号模块。默认注释,保证编译速度 -->
62 62 <!-- <dependency>-->
... ... @@ -66,26 +66,26 @@
66 66 <!-- </dependency>-->
67 67  
68 68 <!-- 商城相关模块。默认注释,保证编译速度-->
69   - <dependency>
70   - <groupId>cn.iocoder.boot</groupId>
71   - <artifactId>yudao-module-promotion-biz</artifactId>
72   - <version>${revision}</version>
73   - </dependency>
74   - <dependency>
75   - <groupId>cn.iocoder.boot</groupId>
76   - <artifactId>yudao-module-product-biz</artifactId>
77   - <version>${revision}</version>
78   - </dependency>
79   - <dependency>
80   - <groupId>cn.iocoder.boot</groupId>
81   - <artifactId>yudao-module-trade-biz</artifactId>
82   - <version>${revision}</version>
83   - </dependency>
84   - <dependency>
85   - <groupId>cn.iocoder.boot</groupId>
86   - <artifactId>yudao-module-statistics-biz</artifactId>
87   - <version>${revision}</version>
88   - </dependency>
  69 +<!-- <dependency>-->
  70 +<!-- <groupId>cn.iocoder.boot</groupId>-->
  71 +<!-- <artifactId>yudao-module-promotion-biz</artifactId>-->
  72 +<!-- <version>${revision}</version>-->
  73 +<!-- </dependency>-->
  74 +<!-- <dependency>-->
  75 +<!-- <groupId>cn.iocoder.boot</groupId>-->
  76 +<!-- <artifactId>yudao-module-product-biz</artifactId>-->
  77 +<!-- <version>${revision}</version>-->
  78 +<!-- </dependency>-->
  79 +<!-- <dependency>-->
  80 +<!-- <groupId>cn.iocoder.boot</groupId>-->
  81 +<!-- <artifactId>yudao-module-trade-biz</artifactId>-->
  82 +<!-- <version>${revision}</version>-->
  83 +<!-- </dependency>-->
  84 +<!-- <dependency>-->
  85 +<!-- <groupId>cn.iocoder.boot</groupId>-->
  86 +<!-- <artifactId>yudao-module-statistics-biz</artifactId>-->
  87 +<!-- <version>${revision}</version>-->
  88 +<!-- </dependency>-->
89 89  
90 90 <!-- CRM 相关模块。默认注释,保证编译速度 -->
91 91 <!-- <dependency>-->
... ...
yudao-server/src/main/resources/application.yaml
... ... @@ -154,9 +154,9 @@ spring:
154 154 embedding:
155 155 transformer:
156 156 onnx:
157   - model-uri: http://test.yudao.iocoder.cn/model.onnx
  157 + model-uri: https://raw.gitcode.com/yudaocode/yudao-demo/raw/master/yudao-static/ai/model.onnx
158 158 tokenizer:
159   - uri: http://test.yudao.iocoder.cn/tokenizer.json
  159 + uri: https://raw.gitcode.com/yudaocode/yudao-demo/raw/master/yudao-static/ai/tokenizer.json
160 160 qianfan: # 文心一言
161 161 api-key: x0cuLZ7XsaTCU08vuJWO87Lg
162 162 secret-key: R9mYF9dl9KASgi5RUq0FQt3wRisSnOcK
... ... @@ -245,7 +245,7 @@ yudao:
245 245 codegen:
246 246 base-package: ${yudao.info.base-package}
247 247 db-schemas: ${spring.datasource.dynamic.datasource.master.name}
248   - front-type: 10 # 前端模版的类型,参见 CodegenFrontTypeEnum 枚举类
  248 + front-type: 20 # 前端模版的类型,参见 CodegenFrontTypeEnum 枚举类
249 249 tenant: # 多租户相关配置项
250 250 enable: true
251 251 ignore-urls:
... ...